[wip] impl Client for ureq::Agent

- remove `sharry::alias` and `sharry::share` (reduced to IDs)
- use `sharry_share_create` in `AppState`
This commit is contained in:
Jörn-Michael Miehe 2025-06-08 21:31:50 +00:00
parent 90cecd015e
commit 09af480379
10 changed files with 60 additions and 126 deletions

View file

@ -13,7 +13,9 @@ use serde::{Deserialize, Serialize};
use super::{ use super::{
cli::Cli, cli::Cli,
sharry::{Alias, ChunkState, FileChecked, FileUploading, Share, SharryFile, UploadError}, sharry::{
ChunkState, Client, ClientError, FileChecked, FileUploading, SharryFile, UploadError, Uri,
},
}; };
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -23,8 +25,9 @@ pub struct AppState {
#[serde(skip)] #[serde(skip)]
progress: Option<ProgressBar>, progress: Option<ProgressBar>,
alias: Alias, uri: Uri,
share: Share, alias_id: String,
share_id: String,
files: VecDeque<FileState>, files: VecDeque<FileState>,
} }
@ -88,27 +91,30 @@ impl AppState {
Self { Self {
file_name, file_name,
progress: None, progress: None,
alias: state.alias, uri: state.uri,
share: state.share, alias_id: state.alias_id,
share_id: state.share_id,
files: state.files, files: state.files,
} }
}) })
.ok() .ok()
} }
pub fn from_args(args: &Cli, http: &ureq::Agent) -> Result<Self, ureq::Error> { pub fn from_args(args: &Cli, http: &impl Client) -> Result<Self, ClientError> {
let file_name = Self::cache_file(args); let file_name = Self::cache_file(args);
let alias = args.get_alias();
let share = Share::create(http, &alias, args.get_share_request())?; let uri = args.get_uri();
let alias_id = args.alias.clone();
let share_id = http.sharry_share_create(&uri, &alias_id, args.get_share_request())?;
let files: VecDeque<_> = args.files.clone().into_iter().map(FileState::C).collect(); let files: VecDeque<_> = args.files.clone().into_iter().map(FileState::C).collect();
Ok(Self { Ok(Self {
file_name, file_name,
progress: None, progress: None,
alias, uri,
share, alias_id,
share_id,
files, files,
}) })
} }
@ -123,7 +129,9 @@ impl AppState {
chunk_size: usize, chunk_size: usize,
) -> Result<Option<()>, UploadError> { ) -> Result<Option<()>, UploadError> {
let uploading = if let Some(state) = self.files.pop_front() { let uploading = if let Some(state) = self.files.pop_front() {
state.start_upload(http, &self.alias, &self.share).unwrap() // HACK unwrap state
.start_upload(http, &self.alias_id, &self.share_id)
.unwrap() // HACK unwrap
} else { } else {
return Ok(None); return Ok(None);
}; };
@ -152,7 +160,7 @@ impl AppState {
bar bar
}); });
match uploading.upload_chunk(http, &self.alias, chunk_size) { match uploading.upload_chunk(http, &self.alias_id, chunk_size) {
ChunkState::Ok(upl) => { ChunkState::Ok(upl) => {
bar.set_position(upl.get_offset()); bar.set_position(upl.get_offset());
self.files.push_front(FileState::U(upl)); self.files.push_front(FileState::U(upl));
@ -166,7 +174,7 @@ impl AppState {
debug!("Finished {:?}!", path.display()); debug!("Finished {:?}!", path.display());
bar.finish(); bar.finish();
self.progress = None; self.progress = None;
self.share.notify(http, &self.alias).unwrap(); // HACK unwrap self.share_id.notify(http, &self.alias_id).unwrap(); // HACK unwrap
Ok(self.files.front().map(drop)) Ok(self.files.front().map(drop))
} }

View file

@ -5,7 +5,7 @@ use std::{
use clap::{Parser, builder::PossibleValuesParser}; use clap::{Parser, builder::PossibleValuesParser};
use super::sharry::{Alias, FileChecked, NewShareRequest, Uri}; use super::sharry::{FileChecked, NewShareRequest, Uri};
#[derive(Parser, Debug, Hash)] #[derive(Parser, Debug, Hash)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@ -46,7 +46,7 @@ pub struct Cli {
url: String, url: String,
/// ID of a public alias to use /// ID of a public alias to use
alias: String, pub alias: String,
/// Files to upload to the new share /// Files to upload to the new share
#[arg(value_name = "FILE", required = true, value_parser = parse_sharry_file)] #[arg(value_name = "FILE", required = true, value_parser = parse_sharry_file)]
@ -66,8 +66,8 @@ impl Cli {
(!self.timeout.is_zero()).then_some(self.timeout) (!self.timeout.is_zero()).then_some(self.timeout)
} }
pub fn get_alias(&self) -> Alias { pub fn get_uri(&self) -> Uri {
Alias::new(Uri::with_protocol(&self.protocol, &self.url), &self.alias) Uri::with_protocol(&self.protocol, &self.url)
} }
pub fn get_share_request(&self) -> NewShareRequest { pub fn get_share_request(&self) -> NewShareRequest {
@ -83,7 +83,7 @@ impl Cli {
}; };
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
(self.get_alias(), file_refs).hash(&mut hasher); (self.get_uri(), &self.alias, file_refs).hash(&mut hasher);
format!("{:x}", hasher.finish()) format!("{:x}", hasher.finish())
} }

View file

@ -18,6 +18,7 @@ use ureq::Agent;
use appstate::AppState; use appstate::AppState;
use cli::Cli; use cli::Cli;
use sharry::ClientError;
fn main() { fn main() {
println!( println!(
@ -64,8 +65,11 @@ fn main() {
} }
Err(e) => { Err(e) => {
if let Some(cause) = match e { if let Some(cause) = match e {
ureq::Error::StatusCode(403) => Some("Alias ID"), ClientError::ResponseStatus {
ureq::Error::Io(_) => Some("URL"), actual: _,
expected: 403,
} => Some("Alias ID"),
ClientError::FileIO(_) => Some("URL"),
_ => None, _ => None,
} { } {
info!("handling error: {e:?}"); info!("handling error: {e:?}");

View file

@ -1,36 +0,0 @@
use std::fmt::{Debug, Display};
use log::debug;
use serde::{Deserialize, Serialize};
use ureq::RequestBuilder;
use super::api::Uri;
#[derive(Serialize, Deserialize, Debug, Hash)]
pub struct Alias {
pub(super) uri: Uri,
pub(super) id: String,
}
pub(super) trait SharryAlias {
fn sharry_header(self, alias: &Alias) -> Self;
}
impl<B> SharryAlias for RequestBuilder<B> {
fn sharry_header(self, alias: &Alias) -> Self {
self.header("Sharry-Alias", &alias.id)
}
}
impl Alias {
pub fn new(uri: Uri, id: impl Into<String>) -> Self {
Self { uri, id: id.into() }
}
pub(super) fn get_endpoint(&self, endpoint: impl Display + Debug) -> String {
let uri = format!("{}/{}", self.uri, endpoint);
debug!("endpoint uri: {uri:?}");
uri
}
}

View file

@ -1,6 +1,7 @@
use std::{error::Error, io}; use std::{error::Error, fmt::Display, io};
use log::debug; use log::debug;
use thiserror::Error;
use super::{ use super::{
api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri}, api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri},
@ -33,7 +34,7 @@ pub trait Client {
// fn sharry_file_patch(&self); // fn sharry_file_patch(&self);
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, Error)]
pub enum ClientError { pub enum ClientError {
#[error("file I/O error: {0}")] #[error("file I/O error: {0}")]
FileIO(#[from] io::Error), FileIO(#[from] io::Error),
@ -49,9 +50,20 @@ pub enum ClientError {
#[error("unexpected response content: {0}")] #[error("unexpected response content: {0}")]
ResponseContent(String), ResponseContent(String),
// }
// #[error("could not parse offset header")]
// ResponseOffset, impl ClientError {
fn req_err(msg: impl Display) -> Self {
ClientError::Request(msg.to_string())
}
fn res_parse_err(msg: impl Display) -> Self {
ClientError::ResponseParsing(msg.to_string())
}
fn res_content_err(msg: impl Display) -> Self {
ClientError::ResponseContent(msg.to_string())
}
} }
impl Client for ureq::Agent { impl Client for ureq::Agent {
@ -67,10 +79,10 @@ impl Client for ureq::Agent {
self.post(endpoint) self.post(endpoint)
.header("Sharry-Alias", alias_id) .header("Sharry-Alias", alias_id)
.send_json(data) .send_json(data)
.map_err(|e| ClientError::Request(e.to_string()))? .map_err(|e| ClientError::req_err(e))?
.body_mut() .body_mut()
.read_json::<NewShareResponse>() .read_json::<NewShareResponse>()
.map_err(|e| ClientError::ResponseParsing(e.to_string()))? .map_err(|e| ClientError::res_parse_err(e))?
}; };
debug!("response: {res:?}"); debug!("response: {res:?}");
@ -78,7 +90,7 @@ impl Client for ureq::Agent {
if res.success && (res.message == "Share created.") { if res.success && (res.message == "Share created.") {
Ok(res.id) Ok(res.id)
} else { } else {
Err(ClientError::ResponseContent(format!("{res:?}"))) Err(ClientError::res_content_err(format!("{res:?}")))
} }
} }
@ -94,10 +106,10 @@ impl Client for ureq::Agent {
self.post(endpoint) self.post(endpoint)
.header("Sharry-Alias", alias_id) .header("Sharry-Alias", alias_id)
.send_empty() .send_empty()
.map_err(|e| ClientError::Request(e.to_string()))? .map_err(|e| ClientError::req_err(e))?
.body_mut() .body_mut()
.read_json::<NotifyShareResponse>() .read_json::<NotifyShareResponse>()
.map_err(|e| ClientError::ResponseParsing(e.to_string()))? .map_err(|e| ClientError::res_parse_err(e))?
}; };
debug!("response: {res:?}"); debug!("response: {res:?}");
@ -122,7 +134,7 @@ impl Client for ureq::Agent {
.header("Sharry-File-Name", file.get_name()) .header("Sharry-File-Name", file.get_name())
.header("Upload-Length", size) .header("Upload-Length", size)
.send_empty() .send_empty()
.map_err(|e| ClientError::Request(e.to_string()))? .map_err(|e| ClientError::req_err(e))?
}; };
if res.status() != ureq::http::StatusCode::CREATED { if res.status() != ureq::http::StatusCode::CREATED {
@ -133,9 +145,9 @@ impl Client for ureq::Agent {
} }
let location = (res.headers().get("Location")) let location = (res.headers().get("Location"))
.ok_or_else(|| ClientError::ResponseParsing("Location header not found".to_owned()))? .ok_or_else(|| ClientError::res_parse_err("Location header not found"))?
.to_str() .to_str()
.map_err(|_| ClientError::ResponseParsing("Location header invalid".to_owned()))? .map_err(|e| ClientError::res_parse_err(e))?
.to_string(); .to_string();
debug!("patch uri: {location}"); debug!("patch uri: {location}");

View file

@ -8,7 +8,7 @@ use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ureq::http::{HeaderValue, StatusCode}; use ureq::http::{HeaderValue, StatusCode};
use super::{Alias, FileUploading, Share, SharryAlias, SharryFile}; use super::{FileUploading, SharryFile};
#[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct FileChecked { pub struct FileChecked {

View file

@ -9,8 +9,6 @@ use std::{
pub use checked::FileChecked; pub use checked::FileChecked;
pub use uploading::{ChunkState, FileUploading, UploadError}; pub use uploading::{ChunkState, FileUploading, UploadError};
use super::{Alias, Share, alias::SharryAlias};
pub trait SharryFile<'t> { pub trait SharryFile<'t> {
/// extract the filename part of a `Path` reference /// extract the filename part of a `Path` reference
/// ///

View file

@ -9,7 +9,7 @@ use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ureq::http::{HeaderValue, StatusCode}; use ureq::http::{HeaderValue, StatusCode};
use super::{Alias, SharryAlias, SharryFile}; use super::SharryFile;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct FileUploading { pub struct FileUploading {

View file

@ -1,13 +1,9 @@
#![allow(unused_imports)] #![allow(unused_imports)]
mod alias;
mod api; mod api;
mod client; mod client;
mod file; mod file;
mod share;
pub use alias::Alias;
pub use api::{NewShareRequest, Uri}; pub use api::{NewShareRequest, Uri};
// pub use client::SharryClient; pub use client::{Client, ClientError};
pub use file::{ChunkState, FileChecked, FileUploading, SharryFile, UploadError}; pub use file::{ChunkState, FileChecked, FileUploading, SharryFile, UploadError};
pub use share::Share;

View file

@ -1,48 +0,0 @@
use log::debug;
use serde::{Deserialize, Serialize};
use super::{
alias::{Alias, SharryAlias},
api::{NewShareRequest, NewShareResponse, NotifyShareResponse},
};
#[derive(Serialize, Deserialize, Debug)]
pub struct Share {
pub(super) id: String,
}
impl Share {
pub fn create(
http: &ureq::Agent,
alias: &Alias,
data: NewShareRequest,
) -> Result<Self, ureq::Error> {
let res = (http.post(alias.get_endpoint("alias/upload/new")))
.sharry_header(alias)
.send_json(data)?
.body_mut()
.read_json::<NewShareResponse>()?;
debug!("response: {res:?}");
if !(res.success && (res.message == "Share created.")) {
return Err(ureq::Error::Other("unexpected json response".into()));
}
Ok(Self { id: res.id })
}
pub fn notify(&self, http: &ureq::Agent, alias: &Alias) -> Result<(), ureq::Error> {
let endpoint = alias.get_endpoint(format!("alias/mail/notify/{}", self.id));
let res = (http.post(endpoint))
.sharry_header(alias)
.send_empty()?
.body_mut()
.read_json::<NotifyShareResponse>()?;
debug!("response: {res:?}");
Ok(())
}
}