From 65c127b86fa8b6a7bb106771c7fe06ce862a60fa Mon Sep 17 00:00:00 2001 From: trisua Date: Sun, 8 Jun 2025 00:42:56 -0400 Subject: [PATCH] add: ability to store entire directories --- README.md | 20 +++++++ src/engine/fs.rs | 1 - src/engine/rentry.rs | 1 - src/ledger.rs | 68 ++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 131 +++++++++++++++++++++++++++++++++++++++---- 6 files changed, 210 insertions(+), 12 deletions(-) create mode 100644 src/ledger.rs diff --git a/README.md b/README.md index 8438cf6..377106f 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,28 @@ Delete file and all chunk off remote: renbin -i ./path/to/file.ext.toml -e rentry -x ``` +Store entire directory as ledger: + +``` +renbin -i ./ -r -s target -s .git -n dir_name -e rentry +``` + +Note that `-s` (`--skip`) is used to ignore directories/files. Any file matching the names given through skip will be ignored. + +The `-n` (`--name`) flag is required to give a name to the ledger. + +Restore from ledger: + +``` +renbin -i ./dir_name.toml -r -e rentry -d +``` + ### Local You can also store files locally using `-e fs` (`--engine fs`). This mode is generally **_much_** quicker. This is the mode that will be used by default if no engine flag is provided. Local files are split into chunks of exactly 200 KB, while the rentry engine splits files into chunks of 150 KB. While larger chunks _could_ be used, that's not as fun. + +``` + +``` diff --git a/src/engine/fs.rs b/src/engine/fs.rs index a77697b..d6da4f2 100644 --- a/src/engine/fs.rs +++ b/src/engine/fs.rs @@ -34,7 +34,6 @@ impl Engine for FsEngine { descriptor.chunks.push(id); } - descriptor.write(PathBufD::current().join(format!("{}.toml", descriptor.name)))?; Ok(descriptor) } diff --git a/src/engine/rentry.rs b/src/engine/rentry.rs index db56a8e..50ac0da 100644 --- a/src/engine/rentry.rs +++ b/src/engine/rentry.rs @@ -145,7 +145,6 @@ impl Engine for RentryEngine { } // ... - descriptor.write(PathBufD::current().join(format!("{}.toml", descriptor.name)))?; Ok(descriptor) } diff --git a/src/ledger.rs b/src/ledger.rs new file mode 100644 index 0000000..a113ed4 --- /dev/null +++ b/src/ledger.rs @@ -0,0 +1,68 @@ +use crate::FileDescriptor; +use pathbufd::PathBufD; +use serde::{Deserialize, Serialize}; +use std::{ + fs::{metadata, read_dir, read_to_string, write}, + io::Result, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Ledger { + pub name: String, + pub dirs: Vec, + pub files: Vec, +} + +impl Ledger { + /// Read a [`Ledger`] from the given `path`. + pub fn read(path: PathBufD) -> Self { + toml::from_str(&read_to_string(path).expect("failed to read file")) + .expect("failed to deserialize file") + } + + /// Write a [`Ledger`] into the given `path`. + pub fn write(&self, path: PathBufD) -> Result<()> { + write( + path, + toml::to_string_pretty(&self).expect("failed to serialize file"), + ) + } + + /// Recursively collect paths from a directory. + /// + /// # Returns + /// `(files, dirs)` + pub fn collect_paths(root: &PathBufD, skip: &Vec) -> (Vec, Vec) { + let mut dirs = Vec::new(); + let mut files = Vec::new(); + + for x in read_dir(&root).expect("failed to read dir") { + let x = x.unwrap(); + let path = root.join(x.file_name()); + + if skip.contains(&x.file_name().into_string().unwrap()) { + continue; + } + + let stat = metadata(&path).expect("failed to stat file"); + + if stat.is_dir() { + dirs.push(x.path().to_str().unwrap().replace("../", "")); + + let (files_, dirs_) = Self::collect_paths(&path, skip); + + for file in files_ { + files.push(file); + } + + for dir in dirs_ { + dirs.push(dir); + } + } else { + files.push(x.path().into()); + } + } + + (files, dirs) + } +} diff --git a/src/lib.rs b/src/lib.rs index be248fd..429ebba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod engine; +pub mod ledger; use aes_gcm::{ Aes256Gcm, Nonce, diff --git a/src/main.rs b/src/main.rs index c864049..be18bf3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,16 @@ extern crate renbin; use clap::Parser; +use pathbufd::PathBufD; use renbin::{ FileDescriptor, engine::{Engine, fs::FsEngine, rentry::RentryEngine}, + ledger::Ledger, }; use std::{ - fs::{read, remove_file}, + fs::{create_dir, read, remove_file}, path::PathBuf, + str::FromStr, }; #[derive(Parser, Debug)] @@ -17,8 +20,14 @@ struct Args { decode: bool, #[arg(short = 'x', long = "delete", action)] delete: bool, + #[arg(short = 'r', long = "recursive", action)] + recursive: bool, + #[arg(short = 's', long = "skip", action)] + skip: Vec, + #[arg(short = 'n', long = "name")] + name: Option, #[arg(short = 'i', long = "input")] - path: String, + input: String, #[arg(short = 'e', long = "engine")] #[clap(default_value = "fs")] engine: String, @@ -28,9 +37,103 @@ struct Args { async fn main() { let args = Args::parse(); + if args.recursive + && let Some(name) = args.name + && !args.decode + { + // build ledger + let pathbuf = PathBuf::from_str(&args.input).unwrap(); + let (files, dirs) = Ledger::collect_paths(&pathbuf.clone().into(), &args.skip); + + Ledger { + name: name.clone(), + dirs, + files: { + let mut descriptors = Vec::new(); + + if args.engine == "fs" { + for file in files { + let descriptor = FsEngine + .process( + file.as_path() + .to_str() + .unwrap() + .to_string() + .replace("../", ""), + read(file).expect("failed to read file"), + ) + .await + .expect("failed to process file"); + + descriptors.push(descriptor); + } + } else if args.engine == "rentry" { + let mut engine = RentryEngine::new(); + engine.auth().await.expect("failed to extract csrf token"); + + for file in files { + let descriptor = engine + .process( + file.as_path() + .to_str() + .unwrap() + .to_string() + .replace("../", ""), + read(file).expect("failed to read file"), + ) + .await + .expect("failed to process file"); + + descriptors.push(descriptor); + } + } else { + unreachable!("invalid engine") + }; + + descriptors + }, + } + .write(PathBuf::from_str(&format!("{}.toml", name)).unwrap().into()) + .expect("failed to write ledger"); + + return; + } else if args.recursive && args.decode { + // decode ledger + let path = PathBuf::from(args.input); + let ledger = Ledger::read(path.clone().into()); + + // create dirs + for dir in ledger.dirs { + create_dir(dir).expect("failed to create dir"); + } + + // reconstruct files + for file in ledger.files { + if args.engine == "fs" { + FsEngine + .reconstruct(file) + .await + .expect("failed to reconstruct file"); + } else if args.engine == "rentry" { + let mut engine = RentryEngine::new(); + engine.auth().await.expect("failed to extract csrf token"); + + engine + .reconstruct(file) + .await + .expect("failed to reconstruct file"); + } else { + panic!("unknown engine type"); + }; + } + + // ... + return; + } + if args.delete { // delete - let path = PathBuf::from(args.path); + let path = PathBuf::from(args.input); if args.engine == "fs" { FsEngine @@ -55,27 +158,35 @@ async fn main() { if !args.decode { // encode - let pathbuf = PathBuf::from(args.path); + let pathbuf = PathBuf::from(args.input); if args.engine == "fs" { - FsEngine + let descriptor = FsEngine .process( pathbuf.file_name().unwrap().to_str().unwrap().to_string(), read(pathbuf).expect("failed to read file"), ) .await - .expect("failed to process file") + .expect("failed to process file"); + + descriptor + .write(PathBufD::current().join(format!("{}.toml", descriptor.name))) + .expect("failed to write descriptor"); } else if args.engine == "rentry" { let mut engine = RentryEngine::new(); engine.auth().await.expect("failed to extract csrf token"); - engine + let descriptor = engine .process( pathbuf.file_name().unwrap().to_str().unwrap().to_string(), read(pathbuf).expect("failed to read file"), ) .await - .expect("failed to process file") + .expect("failed to process file"); + + descriptor + .write(PathBufD::current().join(format!("{}.toml", descriptor.name))) + .expect("failed to write descriptor"); } else { panic!("unknown engine type"); }; @@ -83,7 +194,7 @@ async fn main() { // decode if args.engine == "fs" { FsEngine - .reconstruct(FileDescriptor::read(PathBuf::from(args.path).into())) + .reconstruct(FileDescriptor::read(PathBuf::from(args.input).into())) .await .expect("failed to reconstruct file"); } else if args.engine == "rentry" { @@ -91,7 +202,7 @@ async fn main() { engine.auth().await.expect("failed to extract csrf token"); engine - .reconstruct(FileDescriptor::read(PathBuf::from(args.path).into())) + .reconstruct(FileDescriptor::read(PathBuf::from(args.input).into())) .await .expect("failed to reconstruct file"); } else {