mirror of
https://git.sr.ht/~nixgoat/vento
synced 2025-07-30 10:20:50 +00:00
Compare commits
99 commits
Author | SHA1 | Date | |
---|---|---|---|
|
e96110748e | ||
|
46ac7ee8b7 | ||
|
600c3e4f3a | ||
|
92b2942471 | ||
|
cba202fd0d | ||
|
73ef278372 | ||
|
2b8126eb53 | ||
|
343c97e92c | ||
|
9589fcb674 | ||
|
e9aa1b2ee1 | ||
|
1506dc811c | ||
|
63777e6f61 | ||
|
2ab970c371 | ||
|
bb02a070f9 | ||
|
a841ba974c | ||
|
52a830331c | ||
|
56c12f5d4e | ||
|
925513a136 | ||
|
c1576270b9 | ||
|
d80ccbea07 | ||
|
17217173f4 | ||
|
1e2fb49dac | ||
|
941d4c07d5 | ||
|
b5c0818719 | ||
|
0424ec2ba9 | ||
|
27b42e963e | ||
|
67d2da8767 | ||
|
5e2763f5df | ||
|
75fb04d674 | ||
|
d148a96e06 | ||
|
2a4ee258a3 | ||
|
13d0889ad1 | ||
|
c5cc1ea97a | ||
|
faf8036b19 | ||
|
b6162a9dcb | ||
|
791cdf3193 | ||
|
4427dadcfc | ||
|
9e5e0716d1 | ||
|
986c0f7f20 | ||
|
6750a387db | ||
|
86d3a00f02 | ||
|
1b533160e6 | ||
|
5a0988d2c6 | ||
|
833669af3c | ||
|
dadb0d721f | ||
|
ab313e2f50 | ||
|
ef3dcf8acc | ||
|
1cbfc5a568 | ||
|
19aa1955c8 | ||
|
c1533e6b41 | ||
|
6ad43bbf82 | ||
|
af0a27464b | ||
|
0434457475 | ||
|
4f0c52ac16 | ||
|
69ce377ec1 | ||
|
26699fe765 | ||
|
f8750f3515 | ||
|
5f5ae6e41e | ||
|
839d4dcc76 | ||
|
d0ddc93b47 | ||
|
3b9a405614 | ||
|
5e05bfa2ef | ||
|
d51163ef09 | ||
|
b9cea29592 | ||
|
4712c67bc2 | ||
|
b627d304d8 | ||
|
56ecc7d580 | ||
|
26cbbef870 | ||
|
9007b775e2 | ||
|
21c477e2ce | ||
|
0c08bc0a29 | ||
|
96178a11b3 | ||
|
a98a8147e9 | ||
|
6ec32037db | ||
|
6fa0cba8ca | ||
|
45dfcc9f19 | ||
|
8fe5bca167 | ||
|
ef5205ce3a | ||
|
3cd5ad758c | ||
|
d3a2391cf2 | ||
|
ac986f8822 | ||
|
aa233555a4 | ||
|
13e28e445c | ||
|
61235e7a63 | ||
|
edcf1408ee | ||
|
76ad4aad5a | ||
|
756c56ea9f | ||
|
d24727d301 | ||
|
d11a200d6f | ||
|
6c51a8da6e | ||
|
445b5b1671 | ||
|
6c10953e88 | ||
|
28bed7faff | ||
|
30f99bbda4 | ||
|
96588e7642 | ||
|
8034a25eb9 | ||
|
98a6b8deca | ||
|
cb2b59e641 | ||
|
c2e276c6e2 |
1283
Cargo.lock
generated
1283
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
39
Cargo.toml
39
Cargo.toml
|
@ -1,11 +1,42 @@
|
||||||
[package]
|
[package]
|
||||||
name = "vento"
|
name = "vento"
|
||||||
version = "0.1.0"
|
version = "1.4.0-alpha"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
description = "A CLI inventory for your files"
|
||||||
|
authors = ["Lux Aliaga <lux@nixgoat.me>"]
|
||||||
|
repository = "https://git.sr.ht/~nixgoat/vento"
|
||||||
|
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
keywords = ["utility", "file-manager", "inventory"]
|
||||||
|
categories = ["command-line-utilities", "filesystem"]
|
||||||
|
|
||||||
|
build = "build.rs"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dirs = "4.0.0"
|
dirs = "5.0"
|
||||||
colored = "2.0.0"
|
colored = "2"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.3"
|
||||||
|
anyhow = "1.0"
|
||||||
|
size_format = "1.0.2"
|
||||||
|
config = "0.14"
|
||||||
|
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"
|
||||||
|
anyhow = "1.0.65"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true
|
||||||
|
opt-level = "z"
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = "abort"
|
||||||
|
|
32
Makefile.toml
Normal file
32
Makefile.toml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
[tasks.install-binary]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["install", "--path", "."]
|
||||||
|
|
||||||
|
[tasks.install-manpages]
|
||||||
|
condition = { platforms = ["linux"] }
|
||||||
|
script = [
|
||||||
|
"cp /tmp/vento-man/* $HOME/.local/share/man/man1/",
|
||||||
|
"rm -rf /tmp/vento-man"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tasks.install]
|
||||||
|
dependencies = [
|
||||||
|
"install-binary",
|
||||||
|
"install-manpages"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tasks.clean-binary]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["clean"]
|
||||||
|
|
||||||
|
[tasks.clean-manpages]
|
||||||
|
condition = { platforms = ["linux"] }
|
||||||
|
script = [
|
||||||
|
"rm -rf /tmp/vento-man"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tasks.clean]
|
||||||
|
dependencies = [
|
||||||
|
"clean-binary",
|
||||||
|
"clean-manpages"
|
||||||
|
]
|
86
README.md
86
README.md
|
@ -1,25 +1,93 @@
|
||||||
# Vento
|

