From b999c3596588cb5d53cf0ddb0fade6861ac3ee9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Sat, 31 May 2025 15:27:52 +0000 Subject: [PATCH] various minor refactorings mostly targeting CLI ergonomy --- src/cli.rs | 22 +++++++++++++++------- src/main.rs | 16 ++++++++-------- src/sharry/alias.rs | 2 +- src/sharry/file/chunks.rs | 38 +++++++++++++++++++++++--------------- src/sharry/file/mod.rs | 25 ++++++++++++++++--------- 5 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index ada694b..f875f9c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,7 +2,7 @@ use std::time::Duration; use clap::{Parser, builder::PossibleValuesParser}; -use super::sharry::File; +use super::sharry::{File, Alias, Uri, NewShareRequest}; #[derive(Parser, Debug, Hash)] #[command(version, about, long_about = None)] @@ -21,29 +21,29 @@ pub struct Cli { default_value = "https", value_name = "VARIANT", value_parser = PossibleValuesParser::new(["http", "https"]), )] - pub protocol: String, + protocol: String, /// Name of the new share #[arg(short, long, default_value = "ShrUpl Upload", value_name = "TEXT")] - pub name: String, + name: String, /// Description of the new share #[arg(short, long, value_name = "TEXT")] - pub description: Option, + description: Option, /// Maximum number of views for the new share #[arg(short, long, default_value_t = 100, value_name = "N")] - pub max_views: u32, + max_views: u32, /// Chunk size for uploading, in MiB #[arg(short, long, default_value_t = 10, value_name = "N")] pub chunk_size: usize, /// Base URL for Sharry Instance - pub url: String, + url: String, /// ID of a public alias to use - pub alias: String, + alias: String, /// Files to upload to the new share #[arg(value_name = "FILE", required = true, value_parser = parse_sharry_file)] @@ -62,4 +62,12 @@ impl Cli { pub fn get_timeout(&self) -> Option { (!self.timeout.is_zero()).then_some(self.timeout) } + + pub fn get_alias(&self) -> Alias { + Alias::new(Uri::with_protocol(&self.protocol, &self.url), &self.alias) + } + + pub fn get_share_request(&self) -> NewShareRequest { + NewShareRequest::new(&self.name, self.description.as_ref(), self.max_views) + } } diff --git a/src/main.rs b/src/main.rs index d38e939..7a34958 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use log::{error, info}; use ureq::Agent; use cli::Cli; -use sharry::{Alias, NewShareRequest, Share, Uri}; +use sharry::Share; fn main() { env_logger::init(); @@ -20,22 +20,22 @@ fn main() { .build() .into(); - let alias = Alias::new(Uri::with_protocol(args.protocol, args.url), args.alias); - - let share = NewShareRequest::new(args.name, args.description, args.max_views); - let share = Share::create(&agent, &alias, share).unwrap(); + let alias = args.get_alias(); + let share = Share::create(&agent, &alias, args.get_share_request()).unwrap(); info!("share: {share:?}"); for file in args.files { let file = file.create(&agent, &alias, &share).unwrap(); info!("file: {file:?}"); - for chunk in file.chunked(args.chunk_size * 1024 * 1024) { + for chunk in file.chunked(args.chunk_size * 1024 * 1024).seek(0) { info!("chunk len: {}", chunk.bytes.len()); file.upload_chunk(&agent, &alias, &chunk) - .inspect_err(|e| error!("error: {e}")) - .unwrap(); + .unwrap_or_else(|e| { + error!("error: {e}"); + panic!("{e}"); + }); } } diff --git a/src/sharry/alias.rs b/src/sharry/alias.rs index 5f71225..8bd2cf7 100644 --- a/src/sharry/alias.rs +++ b/src/sharry/alias.rs @@ -5,7 +5,7 @@ use ureq::RequestBuilder; use super::api::Uri; -#[derive(Debug)] +#[derive(Debug, Hash)] pub struct Alias { pub(super) api_uri: String, pub(super) id: String, diff --git a/src/sharry/file/chunks.rs b/src/sharry/file/chunks.rs index 957f0fb..7f81246 100644 --- a/src/sharry/file/chunks.rs +++ b/src/sharry/file/chunks.rs @@ -1,23 +1,31 @@ use std::{ fs::File, io::{Read, Seek, SeekFrom}, - path::PathBuf, + path::Path, }; use log::error; pub struct FileChunks<'t> { - path: &'t PathBuf, - cnum: u64, - csize: usize, + file_path: &'t Path, + chunk_index: u64, + chunk_size: usize, } impl<'t> FileChunks<'t> { - pub(super) fn new(path: &'t PathBuf, chunk_size: usize) -> Self { + pub(super) fn new(path: &'t Path, chunk_size: usize) -> Self { Self { - path, - cnum: 0, - csize: chunk_size, + file_path: path, + chunk_index: 0, + chunk_size, + } + } + + pub fn seek(self, chunk_index: u64) -> Self { + Self { + file_path: self.file_path, + chunk_index, + chunk_size: self.chunk_size, } } } @@ -26,23 +34,23 @@ impl Iterator for FileChunks<'_> { type Item = Chunk; fn next(&mut self) -> Option { - let offset = { - let csize: u64 = self.csize.try_into().unwrap(); - self.cnum * csize - }; + let offset = self.chunk_index + * u64::try_from(self.chunk_size) + .inspect_err(|e| error!("Error converting to u64: {e}")) + .ok()?; - let mut f = File::open(self.path) + let mut f = File::open(self.file_path) .inspect_err(|e| error!("Error opening file: {e}")) .ok()?; f.seek(SeekFrom::Start(offset)).ok()?; - let mut bytes = vec![0; self.csize]; + let mut bytes = vec![0; self.chunk_size]; let read_len = (f.read(&mut bytes)) .inspect_err(|e| error!("Error reading file: {e}")) .ok()?; bytes.truncate(read_len); - self.cnum += 1; + self.chunk_index += 1; Some(Self::Item { offset, bytes }).filter(|c| !c.bytes.is_empty()) } diff --git a/src/sharry/file/mod.rs b/src/sharry/file/mod.rs index 08bf70c..434b987 100644 --- a/src/sharry/file/mod.rs +++ b/src/sharry/file/mod.rs @@ -3,8 +3,9 @@ mod chunks; use std::{ ffi::OsStr, fs::metadata, + hash::{Hash, Hasher}, io::{self, ErrorKind}, - path::{Path, PathBuf}, + path::{Path, PathBuf, absolute}, }; use log::{debug, error}; @@ -16,29 +17,35 @@ use super::{ }; pub use chunks::{Chunk, FileChunks}; -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone)] pub struct File { - path: PathBuf, + abs_path: PathBuf, name: String, size: u64, patch_uri: Option, } +impl Hash for File { + fn hash(&self, state: &mut H) { + self.abs_path.hash(state); + } +} + impl File { pub fn new(path: impl AsRef) -> io::Result { - let path = path.as_ref().to_owned(); + let abs_path = absolute(path)?; - let m = metadata(&path)?; + let m = metadata(&abs_path)?; if !m.is_file() { return Err(io::Error::new(ErrorKind::NotFound, "not a file")); } - let name = (path.file_name().and_then(OsStr::to_str)) + let name = (abs_path.file_name().and_then(OsStr::to_str)) .ok_or_else(|| io::Error::new(ErrorKind::NotFound, "bad file name"))? .to_string(); Ok(Self { - path, + abs_path, name, size: m.len(), patch_uri: None, @@ -76,7 +83,7 @@ impl File { debug!("received uri: {location}"); Ok(Self { - path: self.path, + abs_path: self.abs_path, name: self.name, size: self.size, patch_uri: Some(location), @@ -84,7 +91,7 @@ impl File { } pub fn chunked(&self, chunk_size: usize) -> FileChunks { - FileChunks::new(&self.path, chunk_size) + FileChunks::new(&self.abs_path, chunk_size) } pub fn upload_chunk(