use log::{debug, trace}; use crate::{ file::{self, FileTrait}, sharry::{self, ClientError, Uri}, }; fn find_cause( uri: &Uri, alias_id: &str, share_id: Option<&str>, file_id: Option<&str>, ) -> impl FnOnce(ureq::Error) -> ClientError { move |error| match error { ureq::Error::StatusCode(403) => { trace!("HTTP Error 403: Alias not found!"); ClientError::InvalidParameter(sharry::Parameter::AliasID(alias_id.to_owned())) } ureq::Error::StatusCode(404) => { trace!("HTTP Error 404: Share and/or file may have been deleted!"); if let Some(file_id) = file_id { ClientError::InvalidParameter(sharry::Parameter::FileID(file_id.to_owned())) } else if let Some(share_id) = share_id { ClientError::InvalidParameter(sharry::Parameter::ShareID(share_id.to_owned())) } else { ClientError::unknown(error) } } ureq::Error::Io(error) => { trace!("std::io::Error {error:?}"); if let Some(msg) = error.get_ref().map(ToString::to_string) { if msg.starts_with("failed to lookup address information") { ClientError::InvalidParameter(sharry::Parameter::Uri(uri.to_string())) } else { error.into() } } else { error.into() } } error => ClientError::unknown(error), } } impl sharry::Client for ureq::Agent { fn share_create( &self, uri: &Uri, alias_id: &str, data: sharry::NewShareRequest, ) -> sharry::Result { let res = { let endpoint = uri.share_create(); let mut res = self .post(&endpoint) .header("Sharry-Alias", alias_id) .send_json(data) .map_err(find_cause(uri, alias_id, None, None))?; trace!("{endpoint:?} response: {res:?}"); ClientError::res_status_check(res.status(), ureq::http::StatusCode::OK)?; res.body_mut() .read_json::() .map_err(ClientError::response)? }; debug!("{res:?}"); if res.success && (res.message == "Share created.") { trace!("new share id: {:?}", res.id); Ok(res.id) } else { Err(ClientError::response(format!("{res:?}"))) } } fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> sharry::Result<()> { let res = { let endpoint = uri.share_notify(share_id); let mut res = self .post(&endpoint) .header("Sharry-Alias", alias_id) .send_empty() .map_err(find_cause(uri, alias_id, Some(share_id), None))?; trace!("{endpoint:?} response: {res:?}"); ClientError::res_status_check(res.status(), ureq::http::StatusCode::OK)?; res.body_mut() .read_json::() .map_err(ClientError::response)? }; debug!("{res:?}"); Ok(()) } fn file_create( &self, uri: &Uri, alias_id: &str, share_id: &str, file: &file::Checked, ) -> sharry::Result { let res = { let endpoint = uri.file_create(share_id); let res = self .post(&endpoint) .header("Sharry-Alias", alias_id) .header("Sharry-File-Name", file.get_name()) .header("Upload-Length", file.get_size()) .send_empty() .map_err(find_cause(uri, alias_id, Some(share_id), None))?; trace!("{endpoint:?} response: {res:?}"); ClientError::res_status_check(res.status(), ureq::http::StatusCode::CREATED)?; res }; let location = (res.headers().get("Location")) .ok_or_else(|| ClientError::response("Location header not found"))? .to_str() .map_err(ClientError::response)? .to_string(); let file_id = Self::get_file_id(&location)?; debug!("location: {location:?}, file_id: {file_id:?}"); Ok(file_id.to_owned()) } fn file_patch( &self, uri: &Uri, alias_id: &str, share_id: &str, chunk: &file::Chunk, ) -> sharry::Result<()> { let res = { let endpoint = uri.file_patch(share_id, chunk.get_file_id()); let res = self .patch(&endpoint) .header("Sharry-Alias", alias_id) .header("Upload-Offset", chunk.get_offset()) .send(chunk.get_data()) .map_err(find_cause( uri, alias_id, Some(share_id), Some(chunk.get_file_id()), ))?; trace!("{endpoint:?} response: {res:?}"); ClientError::res_status_check(res.status(), ureq::http::StatusCode::NO_CONTENT)?; res }; let res_offset = (res.headers().get("Upload-Offset")) .ok_or_else(|| ClientError::response("Upload-Offset header not found"))? .to_str() .map_err(ClientError::response)? .parse::() .map_err(ClientError::response)?; if chunk.get_behind() == res_offset { Ok(()) } else { Err(ClientError::response(format!( "Unexpected Upload-Offset: {} (expected {})", res_offset, chunk.get_behind() ))) } } }