Merge branch 'develop' into feature/unit_tests
This commit is contained in:
commit
a7cddf3205
6 changed files with 105 additions and 57 deletions
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<()> {
|
||||||
|
|
|
||||||
16
src/cli.rs
16
src/cli.rs
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:?}",
|
||||||
|
|
|
||||||
|
|
@ -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,35 +25,70 @@ 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"))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// arbitrary endpoint in the Sharry API v2
|
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
|
||||||
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);
|
||||||
trace!("endpoint: {uri:?}");
|
trace!("endpoint: {uri:?}");
|
||||||
uri
|
uri
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sharry API endpoint to create a new share
|
/// Sharry API endpoint to create a new share
|
||||||
pub fn share_create(&self) -> String {
|
pub fn share_create(&self) -> String {
|
||||||
self.endpoint(format_args!("alias/upload/new"))
|
self.endpoint(format_args!("alias/upload/new"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sharry API endpoint to ping a share's notification hook
|
/// Sharry API endpoint to ping a share's notification hook
|
||||||
pub fn share_notify(&self, share_id: &super::ShareID) -> String {
|
pub fn share_notify(&self, share_id: &super::ShareID) -> String {
|
||||||
self.endpoint(format_args!("alias/mail/notify/{share_id}"))
|
self.endpoint(format_args!("alias/mail/notify/{share_id}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sharry API endpoint to create a new file inside a share
|
/// Sharry API endpoint to create a new file inside a share
|
||||||
pub fn file_create(&self, share_id: &super::ShareID) -> String {
|
pub fn file_create(&self, share_id: &super::ShareID) -> String {
|
||||||
self.endpoint(format_args!("alias/upload/{share_id}/files/tus"))
|
self.endpoint(format_args!("alias/upload/{share_id}/files/tus"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sharry API endpoint to push data into a file inside a share
|
/// Sharry API endpoint to push data into a file inside a share
|
||||||
pub fn file_patch(&self, share_id: &super::ShareID, file_id: &super::FileID) -> String {
|
pub fn file_patch(&self, share_id: &super::ShareID, file_id: &super::FileID) -> String {
|
||||||
self.endpoint(format_args!("alias/upload/{share_id}/files/tus/{file_id}"))
|
self.endpoint(format_args!("alias/upload/{share_id}/files/tus/{file_id}"))
|
||||||
}
|
}
|
||||||
|
|
@ -69,11 +105,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_traits_working() {
|
fn basic_traits_working() {
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
// simple http host
|
// simple http host
|
||||||
"http://example.com",
|
"http://example.com",
|
||||||
// https host with port
|
// https host with port
|
||||||
"https://my-host:8080",
|
"https://my-host:8080",
|
||||||
// custom scheme
|
// custom scheme
|
||||||
"custom+scheme://host",
|
"custom+scheme://host",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue