From a6d47bf1832c7ae0d7d7b75f49c3bdb5bd5b7384 Mon Sep 17 00:00:00 2001 From: ChanTsune <41658782+ChanTsune@users.noreply.github.com> Date: Wed, 20 Sep 2023 23:53:37 +0900 Subject: [PATCH 1/3] :truck: move private function module --- cli/src/command.rs | 1 + cli/src/command/commons.rs | 133 +++++++++++++++++++++++++++++++++++ cli/src/command/create.rs | 138 +++---------------------------------- 3 files changed, 144 insertions(+), 128 deletions(-) create mode 100644 cli/src/command/commons.rs diff --git a/cli/src/command.rs b/cli/src/command.rs index 817d8543..f08af921 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -1,3 +1,4 @@ +mod commons; pub mod create; pub mod extract; pub mod list; diff --git a/cli/src/command/commons.rs b/cli/src/command/commons.rs new file mode 100644 index 00000000..28c98f13 --- /dev/null +++ b/cli/src/command/commons.rs @@ -0,0 +1,133 @@ +use crate::cli::{CipherAlgorithmArgs, CompressionAlgorithmArgs}; +use libpna::{Entry, EntryBuilder, EntryName, EntryReference, Permission, WriteOption}; +#[cfg(unix)] +use nix::unistd::{Group, User}; +use std::fs::metadata; +use std::io::Write; +#[cfg(unix)] +use std::os::unix::fs::{MetadataExt, PermissionsExt}; +use std::time::UNIX_EPOCH; +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +pub(crate) fn collect_items( + files: Vec, + recursive: bool, + keep_dir: bool, +) -> io::Result> { + fn collect_items( + result: &mut Vec, + path: &Path, + recursive: bool, + keep_dir: bool, + ) -> io::Result<()> { + if path.is_dir() { + if keep_dir { + result.push(path.to_path_buf()); + } + if recursive { + for p in std::fs::read_dir(path)? { + collect_items(result, &p?.path(), recursive, keep_dir)?; + } + } + } else if path.is_file() { + result.push(path.to_path_buf()); + } + Ok(()) + } + let mut target_items = vec![]; + for p in files { + collect_items(&mut target_items, p.as_ref(), recursive, keep_dir)?; + } + Ok(target_items) +} + +pub(crate) fn create_entry( + path: &Path, + option: WriteOption, + keep_timestamp: bool, + keep_permission: bool, +) -> io::Result { + if path.is_symlink() { + let source = fs::read_link(path)?; + let entry = EntryBuilder::new_symbolic_link( + EntryName::from_path_lossy(path), + EntryReference::try_from(source.as_path()).unwrap(), + )?; + return apply_metadata(entry, path, keep_timestamp, keep_permission)?.build(); + } else if path.is_file() { + let mut entry = EntryBuilder::new_file(EntryName::from_path_lossy(path), option)?; + entry.write_all(&fs::read(path)?)?; + return apply_metadata(entry, path, keep_timestamp, keep_permission)?.build(); + } else if path.is_dir() { + let entry = EntryBuilder::new_dir(EntryName::from_path_lossy(path)); + return apply_metadata(entry, path, keep_timestamp, keep_permission)?.build(); + } + Err(io::Error::new( + io::ErrorKind::Unsupported, + "Currently not a regular file is not supported.", + )) +} + +pub(crate) fn entry_option( + compression: CompressionAlgorithmArgs, + cipher: CipherAlgorithmArgs, + password: Option, +) -> WriteOption { + let mut option_builder = WriteOption::builder(); + let (algorithm, level) = compression.algorithm(); + option_builder.compression(algorithm); + if let Some(level) = level { + option_builder.compression_level(level); + } + option_builder + .encryption(if password.is_some() { + cipher.algorithm() + } else { + libpna::Encryption::No + }) + .cipher_mode(cipher.mode()) + .password(password); + option_builder.build() +} + +pub(crate) fn apply_metadata( + mut entry: EntryBuilder, + path: &Path, + keep_timestamp: bool, + keep_permission: bool, +) -> io::Result { + if keep_timestamp || keep_permission { + let meta = metadata(path)?; + if keep_timestamp { + if let Ok(c) = meta.created() { + if let Ok(created_since_unix_epoch) = c.duration_since(UNIX_EPOCH) { + entry.created(created_since_unix_epoch); + } + } + if let Ok(m) = meta.modified() { + if let Ok(modified_since_unix_epoch) = m.duration_since(UNIX_EPOCH) { + entry.modified(modified_since_unix_epoch); + } + } + } + #[cfg(unix)] + if keep_permission { + let mode = meta.permissions().mode() as u16; + let uid = meta.uid(); + let gid = meta.gid(); + let user = User::from_uid(uid.into())?.unwrap(); + let group = Group::from_gid(gid.into())?.unwrap(); + entry.permission(Permission::new( + uid.into(), + user.name, + gid.into(), + group.name, + mode, + )); + } + } + Ok(entry) +} diff --git a/cli/src/command/create.rs b/cli/src/command/create.rs index c5697aa5..069a367c 100644 --- a/cli/src/command/create.rs +++ b/cli/src/command/create.rs @@ -1,24 +1,22 @@ use crate::{ - cli::{CipherAlgorithmArgs, CompressionAlgorithmArgs, CreateArgs, Verbosity}, - command::{ask_password, check_password, Let}, + cli::{CreateArgs, Verbosity}, + command::{ + ask_password, check_password, + commons::{collect_items, create_entry, entry_option}, + Let, + }, utils::part_name, }; use bytesize::ByteSize; use indicatif::{HumanDuration, ProgressBar, ProgressStyle}; use libpna::{ - ArchiveWriter, Entry, EntryBuilder, EntryName, EntryPart, EntryReference, Permission, - SolidEntriesBuilder, WriteOption, MIN_CHUNK_BYTES_SIZE, PNA_HEADER, + ArchiveWriter, EntryPart, SolidEntriesBuilder, WriteOption, MIN_CHUNK_BYTES_SIZE, PNA_HEADER, }; -#[cfg(unix)] -use nix::unistd::{Group, User}; use rayon::ThreadPoolBuilder; -#[cfg(unix)] -use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::{ - fs::{self, metadata, File}, - io::{self, Write}, - path::{Path, PathBuf}, - time::{Instant, UNIX_EPOCH}, + fs::{self, File}, + io, + time::Instant, }; pub(crate) fn create_archive(args: CreateArgs, verbosity: Verbosity) -> io::Result<()> { @@ -180,34 +178,6 @@ pub(crate) fn create_archive(args: CreateArgs, verbosity: Verbosity) -> io::Resu Ok(()) } -fn collect_items(files: Vec, recursive: bool, keep_dir: bool) -> io::Result> { - fn collect_items( - result: &mut Vec, - path: &Path, - recursive: bool, - keep_dir: bool, - ) -> io::Result<()> { - if path.is_dir() { - if keep_dir { - result.push(path.to_path_buf()); - } - if recursive { - for p in fs::read_dir(path)? { - collect_items(result, &p?.path(), recursive, keep_dir)?; - } - } - } else if path.is_file() { - result.push(path.to_path_buf()); - } - Ok(()) - } - let mut target_items = vec![]; - for p in files { - collect_items(&mut target_items, p.as_ref(), recursive, keep_dir)?; - } - Ok(target_items) -} - fn split_to_parts(mut entry_part: EntryPart, first: usize, max: usize) -> Vec { let mut parts = vec![]; let mut split_size = first; @@ -226,91 +196,3 @@ fn split_to_parts(mut entry_part: EntryPart, first: usize, max: usize) -> Vec io::Result { - if path.is_symlink() { - let source = fs::read_link(path)?; - let entry = EntryBuilder::new_symbolic_link( - EntryName::from_path_lossy(path), - EntryReference::try_from(source.as_path()).unwrap(), - )?; - return apply_metadata(entry, path, keep_timestamp, keep_permission)?.build(); - } else if path.is_file() { - let mut entry = EntryBuilder::new_file(EntryName::from_path_lossy(path), option)?; - entry.write_all(&fs::read(path)?)?; - return apply_metadata(entry, path, keep_timestamp, keep_permission)?.build(); - } else if path.is_dir() { - let entry = EntryBuilder::new_dir(EntryName::from_path_lossy(path)); - return apply_metadata(entry, path, keep_timestamp, keep_permission)?.build(); - } - Err(io::Error::new( - io::ErrorKind::Unsupported, - "Currently not a regular file is not supported.", - )) -} - -fn entry_option( - compression: CompressionAlgorithmArgs, - cipher: CipherAlgorithmArgs, - password: Option, -) -> WriteOption { - let mut option_builder = WriteOption::builder(); - let (algorithm, level) = compression.algorithm(); - option_builder.compression(algorithm); - if let Some(level) = level { - option_builder.compression_level(level); - } - option_builder - .encryption(if password.is_some() { - cipher.algorithm() - } else { - libpna::Encryption::No - }) - .cipher_mode(cipher.mode()) - .password(password); - option_builder.build() -} - -fn apply_metadata( - mut entry: EntryBuilder, - path: &Path, - keep_timestamp: bool, - keep_permission: bool, -) -> io::Result { - if keep_timestamp || keep_permission { - let meta = metadata(path)?; - if keep_timestamp { - if let Ok(c) = meta.created() { - if let Ok(created_since_unix_epoch) = c.duration_since(UNIX_EPOCH) { - entry.created(created_since_unix_epoch); - } - } - if let Ok(m) = meta.modified() { - if let Ok(modified_since_unix_epoch) = m.duration_since(UNIX_EPOCH) { - entry.modified(modified_since_unix_epoch); - } - } - } - #[cfg(unix)] - if keep_permission { - let mode = meta.permissions().mode() as u16; - let uid = meta.uid(); - let gid = meta.gid(); - let user = User::from_uid(uid.into())?.unwrap(); - let group = Group::from_gid(gid.into())?.unwrap(); - entry.permission(Permission::new( - uid.into(), - user.name, - gid.into(), - group.name, - mode, - )); - } - } - Ok(entry) -} From 2eadc84f6fe91ef6bb1d7fa87c3ea263f45929d9 Mon Sep 17 00:00:00 2001 From: ChanTsune <41658782+ChanTsune@users.noreply.github.com> Date: Thu, 21 Sep 2023 00:09:38 +0900 Subject: [PATCH 2/3] :zap: Add `--keep-dir`, `--keep-timestamp`,`--keep-permission` options to append subcommand --- cli/src/cli.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 73d5be62..bab67315 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -90,6 +90,12 @@ pub(crate) struct AppendArgs { pub(crate) recursive: bool, #[arg(long, help = "Overwrite file")] pub(crate) overwrite: bool, + #[arg(long, help = "Archiving the directories")] + pub(crate) keep_dir: bool, + #[arg(long, help = "Archiving the timestamp of the files")] + pub(crate) keep_timestamp: bool, + #[arg(long, help = "Archiving the permissions of the files")] + pub(crate) keep_permission: bool, #[command(flatten)] pub(crate) compression: CompressionAlgorithmArgs, #[command(flatten)] From 56725d373f2ebb3667a653eb3955e968ef3583a5 Mon Sep 17 00:00:00 2001 From: ChanTsune <41658782+ChanTsune@users.noreply.github.com> Date: Thu, 21 Sep 2023 00:12:29 +0900 Subject: [PATCH 3/3] :sparkles: Implement append subcommand beta --- cli/src/command.rs | 3 ++- cli/src/command/append.rs | 53 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 cli/src/command/append.rs diff --git a/cli/src/command.rs b/cli/src/command.rs index f08af921..f625f0b9 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -1,3 +1,4 @@ +pub mod append; mod commons; pub mod create; pub mod extract; @@ -11,7 +12,7 @@ pub fn entry(cli: Cli) -> io::Result<()> { Commands::Create(args) => { create::create_archive(args, cli.verbosity.verbosity())?; } - Commands::Append(args) => todo!("Append archive {}", args.file.archive.display()), + Commands::Append(args) => append::append_to_archive(args, cli.verbosity.verbosity())?, Commands::Extract(args) => { extract::extract_archive(args, cli.verbosity.verbosity())?; } diff --git a/cli/src/command/append.rs b/cli/src/command/append.rs new file mode 100644 index 00000000..8fbc1259 --- /dev/null +++ b/cli/src/command/append.rs @@ -0,0 +1,53 @@ +use crate::command::commons::{create_entry, entry_option}; +use crate::{ + cli::{AppendArgs, Verbosity}, + command::{ask_password, check_password, commons::collect_items}, +}; +use libpna::ArchiveReader; +use rayon::ThreadPoolBuilder; +use std::fs::File; +use std::io; + +pub(crate) fn append_to_archive(args: AppendArgs, verbosity: Verbosity) -> io::Result<()> { + let password = ask_password(args.password)?; + check_password(&password, &args.cipher); + let archive = args.file.archive; + if !archive.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("{} is not exists", archive.display()), + )); + } + let pool = ThreadPoolBuilder::default() + .build() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + let file = File::options().write(true).read(true).open(&archive)?; + let mut archive = ArchiveReader::read_header(file)?; + archive.seek_to_end()?; + + let target_items = collect_items(args.file.files, args.recursive, args.keep_dir)?; + let (tx, rx) = std::sync::mpsc::channel(); + let option = entry_option(args.compression, args.cipher, password); + for file in target_items { + let option = option.clone(); + let keep_timestamp = args.keep_timestamp; + let keep_permission = args.keep_permission; + let tx = tx.clone(); + pool.spawn_fifo(move || { + if verbosity == Verbosity::Verbose { + println!("Adding: {}", file.display()); + } + tx.send(create_entry(&file, option, keep_timestamp, keep_permission)) + .unwrap_or_else(|e| panic!("{e}: {}", file.display())); + }); + } + + drop(tx); + + for entry in rx.into_iter() { + archive.add_entry(entry?)?; + } + archive.finalize()?; + Ok(()) +}