1
0
Fork 0
mirror of https://git.sr.ht/~nixgoat/vento synced 2024-11-21 22:33:01 +00:00

Compare commits

...

4 commits

Author SHA1 Message Date
Lux Aliaga e96110748e
history: Add history migration
This adds an option to the vento command which allows users to migrate
from the old history file to the newer database. It parses the history
file, writes the changes into the database, and then deletes the
previous file.
2024-02-22 01:14:41 -03:00
Lux Aliaga 46ac7ee8b7
history: Add history viewer
It works by showing n changes before and after the current position
in a table. Requires an 83+ column wide terminal. Command usage is
"vento -v [<n>]".
2024-02-22 01:14:41 -03:00
Lux Aliaga 600c3e4f3a
history: Implement new database system
The new database system uses SQLite3, and allows users to move across
a linear history of their actions, including moving forward from a
previous action. Whenever a user runs a new action, the actions after
the current position within the history will be removed.
2024-02-22 01:14:35 -03:00
Lux Aliaga 92b2942471
manifest: Bump version to v1.4-alpha
After a few months, it's time to prepare the next Vento release. I've
been cooking some ideas, so this one might be big. 👀
2024-02-21 20:16:56 -03:00
11 changed files with 1303 additions and 369 deletions

834
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "vento" name = "vento"
version = "1.3.0" version = "1.4.0-alpha"
edition = "2021" edition = "2021"
readme = "README.md" readme = "README.md"
@ -21,11 +21,14 @@ colored = "2"
fs_extra = "1.3" fs_extra = "1.3"
anyhow = "1.0" anyhow = "1.0"
size_format = "1.0.2" size_format = "1.0.2"
config = "0.13" config = "0.14"
xz2 = "0.1" xz2 = "0.1"
tar = "0.4" tar = "0.4"
clap = { version = "4.3.23", features = ["derive"] } clap = { version = "4.3.23", features = ["derive"] }
serde = "1.0" serde = "1.0"
rusqlite = { version = "0.31.0", features = ["bundled"] }
chrono = "0.4"
termion = "3.0.0"
[build-dependencies] [build-dependencies]
man = "0.3.0" man = "0.3.0"

View file

@ -60,7 +60,25 @@ fn vento() -> Result<Page> {
Flag::new() Flag::new()
.short("-u") .short("-u")
.long("--undo") .long("--undo")
.help("Undoes the last action"), .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"),
) )
.flag( .flag(
Flag::new() Flag::new()

View file

@ -44,7 +44,7 @@ fn main() -> Result<()> {
let slot = unwrapped_slot.as_str(); let slot = unwrapped_slot.as_str();
let out = cli.output.unwrap_or(get_current_dir()?); let out = cli.output.unwrap_or(get_current_dir()?);
item::drop(&cli.file, slot, out, true, cli.slot.is_some())?; item::drop(&cli.file, slot, out, true, cli.slot.is_some(), true)?;
Ok(()) Ok(())
} }

View file

@ -40,6 +40,6 @@ fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
let slot = cli.slot.clone().unwrap_or(String::from("active")); let slot = cli.slot.clone().unwrap_or(String::from("active"));
item::take(&cli.file, &slot, true, cli.slot.is_some())?; item::take(&cli.file, &slot, true, cli.slot.is_some(), true)?;
Ok(()) Ok(())
} }

View file

