2025-06-24 16:29:38 +00:00
|
|
|
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};
|
2025-06-24 19:07:22 +00:00
|
|
|
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
|
|
|
|
2025-06-18 13:04:04 +00:00
|
|
|
use crate::{
|
2025-06-24 00:18:26 +00:00
|
|
|
file::{Checked, FileTrait},
|
2025-06-27 08:34:13 +00:00
|
|
|
sharry::{AliasID, Uri, json::NewShareRequest},
|
2025-06-10 18:20:52 +00:00
|
|
|
};
|
2025-05-28 12:35:35 +00:00
|
|
|
|
2025-06-24 16:29:38 +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")]
|
2025-06-25 22:47:55 +00:00
|
|
|
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")]
|
2025-05-31 15:27:52 +00:00
|
|
|
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")]
|
2025-05-31 15:27:52 +00:00
|
|
|
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,
|
2025-06-25 22:52:46 +00:00
|
|
|
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
|
|
|
|
2025-06-25 22:47:55 +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
|
2025-05-31 15:27:52 +00:00
|
|
|
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)]
|
2025-06-10 18:20:52 +00:00
|
|
|
pub files: Vec<Checked>,
|
2025-05-28 12:35:35 +00:00
|
|
|
}
|
|
|
|
|
|
2025-06-12 23:01:16 +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)
|
2025-06-12 23:01:16 +00:00
|
|
|
.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())
|
2025-06-12 23:01:16 +00:00
|
|
|
.field("hash", &self.get_hash())
|
|
|
|
|
.finish_non_exhaustive()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-18 13:04:04 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2025-06-18 13:04:04 +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
|
|
|
|
2025-06-24 16:29:38 +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 {
|
2025-06-25 21:07:24 +00:00
|
|
|
#[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)
|
|
|
|
|
}
|
2025-05-31 15:27:52 +00:00
|
|
|
|
2025-06-25 21:07:24 +00:00
|
|
|
#[must_use]
|
2025-06-08 21:31:50 +00:00
|
|
|
pub fn get_uri(&self) -> Uri {
|
2025-07-03 12:58:53 +00:00
|
|
|
Uri::from(self.url.clone())
|
2025-05-31 15:27:52 +00:00
|
|
|
}
|
|
|
|
|
|
2025-06-25 21:07:24 +00:00
|
|
|
#[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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-25 22:47:55 +00:00
|
|
|
#[must_use]
|
|
|
|
|
pub fn should_hash(&self) -> bool {
|
|
|
|
|
!self.no_hash
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-25 21:07:24 +00:00
|
|
|
#[must_use]
|
2025-05-31 15:27:52 +00:00
|
|
|
pub fn get_share_request(&self) -> NewShareRequest {
|
2025-07-02 11:39:49 +00:00
|
|
|
NewShareRequest::new(&self.share_name, self.max_views)
|
|
|
|
|
.description(self.description.as_ref())
|
2025-05-31 15:27:52 +00:00
|
|
|
}
|
2025-06-02 23:57:17 +00:00
|
|
|
|
2025-06-25 21:07:24 +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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-24 00:18:26 +00:00
|
|
|
pub fn file_names(&self) -> Vec<&str> {
|
|
|
|
|
self.files.iter().map(FileTrait::get_name).collect()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-25 21:07:24 +00:00
|
|
|
#[must_use]
|
2025-06-02 23:57:17 +00:00
|
|
|
pub fn get_hash(&self) -> String {
|
2025-06-24 19:07:22 +00:00
|
|
|
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
|
|
|
|
2025-06-24 16:29:38 +00:00
|
|
|
for chk in sorted(&self.files) {
|
2025-06-24 19:07:22 +00:00
|
|
|
hasher.update(chk.as_ref());
|
2025-06-24 16:29:38 +00:00
|
|
|
}
|
2025-06-02 23:57:17 +00:00
|
|
|
|
2025-07-03 14:20:30 +00:00
|
|
|
BASE64URL.encode(hasher.finalize())
|
2025-06-02 23:57:17 +00:00
|
|
|
}
|
2025-05-28 13:42:31 +00:00
|
|
|
}
|