shrupl/src/cli.rs

160 lines
4.2 KiB
Rust
Raw Normal View History

use std::{convert::Infallible, fmt, io, time::Duration};
2025-05-28 00:07:59 +00:00
2025-07-03 16:20:39 +00:00
use base64::prelude::{BASE64_URL_SAFE_NO_PAD as BASE64URL, Engine};
use blake2b_simd::Params as Blake2b;
2025-07-03 12:58:53 +00:00
use clap::{Parser, builder::TypedValueParser, value_parser};
2025-06-18 18:58:49 +00:00
use log::LevelFilter;
2025-05-28 00:07:59 +00:00
use crate::{
file::{Checked, FileTrait},
2025-06-27 08:34:13 +00:00
sharry::{AliasID, Uri, json::NewShareRequest},
};
2025-05-28 12:35:35 +00:00
#[derive(Parser)]
2025-05-28 00:07:59 +00:00
#[command(version, about, long_about = None)]
pub struct Cli {
2025-05-28 13:42:31 +00:00
/// Timeout in seconds for HTTP actions (set 0 or invalid to disable)
#[arg(
2025-06-02 23:57:17 +00:00
short, long,
2025-05-28 13:42:31 +00:00
default_value = "10", value_name = "SECS",
value_parser = parse_seconds,
)]
timeout: Duration,
2025-05-28 00:07:59 +00:00
2025-06-18 18:44:24 +00:00
/// Number of times actions are retried
#[arg(short, long, default_value_t = 5, value_name = "N")]
retry_limit: u32,
2025-05-28 00:07:59 +00:00
/// Name of the new share
2025-05-28 12:35:35 +00:00
#[arg(short, long, default_value = "ShrUpl Upload", value_name = "TEXT")]
share_name: String,
2025-05-28 00:07:59 +00:00
/// Description of the new share
2025-05-28 00:21:14 +00:00
#[arg(short, long, value_name = "TEXT")]
description: Option<String>,
2025-05-28 00:07:59 +00:00
/// Maximum number of views for the new share
2025-05-28 12:35:35 +00:00
#[arg(short, long, default_value_t = 100, value_name = "N")]
max_views: u32,
2025-05-28 00:07:59 +00:00
/// Chunk size for uploading, in MiB
2025-06-15 00:46:02 +00:00
#[arg(
short, long,
default_value_t = 4, value_name = "M",
2025-06-15 00:46:02 +00:00
value_parser = value_parser!(u32).range(1..).map(|s| s as usize),
)]
2025-05-28 00:21:14 +00:00
pub chunk_size: usize,
2025-05-28 00:07:59 +00:00
/// Don't hash files before uploading
#[arg(short, long)]
no_hash: bool,
2025-06-18 18:58:49 +00:00
/// Increase output verbosity
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
2025-05-28 00:07:59 +00:00
/// Base URL for Sharry Instance
url: String,
2025-05-28 00:07:59 +00:00
/// ID of a public alias to use
2025-06-27 01:47:38 +00:00
pub alias: AliasID,
2025-05-28 00:07:59 +00:00
/// Files to upload to the new share
2025-05-28 12:35:35 +00:00
#[arg(value_name = "FILE", required = true, value_parser = parse_sharry_file)]
pub files: Vec<Checked>,
2025-05-28 12:35:35 +00:00
}
impl fmt::Debug for Cli {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Cli")
.field("uri", &self.get_uri())
2025-06-18 18:44:24 +00:00
.field("retry_limit", &self.retry_limit)
.field("alias", &self.alias)
.field("timeout", &self.get_timeout())
.field("chunk_size", &self.chunk_size)
.field("share_request", &self.get_share_request())
.field("files", &self.files)
2025-06-18 18:58:49 +00:00
.field("level_filter", &self.get_level_filter())
.field("hash", &self.get_hash())
.finish_non_exhaustive()
}
}
fn parse_seconds(data: &str) -> Result<Duration, Infallible> {
2025-05-28 13:42:31 +00:00
data.parse().or(Ok(0)).map(Duration::from_secs)
2025-05-28 12:35:35 +00:00
}
fn parse_sharry_file(data: &str) -> io::Result<Checked> {
Checked::new(data)
2025-05-28 00:07:59 +00:00
}
2025-05-28 13:42:31 +00:00
fn sorted<T>(values: &[T]) -> Vec<&T>
where
T: Ord,
{
let mut refs: Vec<_> = values.iter().collect();
refs.sort_unstable();
refs
}
2025-05-28 13:42:31 +00:00
impl Cli {
#[must_use]
2025-05-28 13:42:31 +00:00
pub fn get_timeout(&self) -> Option<Duration> {
(!self.timeout.is_zero()).then_some(self.timeout)
}
#[must_use]
pub fn get_uri(&self) -> Uri {
2025-07-03 12:58:53 +00:00
Uri::from(self.url.clone())
}
#[must_use]
2025-06-18 18:44:24 +00:00
pub fn may_retry(&self, tries: u32) -> bool {
match self.retry_limit {
0 => true,
limit => tries < limit,
}
}
#[must_use]
pub fn should_hash(&self) -> bool {
!self.no_hash
}
#[must_use]
pub fn get_share_request(&self) -> NewShareRequest {
NewShareRequest::new(&self.share_name, self.max_views)
.description(self.description.as_ref())
}
2025-06-02 23:57:17 +00:00
#[must_use]
2025-06-18 18:58:49 +00:00
pub fn get_level_filter(&self) -> LevelFilter {
match self.verbose {
0 => LevelFilter::Error,
1 => LevelFilter::Warn,
2 => LevelFilter::Info,
3 => LevelFilter::Debug,
_ => LevelFilter::Trace,
}
}
pub fn file_names(&self) -> Vec<&str> {
self.files.iter().map(FileTrait::get_name).collect()
}
#[must_use]
2025-06-02 23:57:17 +00:00
pub fn get_hash(&self) -> String {
let mut hasher = Blake2b::new().hash_length(16).to_state();
hasher.update(self.get_uri().as_ref());
2025-06-28 09:24:46 +00:00
hasher.update(self.alias.as_ref().as_bytes());
2025-06-02 23:57:17 +00:00
for chk in sorted(&self.files) {
hasher.update(chk.as_ref());
}
2025-06-02 23:57:17 +00:00
BASE64URL.encode(hasher.finalize())
2025-06-02 23:57:17 +00:00
}
2025-05-28 13:42:31 +00:00
}