Merge branch 'develop' into feature/unit_tests

This commit is contained in:
Jörn-Michael Miehe 2025-07-03 13:03:26 +00:00
commit a7cddf3205
6 changed files with 105 additions and 57 deletions

View file

@ -62,7 +62,7 @@ impl AppState {
if let Some(upl) = self.inner.peek_uploading() { if let Some(upl) = self.inner.peek_uploading() {
if bar.length().is_none() { if bar.length().is_none() {
bar.set_length(upl.get_size()); bar.set_length(upl.get_size());
bar.set_message(upl.get_name().to_owned()); bar.set_message(upl.get_name().to_string());
bar.enable_steady_tick(Duration::from_millis(100)); bar.enable_steady_tick(Duration::from_millis(100));
} }

View file

@ -169,23 +169,22 @@ impl CacheFile {
} }
pub fn rewind_chunk(mut self) -> Option<Self> { pub fn rewind_chunk(mut self) -> Option<Self> {
self.uploading = Some( let upl = self
self.uploading .uploading
.take() .take()
.expect("rewind_chunk called while not uploading") .expect("rewind_chunk called while not uploading");
.rewind()?,
);
self.uploading = Some(upl.rewind()?);
Some(self) Some(self)
} }
pub fn abort_upload(&mut self) { pub fn abort_upload(&mut self) {
self.files.push_front( let upl = self
self.uploading .uploading
.take() .take()
.expect("abort_upload called while not uploading") .expect("abort_upload called while not uploading");
.abort(),
); self.files.push_front(upl.abort());
} }
pub fn share_notify(&self, client: &impl Client) -> crate::Result<()> { pub fn share_notify(&self, client: &impl Client) -> crate::Result<()> {

View file

@ -2,11 +2,7 @@ use std::{convert::Infallible, fmt, io, time::Duration};
use base64ct::{Base64UrlUnpadded, Encoding}; use base64ct::{Base64UrlUnpadded, Encoding};
use blake2b_simd::Params as Blake2b; use blake2b_simd::Params as Blake2b;
use clap::{ use clap::{Parser, builder::TypedValueParser, value_parser};
Parser,
builder::{PossibleValuesParser, TypedValueParser},
value_parser,
};
use log::LevelFilter; use log::LevelFilter;
use crate::{ use crate::{
@ -25,14 +21,6 @@ pub struct Cli {
)] )]
timeout: Duration, timeout: Duration,
/// Protocol for Sharry instance
#[arg(
short, long,
default_value = "https", value_name = "VARIANT",
value_parser = PossibleValuesParser::new(["http", "https"]),
)]
protocol: String,
/// Number of times actions are retried /// Number of times actions are retried
#[arg(short, long, default_value_t = 5, value_name = "N")] #[arg(short, long, default_value_t = 5, value_name = "N")]
retry_limit: u32, retry_limit: u32,
@ -118,7 +106,7 @@ impl Cli {
#[must_use] #[must_use]
pub fn get_uri(&self) -> Uri { pub fn get_uri(&self) -> Uri {
Uri::new(&self.protocol, &self.url) Uri::from(self.url.clone())
} }
#[must_use] #[must_use]

View file

@ -46,13 +46,11 @@ impl Uploading {
self.offset self.offset
} }
pub fn rewind(self) -> Option<Self> { pub fn rewind(mut self) -> Option<Self> {
if let Some(last_offset) = self.last_offset { if let Some(last_offset) = self.last_offset {
Some(Self { self.last_offset = None;
last_offset: None, self.offset = last_offset;
offset: last_offset, Some(self)
..self
})
} else { } else {
warn!("attempted to rewind twice"); warn!("attempted to rewind twice");
None None

View file

@ -86,7 +86,7 @@ impl TryFrom<String> for FileID {
.captures(&value) .captures(&value)
.and_then(|caps| caps.name("fid").map(|m| m.as_str())) .and_then(|caps| caps.name("fid").map(|m| m.as_str()))
{ {
let result = Self(fid.to_owned()); let result = Self(fid.to_string());
debug!("{result:?}"); debug!("{result:?}");
Ok(result) Ok(result)
@ -158,7 +158,8 @@ mod tests {
]; ];
for (good, expected_fid) in cases { for (good, expected_fid) in cases {
let file_id = FileID::try_from(good.to_owned()).expect("URL should parse successfully"); let file_id =
FileID::try_from(good.to_string()).expect("URL should parse successfully");
assert_eq!( assert_eq!(
file_id.0, expected_fid, file_id.0, expected_fid,
"Expected `{good}` → FileID({expected_fid}), got {file_id:?}", "Expected `{good}` → FileID({expected_fid}), got {file_id:?}",

View file

@ -1,6 +1,7 @@
use std::fmt; use std::{fmt, sync::LazyLock};
use log::trace; use log::{debug, trace};
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// ID of a file in a Sharry share /// ID of a file in a Sharry share
@ -24,12 +25,47 @@ impl AsRef<[u8]> for Uri {
} }
} }
impl Uri { impl From<String> for Uri {
/// create a new Sharry URI fn from(value: String) -> Self {
pub fn new(scheme: impl fmt::Display, host: impl fmt::Display) -> Self { fn parse_url(value: &str) -> Option<(String, String)> {
Self(format!("{scheme}://{host}")) /// Pattern breakdown:
/// - `^(?P<scheme>[^:/?#]+)://` - capture scheme (anything but `:/?#`) + `"://"`
/// - `(?P<host>[^/?#]+)` - capture authority/host (anything but `/?#`)
/// - `(/.*)?` - maybe trailing slash and some path
/// - `$` - end of string
static SHARRY_URI_RE: LazyLock<Regex> = LazyLock::new(|| {
trace!("compiling SHARRY_URI_RE");
Regex::new(r"^(?P<scheme>[^:/?#]+)://(?P<host>[^/?#]+)(/.*)?$")
.expect("Regex compilation failed")
});
SHARRY_URI_RE.captures(value).map(|caps| {
let captured = |name| {
caps.name(name)
.expect(&format!("{name} not captured"))
.as_str()
.to_string()
};
(captured("scheme"), captured("host"))
})
} }
trace!("TryFrom {value:?}");
if let Some((scheme, host)) = parse_url(&value) {
let result = Self(format!("{scheme}://{host}"));
debug!("{result:?}");
result
} else {
Self(value)
}
}
}
impl Uri {
/// arbitrary endpoint in the Sharry API v2 /// arbitrary endpoint in the Sharry API v2
fn endpoint(&self, path: fmt::Arguments) -> String { fn endpoint(&self, path: fmt::Arguments) -> String {
let uri = format!("{}/api/v2/{path}", self.0); let uri = format!("{}/api/v2/{path}", self.0);
@ -85,19 +121,45 @@ mod tests {
} }
#[test] #[test]
fn test_new() { fn valid_urls_produce_expected_uri() {
let cases = vec![ let cases = vec![
// simple http host // simple http host
("http", "example.com", "http://example.com"), ("http://example.com", "http://example.com"),
// https host with port // https host with port
("https", "my-host:8080", "https://my-host:8080"), ("https://my-host:8080", "https://my-host:8080"),
// trailing slash
("scheme://host/", "scheme://host"),
// with path
("scheme://host/path/to/whatever", "scheme://host"),
// custom scheme // custom scheme
("custom+scheme", "host", "custom+scheme://host"), ("custom+scheme://host", "custom+scheme://host"),
]; ];
for (scheme, host, expected) in cases { for (good, expected) in cases {
let uri = Uri::new(scheme, host); let uri = Uri::from(good.to_string());
assert_eq!(&expected, &uri.to_string()); check_trait(&expected, &uri.to_string(), "From<String>", "Uri");
}
}
#[test]
fn invalid_urls_passed_through() {
let cases = vec![
// missing “://”
"http:/example.com",
// missing scheme
"://example.com",
// missing host
"http://",
"ftp://?query",
// totally malformed
"just-a-string",
"",
"///",
];
for bad in cases {
let uri = Uri::from(bad.to_string());
check_trait(&bad, &uri.to_string(), "From<String>", "Uri");
} }
} }