[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::{
cli::Cli,
sharry::{Alias, ChunkState, FileChecked, FileUploading, Share, SharryFile, UploadError},
sharry::{
ChunkState, Client, ClientError, FileChecked, FileUploading, SharryFile, UploadError, Uri,
},
};
#[derive(Serialize, Deserialize, Debug)]
@ -23,8 +25,9 @@ pub struct AppState {
#[serde(skip)]
progress: Option<ProgressBar>,
alias: Alias,
share: Share,
uri: Uri,
alias_id: String,
share_id: String,
files: VecDeque<FileState>,
}
@ -88,27 +91,30 @@ impl AppState {
Self {
file_name,
progress: None,
alias: state.alias,
share: state.share,
uri: state.uri,
alias_id: state.alias_id,
share_id: state.share_id,
files: state.files,
}
})
.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 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();
Ok(Self {
file_name,
progress: None,
alias,
share,
uri,
alias_id,
share_id,
files,
})
}
@ -123,7 +129,9 @@ impl AppState {
chunk_size: usize,
) -> Result<Option<()>, UploadError> {
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 {
return Ok(None);
};
@ -152,7 +160,7 @@ impl AppState {
bar
});
match uploading.upload_chunk(http, &self.alias, chunk_size) {
match uploading.upload_chunk(http, &self.alias_id, chunk_size) {
ChunkState::Ok(upl) => {
bar.set_position(upl.get_offset());
self.files.push_front(FileState::U(upl));
@ -166,7 +174,7 @@ impl AppState {
debug!("Finished {:?}!", path.display());
bar.finish();
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))
}

View file

@ -5,7 +5,7 @@ use std::{
use clap::{Parser, builder::PossibleValuesParser};
use super::sharry::{Alias, FileChecked, NewShareRequest, Uri};
use super::sharry::{FileChecked, NewShareRequest, Uri};
#[derive(Parser, Debug, Hash)]
#[command(version, about, long_about = None)]
@ -46,7 +46,7 @@ pub struct Cli {
url: String,
/// ID of a public alias to use
alias: String,
pub alias: String,
/// Files to upload to the new share
#[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)
}
pub fn get_alias(&self) -> Alias {
Alias::new(Uri::with_protocol(&self.protocol, &self.url), &self.alias)
pub fn get_uri(&self) -> Uri {
Uri::with_protocol(&self.protocol, &self.url)
}
pub fn get_share_request(&self) -> NewShareRequest {
@ -83,7 +83,7 @@ impl Cli {
};
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())
}

View file

@ -18,6 +18,7 @@ use ureq::Agent;
use appstate::AppState;
use cli::Cli;
use sharry::ClientError;
fn main() {
println!(
@ -64,8 +65,11 @@ fn main() {
}
Err(e) => {
if let Some(cause) = match e {
ureq::Error::StatusCode(403) => Some("Alias ID"),
ureq::Error::Io(_) => Some("URL"),
ClientError::ResponseStatus {
actual: _,
expected: 403,
} => Some("Alias ID"),
ClientError::FileIO(_) => Some("URL"),
_ => None,
} {
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 thiserror::Error;
use super::{
api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri},
@ -33,7 +34,7 @@ pub trait Client {
// fn sharry_file_patch(&self);
}
#[derive(Debug, thiserror::Error)]
#[derive(Debug, Error)]
pub enum ClientError {
#[error("file I/O error: {0}")]
FileIO(#[from] io::Error),
@ -49,9 +50,20 @@ pub enum ClientError {
#[error("unexpected response content: {0}")]
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 {
@ -67,10 +79,10 @@ impl Client for ureq::Agent {
self.post(endpoint)
.header("Sharry-Alias", alias_id)
.send_json(data)
.map_err(|e| ClientError::Request(e.to_string()))?
.map_err(|e| ClientError::req_err(e))?
.body_mut()
.read_json::<NewShareResponse>()
.map_err(|e| ClientError::ResponseParsing(e.to_string()))?
.map_err(|e| ClientError::res_parse_err(e))?
};
debug!("response: {res:?}");
@ -78,7 +90,7 @@ impl Client for ureq::Agent {
if res.success && (res.message == "Share created.") {
Ok(res.id)
} 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)
.header("Sharry-Alias", alias_id)
.send_empty()
.map_err(|e| ClientError::Request(e.to_string()))?
.map_err(|e| ClientError::req_err(e))?
.body_mut()
.read_json::<NotifyShareResponse>()
.map_err(|e| ClientError::ResponseParsing(e.to_string()))?
.map_err(|e| ClientError::res_parse_err(e))?
};
debug!("response: {res:?}");
@ -122,7 +134,7 @@ impl Client for ureq::Agent {
.header("Sharry-File-Name", file.get_name())
.header("Upload-Length", size)
.send_empty()
.map_err(|e| ClientError::Request(e.to_string()))?
.map_err(|e| ClientError::req_err(e))?
};
if res.status() != ureq::http::StatusCode::CREATED {
@ -133,9 +145,9 @@ impl Client for ureq::Agent {
}
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()
.map_err(|_| ClientError::ResponseParsing("Location header invalid".to_owned()))?
.map_err(|e| ClientError::res_parse_err(e))?
.to_string();
debug!("patch uri: {location}");

View file

@ -8,7 +8,7 @@ use log::debug;
use serde::{Deserialize, Serialize};
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)]
pub struct FileChecked {

View file

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

View file

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

View file

@ -1,13 +1,9 @@
#![allow(unused_imports)]
mod alias;
mod api;
mod client;
mod file;
mod share;
pub use alias::Alias;
pub use api::{NewShareRequest, Uri};
// pub use client::SharryClient;
pub use client::{Client, ClientError};
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(())
}
}