mirror of
https://git.sr.ht/~nixgoat/vento
synced 2025-07-27 07:30:50 +00:00
Compare commits
No commits in common. "e96110748e525ec526d70bd207e2a2a1d6761cd5" and "cba202fd0d20f4465673a43894eafea3fce19a6b" have entirely different histories.
e96110748e
...
cba202fd0d
798
Cargo.lock
generated
798
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "vento"
|
||||
version = "1.4.0-alpha"
|
||||
version = "1.3.0"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
|
@ -21,14 +21,11 @@ colored = "2"
|
|||
fs_extra = "1.3"
|
||||
anyhow = "1.0"
|
||||
size_format = "1.0.2"
|
||||
config = "0.14"
|
||||
config = "0.13"
|
||||
xz2 = "0.1"
|
||||
tar = "0.4"
|
||||
clap = { version = "4.3.23", features = ["derive"] }
|
||||
serde = "1.0"
|
||||
rusqlite = { version = "0.31.0", features = ["bundled"] }
|
||||
chrono = "0.4"
|
||||
termion = "3.0.0"
|
||||
|
||||
[build-dependencies]
|
||||
man = "0.3.0"
|
||||
|
|
20
build.rs
20
build.rs
|
@ -60,25 +60,7 @@ fn vento() -> Result<Page> {
|
|||
Flag::new()
|
||||
.short("-u")
|
||||
.long("--undo")
|
||||
.help("Undoes actions by a certain amount of steps"),
|
||||
)
|
||||
.flag(
|
||||
Flag::new()
|
||||
.short("-r")
|
||||
.long("--redo")
|
||||
.help("Redoes actions by a certain amount of steps"),
|
||||
)
|
||||
.flag(
|
||||
Flag::new()
|
||||
.short("-v")
|
||||
.long("--view")
|
||||
.help("Shows log of actions"),
|
||||
)
|
||||
.flag(
|
||||
Flag::new()
|
||||
.short("-m")
|
||||
.long("--migrate")
|
||||
.help("Migrates history file to database"),
|
||||
.help("Undoes the last action"),
|
||||
)
|
||||
.flag(
|
||||
Flag::new()
|
||||
|
|
|
@ -44,7 +44,7 @@ fn main() -> Result<()> {
|
|||
let slot = unwrapped_slot.as_str();
|
||||
let out = cli.output.unwrap_or(get_current_dir()?);
|
||||
|
||||
item::drop(&cli.file, slot, out, true, cli.slot.is_some(), true)?;
|
||||
item::drop(&cli.file, slot, out, true, cli.slot.is_some())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -40,6 +40,6 @@ fn main() -> Result<()> {
|
|||
let cli = Cli::parse();
|
||||
let slot = cli.slot.clone().unwrap_or(String::from("active"));
|
||||
|
||||
item::take(&cli.file, &slot, true, cli.slot.is_some(), true)?;
|
||||
item::take(&cli.file, &slot, true, cli.slot.is_some())?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -39,21 +39,9 @@ struct Cli {
|
|||
#[arg(short = 'c', long)]
|
||||
switch: bool,
|
||||
|
||||
/// Undo actions by a certain amount of steps
|
||||
#[arg(short, long, value_name="STEPS", default_missing_value = "1", num_args = ..=1)]
|
||||
undo: Option<usize>,
|
||||
|
||||
/// Redo actions by a certain amount of steps
|
||||
#[arg(short, long, value_name="STEPS", default_missing_value = "1", num_args = ..=1)]
|
||||
redo: Option<usize>,
|
||||
|
||||
/// View log of actions
|
||||
#[arg(short = 'v', long, value_name="LENGTH", default_missing_value = "2", num_args = ..=1)]
|
||||
view: Option<isize>,
|
||||
|
||||
/// Migrate history file to database
|
||||
/// Undo the last action
|
||||
#[arg(short, long)]
|
||||
migrate: bool,
|
||||
undo: bool,
|
||||
|
||||
/// Export an inventory
|
||||
#[arg(short, long, value_names = &["SLOT", "ARCHIVE"], num_args = ..=2)]
|
||||
|
@ -85,17 +73,11 @@ fn main() -> Result<()> {
|
|||
let dir = unwrapped_dir.as_str();
|
||||
|
||||
if cli.switch {
|
||||
inv::switch(true, true)?
|
||||
inv::switch(true)?
|
||||
} else if cli.undo {
|
||||
history::undo()?
|
||||
} else if cli.init {
|
||||
inv::init()?
|
||||
} else if cli.undo.is_some() {
|
||||
history::undo(cli.undo.unwrap_or(1))?
|
||||
} else if cli.redo.is_some() {
|
||||
history::redo(cli.redo.unwrap_or(1))?
|
||||
} else if cli.view.is_some() {
|
||||
history::view(cli.view.unwrap_or(2))?
|
||||
} else if cli.migrate {
|
||||
history::migrate()?;
|
||||
} else if cli.export_inv.is_some() {
|
||||
let unwrapped_export_inv = cli.export_inv.unwrap();
|
||||
let export_inv_values = match unwrapped_export_inv.len() {
|
||||
|
|
|
@ -21,11 +21,11 @@ use crate::message::{throw_error, ErrorType};
|
|||
use anyhow::Result;
|
||||
use colored::control::set_override;
|
||||
use config::Config;
|
||||
use rusqlite::Connection;
|
||||
use serde::Deserialize;
|
||||
use std::env::current_dir;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub struct Settings {
|
||||
pub vento_dir: PathBuf,
|
||||
|
@ -33,15 +33,11 @@ pub struct Settings {
|
|||
pub inactive_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HistoryData {
|
||||
pub id: i32,
|
||||
pub path: Option<PathBuf>,
|
||||
pub file: Option<String>,
|
||||
pub slot: Option<String>,
|
||||
pub path: PathBuf,
|
||||
pub file: String,
|
||||
pub slot: String,
|
||||
pub action: Action,
|
||||
pub time: i64,
|
||||
pub current: i32,
|
||||
}
|
||||
|
||||
pub struct DeserializedConfig {
|
||||
|
@ -64,7 +60,6 @@ struct History {
|
|||
display_dir: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Action {
|
||||
Take,
|
||||
Drop,
|
||||
|
@ -144,48 +139,26 @@ pub fn parse_config() -> Result<DeserializedConfig> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Writes an action into the history database
|
||||
/// Writes an action into the history file
|
||||
pub fn history(data: HistoryData) -> Result<()> {
|
||||
let mut path = env_config()?.vento_dir;
|
||||
path.push("history.db3");
|
||||
let db = Connection::open(path)?;
|
||||
let mut last_path = env_config()?.vento_dir;
|
||||
last_path.push("last");
|
||||
let mut last_file = File::create(last_path)?;
|
||||
|
||||
// Create table if it doesn't exist.
|
||||
db.execute(
|
||||
"CREATE TABLE IF NOT EXISTS history (
|
||||
id INTEGER PRIMARY KEY,
|
||||
path TEXT,
|
||||
file TEXT,
|
||||
slot TEXT,
|
||||
action TEXT NOT NULL,
|
||||
time INTEGER NOT NULL,
|
||||
current INTEGER NOT NULL)",
|
||||
(),
|
||||
)?;
|
||||
|
||||
// Remove future actions
|
||||
let mut current = db.prepare("SELECT id FROM history WHERE current = 1")?;
|
||||
let actions = current.query_map([], |row| row.get(0))?;
|
||||
let lastaction: i64 = actions.last().unwrap_or(Ok(0))?;
|
||||
db.execute("DELETE FROM history WHERE id > ?1", [lastaction])?;
|
||||
|
||||
// Unset current actions
|
||||
db.execute("UPDATE history SET current = 0 WHERE current = 1", ())?;
|
||||
|
||||
// Insert action into table
|
||||
db.execute(
|
||||
"INSERT INTO history (path, file, slot, action, time, current) VALUES (?1, ?2, ?3, ?4, ?5, 1)",
|
||||
(
|
||||
data.path.unwrap_or_default().to_str(),
|
||||
data.file,
|
||||
data.slot,
|
||||
match data.action {
|
||||
Action::Take => "take",
|
||||
Action::Drop => "drop",
|
||||
Action::Switch => "switch",
|
||||
},
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::new(0, 0)).as_secs(),
|
||||
),
|
||||
write!(
|
||||
&mut last_file,
|
||||
"{}
|
||||
{}
|
||||
{}
|
||||
{}",
|
||||
data.path.to_str().unwrap(),
|
||||
data.file,
|
||||
data.slot,
|
||||
match data.action {
|
||||
Action::Take => "take",
|
||||
Action::Drop => "drop",
|
||||
Action::Switch => "switch",
|
||||
}
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
|
676
src/history.rs
676
src/history.rs
|
@ -18,599 +18,26 @@
|
|||
*/
|
||||
|
||||
use crate::{
|
||||
common::{self, env_config, parse_config, Action, HistoryData},
|
||||
common::{env_config, parse_config},
|
||||
inv, item,
|
||||
message::{append_emoji, throw_error, EmojiType, ErrorType},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use chrono::prelude::*;
|
||||
use colored::Colorize;
|
||||
use rusqlite::Connection;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Undoes actions made by Vento using the history database located on the Vento directory
|
||||
pub fn undo(steps: usize) -> Result<()> {
|
||||
let path: PathBuf = [
|
||||
env_config()?.vento_dir,
|
||||
Path::new("history.db3").to_path_buf(),
|
||||
]
|
||||
.iter()
|
||||
.collect();
|
||||
let db = Connection::open(path)?;
|
||||
|
||||
// Determine if step amount is greater than the position of the action
|
||||
let mut current = db.prepare("SELECT id FROM history WHERE current = 1")?;
|
||||
let actions = current.query_map([], |row| row.get(0))?;
|
||||
let last_action: usize = actions.last().unwrap_or(Ok(0))?;
|
||||
|
||||
if last_action <= steps {
|
||||
throw_error(ErrorType::InvalidStepsLength)?;
|
||||
}
|
||||
|
||||
let final_dest = last_action - steps;
|
||||
|
||||
// Calculates how many actions need to be undone
|
||||
let mut undo_queue_transaction = db.prepare(
|
||||
"SELECT id, path, file, slot, action FROM history WHERE id > ?2 AND id <= ?1 ORDER BY id DESC",
|
||||
)?;
|
||||
let undo_queue = undo_queue_transaction.query_map([last_action, final_dest], |row| {
|
||||
Ok(HistoryData {
|
||||
id: row.get(0)?,
|
||||
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
|
||||
file: row.get(2)?,
|
||||
slot: row.get(3)?,
|
||||
action: match row.get::<_, String>(4)?.as_str() {
|
||||
"take" => Action::Take,
|
||||
"drop" => Action::Drop,
|
||||
"switch" => Action::Switch,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
time: 0,
|
||||
current: 0,
|
||||
})
|
||||
})?;
|
||||
|
||||
// Undoes actions for each step
|
||||
for raw_step in undo_queue {
|
||||
let step = raw_step?;
|
||||
|
||||
match step.action {
|
||||
Action::Take => {
|
||||
item::drop(
|
||||
&step.file.unwrap(),
|
||||
&step.slot.unwrap(),
|
||||
step.path.unwrap(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
Action::Drop => {
|
||||
let path: String = [
|
||||
String::from(step.path.unwrap().to_str().unwrap()),
|
||||
step.file.unwrap(),
|
||||
]
|
||||
.join("/");
|
||||
item::take(&path, step.slot.unwrap().as_str(), false, false, false)?;
|
||||
}
|
||||
Action::Switch => inv::switch(false, false)?,
|
||||
}
|
||||
|
||||
db.execute("UPDATE history SET current = 0 WHERE current = 1", ())?;
|
||||
db.execute(
|
||||
"UPDATE history SET current = 1 WHERE id = ?1",
|
||||
[step.id - 1],
|
||||
)?;
|
||||
}
|
||||
|
||||
// Prepares to display details of the final position
|
||||
let mut final_transaction = db.prepare("SELECT * FROM history WHERE current = 1")?;
|
||||
let final_action_iter = final_transaction.query_map([], |row| {
|
||||
Ok(HistoryData {
|
||||
id: row.get(0)?,
|
||||
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
|
||||
file: row.get(2)?,
|
||||
slot: row.get(3)?,
|
||||
action: match row.get::<_, String>(4)?.as_str() {
|
||||
"take" => Action::Take,
|
||||
"drop" => Action::Drop,
|
||||
"switch" => Action::Switch,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
time: row.get(5)?,
|
||||
current: row.get::<_, i32>(5)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
let final_action = final_action_iter.last().unwrap()?;
|
||||
|
||||
// Formats the current action's timestamp to readable, local time
|
||||
let timestamp = final_action.time;
|
||||
let naive = NaiveDateTime::from_timestamp_opt(timestamp, 0);
|
||||
let datetime = TimeZone::from_utc_datetime(&Local, &naive.unwrap());
|
||||
let newdate = datetime.format("%Y-%m-%d, %H:%M:%S");
|
||||
|
||||
println!(
|
||||
"{}{}{}{}{}{}",
|
||||
append_emoji(EmojiType::Success)?,
|
||||
"Rolled back to ".green(),
|
||||
match final_action.action {
|
||||
Action::Take => "Take",
|
||||
Action::Drop => "Drop",
|
||||
Action::Switch => "Switch",
|
||||
}
|
||||
.bold(),
|
||||
" action, on ".green(),
|
||||
newdate,
|
||||
match final_action.action {
|
||||
Action::Take => format!(
|
||||
"{}{}{}{}{}{}{}",
|
||||
" (".green(),
|
||||
final_action.file.unwrap().bold(),
|
||||
", ".green(),
|
||||
match parse_config()?.history_display_dir {
|
||||
true => format!(
|
||||
"{} {} ",
|
||||
"from".green(),
|
||||
final_action.path.unwrap().to_str().unwrap(),
|
||||
),
|
||||
_ => String::new(),
|
||||
},
|
||||
"to ".green(),
|
||||
match final_action.slot.clone().unwrap().as_str() {
|
||||
"active" => final_action.slot.unwrap().green(),
|
||||
"inactive" => final_action.slot.unwrap().blue(),
|
||||
_ => final_action.slot.unwrap().red(),
|
||||
}
|
||||
.bold(),
|
||||
" slot)".green(),
|
||||
),
|
||||
Action::Drop => format!(
|
||||
"{}{}{}{}{}{}{}",
|
||||
" (".green(),
|
||||
final_action.file.unwrap().bold(),
|
||||
", from ".green(),
|
||||
match final_action.slot.clone().unwrap().as_str() {
|
||||
"active" => final_action.slot.unwrap().green(),
|
||||
"inactive" => final_action.slot.unwrap().blue(),
|
||||
_ => final_action.slot.unwrap().red(),
|
||||
}
|
||||
.bold(),
|
||||
" slot".green(),
|
||||
match parse_config()?.history_display_dir {
|
||||
true => format!(
|
||||
" {} {}",
|
||||
"to".green(),
|
||||
final_action.path.unwrap().to_str().unwrap(),
|
||||
),
|
||||
false => String::new(),
|
||||
},
|
||||
")".green(),
|
||||
),
|
||||
_ => String::from(""),
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Redoes actions made by Vento using the history database located on the Vento directory
|
||||
pub fn redo(steps: usize) -> Result<()> {
|
||||
let path: PathBuf = [
|
||||
env_config()?.vento_dir,
|
||||
Path::new("history.db3").to_path_buf(),
|
||||
]
|
||||
.iter()
|
||||
.collect();
|
||||
let db = Connection::open(path)?;
|
||||
|
||||
// Determine if step amount is greater than the position of the action
|
||||
let mut current = db.prepare("SELECT id FROM history WHERE current = 1")?;
|
||||
let actions = current.query_map([], |row| row.get(0))?;
|
||||
let last_action: usize = actions.last().unwrap_or(Ok(0))?;
|
||||
|
||||
// Determine table size
|
||||
let mut size_transaction = db.prepare("SELECT id FROM history")?;
|
||||
let size_actions = size_transaction.query_map([], |row| row.get(0))?;
|
||||
let size: usize = size_actions.last().unwrap_or(Ok(0))?;
|
||||
|
||||
if size - last_action < steps {
|
||||
throw_error(ErrorType::InvalidStepsLength)?;
|
||||
}
|
||||
|
||||
let final_dest = last_action + steps;
|
||||
|
||||
// Calculates how many actions need to be redone
|
||||
let mut redo_queue_transaction = db.prepare(
|
||||
"SELECT id, path, file, slot, action FROM history WHERE id > ?1 AND id <= ?2 ORDER BY id ASC",
|
||||
)?;
|
||||
let redo_queue = redo_queue_transaction.query_map([last_action, final_dest], |row| {
|
||||
Ok(HistoryData {
|
||||
id: row.get(0)?,
|
||||
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
|
||||
file: row.get(2)?,
|
||||
slot: row.get(3)?,
|
||||
action: match row.get::<_, String>(4)?.as_str() {
|
||||
"take" => Action::Take,
|
||||
"drop" => Action::Drop,
|
||||
"switch" => Action::Switch,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
time: 0,
|
||||
current: 0,
|
||||
})
|
||||
})?;
|
||||
|
||||
// Redoes actions for each step
|
||||
for raw_step in redo_queue {
|
||||
let step = raw_step?;
|
||||
|
||||
match step.action {
|
||||
Action::Take => {
|
||||
let path: String = [
|
||||
String::from(step.path.unwrap().to_str().unwrap()),
|
||||
step.file.unwrap(),
|
||||
]
|
||||
.join("/");
|
||||
item::take(&path, step.slot.unwrap().as_str(), false, false, false)?;
|
||||
}
|
||||
Action::Drop => {
|
||||
item::drop(
|
||||
&step.file.unwrap(),
|
||||
&step.slot.unwrap(),
|
||||
step.path.unwrap(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
Action::Switch => inv::switch(false, false)?,
|
||||
}
|
||||
|
||||
db.execute("UPDATE history SET current = 0 WHERE current = 1", ())?;
|
||||
db.execute("UPDATE history SET current = 1 WHERE id = ?1", [step.id])?;
|
||||
}
|
||||
|
||||
// Prepares to display details of the final position
|
||||
let mut final_transaction = db.prepare("SELECT * FROM history WHERE current = 1")?;
|
||||
let final_action_iter = final_transaction.query_map([], |row| {
|
||||
Ok(HistoryData {
|
||||
id: row.get(0)?,
|
||||
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
|
||||
file: row.get(2)?,
|
||||
slot: row.get(3)?,
|
||||
action: match row.get::<_, String>(4)?.as_str() {
|
||||
"take" => Action::Take,
|
||||
"drop" => Action::Drop,
|
||||
"switch" => Action::Switch,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
time: row.get(5)?,
|
||||
current: row.get::<_, i32>(5)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
let final_action = final_action_iter.last().unwrap()?;
|
||||
|
||||
// Formats the current action's timestamp to readable, local time
|
||||
let timestamp = final_action.time;
|
||||
let naive = NaiveDateTime::from_timestamp_opt(timestamp, 0);
|
||||
let datetime = TimeZone::from_utc_datetime(&Local, &naive.unwrap());
|
||||
let newdate = datetime.format("%Y-%m-%d, %H:%M:%S");
|
||||
|
||||
// Prints transaction result
|
||||
println!(
|
||||
"{}{}{}{}{}{}",
|
||||
append_emoji(EmojiType::Success)?,
|
||||
"Returned to ".green(),
|
||||
match final_action.action {
|
||||
Action::Take => "Take",
|
||||
Action::Drop => "Drop",
|
||||
Action::Switch => "Switch",
|
||||
}
|
||||
.bold(),
|
||||
" action, on ".green(),
|
||||
newdate,
|
||||
match final_action.action {
|
||||
Action::Take => format!(
|
||||
"{}{}{}{}{}{}{}",
|
||||
" (".green(),
|
||||
final_action.file.unwrap().bold(),
|
||||
", ".green(),
|
||||
match parse_config()?.history_display_dir {
|
||||
true => format!(
|
||||
"{} {} ",
|
||||
"from".green(),
|
||||
final_action.path.unwrap().to_str().unwrap(),
|
||||
),
|
||||
_ => String::new(),
|
||||
},
|
||||
"to ".green(),
|
||||
match final_action.slot.clone().unwrap().as_str() {
|
||||
"active" => final_action.slot.unwrap().green(),
|
||||
"inactive" => final_action.slot.unwrap().blue(),
|
||||
_ => final_action.slot.unwrap().red(),
|
||||
}
|
||||
.bold(),
|
||||
" slot)".green(),
|
||||
),
|
||||
Action::Drop => format!(
|
||||
"{}{}{}{}{}{}{}",
|
||||
" (".green(),
|
||||
final_action.file.unwrap().bold(),
|
||||
", from ".green(),
|
||||
match final_action.slot.clone().unwrap().as_str() {
|
||||
"active" => final_action.slot.unwrap().green(),
|
||||
"inactive" => final_action.slot.unwrap().blue(),
|
||||
_ => final_action.slot.unwrap().red(),
|
||||
}
|
||||
.bold(),
|
||||
" slot".green(),
|
||||
match parse_config()?.history_display_dir {
|
||||
true => format!(
|
||||
" {} {}",
|
||||
"to".green(),
|
||||
final_action.path.unwrap().to_str().unwrap(),
|
||||
),
|
||||
false => String::new(),
|
||||
},
|
||||
")".green(),
|
||||
),
|
||||
_ => String::from(""),
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Displays n actions before and after the current action
|
||||
pub fn view(length: isize) -> Result<()> {
|
||||
let path: PathBuf = [
|
||||
env_config()?.vento_dir,
|
||||
Path::new("history.db3").to_path_buf(),
|
||||
]
|
||||
.iter()
|
||||
.collect();
|
||||
let db = Connection::open(path)?;
|
||||
|
||||
// Determine table size
|
||||
let mut size_transaction = db.prepare("SELECT id FROM history")?;
|
||||
let size_actions = size_transaction.query_map([], |row| row.get(0))?;
|
||||
let size: isize = size_actions.last().unwrap_or(Ok(0))?;
|
||||
|
||||
let (x, _) = termion::terminal_size().unwrap();
|
||||
|
||||
// If there's no history, don't print the table
|
||||
if size == 0 {
|
||||
println!(
|
||||
"{}{}",
|
||||
append_emoji(EmojiType::Success)?,
|
||||
"No data to show".green()
|
||||
);
|
||||
}
|
||||
|
||||
// Find last action
|
||||
let mut current = db.prepare("SELECT id FROM history WHERE current = 1")?;
|
||||
let actions = current.query_map([], |row| row.get(0))?;
|
||||
let last_action: isize = actions.last().unwrap_or(Ok(0))?;
|
||||
|
||||
let mut forward: isize = last_action + length;
|
||||
let mut backward: isize = last_action - length;
|
||||
let total_range: isize = length * 2;
|
||||
|
||||
// Changes ranges in case they exceed the table margins
|
||||
if forward >= size {
|
||||
forward = size;
|
||||
backward = size - total_range;
|
||||
} else if backward < 1 {
|
||||
backward = 1;
|
||||
forward = total_range + 1;
|
||||
}
|
||||
|
||||
// Read from table
|
||||
let mut history_transaction =
|
||||
db.prepare("SELECT * FROM history WHERE id >= ?1 AND id <= ?2")?;
|
||||
let history = history_transaction.query_map([backward, forward], |row| {
|
||||
Ok(HistoryData {
|
||||
id: row.get(0)?,
|
||||
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
|
||||
file: row.get(2)?,
|
||||
slot: row.get(3)?,
|
||||
action: match row.get::<_, String>(4)?.as_str() {
|
||||
"take" => Action::Take,
|
||||
"drop" => Action::Drop,
|
||||
"switch" => Action::Switch,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
time: row.get(5)?,
|
||||
current: row.get(6)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
// Terminal needs to be at least 83 columns wide
|
||||
if x < 83 {
|
||||
throw_error(ErrorType::SmallTerminal)?;
|
||||
}
|
||||
|
||||
let mut space_left: usize = (x - 83).into();
|
||||
|
||||
// Append separators to ID
|
||||
let mut id_separators = String::new();
|
||||
if size.to_string().len() > 2 {
|
||||
for _ in 0..size.to_string().len() - 2 {
|
||||
id_separators.insert(id_separators.len(), '-')
|
||||
}
|
||||
space_left = space_left - size.to_string().len() + 2;
|
||||
}
|
||||
|
||||
// Append separators to path column
|
||||
let mut path_separators = String::new();
|
||||
let mut file_separators = String::new();
|
||||
|
||||
// Calculate spaces left to add padding to the path and file separators
|
||||
space_left /= 3;
|
||||
for _ in 0..space_left * 2 {
|
||||
path_separators.insert(path_separators.len(), '-')
|
||||
}
|
||||
for _ in 0..space_left {
|
||||
file_separators.insert(file_separators.len(), '-')
|
||||
}
|
||||
|
||||
let separator = format!(
|
||||
"+----{}+---------------------+--------+------------------{}+----------{}+----------+---+",
|
||||
id_separators, path_separators, file_separators
|
||||
);
|
||||
|
||||
// Render the first column names
|
||||
println!("{}", separator);
|
||||
print!("| ");
|
||||
if size.to_string().len() > 2 {
|
||||
for _ in 0..size.to_string().len() - 2 {
|
||||
print!(" ")
|
||||
}
|
||||
}
|
||||
print!("ID | Date | Action | Path ");
|
||||
for _ in 0..space_left * 2 {
|
||||
print!(" ")
|
||||
}
|
||||
print!("| File ");
|
||||
for _ in 0..space_left {
|
||||
print!(" ")
|
||||
}
|
||||
println!("| Slot | C |\n{}", separator);
|
||||
|
||||
// Print the rows
|
||||
for raw_step in history {
|
||||
let step = raw_step?;
|
||||
|
||||
// Format timestamp on row
|
||||
let timestamp = step.time;
|
||||
let naive = NaiveDateTime::from_timestamp_opt(timestamp, 0);
|
||||
let datetime = TimeZone::from_utc_datetime(&Local, &naive.unwrap());
|
||||
let fdate = datetime.format("%Y-%m-%d %H:%M:%S");
|
||||
|
||||
// Add spacing for ID column
|
||||
let mut id_pad = String::new();
|
||||
let id = step.id.to_string().len();
|
||||
|
||||
if size.to_string().len() >= 2 {
|
||||
let id_pad_len = size.to_string().len();
|
||||
for x in 0..id_pad_len - id {
|
||||
id_pad.insert(x, ' ');
|
||||
}
|
||||
} else {
|
||||
id_pad.insert(0, ' ');
|
||||
}
|
||||
|
||||
// Add spacing to fit inside the file column
|
||||
let file_len = match &step.file {
|
||||
Some(x) => x.len(),
|
||||
None => 0,
|
||||
};
|
||||
let mut file_pad = String::new();
|
||||
let mut file = step.file.unwrap_or(String::from(""));
|
||||
let file_column_len;
|
||||
if file_len > space_left + 8 {
|
||||
file_column_len = 0;
|
||||
let mut reversed: String = file.chars().rev().collect();
|
||||
for _ in 0..file_len - space_left - 5 {
|
||||
reversed.pop();
|
||||
}
|
||||
file = reversed.chars().rev().collect();
|
||||
for x in 0..3 {
|
||||
file.insert(x, '.');
|
||||
}
|
||||
} else {
|
||||
file_column_len = space_left + 8 - file_len
|
||||
}
|
||||
|
||||
for x in 0..file_column_len {
|
||||
file_pad.insert(x, ' ');
|
||||
}
|
||||
|
||||
// Add spacing to fit inside the path column
|
||||
let mut path_pad = String::new();
|
||||
let mut path = step
|
||||
.path
|
||||
.unwrap_or(PathBuf::new())
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let path_len = path.len();
|
||||
let path_column_len;
|
||||
if path_len > space_left * 2 + 16 {
|
||||
path_column_len = 0;
|
||||
let mut reversed: String = path.chars().rev().collect();
|
||||
for _ in 0..path_len - space_left * 2 - 13 {
|
||||
reversed.pop();
|
||||
}
|
||||
path = reversed.chars().rev().collect();
|
||||
for x in 0..3 {
|
||||
path.insert(x, '.');
|
||||
}
|
||||
} else {
|
||||
path_column_len = space_left * 2 + 16 - path_len;
|
||||
}
|
||||
|
||||
for _ in 0..path_column_len {
|
||||
path_pad.insert(path_pad.len(), ' ');
|
||||
}
|
||||
|
||||
// Add spacing on slot column
|
||||
let mut slot = step.slot.unwrap_or(String::from(""));
|
||||
if slot == "active" {
|
||||
slot = String::from("active ");
|
||||
} else if slot == "inactive" {
|
||||
slot = String::from("inactive");
|
||||
} else {
|
||||
slot = String::from(" ")
|
||||
}
|
||||
|
||||
println!(
|
||||
"| {}{} | {} | {} | {}{} | {}{} | {} | {} |",
|
||||
id_pad,
|
||||
step.id,
|
||||
fdate,
|
||||
match step.action {
|
||||
Action::Take => "Take ",
|
||||
Action::Drop => "Drop ",
|
||||
Action::Switch => "Switch",
|
||||
},
|
||||
path,
|
||||
path_pad,
|
||||
file,
|
||||
file_pad,
|
||||
slot,
|
||||
match step.current {
|
||||
0 => " ",
|
||||
1 => "*",
|
||||
_ => " ",
|
||||
}
|
||||
);
|
||||
}
|
||||
println!("{}", separator);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Migrate old "last" file into the history database
|
||||
pub fn migrate() -> Result<()> {
|
||||
// Get last file from previous location
|
||||
let last_path: PathBuf = [env_config()?.vento_dir, Path::new("last").to_path_buf()]
|
||||
/// Undoes the last action made by Vento using the history file located on the Vento directory
|
||||
pub fn undo() -> Result<()> {
|
||||
let lastpath: PathBuf = [env_config()?.vento_dir, Path::new("last").to_path_buf()]
|
||||
.iter()
|
||||
.collect();
|
||||
|
||||
if !last_path.is_file() {
|
||||
throw_error(ErrorType::NoFileOrDir)?;
|
||||
}
|
||||
|
||||
let last_file = fs::read_to_string(&last_path)?;
|
||||
let lastfile = fs::read_to_string(lastpath)?;
|
||||
|
||||
let mut contents = vec![];
|
||||
|
||||
for line in last_file.lines() {
|
||||
for line in lastfile.lines() {
|
||||
contents.push(line);
|
||||
}
|
||||
|
||||
|
@ -618,28 +45,77 @@ pub fn migrate() -> Result<()> {
|
|||
throw_error(ErrorType::InvalidHistoryLength)?;
|
||||
}
|
||||
|
||||
// Write contents of file into history database
|
||||
common::history(HistoryData {
|
||||
id: 0,
|
||||
path: Some(Path::new(contents[0]).to_path_buf()),
|
||||
file: Some(String::from(contents[1])),
|
||||
slot: Some(String::from(contents[2])),
|
||||
action: match contents[3] {
|
||||
"take" => Action::Take,
|
||||
"drop" => Action::Drop,
|
||||
"switch" => Action::Switch,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
time: 0,
|
||||
current: 1,
|
||||
})?;
|
||||
|
||||
fs::remove_file(last_path)?;
|
||||
match contents[3] {
|
||||
"take" => {
|
||||
let destpath = Path::new(contents[0]).to_path_buf();
|
||||
item::drop(
|
||||
&String::from(contents[1]),
|
||||
contents[2],
|
||||
destpath,
|
||||
false,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
"drop" => {
|
||||
let path = vec![contents[0], contents[1]].join("/");
|
||||
item::take(&path, contents[2], false, false)?;
|
||||
}
|
||||
"switch" => {
|
||||
inv::switch(false)?;
|
||||
}
|
||||
_ => throw_error(ErrorType::IllegalAction)?,
|
||||
}
|
||||
|
||||
println!(
|
||||
"{}{}",
|
||||
"{}{}{}{}",
|
||||
append_emoji(EmojiType::Success)?,
|
||||
"Migrated history file to database".green()
|
||||
match contents[3] {
|
||||
"take" => "Take",
|
||||
"drop" => "Drop",
|
||||
"switch" => "Switch",
|
||||
_ => "Unknown",
|
||||
}
|
||||
.bold(),
|
||||
" action undone".green(),
|
||||
match contents[3] {
|
||||
"take" => format!(
|
||||
"{}{}{}{}{}{}{}",
|
||||
" (".green(),
|
||||
contents[1].bold(),
|
||||
", ".green(),
|
||||
match parse_config()?.history_display_dir {
|
||||
true => format!("{} {} ", "from".green(), contents[0],),
|
||||
_ => String::new(),
|
||||
},
|
||||
"to ".green(),
|
||||
match contents[2] {
|
||||
"active" => contents[2].green(),
|
||||
"inactive" => contents[2].blue(),
|
||||
_ => contents[2].red(),
|
||||
}
|
||||
.bold(),
|
||||
" slot)".green(),
|
||||
),
|
||||
"drop" => format!(
|
||||
"{}{}{}{}{}{}{}",
|
||||
" (".green(),
|
||||
contents[1].bold(),
|
||||
", from ".green(),
|
||||
match contents[2] {
|
||||
"active" => contents[2].green(),
|
||||
"inactive" => contents[2].blue(),
|
||||
_ => contents[2].red(),
|
||||
}
|
||||
.bold(),
|
||||
" slot".green(),
|
||||
match parse_config()?.history_display_dir {
|
||||
true => format!(" {} {}", "to".green(), contents[0],),
|
||||
false => String::new(),
|
||||
},
|
||||
")".green(),
|
||||
),
|
||||
_ => String::from(""),
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
|
19
src/inv.rs
19
src/inv.rs
|
@ -178,7 +178,7 @@ pub fn list(slot: &str, dir: &str, display_slot: bool) -> Result<()> {
|
|||
}
|
||||
|
||||
/// Switches inevntory slots between each other, making the currently active inventory inactive and viceversa
|
||||
pub fn switch(message: bool, save_history: bool) -> Result<()> {
|
||||
pub fn switch(message: bool) -> Result<()> {
|
||||
let ventodir = &common::env_config()?.vento_dir;
|
||||
let active = &common::env_config()?.active_dir;
|
||||
let inactive = &common::env_config()?.inactive_dir;
|
||||
|
@ -192,17 +192,12 @@ pub fn switch(message: bool, save_history: bool) -> Result<()> {
|
|||
fs::rename(inactive, active).context(rename_error)?;
|
||||
fs::rename(&temp, inactive).context(rename_error)?;
|
||||
|
||||
if save_history {
|
||||
common::history(common::HistoryData {
|
||||
id: 0,
|
||||
path: None,
|
||||
file: None,
|
||||
slot: None,
|
||||
action: common::Action::Switch,
|
||||
current: 1,
|
||||
time: 0,
|
||||
})?;
|
||||
}
|
||||
common::history(common::HistoryData {
|
||||
path: PathBuf::new(),
|
||||
file: String::new(),
|
||||
slot: String::new(),
|
||||
action: common::Action::Switch,
|
||||
})?;
|
||||
|
||||
if message {
|
||||
println!(
|
||||
|
|
43
src/item.rs
43
src/item.rs
|
@ -28,13 +28,7 @@ use std::fs;
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Takes a file or directory and stores it in an inventory slot
|
||||
pub fn take(
|
||||
file: &String,
|
||||
slot: &str,
|
||||
message: bool,
|
||||
display_slot: bool,
|
||||
save_history: bool,
|
||||
) -> Result<()> {
|
||||
pub fn take(file: &String, slot: &str, message: bool, display_slot: bool) -> Result<()> {
|
||||
let ventodir = &env_config()?.vento_dir;
|
||||
|
||||
if !ventodir.is_dir() {
|
||||
|
@ -84,17 +78,12 @@ pub fn take(
|
|||
throw_error(ErrorType::NoFileOrDir)?;
|
||||
}
|
||||
|
||||
if save_history {
|
||||
history(HistoryData {
|
||||
id: 0,
|
||||
path: Some(sourcelocation.clone()),
|
||||
file: Some(String::from(filename)),
|
||||
slot: Some(String::from(slot)),
|
||||
action: Action::Take,
|
||||
current: 1,
|
||||
time: 0,
|
||||
})?;
|
||||
}
|
||||
history(HistoryData {
|
||||
path: sourcelocation.clone(),
|
||||
file: String::from(filename),
|
||||
slot: String::from(slot),
|
||||
action: Action::Take,
|
||||
})?;
|
||||
|
||||
if message {
|
||||
println!(
|
||||
|
@ -136,7 +125,6 @@ pub fn drop(
|
|||
dest: PathBuf,
|
||||
message: bool,
|
||||
display_slot: bool,
|
||||
save_history: bool,
|
||||
) -> Result<()> {
|
||||
// Drops a file or directory
|
||||
let ventodir = &env_config()?.vento_dir;
|
||||
|
@ -192,17 +180,12 @@ pub fn drop(
|
|||
|
||||
destpath.pop();
|
||||
|
||||
if save_history {
|
||||
history(HistoryData {
|
||||
id: 0,
|
||||
path: Some(destpath.clone()),
|
||||
file: Some(String::from(file)),
|
||||
slot: Some(String::from(slot)),
|
||||
action: Action::Drop,
|
||||
current: 1,
|
||||
time: 0,
|
||||
})?;
|
||||
}
|
||||
history(HistoryData {
|
||||
path: destpath.clone(),
|
||||
file: String::from(file),
|
||||
slot: String::from(slot),
|
||||
action: Action::Drop,
|
||||
})?;
|
||||
|
||||
if message {
|
||||
println!(
|
||||
|
|
|
@ -28,8 +28,6 @@ pub enum ErrorType {
|
|||
NoCurrentDirectory,
|
||||
NoHomeDirectory,
|
||||
InvalidHistoryLength,
|
||||
InvalidStepsLength,
|
||||
SmallTerminal,
|
||||
IllegalAction,
|
||||
NotInitialized,
|
||||
NoAccessParent,
|
||||
|
@ -70,9 +68,7 @@ pub fn throw_error(error: ErrorType) -> Result<()> {
|
|||
ErrorType::SpecifySlot => "You need to specify a slot",
|
||||
ErrorType::NoCurrentDirectory => "Vento was unable to detect your current directory. Have you configured your environment correctly?",
|
||||
ErrorType::NoHomeDirectory => "Vento was unable to detect your home directory. Have you configured your environment correctly?",
|
||||
ErrorType::InvalidHistoryLength => "Invalid history length",
|
||||
ErrorType::InvalidStepsLength => "Invalid steps length",
|
||||
ErrorType::SmallTerminal => "Your terminal needs to be at least 83 columns wide",
|
||||
ErrorType::InvalidHistoryLength => "Invalid history length",
|
||||
ErrorType::IllegalAction => "Illegal action",
|
||||
ErrorType::NotInitialized => "Vento not initialized. Run \"vento -i\" to initialize Vento",
|
||||
ErrorType::NoAccessParent => "Cannot access parent",
|
||||
|
|
Loading…
Reference in a new issue