diff --git a/src/appstate.rs b/src/appstate.rs index 4f68f5d..7326608 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -13,7 +13,8 @@ use serde::{Deserialize, Serialize}; use super::{ cli::Cli, - sharry::{Client, ClientError, FileChecked, FileUploading, SharryFile, Uri}, + file::{self, FileTrait}, + sharry::{self, Client, Uri}, }; #[derive(Serialize, Deserialize, Debug)] @@ -31,8 +32,8 @@ pub struct AppState { #[derive(Serialize, Deserialize, Debug)] enum FileState { - C(FileChecked), - U(FileUploading), + C(file::Checked), + U(file::Uploading), } impl FileState { @@ -49,9 +50,12 @@ impl FileState { uri: &Uri, alias_id: &str, share_id: &str, - ) -> Result { + ) -> sharry::Result { match self { - FileState::C(checked) => http.sharry_file_create(uri, alias_id, share_id, checked), + FileState::C(checked) => { + let endpoint = &uri.endpoint(format!("alias/upload/{}/files/tus", share_id)); + checked.start_upload(http, endpoint, alias_id) + } FileState::U(uploading) => Ok(uploading), } } @@ -99,12 +103,16 @@ impl AppState { .ok() } - pub fn from_args(args: &Cli, http: &impl Client) -> Result { + pub fn from_args(args: &Cli, http: &impl Client) -> sharry::Result { let file_name = Self::cache_file(args); 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 share_id = http.share_create( + &uri.endpoint("alias/upload/new"), + &alias_id, + args.get_share_request(), + )?; let files: VecDeque<_> = args.files.clone().into_iter().map(FileState::C).collect(); @@ -126,11 +134,9 @@ impl AppState { &mut self, http: &ureq::Agent, chunk_size: usize, - ) -> Result, UploadError> { + ) -> sharry::Result> { let uploading = if let Some(state) = self.files.pop_front() { - state - .start_upload(http, &self.uri, &self.alias_id, &self.share_id) - .unwrap() // HACK unwrap + state.start_upload(http, &self.uri, &self.alias_id, &self.share_id)? } else { return Ok(None); }; diff --git a/src/cli.rs b/src/cli.rs index 5e853d1..070d9f7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,7 +5,10 @@ use std::{ use clap::{Parser, builder::PossibleValuesParser}; -use super::sharry::{FileChecked, NewShareRequest, Uri}; +use super::{ + file::Checked, + sharry::{NewShareRequest, Uri}, +}; #[derive(Parser, Debug, Hash)] #[command(version, about, long_about = None)] @@ -50,15 +53,15 @@ pub struct Cli { /// Files to upload to the new share #[arg(value_name = "FILE", required = true, value_parser = parse_sharry_file)] - pub files: Vec, + pub files: Vec, } fn parse_seconds(data: &str) -> Result { data.parse().or(Ok(0)).map(Duration::from_secs) } -fn parse_sharry_file(data: &str) -> Result { - FileChecked::new(data).map_err(|e| e.to_string()) +fn parse_sharry_file(data: &str) -> Result { + Checked::new(data).map_err(|e| e.to_string()) } impl Cli { diff --git a/src/sharry/file/checked.rs b/src/file/checked.rs similarity index 60% rename from src/sharry/file/checked.rs rename to src/file/checked.rs index a6ec6ba..16d16ca 100644 --- a/src/sharry/file/checked.rs +++ b/src/file/checked.rs @@ -5,15 +5,17 @@ use std::{ use serde::{Deserialize, Serialize}; -use super::SharryFile; +use crate::sharry; + +use super::{FileTrait, Uploading}; #[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] -pub struct FileChecked { - pub(super) path: PathBuf, - pub(super) size: u64, +pub struct Checked { + path: PathBuf, + size: u64, } -impl FileChecked { +impl Checked { pub fn new(value: impl AsRef) -> io::Result { let meta = fs::metadata(&value)?; if meta.is_file() { @@ -28,14 +30,25 @@ impl FileChecked { )) } } + + pub fn start_upload( + self, + client: &impl sharry::Client, + endpoint: &str, + alias_id: &str, + ) -> sharry::Result { + let patch_uri = client.file_create(endpoint, alias_id, self.get_name(), self.size)?; + + Ok(Uploading::new(self.path, self.size, patch_uri)) + } } -impl<'t> SharryFile<'t> for FileChecked { +impl<'t> FileTrait<'t> for Checked { /// get a reference to the file's name /// /// Uses `SharryFile::extract_file_name`, which may **panic**! fn get_name(&'t self) -> &'t str { - ::extract_file_name(&self.path) + ::extract_file_name(&self.path) } fn get_size(&self) -> u64 { diff --git a/src/sharry/file/mod.rs b/src/file/mod.rs similarity index 74% rename from src/sharry/file/mod.rs rename to src/file/mod.rs index 7ae4823..513d562 100644 --- a/src/sharry/file/mod.rs +++ b/src/file/mod.rs @@ -1,15 +1,12 @@ mod checked; mod uploading; -use std::{ - ffi::OsStr, - path::{Path, PathBuf}, -}; +use std::{ffi::OsStr, path::Path}; -pub use checked::FileChecked; -pub use uploading::FileUploading; +pub use checked::Checked; +pub use uploading::Uploading; -pub trait SharryFile<'t> { +pub trait FileTrait<'t> { /// extract the filename part of a `Path` reference /// /// # Panics diff --git a/src/sharry/file/uploading.rs b/src/file/uploading.rs similarity index 83% rename from src/sharry/file/uploading.rs rename to src/file/uploading.rs index 1bd9f8a..aded973 100644 --- a/src/sharry/file/uploading.rs +++ b/src/file/uploading.rs @@ -6,17 +6,17 @@ use std::{ use serde::{Deserialize, Serialize}; -use super::{FileChecked, SharryFile}; +use super::FileTrait; #[derive(Serialize, Deserialize, Debug)] -pub struct FileUploading { +pub struct Uploading { path: PathBuf, size: u64, patch_uri: String, offset: u64, } -impl fmt::Display for FileUploading { +impl fmt::Display for Uploading { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -28,11 +28,11 @@ impl fmt::Display for FileUploading { } } -impl FileUploading { - pub fn new(file: FileChecked, patch_uri: String) -> Self { +impl Uploading { + pub(super) fn new(path: PathBuf, size: u64, patch_uri: String) -> Self { Self { - path: file.path, - size: file.size, + path, + size, patch_uri, offset: 0, } @@ -74,12 +74,12 @@ impl FileUploading { } } -impl<'t> SharryFile<'t> for FileUploading { +impl<'t> FileTrait<'t> for Uploading { /// get a reference to the file's name /// /// Uses `SharryFile::extract_file_name`, which may **panic**! fn get_name(&'t self) -> &'t str { - ::extract_file_name(&self.path) + ::extract_file_name(&self.path) } fn get_size(&self) -> u64 { diff --git a/src/main.rs b/src/main.rs index 10379c5..8c15900 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod appstate; mod cli; +mod file; mod sharry; use std::{ @@ -69,7 +70,7 @@ fn main() { actual: _, expected: 403, } => Some("Alias ID"), - ClientError::FileIO(_) => Some("URL"), + // ClientError::FileIO(_) => Some("URL"), _ => None, } { info!("handling error: {e:?}"); diff --git a/src/sharry/api.rs b/src/sharry/api.rs index 079dcda..2b5ddb5 100644 --- a/src/sharry/api.rs +++ b/src/sharry/api.rs @@ -10,19 +10,19 @@ pub struct Uri { } impl Uri { - pub(super) fn get_endpoint(&self, endpoint: impl fmt::Display + fmt::Debug) -> String { - let uri = format!("{}/{}", self, endpoint); - debug!("endpoint uri: {uri:?}"); - - uri - } - pub fn with_protocol(protocol: impl Into, base_url: impl Into) -> Self { Self { protocol: protocol.into(), base_url: base_url.into(), } } + + pub fn endpoint(&self, endpoint: impl fmt::Display) -> String { + let uri = format!("{}/{}", self, endpoint); + debug!("endpoint: {uri:?}"); + + uri + } } impl fmt::Display for Uri { diff --git a/src/sharry/client.rs b/src/sharry/client.rs index 26065f4..bd6a5ea 100644 --- a/src/sharry/client.rs +++ b/src/sharry/client.rs @@ -1,51 +1,32 @@ -use std::{error::Error, fmt::Display, io}; +use std::fmt; -use log::debug; +use log::{debug, trace}; use thiserror::Error; -use super::{ - api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri}, - file::{FileChecked, FileUploading, SharryFile}, -}; +use super::api::{NewShareRequest, NewShareResponse, NotifyShareResponse}; + +pub type Result = std::result::Result; pub trait Client { - fn sharry_share_create( - &self, - uri: &Uri, - alias_id: &str, - data: NewShareRequest, - ) -> Result; + fn share_create(&self, endpoint: &str, alias_id: &str, data: NewShareRequest) + -> Result; - fn sharry_share_notify( - &self, - uri: &Uri, - alias_id: &str, - share_id: &str, - ) -> Result<(), ClientError>; + fn share_notify(&self, endpoint: &str, alias_id: &str) -> Result<()>; - fn sharry_file_create( + fn file_create( &self, - uri: &Uri, + endpoint: &str, alias_id: &str, - share_id: &str, file_name: &str, file_size: u64, - ) -> Result; + ) -> Result; - fn sharry_file_patch( - &self, - patch_uri: &str, - alias_id: &str, - offset: u64, - chunk: &[u8], - ) -> Result; + fn file_patch(&self, patch_uri: &str, alias_id: &str, offset: u64, chunk: &[u8]) + -> Result; } #[derive(Debug, Error)] pub enum ClientError { - #[error("file I/O error: {0}")] - FileIO(#[from] io::Error), - #[error("network request failed: {0}")] Request(String), @@ -60,19 +41,15 @@ pub enum ClientError { } impl ClientError { - fn req_err(msg: impl Display) -> Self { + fn req_err(msg: impl fmt::Display) -> Self { Self::Request(msg.to_string()) } - fn res_parse_err(msg: impl Display) -> Self { + fn res_parse_err(msg: impl fmt::Display) -> Self { Self::ResponseParsing(msg.to_string()) } - fn res_content_err(msg: impl Display) -> Self { - Self::ResponseContent(msg.to_string()) - } - - fn res_check_status(actual: T, expected: T) -> Result<(), Self> + fn res_check_status(actual: T, expected: T) -> Result<()> where T: Into + Eq, { @@ -88,75 +65,77 @@ impl ClientError { } impl Client for ureq::Agent { - fn sharry_share_create( + fn share_create( &self, - uri: &Uri, + endpoint: &str, alias_id: &str, data: NewShareRequest, - ) -> Result { - let res = { - let endpoint = uri.get_endpoint("alias/upload/new"); + ) -> Result { + // let endpoint = uri.get_endpoint("alias/upload/new"); - self.post(endpoint) - .header("Sharry-Alias", alias_id) - .send_json(data) - .map_err(|e| ClientError::req_err(e))? - .body_mut() - .read_json::() - .map_err(|e| ClientError::res_parse_err(e))? - }; + let mut res = self + .post(endpoint) + .header("Sharry-Alias", alias_id) + .send_json(data) + .map_err(ClientError::req_err)?; - debug!("response: {res:?}"); + trace!("{endpoint:?} response: {res:?}"); + ClientError::res_check_status(res.status(), ureq::http::StatusCode::OK)?; + + let res = res + .body_mut() + .read_json::() + .map_err(ClientError::res_parse_err)?; + + debug!("{res:?}"); if res.success && (res.message == "Share created.") { Ok(res.id) } else { - Err(ClientError::res_content_err(format!("{res:?}"))) + Err(ClientError::ResponseContent(format!("{res:?}"))) } } - fn sharry_share_notify( - &self, - uri: &Uri, - alias_id: &str, - share_id: &str, - ) -> Result<(), ClientError> { - let res = { - let endpoint = uri.get_endpoint(format!("alias/mail/notify/{}", share_id)); + fn share_notify(&self, endpoint: &str, alias_id: &str) -> Result<()> { + // let endpoint = uri.get_endpoint(format!("alias/mail/notify/{}", share_id)); - self.post(endpoint) - .header("Sharry-Alias", alias_id) - .send_empty() - .map_err(|e| ClientError::req_err(e))? - .body_mut() - .read_json::() - .map_err(|e| ClientError::res_parse_err(e))? - }; + let mut res = self + .post(endpoint) + .header("Sharry-Alias", alias_id) + .send_empty() + .map_err(|e| ClientError::req_err(e))?; - debug!("response: {res:?}"); + trace!("{endpoint:?} response: {res:?}"); + ClientError::res_check_status(res.status(), ureq::http::StatusCode::OK)?; + + let res = res + .body_mut() + .read_json::() + .map_err(|e| ClientError::res_parse_err(e))?; + + debug!("{res:?}"); Ok(()) } - fn sharry_file_create( + fn file_create( &self, - uri: &Uri, + endpoint: &str, alias_id: &str, - share_id: &str, file_name: &str, file_size: u64, - ) -> Result { - let res = { - let endpoint = uri.get_endpoint(format!("alias/upload/{}/files/tus", share_id)); + ) -> Result { + // let endpoint = uri.get_endpoint(format!("alias/upload/{}/files/tus", share_id)); - self.post(endpoint) - .header("Sharry-Alias", alias_id) - .header("Sharry-File-Name", file_name) - .header("Upload-Length", file_size) - .send_empty() - .map_err(ClientError::req_err)? - }; + let res = self + .post(endpoint) + .header("Sharry-Alias", alias_id) + .header("Sharry-File-Name", file_name) + .header("Upload-Length", file_size) + .send_empty() + .map_err(ClientError::req_err)?; + trace!("{endpoint:?} response: {res:?}"); ClientError::res_check_status(res.status(), ureq::http::StatusCode::CREATED)?; let location = (res.headers().get("Location")) @@ -165,18 +144,18 @@ impl Client for ureq::Agent { .map_err(ClientError::res_parse_err)? .to_string(); - debug!("patch uri: {location}"); + debug!("{location:?}"); Ok(location) } - fn sharry_file_patch( + fn file_patch( &self, patch_uri: &str, alias_id: &str, offset: u64, chunk: &[u8], - ) -> Result { + ) -> Result { let res = self .patch(patch_uri) .header("Sharry-Alias", alias_id) @@ -184,6 +163,7 @@ impl Client for ureq::Agent { .send(chunk) .map_err(ClientError::req_err)?; + trace!("{patch_uri:?} response: {res:?}"); ClientError::res_check_status(res.status(), ureq::http::StatusCode::NO_CONTENT)?; let res_offset = (res.headers().get("Upload-Offset")) diff --git a/src/sharry/mod.rs b/src/sharry/mod.rs index 2f092d3..e230f9d 100644 --- a/src/sharry/mod.rs +++ b/src/sharry/mod.rs @@ -1,9 +1,5 @@ -#![allow(unused_imports)] - mod api; mod client; -mod file; pub use api::{NewShareRequest, Uri}; -pub use client::{Client, ClientError}; -pub use file::{FileChecked, FileUploading, SharryFile}; +pub use client::{Client, ClientError, Result};