2025-06-08 01:20:41 +00:00
|
|
|
use std::{error::Error, io};
|
|
|
|
|
|
|
|
|
|
use log::debug;
|
|
|
|
|
|
2025-06-08 17:13:01 +00:00
|
|
|
use super::{
|
|
|
|
|
api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri},
|
|
|
|
|
file::{FileChecked, FileUploading, SharryFile},
|
|
|
|
|
};
|
2025-06-08 01:20:41 +00:00
|
|
|
|
|
|
|
|
pub trait Client {
|
|
|
|
|
fn sharry_share_create(
|
|
|
|
|
&self,
|
|
|
|
|
uri: &Uri,
|
|
|
|
|
alias_id: &str,
|
|
|
|
|
data: NewShareRequest,
|
|
|
|
|
) -> Result<String, ClientError>;
|
|
|
|
|
|
|
|
|
|
fn sharry_share_notify(
|
|
|
|
|
&self,
|
|
|
|
|
uri: &Uri,
|
|
|
|
|
alias_id: &str,
|
|
|
|
|
share_id: &str,
|
|
|
|
|
) -> Result<(), ClientError>;
|
|
|
|
|
|
2025-06-08 17:13:01 +00:00
|
|
|
fn sharry_file_create(
|
|
|
|
|
&self,
|
|
|
|
|
uri: &Uri,
|
|
|
|
|
alias_id: &str,
|
|
|
|
|
share_id: &str,
|
|
|
|
|
file: FileChecked,
|
|
|
|
|
) -> Result<FileUploading, ClientError>;
|
2025-06-08 01:20:41 +00:00
|
|
|
|
|
|
|
|
// fn sharry_file_patch(&self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
|
pub enum ClientError {
|
|
|
|
|
#[error("file I/O error: {0}")]
|
|
|
|
|
FileIO(#[from] io::Error),
|
|
|
|
|
|
|
|
|
|
#[error("network request failed: {0}")]
|
|
|
|
|
Request(String),
|
|
|
|
|
|
|
|
|
|
#[error("response parsing failed: {0}")]
|
|
|
|
|
ResponseParsing(String),
|
2025-06-08 17:13:01 +00:00
|
|
|
|
|
|
|
|
#[error("unexpected response status: {actual} (expected {expected})")]
|
|
|
|
|
ResponseStatus { actual: u16, expected: u16 },
|
|
|
|
|
|
2025-06-08 01:20:41 +00:00
|
|
|
#[error("unexpected response content: {0}")]
|
|
|
|
|
ResponseContent(String),
|
|
|
|
|
//
|
|
|
|
|
// #[error("could not parse offset header")]
|
|
|
|
|
// ResponseOffset,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Client for ureq::Agent {
|
|
|
|
|
fn sharry_share_create(
|
|
|
|
|
&self,
|
|
|
|
|
uri: &Uri,
|
|
|
|
|
alias_id: &str,
|
|
|
|
|
data: NewShareRequest,
|
|
|
|
|
) -> Result<String, ClientError> {
|
|
|
|
|
let res = {
|
|
|
|
|
let endpoint = uri.get_endpoint("alias/upload/new");
|
|
|
|
|
|
|
|
|
|
self.post(endpoint)
|
|
|
|
|
.header("Sharry-Alias", alias_id)
|
|
|
|
|
.send_json(data)
|
|
|
|
|
.map_err(|e| ClientError::Request(e.to_string()))?
|
|
|
|
|
.body_mut()
|
|
|
|
|
.read_json::<NewShareResponse>()
|
|
|
|
|
.map_err(|e| ClientError::ResponseParsing(e.to_string()))?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
debug!("response: {res:?}");
|
|
|
|
|
|
|
|
|
|
if res.success && (res.message == "Share created.") {
|
|
|
|
|
Ok(res.id)
|
|
|
|
|
} else {
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
self.post(endpoint)
|
|
|
|
|
.header("Sharry-Alias", alias_id)
|
|
|
|
|
.send_empty()
|
|
|
|
|
.map_err(|e| ClientError::Request(e.to_string()))?
|
|
|
|
|
.body_mut()
|
|
|
|
|
.read_json::<NotifyShareResponse>()
|
|
|
|
|
.map_err(|e| ClientError::ResponseParsing(e.to_string()))?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
debug!("response: {res:?}");
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-06-08 17:13:01 +00:00
|
|
|
|
|
|
|
|
fn sharry_file_create(
|
|
|
|
|
&self,
|
|
|
|
|
uri: &Uri,
|
|
|
|
|
alias_id: &str,
|
|
|
|
|
share_id: &str,
|
|
|
|
|
file: FileChecked,
|
|
|
|
|
) -> Result<FileUploading, ClientError> {
|
|
|
|
|
let size = file.get_size();
|
|
|
|
|
|
|
|
|
|
let res = {
|
|
|
|
|
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.get_name())
|
|
|
|
|
.header("Upload-Length", size)
|
|
|
|
|
.send_empty()
|
|
|
|
|
.map_err(|e| ClientError::Request(e.to_string()))?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if res.status() != ureq::http::StatusCode::CREATED {
|
|
|
|
|
return Err(ClientError::ResponseStatus {
|
|
|
|
|
actual: res.status().as_u16(),
|
|
|
|
|
expected: ureq::http::StatusCode::CREATED.as_u16(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let location = (res.headers().get("Location"))
|
|
|
|
|
.ok_or_else(|| ClientError::ResponseParsing("Location header not found".to_owned()))?
|
|
|
|
|
.to_str()
|
|
|
|
|
.map_err(|_| ClientError::ResponseParsing("Location header invalid".to_owned()))?
|
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
|
|
debug!("patch uri: {location}");
|
|
|
|
|
|
|
|
|
|
Ok(FileUploading::new(file.into_path(), size, location))
|
|
|
|
|
}
|
2025-06-08 01:20:41 +00:00
|
|
|
}
|