@ -39,9 +39,21 @@ struct Cli {
#[arg(short = 'c', long)] #[arg(short = 'c', long)]
switch: bool, switch: bool,
/// Undo the last action /// 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
#[arg(short, long)] #[arg(short, long)]
undo: bool, migrate: bool,
/// Export an inventory /// Export an inventory
#[arg(short, long, value_names = &["SLOT", "ARCHIVE"], num_args = ..=2)] #[arg(short, long, value_names = &["SLOT", "ARCHIVE"], num_args = ..=2)]
@ -73,11 +85,17 @@ fn main() -> Result<()> {
let dir = unwrapped_dir.as_str(); let dir = unwrapped_dir.as_str();
if cli.switch { if cli.switch {
inv::switch(true)? inv::switch(true, true)?
} else if cli.undo {
history::undo()?
} else if cli.init { } else if cli.init {
inv::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() { } else if cli.export_inv.is_some() {
let unwrapped_export_inv = cli.export_inv.unwrap(); let unwrapped_export_inv = cli.export_inv.unwrap();
let export_inv_values = match unwrapped_export_inv.len() { let export_inv_values = match unwrapped_export_inv.len() {

View file

@ -21,11 +21,11 @@ use crate::message::{throw_error, ErrorType};
use anyhow::Result; use anyhow::Result;
use colored::control::set_override; use colored::control::set_override;
use config::Config; use config::Config;
use rusqlite::Connection;
use serde::Deserialize; use serde::Deserialize;
use std::env::current_dir; use std::env::current_dir;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub struct Settings { pub struct Settings {
pub vento_dir: PathBuf, pub vento_dir: PathBuf,
@ -33,11 +33,15 @@ pub struct Settings {
pub inactive_dir: PathBuf, pub inactive_dir: PathBuf,
} }
#[derive(Debug)]
pub struct HistoryData { pub struct HistoryData {
pub path: PathBuf, pub id: i32,
pub file: String, pub path: Option<PathBuf>,
pub slot: String, pub file: Option<String>,
pub slot: Option<String>,
pub action: Action, pub action: Action,
pub time: i64,
pub current: i32,
} }
pub struct DeserializedConfig { pub struct DeserializedConfig {
@ -60,6 +64,7 @@ struct History {
display_dir: bool, display_dir: bool,
} }
#[derive(Debug)]
pub enum Action { pub enum Action {
Take, Take,
Drop, Drop,
@ -139,26 +144,48 @@ pub fn parse_config() -> Result<DeserializedConfig> {
}) })
} }
/// Writes an action into the history file /// Writes an action into the history database
pub fn history(data: HistoryData) -> Result<()> { pub fn history(data: HistoryData) -> Result<()> {
let mut last_path = env_config()?.vento_dir; let mut path = env_config()?.vento_dir;
last_path.push("last"); path.push("history.db3");
let mut last_file = File::create(last_path)?; let db = Connection::open(path)?;
write!( // Create table if it doesn't exist.
&mut last_file, db.execute(
"{} "CREATE TABLE IF NOT EXISTS history (
{} id INTEGER PRIMARY KEY,
{} path TEXT,
{}", file TEXT,
data.path.to_str().unwrap(), slot TEXT,
data.file, action TEXT NOT NULL,
data.slot, time INTEGER NOT NULL,
match data.action { current INTEGER NOT NULL)",
Action::Take => "take", (),
Action::Drop => "drop", )?;
Action::Switch => "switch",
} // 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(),
),
)?; )?;
Ok(()) Ok(())

View file

@ -18,98 +18,174 @@
*/ */
use crate::{ use crate::{
common::{env_config, parse_config}, common::{self, env_config, parse_config, Action, HistoryData},
inv, item, inv, item,
message::{append_emoji, throw_error, EmojiType, ErrorType}, message::{append_emoji, throw_error, EmojiType, ErrorType},
}; };
use anyhow::Result; use anyhow::Result;
use chrono::prelude::*;
use colored::Colorize; use colored::Colorize;
use std::fs; use rusqlite::Connection;
use std::path::{Path, PathBuf}; use std::{
fs,
path::{Path, PathBuf},
};
/// Undoes the last action made by Vento using the history file located on the Vento directory /// Undoes actions made by Vento using the history database located on the Vento directory
pub fn undo() -> Result<()> { pub fn undo(steps: usize) -> Result<()> {
let lastpath: PathBuf = [env_config()?.vento_dir, Path::new("last").to_path_buf()] let path: PathBuf = [
.iter() env_config()?.vento_dir,
.collect(); Path::new("history.db3").to_path_buf(),
]
.iter()
.collect();
let db = Connection::open(path)?;
let lastfile = fs::read_to_string(lastpath)?; // 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))?;
let mut contents = vec![]; if last_action <= steps {
throw_error(ErrorType::InvalidStepsLength)?;
for line in lastfile.lines() {
contents.push(line);
} }
if contents.len() != 4 { let final_dest = last_action - steps;
throw_error(ErrorType::InvalidHistoryLength)?;
// 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],
)?;
} }
match contents[3] { // Prepares to display details of the final position
"take" => { let mut final_transaction = db.prepare("SELECT * FROM history WHERE current = 1")?;
let destpath = Path::new(contents[0]).to_path_buf(); let final_action_iter = final_transaction.query_map([], |row| {
item::drop( Ok(HistoryData {
&String::from(contents[1]), id: row.get(0)?,
contents[2], path: Some(PathBuf::from(row.get::<_, String>(1)?)),
destpath, file: row.get(2)?,
false, slot: row.get(3)?,
false, action: match row.get::<_, String>(4)?.as_str() {
)?; "take" => Action::Take,
} "drop" => Action::Drop,
"drop" => { "switch" => Action::Switch,
let path = vec![contents[0], contents[1]].join("/"); _ => unreachable!(),
item::take(&path, contents[2], false, false)?; },
} time: row.get(5)?,
"switch" => { current: row.get::<_, i32>(5)?,
inv::switch(false)?; })
} })?;
_ => throw_error(ErrorType::IllegalAction)?,
} 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!( println!(
"{}{}{}{}", "{}{}{}{}{}{}",
append_emoji(EmojiType::Success)?, append_emoji(EmojiType::Success)?,
match contents[3] { "Rolled back to ".green(),
"take" => "Take", match final_action.action {
"drop" => "Drop", Action::Take => "Take",
"switch" => "Switch", Action::Drop => "Drop",
_ => "Unknown", Action::Switch => "Switch",
} }
.bold(), .bold(),
" action undone".green(), " action, on ".green(),
match contents[3] { newdate,
"take" => format!( match final_action.action {
Action::Take => format!(
"{}{}{}{}{}{}{}", "{}{}{}{}{}{}{}",
" (".green(), " (".green(),
contents[1].bold(), final_action.file.unwrap().bold(),
", ".green(), ", ".green(),
match parse_config()?.history_display_dir { match parse_config()?.history_display_dir {
true => format!("{} {} ", "from".green(), contents[0],), true => format!(
"{} {} ",
"from".green(),
final_action.path.unwrap().to_str().unwrap(),
),
_ => String::new(), _ => String::new(),
}, },
"to ".green(), "to ".green(),
match contents[2] { match final_action.slot.clone().unwrap().as_str() {
"active" => contents[2].green(), "active" => final_action.slot.unwrap().green(),
"inactive" => contents[2].blue(), "inactive" => final_action.slot.unwrap().blue(),
_ => contents[2].red(), _ => final_action.slot.unwrap().red(),
} }
.bold(), .bold(),
" slot)".green(), " slot)".green(),
), ),
"drop" => format!( Action::Drop => format!(
"{}{}{}{}{}{}{}", "{}{}{}{}{}{}{}",
" (".green(), " (".green(),
contents[1].bold(), final_action.file.unwrap().bold(),
", from ".green(), ", from ".green(),
match contents[2] { match final_action.slot.clone().unwrap().as_str() {
"active" => contents[2].green(), "active" => final_action.slot.unwrap().green(),
"inactive" => contents[2].blue(), "inactive" => final_action.slot.unwrap().blue(),
_ => contents[2].red(), _ => final_action.slot.unwrap().red(),
} }
.bold(), .bold(),
" slot".green(), " slot".green(),
match parse_config()?.history_display_dir { match parse_config()?.history_display_dir {
true => format!(" {} {}", "to".green(), contents[0],), true => format!(
" {} {}",
"to".green(),
final_action.path.unwrap().to_str().unwrap(),
),
false => String::new(), false => String::new(),
}, },
")".green(), ")".green(),
@ -120,3 +196,451 @@ pub fn undo() -> Result<()> {
Ok(()) 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()]
.iter()
.collect();
if !last_path.is_file() {
throw_error(ErrorType::NoFileOrDir)?;
}
let last_file = fs::read_to_string(&last_path)?;
let mut contents = vec![];
for line in last_file.lines() {
contents.push(line);
}
if contents.len() != 4 {
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)?;
println!(
"{}{}",
append_emoji(EmojiType::Success)?,
"Migrated history file to database".green()
);
Ok(())
}

View file

@ -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 /// Switches inevntory slots between each other, making the currently active inventory inactive and viceversa
pub fn switch(message: bool) -> Result<()> { pub fn switch(message: bool, save_history: bool) -> Result<()> {
let ventodir = &common::env_config()?.vento_dir; let ventodir = &common::env_config()?.vento_dir;
let active = &common::env_config()?.active_dir; let active = &common::env_config()?.active_dir;
let inactive = &common::env_config()?.inactive_dir; let inactive = &common::env_config()?.inactive_dir;
@ -192,12 +192,17 @@ pub fn switch(message: bool) -> Result<()> {
fs::rename(inactive, active).context(rename_error)?; fs::rename(inactive, active).context(rename_error)?;
fs::rename(&temp, inactive).context(rename_error)?; fs::rename(&temp, inactive).context(rename_error)?;
common::history(common::HistoryData { if save_history {
path: PathBuf::new(), common::history(common::HistoryData {
file: String::new(), id: 0,
slot: String::new(), path: None,
action: common::Action::Switch, file: None,
})?; slot: None,
action: common::Action::Switch,
current: 1,
time: 0,
})?;
}
if message { if message {
println!( println!(

View file

@ -28,7 +28,13 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
/// Takes a file or directory and stores it in an inventory slot /// Takes a file or directory and stores it in an inventory slot
pub fn take(file: &String, slot: &str, message: bool, display_slot: bool) -> Result<()> { pub fn take(
file: &String,
slot: &str,
message: bool,
display_slot: bool,
save_history: bool,
) -> Result<()> {
let ventodir = &env_config()?.vento_dir; let ventodir = &env_config()?.vento_dir;
if !ventodir.is_dir() { if !ventodir.is_dir() {
@ -78,12 +84,17 @@ pub fn take(file: &String, slot: &str, message: bool, display_slot: bool) -> Res
throw_error(ErrorType::NoFileOrDir)?; throw_error(ErrorType::NoFileOrDir)?;
} }
history(HistoryData { if save_history {
path: sourcelocation.clone(), history(HistoryData {
file: String::from(filename), id: 0,
slot: String::from(slot), path: Some(sourcelocation.clone()),
action: Action::Take, file: Some(String::from(filename)),
})?; slot: Some(String::from(slot)),
action: Action::Take,
current: 1,
time: 0,
})?;
}
if message { if message {
println!( println!(
@ -125,6 +136,7 @@ pub fn drop(
dest: PathBuf, dest: PathBuf,
message: bool, message: bool,
display_slot: bool, display_slot: bool,
save_history: bool,
) -> Result<()> { ) -> Result<()> {
// Drops a file or directory // Drops a file or directory
let ventodir = &env_config()?.vento_dir; let ventodir = &env_config()?.vento_dir;
@ -180,12 +192,17 @@ pub fn drop(
destpath.pop(); destpath.pop();
history(HistoryData { if save_history {
path: destpath.clone(), history(HistoryData {
file: String::from(file), id: 0,
slot: String::from(slot), path: Some(destpath.clone()),
action: Action::Drop, file: Some(String::from(file)),
})?; slot: Some(String::from(slot)),
action: Action::Drop,
current: 1,
time: 0,
})?;
}
if message { if message {
println!( println!(

View file

@ -28,6 +28,8 @@ pub enum ErrorType {
NoCurrentDirectory, NoCurrentDirectory,
NoHomeDirectory, NoHomeDirectory,
InvalidHistoryLength, InvalidHistoryLength,
InvalidStepsLength,
SmallTerminal,
IllegalAction, IllegalAction,
NotInitialized, NotInitialized,
NoAccessParent, NoAccessParent,
@ -68,7 +70,9 @@ pub fn throw_error(error: ErrorType) -> Result<()> {
ErrorType::SpecifySlot => "You need to specify a slot", 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::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::NoHomeDirectory => "Vento was unable to detect your home directory. Have you configured your environment correctly?",
ErrorType::InvalidHistoryLength => "Invalid history length", ErrorType::InvalidHistoryLength => "Invalid history length",
ErrorType::InvalidStepsLength => "Invalid steps length",
ErrorType::SmallTerminal => "Your terminal needs to be at least 83 columns wide",
ErrorType::IllegalAction => "Illegal action", ErrorType::IllegalAction => "Illegal action",
ErrorType::NotInitialized => "Vento not initialized. Run \"vento -i\" to initialize Vento", ErrorType::NotInitialized => "Vento not initialized. Run \"vento -i\" to initialize Vento",
ErrorType::NoAccessParent => "Cannot access parent", ErrorType::NoAccessParent => "Cannot access parent",