|
||||||
|
|
||||||
|
[](https://crates.io/crates/vento)
|
||||||
|
[](https://crates.io/crates/vento)
|
||||||
|
[](https://git.sr.ht/~nixgoat/vento/tree/master/item/LICENSE.md)
|
||||||
|
|
||||||
Vento is a utility which allows you to manage your files as if you're playing an old text adventure. It's made in Rust and originally inspired by [Chesapeake's Inventory](https://github.com/mothdotmonster/inventory).
|
Vento is a utility which allows you to manage your files as if you're playing an old text adventure. It's made in Rust and originally inspired by [Chesapeake's Inventory](https://github.com/mothdotmonster/inventory).
|
||||||
|
|
||||||
## Install
|
[](https://asciinema.org/a/524454)
|
||||||
|
|
||||||
Clone it using Git and build it with [Cargo](https://rustup.rs/)!
|
## Installation
|
||||||
|
|
||||||
|
### 1) Cargo (Recommended)
|
||||||
|
|
||||||
|
Make sure Rust is installed, along with `cargo`, Rust's package manager.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo install vento
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) Manually
|
||||||
|
|
||||||
|
Clone the repository using `git`.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://git.sr.ht/~nixgoat/vento && cd vento
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out to the latest stable release.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git checkout v1.3
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.a) cargo-make
|
||||||
|
|
||||||
|
This install method additionally installs the manpages for Vento. Make sure Rust, `cargo` and `cargo-make` are installed.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.b) Cargo
|
||||||
|
|
||||||
|
Make sure Rust is installed, along with `cargo`, Rust's package manager.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://codeberg.org/nixgoat/vento.git && cd vento
|
|
||||||
$ cargo install --path .
|
$ cargo install --path .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quickstart
|
## Quick Start
|
||||||
|
|
||||||
After installing, run `vento init`. This will create a `.vento` folder in your home directory which will store your inventories. After which, you can run `vento list` to display the files in your inventories, `vento take` to move a file into your active inventory and `vento drop` to drop a file out of it. If you're stuck, run `vento help`.
|
After installing, run:
|
||||||
|
|
||||||
## TODO:
|
```
|
||||||
|
$ vento -i
|
||||||
|
```
|
||||||
|
|
||||||
- [ ] Better implementation of `list`
|
This will create a `.vento` folder in your home directory, which will store your inventories. Some basic commands include:
|
||||||
- [ ] Code cleanup
|
|
||||||
|
```
|
||||||
|
// listing files in the currently active inventory
|
||||||
|
$ vento
|
||||||
|
|
||||||
|
// switching inventory slots
|
||||||
|
$ vento -c
|
||||||
|
|
||||||
|
// undoing last action
|
||||||
|
$ vento -u
|
||||||
|
|
||||||
|
// taking a file or directory
|
||||||
|
$ take <file|directory>
|
||||||
|
|
||||||
|
// dropping a file or directory
|
||||||
|
$ drop <file|directory> [destination]
|
||||||
|
```
|
||||||
|
|
||||||
|
For additional documentation, you can check the documentation for each command by running the following.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ (command) -h
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if Vento was installed through `cargo-make`, check the manpages by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ man (command)
|
||||||
|
```
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- [Chesapeake](https://moth.monster/) for the original concept
|
- [Chesapeake](https://moth.monster/) for the original concept
|
||||||
|
- [jo!](https://sr.ht/~j0lol/) for helping me with Rust concepts!
|
||||||
|
|
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
204
build.rs
Normal file
204
build.rs
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
/*
|
||||||
|
* Vento, a CLI inventory for your files.
|
||||||
|
* Copyright (C) 2022 Lux Aliaga
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use man::prelude::*;
|
||||||
|
use std::env;
|
||||||
|
use std::fs::{create_dir_all, File};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
struct Page {
|
||||||
|
content: String,
|
||||||
|
file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
if cfg!(unix) {
|
||||||
|
let pages = [vento()?, take()?, drop()?, ventotoml()?];
|
||||||
|
|
||||||
|
let tempdir = env::temp_dir().join("vento-man");
|
||||||
|
|
||||||
|
create_dir_all(tempdir.clone())?;
|
||||||
|
|
||||||
|
for page in &pages {
|
||||||
|
let tempfile = tempdir.join(&page.file);
|
||||||
|
let mut file = File::create(tempfile).unwrap();
|
||||||
|
write!(&mut file, "{}", &page.content).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vento() -> Result<Page> {
|
||||||
|
let content = Manual::new("vento")
|
||||||
|
.about("a CLI inventory for your files")
|
||||||
|
.author(Author::new("Lux Aliaga").email("lux@nixgoat.me"))
|
||||||
|
.description("List files and directories in the currently active inventory, the files in SLOT, the files in DIRECTORY or the files in DIRECTORY in SLOT.")
|
||||||
|
.flag(
|
||||||
|
Flag::new()
|
||||||
|
.short("-c")
|
||||||
|
.long("--switch")
|
||||||
|
.help("Switches inventory slots"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
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"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new()
|
||||||
|
.short("-e")
|
||||||
|
.long("--export-inv")
|
||||||
|
.help("Exports an inventory"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new()
|
||||||
|
.short("-E")
|
||||||
|
.long("--export-dir")
|
||||||
|
.help("Exports the Vento directory"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new()
|
||||||
|
.short("-g")
|
||||||
|
.long("--import-inv")
|
||||||
|
.help("Imports an inventory archive"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new()
|
||||||
|
.short("-G")
|
||||||
|
.long("--import-dir")
|
||||||
|
.help("Imports a Vento directory archive"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new()
|
||||||
|
.short("-i")
|
||||||
|
.long("--init")
|
||||||
|
.help("Initializes Vento with all its respective directories"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new()
|
||||||
|
.short("-h")
|
||||||
|
.long("--help")
|
||||||
|
.help("Shows the help message"),
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
Opt::new("slot")
|
||||||
|
.short("-s")
|
||||||
|
.long("--slot")
|
||||||
|
.help("The slot to list"),
|
||||||
|
)
|
||||||
|
.arg(Arg::new("[DIRECTORY]"))
|
||||||
|
.custom(
|
||||||
|
Section::new("before starting")
|
||||||
|
.paragraph("Vento will first need to initialize the respective directories before usage. Do this by running vento -i.")
|
||||||
|
)
|
||||||
|
.render();
|
||||||
|
|
||||||
|
Ok(Page {
|
||||||
|
content,
|
||||||
|
file: String::from("vento.1"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take() -> Result<Page> {
|
||||||
|
let content = Manual::new("take")
|
||||||
|
.about("a file grabber for Vento")
|
||||||
|
.author(Author::new("Lux Aliaga").email("lux@nixgoat.me"))
|
||||||
|
.description("Take FILE and put it in the inventory.")
|
||||||
|
.option(
|
||||||
|
Opt::new("slot")
|
||||||
|
.short("-s")
|
||||||
|
.long("--slot")
|
||||||
|
.help("The slot to put the file in"),
|
||||||
|
)
|
||||||
|
.arg(Arg::new("FILE"))
|
||||||
|
.render();
|
||||||
|
|
||||||
|
Ok(Page {
|
||||||
|
content,
|
||||||
|
file: String::from("take.1"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop() -> Result<Page> {
|
||||||
|
let content = Manual::new("drop")
|
||||||
|
.about("a file dropper for Vento")
|
||||||
|
.author(Author::new("Lux Aliaga").email("lux@nixgoat.me"))
|
||||||
|
.description("Take FILE off the inventory and drop it in DESTINATION.")
|
||||||
|
.option(
|
||||||
|
Opt::new("slot")
|
||||||
|
.short("-s")
|
||||||
|
.long("--slot")
|
||||||
|
.help("The slot to take the file from"),
|
||||||
|
)
|
||||||
|
.arg(Arg::new("FILE"))
|
||||||
|
.arg(Arg::new("[DESTINATION]"))
|
||||||
|
.render();
|
||||||
|
|
||||||
|
Ok(Page {
|
||||||
|
content,
|
||||||
|
file: String::from("drop.1"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ventotoml() -> Result<Page> {
|
||||||
|
let content = Manual::new("vento.toml")
|
||||||
|
.about("configuration file for Vento")
|
||||||
|
.author(Author::new("Lux Aliaga").email("lux@nixgoat.me"))
|
||||||
|
.description("This is the configuration file for the vento(1), take(1) and drop(1) utilities. Its presence and all its directives are optional. Directives prefixed with \"name.directive\" indicate a separate section in config file, denoted by brackets.")
|
||||||
|
.custom (
|
||||||
|
Section::new("supported directives")
|
||||||
|
.paragraph("directory = \"PATH\": Changes the path in which Vento's inventories are saved in.")
|
||||||
|
.paragraph("display_emoji = (true | false): Sets whether emojis will be prefixed on messages or not.")
|
||||||
|
.paragraph("display_colors = (true | false): Sets whether messages will be colored.")
|
||||||
|
.paragraph("item.display_dir = (true | false): Sets whether item actions will show the paths involved in the operation.")
|
||||||
|
.paragraph("history.display_dir = (true | false): Sets whether history actions will show the paths involved in the operation.")
|
||||||
|
)
|
||||||
|
.custom (
|
||||||
|
Section::new("files")
|
||||||
|
.paragraph("Linux: $XDG_CONFIG_HOME/vento.toml")
|
||||||
|
.paragraph("macOS: $HOME/Library/Application Support/vento.toml")
|
||||||
|
.paragraph("Windows: {FOLDERID_RoamingAppData}\\\\vento.toml")
|
||||||
|
)
|
||||||
|
.render();
|
||||||
|
|
||||||
|
Ok(Page {
|
||||||
|
content,
|
||||||
|
file: String::from("vento.toml.1"),
|
||||||
|
})
|
||||||
|
}
|
133
src/archive.rs
Normal file
133
src/archive.rs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
* Vento, a CLI inventory for your files.
|
||||||
|
* Copyright (C) 2023 Lux Aliaga
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common,
|
||||||
|
message::{append_emoji, EmojiType},
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use colored::Colorize;
|
||||||
|
use std::{fs::File, path::PathBuf};
|
||||||
|
use tar::Archive;
|
||||||
|
use xz2::read::XzDecoder;
|
||||||
|
use xz2::write::XzEncoder;
|
||||||
|
|
||||||
|
/// Exports an inventory slot into an xz tarball
|
||||||
|
pub fn export_inv(slot: &str, output: PathBuf, message: bool) -> Result<()> {
|
||||||
|
let slotdir: PathBuf = match slot {
|
||||||
|
"active" | "a" => common::env_config()?.active_dir,
|
||||||
|
"inactive" | "i" => common::env_config()?.inactive_dir,
|
||||||
|
_ => PathBuf::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let archive = File::create(&output)?;
|
||||||
|
let enc = XzEncoder::new(archive, 9);
|
||||||
|
let mut tar = tar::Builder::new(enc);
|
||||||
|
tar.append_dir_all("", slotdir)?;
|
||||||
|
|
||||||
|
if message {
|
||||||
|
println!(
|
||||||
|
"{}{} {} {} {}",
|
||||||
|
append_emoji(EmojiType::Success)?,
|
||||||
|
"Exported".green(),
|
||||||
|
match slot {
|
||||||
|
"a" | "active" => "active".green(),
|
||||||
|
"i" | "inactive" => "inactive".blue(),
|
||||||
|
_ => slot.red(),
|
||||||
|
}
|
||||||
|
.bold(),
|
||||||
|
"slot into".green(),
|
||||||
|
&output.to_str().unwrap()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exports the Vento directory into an xz tarball
|
||||||
|
pub fn export_dir(output: PathBuf, message: bool) -> Result<()> {
|
||||||
|
let dir: PathBuf = common::env_config()?.vento_dir;
|
||||||
|
|
||||||
|
let archive = File::create(&output)?;
|
||||||
|
let enc = XzEncoder::new(archive, 9);
|
||||||
|
let mut tar = tar::Builder::new(enc);
|
||||||
|
tar.append_dir_all("", dir)?;
|
||||||
|
|
||||||
|
if message {
|
||||||
|
println!(
|
||||||
|
"{}{} {}",
|
||||||
|
append_emoji(EmojiType::Success)?,
|
||||||
|
"Exported Vento directory into".green(),
|
||||||
|
&output.to_str().unwrap()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Imports an xz tarball into an inventory slot
|
||||||
|
pub fn import_inv(input: PathBuf, slot: &str, message: bool) -> Result<()> {
|
||||||
|
let slotdir: PathBuf = match slot {
|
||||||
|
"active" | "a" => common::env_config()?.active_dir,
|
||||||
|
"inactive" | "i" => common::env_config()?.inactive_dir,
|
||||||
|
_ => PathBuf::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tar_xz = File::open(&input)?;
|
||||||
|
let tar = XzDecoder::new(tar_xz);
|
||||||
|
let mut archive = Archive::new(tar);
|
||||||
|
archive.unpack(&slotdir)?;
|
||||||
|
|
||||||
|
if message {
|
||||||
|
println!(
|
||||||
|
"{}{} {} {} {} {}",
|
||||||
|
append_emoji(EmojiType::Success)?,
|
||||||
|
"Imported".green(),
|
||||||
|
&input.to_str().unwrap(),
|
||||||
|
"into".green(),
|
||||||
|
match slot {
|
||||||
|
"a" | "active" => "active".green(),
|
||||||
|
"i" | "inactive" => "inactive".blue(),
|
||||||
|
_ => slot.red(),
|
||||||
|
}
|
||||||
|
.bold(),
|
||||||
|
"slot".green()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Imports an xz tarball into the Vento directory
|
||||||
|
pub fn import_dir(input: PathBuf, message: bool) -> Result<()> {
|
||||||
|
let dir: PathBuf = common::env_config()?.vento_dir;
|
||||||
|
|
||||||
|
let tar_xz = File::open(&input)?;
|
||||||
|
let tar = XzDecoder::new(tar_xz);
|
||||||
|
let mut archive = Archive::new(tar);
|
||||||
|
archive.unpack(&dir)?;
|
||||||
|
|
||||||
|
if message {
|
||||||
|
println!(
|
||||||
|
"{}{} {} {}",
|
||||||
|
append_emoji(EmojiType::Success)?,
|
||||||
|
"Imported".green(),
|
||||||
|
&input.to_str().unwrap(),
|
||||||
|
"into Vento directory".green(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
50
src/bin/drop.rs
Normal file
50
src/bin/drop.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Vento, a CLI inventory for your files.
|
||||||
|
* Copyright (C) 2022 Lux Aliaga
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use vento::{common::get_current_dir, item};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "Drop")]
|
||||||
|
#[command(about = "A file dropper for Vento", long_about = None)]
|
||||||
|
#[command(author, version)]
|
||||||
|
struct Cli {
|
||||||
|
/// Pick a slot to drop the file from
|
||||||
|
#[arg(short, long)]
|
||||||
|
slot: Option<String>,
|
||||||
|
|
||||||
|
/// File to drop from inventory
|
||||||
|
file: String,
|
||||||
|
/// Location to drop file onto
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Handles args in Drop
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let unwrapped_slot = cli.slot.clone().unwrap_or(String::from("active"));
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
45
src/bin/take.rs
Normal file
45
src/bin/take.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Vento, a CLI inventory for your files.
|
||||||
|
* Copyright (C) 2022 Lux Aliaga
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use vento::{common::override_color, item};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "Take")]
|
||||||
|
#[command(about = "A file grabber for Vento", long_about = None)]
|
||||||
|
#[command(author, version)]
|
||||||
|
struct Cli {
|
||||||
|
/// Pick a slot to take the file into
|
||||||
|
#[arg(short, long)]
|
||||||
|
slot: Option<String>,
|
||||||
|
|
||||||
|
/// File to take
|
||||||
|
file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Handles args in Vento
|
||||||
|
override_color()?;
|
||||||
|
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)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
149
src/bin/vento.rs
Normal file
149
src/bin/vento.rs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* Vento, a CLI inventory for your files.
|
||||||
|
* Copyright (C) 2022 Lux Aliaga
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use vento::{
|
||||||
|
archive,
|
||||||
|
common::override_color,
|
||||||
|
history, inv,
|
||||||
|
message::{throw_error, ErrorType},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "Vento")]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
/// Pick slot to list
|
||||||
|
#[arg(short, long)]
|
||||||
|
slot: Option<String>,
|
||||||
|
|
||||||
|
/// Switch slots
|
||||||
|
#[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
|
||||||
|
#[arg(short, long)]
|
||||||
|
migrate: bool,
|
||||||
|
|
||||||
|
/// Export an inventory
|
||||||
|
#[arg(short, long, value_names = &["SLOT", "ARCHIVE"], num_args = ..=2)]
|
||||||
|
export_inv: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Export the Vento directory
|
||||||
|
#[arg(short = 'E', long, default_missing_value = "vento.tar.xz", value_name = "ARCHIVE", num_args = ..=1)]
|
||||||
|
export_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Import an inventory archive
|
||||||
|
#[arg(short = 'g', long, num_args = 1..=2, value_names = &["ARCHIVE", "SLOT"])]
|
||||||
|
import_inv: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Import a Vento directory archive
|
||||||
|
#[arg(short = 'G', long, value_name = "ARCHIVE")]
|
||||||
|
import_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Initialize Vento
|
||||||
|
#[arg(short, long)]
|
||||||
|
init: bool,
|
||||||
|
|
||||||
|
directory: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
override_color()?;
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let unwrapped_dir = cli.directory.unwrap_or(String::new());
|
||||||
|
let dir = unwrapped_dir.as_str();
|
||||||
|
|
||||||
|
if cli.switch {
|
||||||
|
inv::switch(true, true)?
|
||||||
|
} 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() {
|
||||||
|
0 => vec![String::from("active"), String::from("active.tar.xz")],
|
||||||
|
_ => unwrapped_export_inv,
|
||||||
|
};
|
||||||
|
|
||||||
|
archive::export_inv(
|
||||||
|
match export_inv_values[0].as_str() {
|
||||||
|
"active" | "inactive" | "a" | "i" => export_inv_values[0].as_str(),
|
||||||
|
_ => "active",
|
||||||
|
},
|
||||||
|
PathBuf::from(match export_inv_values[0].as_str() {
|
||||||
|
"active" | "inactive" | "a" | "i" => export_inv_values[1].as_str(),
|
||||||
|
_ => export_inv_values[0].as_str(),
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
)?
|
||||||
|
} else if cli.export_dir.is_some() {
|
||||||
|
archive::export_dir(cli.export_dir.unwrap(), true)?
|
||||||
|
} else if cli.import_inv.is_some() {
|
||||||
|
let import_inv_values = &cli
|
||||||
|
.import_inv
|
||||||
|
.unwrap_or(vec![String::new(), String::from("active")]);
|
||||||
|
|
||||||
|
match import_inv_values[0].as_str() {
|
||||||
|
"" | "active" | "inactive" | "a" | "i" => throw_error(ErrorType::SpecifyFile)?,
|
||||||
|
_ => archive::import_inv(
|
||||||
|
PathBuf::from(&import_inv_values[0]),
|
||||||
|
match import_inv_values.len() {
|
||||||
|
2 => match import_inv_values[1].as_str() {
|
||||||
|
"active" | "inactive" | "a" | "i" => import_inv_values[1].as_str(),
|
||||||
|
_ => "active",
|
||||||
|
},
|
||||||
|
_ => "active",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
} else if cli.import_dir.is_some() {
|
||||||
|
archive::import_dir(cli.import_dir.unwrap(), true)?
|
||||||
|
} else {
|
||||||
|
inv::list(
|
||||||
|
cli.slot.clone().unwrap_or(String::from("active")).as_str(),
|
||||||
|
dir,
|
||||||
|
cli.slot.is_some(),
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
197
src/common.rs
197
src/common.rs
|
@ -17,20 +17,199 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::process;
|
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::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use colored::Colorize;
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
pub fn env_config() -> PathBuf { // Configures the directory for Vento
|
pub struct Settings {
|
||||||
let emptypath = PathBuf::new();
|
pub vento_dir: PathBuf,
|
||||||
|
pub active_dir: PathBuf,
|
||||||
|
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 action: Action,
|
||||||
|
pub time: i64,
|
||||||
|
pub current: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeserializedConfig {
|
||||||
|
pub directory: String,
|
||||||
|
pub display_dir: bool,
|
||||||
|
pub history_display_dir: bool,
|
||||||
|
pub display_emoji: bool,
|
||||||
|
pub display_colors: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[allow(unused)]
|
||||||
|
struct Item {
|
||||||
|
display_dir: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[allow(unused)]
|
||||||
|
struct History {
|
||||||
|
display_dir: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Action {
|
||||||
|
Take,
|
||||||
|
Drop,
|
||||||
|
Switch,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides required variables for Vento
|
||||||
|
pub fn env_config() -> Result<Settings> {
|
||||||
let home = match dirs::home_dir() {
|
let home = match dirs::home_dir() {
|
||||||
Option::Some(dir) => dir,
|
Option::Some(dir) => dir,
|
||||||
_ => PathBuf::new()
|
_ => PathBuf::new(),
|
||||||
};
|
};
|
||||||
if home == emptypath {
|
if home == PathBuf::new() {
|
||||||
println!("❌ {}", format!("Vento was unable to detect your home folder. Have you configured your environment correctly?").red());
|
throw_error(ErrorType::NoHomeDirectory)?;
|
||||||
process::exit(1);
|
};
|
||||||
|
let custom_dir = Path::new(&parse_config()?.directory).to_path_buf();
|
||||||
|
let vento_dir: PathBuf = if custom_dir != PathBuf::new() {
|
||||||
|
Path::new(&custom_dir).to_path_buf()
|
||||||
} else {
|
} else {
|
||||||
return [home, Path::new(".vento").to_path_buf()].iter().collect();
|
[home, Path::new(".vento").to_path_buf()].iter().collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let active_dir = [&vento_dir, &Path::new("active").to_path_buf()]
|
||||||
|
.iter()
|
||||||
|
.collect();
|
||||||
|
let inactive_dir = [&vento_dir, &Path::new("inactive").to_path_buf()]
|
||||||
|
.iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Settings {
|
||||||
|
vento_dir,
|
||||||
|
active_dir,
|
||||||
|
inactive_dir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles reading the config file or variables for Vento.
|
||||||
|
pub fn parse_config() -> Result<DeserializedConfig> {
|
||||||
|
let mut directory = String::new();
|
||||||
|
let mut display_dir = true;
|
||||||
|
let mut history_display_dir = true;
|
||||||
|
let mut display_emoji = true;
|
||||||
|
let mut display_colors = true;
|
||||||
|
let mut config = match dirs::config_dir() {
|
||||||
|
Option::Some(dir) => dir,
|
||||||
|
_ => PathBuf::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if config != PathBuf::new() {
|
||||||
|
config.push("vento.toml");
|
||||||
|
if config.is_file() {
|
||||||
|
let settings = Config::builder()
|
||||||
|
.add_source(config::File::with_name(
|
||||||
|
&config.as_path().display().to_string(),
|
||||||
|
))
|
||||||
|
.add_source(config::Environment::with_prefix("VENTO"))
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
directory = match settings.get_string("directory") {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(_) => String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
display_dir = settings.get_bool("item.display_dir").unwrap_or(true);
|
||||||
|
history_display_dir = settings.get_bool("history.display_dir").unwrap_or(true);
|
||||||
|
display_emoji = settings.get_bool("display_emoji").unwrap_or(true);
|
||||||
|
display_colors = settings.get_bool("display_colors").unwrap_or(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(DeserializedConfig {
|
||||||
|
directory,
|
||||||
|
display_dir,
|
||||||
|
history_display_dir,
|
||||||
|
display_emoji,
|
||||||
|
display_colors,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes an action into the history database
|
||||||
|
pub fn history(data: HistoryData) -> Result<()> {
|
||||||
|
let mut path = env_config()?.vento_dir;
|
||||||
|
path.push("history.db3");
|
||||||
|
let db = Connection::open(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(),
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets current directory for commands
|
||||||
|
pub fn get_current_dir() -> Result<PathBuf> {
|
||||||
|
let currentdir = match current_dir() {
|
||||||
|
Ok(dir) => dir,
|
||||||
|
Err(_) => PathBuf::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if currentdir == PathBuf::new() {
|
||||||
|
throw_error(ErrorType::NoCurrentDirectory)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(currentdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets color override if display_colors is disabled
|
||||||
|
pub fn override_color() -> Result<()> {
|
||||||
|
if !parse_config()?.display_colors {
|
||||||
|
set_override(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
646
src/history.rs
Normal file
646
src/history.rs
Normal file
|
@ -0,0 +1,646 @@
|
||||||
|
/*
|
||||||
|
* Vento, a CLI inventory for your files.
|
||||||
|
* Copyright (C) 2022 Lux Aliaga
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common::{self, env_config, parse_config, Action, HistoryData},
|
||||||
|
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},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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()]
|
||||||
|
.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(())
|
||||||
|
}
|
233
src/inv.rs
233
src/inv.rs
|
@ -17,68 +17,215 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{fs, process};
|
use super::{
|
||||||
use std::path::{Path, PathBuf};
|
common,
|
||||||
use std::io::{self, Write};
|
message::{append_emoji, throw_error, EmojiType, ErrorType},
|
||||||
|
};
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use super::common;
|
use size_format::SizeFormatterBinary;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{fs, process};
|
||||||
|
|
||||||
pub fn init() { // Initializes Vento
|
/// Initializes Vento by creating the respective directories it will use
|
||||||
let ventodir: PathBuf = common::env_config();
|
pub fn init() -> Result<()> {
|
||||||
|
let ventodir = &common::env_config()?.vento_dir;
|
||||||
|
|
||||||
if ventodir.is_dir() { // Checks if Vento has already been initialized and prompts the user if they want to initialize it again
|
if ventodir.is_dir() {
|
||||||
|
// Checks if Vento has already been initialized and prompts the user if they want to initialize it again
|
||||||
let mut answer = String::new();
|
let mut answer = String::new();
|
||||||
print!("⚠️ {} {}", format!("WARNING:").bold().red(), "Vento has already been initialized. Reinitializing will delete all files on the directory for Vento. Do you wish to proceed? (y/N) ");
|
print!("{}{} Vento has already been initialized. Reinitializing will delete all files on the directory for Vento. Do you wish to proceed? (y/N) ", append_emoji(EmojiType::Warning)?, "WARNING:".bold().red());
|
||||||
let _ = io::stdout().flush();
|
let _ = io::stdout().flush();
|
||||||
io::stdin()
|
io::stdin().read_line(&mut answer)?;
|
||||||
.read_line(&mut answer)
|
|
||||||
.expect("❌ Failed to read input");
|
|
||||||
match answer.as_str().trim() {
|
match answer.as_str().trim() {
|
||||||
"y" | "Y" => {fs::remove_dir_all(&ventodir).expect("❌ Vento was unable to initalize. Do you have the correct permissions?");},
|
"y" | "Y" => fs::remove_dir_all(ventodir)?,
|
||||||
"n" | "N" | _ => process::exit(0)
|
_ => process::exit(0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
create_slots(ventodir);
|
create_slots()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list(slot: &str) { // Lists files in inventory
|
/// Lists files in the provided slot and/or directory
|
||||||
let ventodir: PathBuf = common::env_config();
|
pub fn list(slot: &str, dir: &str, display_slot: bool) -> Result<()> {
|
||||||
let slotdir: PathBuf = [ventodir.to_path_buf(), Path::new(slot).to_path_buf()].iter().collect();
|
let ventodir = &common::env_config()?.vento_dir;
|
||||||
|
|
||||||
if slotdir.is_dir() { // Checks if inventory selected exists
|
if !ventodir.is_dir() {
|
||||||
println!("🗃️ {}", format!("Files in {} inventory:", match slot {
|
// Detects if Vento hasn't been initialized and bails if so
|
||||||
"active" => format!("{}", slot).bold(),
|
throw_error(ErrorType::NotInitialized)?;
|
||||||
"inactive" | _ => format!("{}", slot).blue().bold()
|
|
||||||
}).green());
|
|
||||||
for file in fs::read_dir(&slotdir).unwrap() {
|
|
||||||
println!(" - {}", file.unwrap().path().file_name().unwrap().to_os_string().into_string().unwrap());
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
println!("❌ {}", format!("Vento was unable to read that slot. Valid slots are {} and {}.", format!("active").green(), format!("inactive").blue()).red());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut slotdir: PathBuf = match slot {
|
||||||
|
"active" | "a" => common::env_config()?.active_dir,
|
||||||
|
"inactive" | "i" => common::env_config()?.inactive_dir,
|
||||||
|
_ => PathBuf::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !dir.is_empty() {
|
||||||
|
// Detects if the directory argument is not empty, and if so appends the path provided to the slot directory variable
|
||||||
|
slotdir = [&slotdir, &Path::new(dir).to_path_buf()].iter().collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir.to_string().contains("..") {
|
||||||
|
// Basically preventing from listing anything out of bounds. ls and dir exist for that
|
||||||
|
throw_error(ErrorType::NoAccessParent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slotdir.is_dir() {
|
||||||
|
// Detects if the consulted slot or directory exists
|
||||||
|
bail!(
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"No such slot or directory. Valid slots are {} and {}",
|
||||||
|
"active".green().bold(),
|
||||||
|
"inactive".blue().bold()
|
||||||
|
)
|
||||||
|
.red()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if fs::read_dir(&slotdir).unwrap().count() == 0 {
|
||||||
|
// Detects if the slot or directory has any contents
|
||||||
|
println!(
|
||||||
|
"{}{}",
|
||||||
|
append_emoji(EmojiType::Inventory)?,
|
||||||
|
format!(
|
||||||
|
"No files in {}{}",
|
||||||
|
if display_slot || !dir.is_empty() {
|
||||||
|
match slot {
|
||||||
|
"active" => slot.bold(),
|
||||||
|
_ => slot.blue().bold(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"inventory".clear()
|
||||||
|
},
|
||||||
|
if !dir.is_empty() {
|
||||||
|
if cfg!(windows) {
|
||||||
|
format!("\\{}", dir)
|
||||||
|
} else {
|
||||||
|
format!("/{}", dir)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.green()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{}{}",
|
||||||
|
append_emoji(EmojiType::Inventory)?,
|
||||||
|
format!(
|
||||||
|
"Files in{}{} ({}):",
|
||||||
|
if display_slot || !dir.is_empty() {
|
||||||
|
format!(
|
||||||
|
" {}",
|
||||||
|
match slot {
|
||||||
|
"active" => slot.bold(),
|
||||||
|
_ => slot.blue().bold(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
if !dir.is_empty() {
|
||||||
|
if cfg!(windows) {
|
||||||
|
format!("\\{}", dir)
|
||||||
|
} else {
|
||||||
|
format!("/{}", dir)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
" inventory".to_string()
|
||||||
|
},
|
||||||
|
format!("{}", fs::read_dir(&slotdir).unwrap().count())
|
||||||
|
.white()
|
||||||
|
.bold()
|
||||||
|
)
|
||||||
|
.green()
|
||||||
|
);
|
||||||
|
for file in fs::read_dir(&slotdir).unwrap() {
|
||||||
|
let file = file.unwrap().path();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
" - [{}] {}{}",
|
||||||
|
if file.clone().is_dir() {
|
||||||
|
"D".blue()
|
||||||
|
} else if file.clone().is_symlink() {
|
||||||
|
"S".yellow()
|
||||||
|
} else {
|
||||||
|
"F".green()
|
||||||
|
},
|
||||||
|
file.clone()
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap(),
|
||||||
|
if file.clone().is_file() {
|
||||||
|
format!(
|
||||||
|
" ({}B)",
|
||||||
|
SizeFormatterBinary::new(file.clone().metadata().unwrap().len())
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn switch() { // Switches between inventory slots
|
/// Switches inevntory slots between each other, making the currently active inventory inactive and viceversa
|
||||||
let ventodir: PathBuf = common::env_config();
|
pub fn switch(message: bool, save_history: bool) -> Result<()> {
|
||||||
let active: PathBuf = [ventodir.to_path_buf(), Path::new("active").to_path_buf()].iter().collect();
|
let ventodir = &common::env_config()?.vento_dir;
|
||||||
let temp: PathBuf = [ventodir.to_path_buf(), Path::new("temp").to_path_buf()].iter().collect();
|
let active = &common::env_config()?.active_dir;
|
||||||
let inactive: PathBuf = [ventodir.to_path_buf(), Path::new("inactive").to_path_buf()].iter().collect();
|
let inactive = &common::env_config()?.inactive_dir;
|
||||||
|
let temp: PathBuf = [ventodir.to_path_buf(), Path::new("temp").to_path_buf()]
|
||||||
|
.iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
fs::rename(&active, &temp).expect("❌ Vento was unable to switch slots. Try running vento init and try again");
|
let rename_error = "Vento was unable to switch slots. Try running \"vento -i\" and try again";
|
||||||
fs::rename(&inactive, &active).expect("❌ Vento was unable to switch slots. Try running vento init and try again");
|
|
||||||
fs::rename(&temp, &inactive).expect("❌ Vento was unable to switch slots. Try running vento init and try again");
|
|
||||||
|
|
||||||
println!("🎉 {}", format!("Switched inventory slots!").green());
|
fs::rename(active, &temp).context(rename_error)?;
|
||||||
|
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,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if message {
|
||||||
|
println!(
|
||||||
|
"{}{}",
|
||||||
|
append_emoji(EmojiType::Success)?,
|
||||||
|
"Switched inventory slots!".green()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used only on init. Creates all required directories
|
||||||
|
fn create_slots() -> Result<()> {
|
||||||
|
let active = &common::env_config()?.active_dir;
|
||||||
|
let inactive = &common::env_config()?.inactive_dir;
|
||||||
|
|
||||||
fn create_slots(dir: PathBuf) { // Used only on init. Creates all required directories.
|
fs::create_dir_all(active)?;
|
||||||
let active: PathBuf = [dir.to_path_buf(), Path::new("active").to_path_buf()].iter().collect();
|
fs::create_dir_all(inactive)?;
|
||||||
let inactive: PathBuf = [dir.to_path_buf(), Path::new("inactive").to_path_buf()].iter().collect();
|
|
||||||
|
|
||||||
fs::create_dir_all(active).expect("❌ Vento was unable to initalize. Do you have the correct permissions?");
|
println!(
|
||||||
fs::create_dir_all(inactive).expect("❌ Vento was unable to initalize. Do you have the correct permissions?");
|
"{}{}",
|
||||||
|
append_emoji(EmojiType::Celebrate)?,
|
||||||
println!("🎉 {}", format!("Vento has been succesfully initialized!").green());
|
"Vento has been succesfully initialized!".green()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
226
src/item.rs
226
src/item.rs
|
@ -17,50 +17,222 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::fs;
|
use super::{
|
||||||
use fs_extra::dir::{CopyOptions, move_dir};
|
common::{env_config, history, parse_config, Action, HistoryData},
|
||||||
use std::path::{Path, PathBuf};
|
message::{append_emoji, throw_error, EmojiType, ErrorType},
|
||||||
|
};
|
||||||
|
use anyhow::{bail, Result};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use super::common;
|
use fs_extra::dir::{move_dir, CopyOptions};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub fn take(file: &String) { // Takes a file or directory
|
/// Takes a file or directory and stores it in an inventory slot
|
||||||
let ventodir = common::env_config();
|
pub fn take(
|
||||||
let active: PathBuf = [ventodir.to_path_buf(), Path::new("active").to_path_buf()].iter().collect();
|
file: &String,
|
||||||
|
slot: &str,
|
||||||
|
message: bool,
|
||||||
|
display_slot: bool,
|
||||||
|
save_history: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ventodir = &env_config()?.vento_dir;
|
||||||
|
|
||||||
|
if !ventodir.is_dir() {
|
||||||
|
// Detects if Vento hasn't been initialized and bails if so
|
||||||
|
throw_error(ErrorType::NotInitialized)?;
|
||||||
|
};
|
||||||
|
let slotdir: PathBuf = match slot {
|
||||||
|
"active" | "a" => env_config()?.active_dir,
|
||||||
|
"inactive" | "i" => env_config()?.inactive_dir,
|
||||||
|
_ => PathBuf::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !slotdir.is_dir() {
|
||||||
|
// Detects if the slot provided exists
|
||||||
|
bail!(
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"No such slot. Valid slots are {} and {}",
|
||||||
|
"active".green().bold(),
|
||||||
|
"inactive".blue().bold()
|
||||||
|
)
|
||||||
|
.red()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
let sourcepath: PathBuf = Path::new(&file).to_path_buf();
|
let sourcepath: PathBuf = Path::new(&file).to_path_buf();
|
||||||
let destpath: PathBuf = [&active, &Path::new(file).to_path_buf()].iter().collect();
|
let mut sourcelocation: PathBuf = fs::canonicalize(&sourcepath)?;
|
||||||
|
sourcelocation.pop();
|
||||||
|
let filename = Path::new(&file).file_name().unwrap().to_str().unwrap();
|
||||||
|
let destpath: PathBuf = [&slotdir, &Path::new(&filename).to_path_buf()]
|
||||||
|
.iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
if Path::exists(&destpath) {
|
if Path::exists(&destpath) {
|
||||||
println!("❌ {}", format!("A file with the same name already exists in your inventory!").red());
|
// Checks if there's a file with the same name in the inventory.
|
||||||
} else if sourcepath.is_file() | sourcepath.is_symlink() {
|
throw_error(ErrorType::ExistsInventory)?;
|
||||||
fs::copy(&file, &destpath).expect("❌ Vento was unable to copy the file.");
|
}
|
||||||
fs::remove_file(&file).expect("❌ Vento was unable to remove the file.");
|
|
||||||
|
if sourcepath.is_file() | sourcepath.is_symlink() {
|
||||||
|
// Checks the path's file type
|
||||||
|
fs::copy(file, &destpath)?;
|
||||||
|
fs::remove_file(file)?;
|
||||||
} else if sourcepath.is_dir() {
|
} else if sourcepath.is_dir() {
|
||||||
let options = CopyOptions::new();
|
let options = CopyOptions::new();
|
||||||
move_dir(&file, &active, &options).expect("❌ Vento was unable to move the directory.");
|
move_dir(file, &slotdir, &options)?;
|
||||||
} else {
|
} else {
|
||||||
println!("❌ {}", format!("No such file or directory.").red());
|
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,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if message {
|
||||||
|
println!(
|
||||||
|
"{}{} {}{}{}",
|
||||||
|
append_emoji(EmojiType::Success)?,
|
||||||
|
"Took".green(),
|
||||||
|
&filename.bold(),
|
||||||
|
match parse_config()?.display_dir {
|
||||||
|
true => format! {"{} {}",
|
||||||
|
" from".green(),
|
||||||
|
&sourcelocation.to_str().unwrap(),
|
||||||
|
},
|
||||||
|
_ => String::new(),
|
||||||
|
},
|
||||||
|
match display_slot {
|
||||||
|
true => format!(
|
||||||
|
"{} {} {}",
|
||||||
|
" to".green(),
|
||||||
|
match slot {
|
||||||
|
"active" => slot.green(),
|
||||||
|
"inactive" => slot.blue(),
|
||||||
|
_ => slot.red(),
|
||||||
|
}
|
||||||
|
.bold(),
|
||||||
|
"slot".green()
|
||||||
|
),
|
||||||
|
_ => String::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drop(file: &String, dest: PathBuf) { // Drops a file or directory
|
/// Drops a file or directory and stores it in an inventory slot
|
||||||
let ventodir = common::env_config();
|
pub fn drop(
|
||||||
// I know I've declared the same variable multiple times through this project. I might move this to the common file and just call it from there every single time I need it, but that'll be for later.
|
file: &String,
|
||||||
let active: PathBuf = [ventodir.to_path_buf(), Path::new("active").to_path_buf()].iter().collect();
|
slot: &str,
|
||||||
|
dest: PathBuf,
|
||||||
|
message: bool,
|
||||||
|
display_slot: bool,
|
||||||
|
save_history: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Drops a file or directory
|
||||||
|
let ventodir = &env_config()?.vento_dir;
|
||||||
|
|
||||||
let sourcepath: PathBuf = [&active, &Path::new(file).to_path_buf()].iter().collect();
|
if !ventodir.is_dir() {
|
||||||
let destpath: PathBuf = [Path::new(&dest).to_path_buf(), Path::new(file).to_path_buf()].iter().collect();
|
// Detects if Vento hasn't been initialized and bails if so
|
||||||
|
throw_error(ErrorType::NotInitialized)?;
|
||||||
|
};
|
||||||
|
|
||||||
if Path::exists(&destpath) { // HAHA YANDEREDEV MOMENT. This checks what method to use for the file/directory the user has picked
|
let slotdir: PathBuf = match slot {
|
||||||
println!("❌ {}", format!("A file with the same name already exists in the destination! Try renaming it or dropping this file somewhere else.").red());
|
"active" | "a" => env_config()?.active_dir,
|
||||||
} else if sourcepath.is_file() | sourcepath.is_symlink() {
|
"inactive" | "i" => env_config()?.inactive_dir,
|
||||||
fs::copy(&sourcepath, &destpath).expect("❌ Vento was unable to copy the file.");
|
_ => PathBuf::new(),
|
||||||
fs::remove_file(&sourcepath).expect("❌ Vento was unable to remove the file.");
|
};
|
||||||
|
|
||||||
|
if !slotdir.is_dir() {
|
||||||
|
// Detects if the slot provided exists
|
||||||
|
bail!(
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"No such slot. Valid slots are {} and {}",
|
||||||
|
"active".green().bold(),
|
||||||
|
"inactive".blue().bold()
|
||||||
|
)
|
||||||
|
.red()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let sourcepath: PathBuf = [&slotdir, &Path::new(file).to_path_buf()].iter().collect();
|
||||||
|
let mut destpath: PathBuf = [
|
||||||
|
Path::new(&dest).to_path_buf(),
|
||||||
|
Path::new(file).to_path_buf(),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if Path::exists(&destpath) {
|
||||||
|
// Checks if there's a file with the same name in the destination path.
|
||||||
|
throw_error(ErrorType::ExistsDestination)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourcepath.is_file() | sourcepath.is_symlink() {
|
||||||
|
// Checks the path's file type
|
||||||
|
fs::copy(&sourcepath, &destpath)?;
|
||||||
|
fs::remove_file(&sourcepath)?;
|
||||||
} else if sourcepath.is_dir() {
|
} else if sourcepath.is_dir() {
|
||||||
let destpath: PathBuf = Path::new(&dest).to_path_buf();
|
let destpath: PathBuf = Path::new(&dest).to_path_buf();
|
||||||
let options = CopyOptions::new();
|
let options = CopyOptions::new();
|
||||||
move_dir(&sourcepath, &destpath, &options).expect("❌ Vento was unable to move the directory.");
|
move_dir(&sourcepath, destpath, &options)?;
|
||||||
} else {
|
} else {
|
||||||
println!("❌ {}", format!("No such file or directory.").red());
|
throw_error(ErrorType::NoFileOrDir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if message {
|
||||||
|
println!(
|
||||||
|
"{}{} {}{}{}",
|
||||||
|
append_emoji(EmojiType::Success)?,
|
||||||
|
"Dropped".green(),
|
||||||
|
&file.bold(),
|
||||||
|
match display_slot {
|
||||||
|
true => format!(
|
||||||
|
"{} {} {}",
|
||||||
|
" from".green(),
|
||||||
|
match slot {
|
||||||
|
"active" => slot.green(),
|
||||||
|
"inactive" => slot.blue(),
|
||||||
|
_ => slot.red(),
|
||||||
|
}
|
||||||
|
.bold(),
|
||||||
|
"slot".green(),
|
||||||
|
),
|
||||||
|
false => String::new(),
|
||||||
|
},
|
||||||
|
match parse_config()?.display_dir {
|
||||||
|
true => format! {"{} {} ",
|
||||||
|
" into".green(),
|
||||||
|
&destpath.to_str().unwrap(),
|
||||||
|
},
|
||||||
|
_ => String::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
25
src/lib.rs
Normal file
25
src/lib.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Vento, a CLI inventory for your files.
|
||||||
|
* Copyright (C) 2022 Lux Aliaga
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod archive;
|
||||||
|
pub mod common;
|
||||||
|
pub mod history;
|
||||||
|
pub mod inv;
|
||||||
|
pub mod item;
|
||||||
|
pub mod message;
|
91
src/main.rs
91
src/main.rs
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
* Vento, a CLI inventory for your files.
|
|
||||||
* Copyright (C) 2022 Lux Aliaga
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::process;
|
|
||||||
use std::path::{Path};
|
|
||||||
use colored::Colorize;
|
|
||||||
|
|
||||||
mod inv;
|
|
||||||
mod item;
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
if args.len() >= 2 { // If the vector for the arguments the command is taking is larger than 2, it most likely means the user has provided an argument
|
|
||||||
match args[1].as_str() {
|
|
||||||
"help" | "h" => help(),
|
|
||||||
"init" | "i" => inv::init(),
|
|
||||||
"list" | "l" => {
|
|
||||||
if args.len() == 3 { // If the user has provided a slot, it'll use it. Otherwise, it'll default to the active slot
|
|
||||||
inv::list(args[2].as_str());
|
|
||||||
} else {
|
|
||||||
inv::list("active");
|
|
||||||
};
|
|
||||||
},
|
|
||||||
"switch" | "s" => inv::switch(),
|
|
||||||
"take" | "t" => {
|
|
||||||
if args.len() == 3 { // Similar thing with list, but change it with a file and it will show an error instead of defaulting to anything
|
|
||||||
item::take(&args[2]);
|
|
||||||
} else {
|
|
||||||
println!("❌ {}", format!("You need to specify a file.").red())
|
|
||||||
};
|
|
||||||
},
|
|
||||||
"drop" | "d" => {
|
|
||||||
if args.len() == 3 { // Tries to get the current directory if the user hasn't provided a "landing location"
|
|
||||||
item::drop(&args[2], match env::current_dir() {
|
|
||||||
Ok(dir) => dir,
|
|
||||||
Err(_) => {
|
|
||||||
println!("❌ {}", format!("Vento was unable to detect your current directory. Have you configured your environment correctly?").red());
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if args.len() == 4 {
|
|
||||||
item::drop(&args[2], Path::new(&args[3]).to_path_buf());
|
|
||||||
} else {
|
|
||||||
println!("❌ {}", format!("You need to specify a file.").red())
|
|
||||||
};
|
|
||||||
},
|
|
||||||
_ => println!("❔ Command not found. Type \"vento help\" to see all commands available.")
|
|
||||||
}
|
|
||||||
} else { // If the user provides no commands, it'll fall back to the help guide
|
|
||||||
help();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn help() { // A quick guide to move around in Vento
|
|
||||||
println!("{}, a CLI inventory for your files
|
|
||||||
© 2022 Lux Aliaga. Licensed under GPLv3
|
|
||||||
|
|
||||||
{}
|
|
||||||
- {}: Takes a file or directory and saves it in your inventory
|
|
||||||
- {}: Drops a file off of your inventory
|
|
||||||
- {}: Lists files in selected inventory
|
|
||||||
- {}: Switches slots
|
|
||||||
- {}: Initializes Vento
|
|
||||||
- {}: Displays this message",
|
|
||||||
format!("Vento").bold().blue(),
|
|
||||||
format!("Usage:").bold(),
|
|
||||||
format!("take | t <file | directory>").bold().green(),
|
|
||||||
format!("drop | d <file | directory> [destination]").bold().green(),
|
|
||||||
format!("list | l [slot]").bold().green(),
|
|
||||||
format!("switch | s").bold().green(),
|
|
||||||
format!("init | i").bold().green(),
|
|
||||||
format!("help | h").bold().green());
|
|
||||||
}
|
|
85
src/message.rs
Normal file
85
src/message.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Vento, a CLI inventory for your files.
|
||||||
|
* Copyright (C) 2023 Lux Aliaga
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::common::parse_config;
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
pub enum ErrorType {
|
||||||
|
TooManyArgs,
|
||||||
|
SpecifySlot,
|
||||||
|
SpecifyFile,
|
||||||
|
NoCurrentDirectory,
|
||||||
|
NoHomeDirectory,
|
||||||
|
InvalidHistoryLength,
|
||||||
|
InvalidStepsLength,
|
||||||
|
SmallTerminal,
|
||||||
|
IllegalAction,
|
||||||
|
NotInitialized,
|
||||||
|
NoAccessParent,
|
||||||
|
ExistsInventory,
|
||||||
|
ExistsDestination,
|
||||||
|
NoFileOrDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EmojiType {
|
||||||
|
Celebrate,
|
||||||
|
Success,
|
||||||
|
Warning,
|
||||||
|
Inventory,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_emoji(message: EmojiType) -> Result<String> {
|
||||||
|
let mut output: String = String::new();
|
||||||
|
|
||||||
|
if parse_config()?.display_emoji {
|
||||||
|
match message {
|
||||||
|
EmojiType::Celebrate => output = String::from("🎉 "),
|
||||||
|
EmojiType::Success => output = String::from("✅ "),
|
||||||
|
EmojiType::Inventory => output = String::from("🗃️ "),
|
||||||
|
EmojiType::Warning => output = String::from("⚠️ "),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays an error and exits
|
||||||
|
pub fn throw_error(error: ErrorType) -> Result<()> {
|
||||||
|
bail!(
|
||||||
|
"{}",
|
||||||
|
match error {
|
||||||
|
ErrorType::TooManyArgs => "Too many arguments",
|
||||||
|
ErrorType::SpecifyFile => "You need to specify a file",
|
||||||
|
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::IllegalAction => "Illegal action",
|
||||||
|
ErrorType::NotInitialized => "Vento not initialized. Run \"vento -i\" to initialize Vento",
|
||||||
|
ErrorType::NoAccessParent => "Cannot access parent",
|
||||||
|
ErrorType::ExistsInventory => "A file with the same name already exists in your inventory!",
|
||||||
|
ErrorType::ExistsDestination => "A file with the same name already exists in the destination! Try renaming it or dropping this file somewhere else",
|
||||||
|
ErrorType::NoFileOrDir => "No such file or directory",
|
||||||
|
}
|
||||||
|
.red()
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue