mod chunks; use std::{ffi::OsStr, fs::metadata, io, path::PathBuf}; use log::{debug, error}; use ureq::{Error::Other, http::StatusCode}; use super::{ alias::{Alias, SharryAlias}, share::Share, }; pub use chunks::{Chunk, FileChunks}; #[derive(Debug)] pub struct File { path: PathBuf, name: String, size: u64, patch_uri: Option, } impl File { pub fn new(path: impl Into) -> io::Result { let path: PathBuf = path.into(); let name = path .file_name() .and_then(OsStr::to_str) .unwrap_or("file.bin") .to_string(); let size = metadata(&path)?.len(); Ok(Self { path, name, size, patch_uri: None, }) } pub fn create(self, http: &ureq::Agent, share: &Share) -> Result { if self.patch_uri.is_some() { return Err(Other("patch_uri already set".into())); } let endpoint = share .alias .get_endpoint(format!("alias/upload/{}/files/tus", share.id)); let res = http .post(endpoint) .sharry_header(share.alias) .header("Sharry-File-Name", &self.name) .header("Upload-Length", self.size) .send_empty()?; if res.status() != StatusCode::CREATED { return Err(Other("unexpected response status".into())); } let location = res .headers() .get("Location") .ok_or_else(|| Other("Location header not found".into()))? .to_str() .map_err(|_| Other("Location header invalid".into()))? .to_string(); debug!("received uri: {location}"); Ok(Self { path: self.path, name: self.name, size: self.size, patch_uri: Some(location), }) } pub fn chunked(&self, chunk_size: usize) -> FileChunks { FileChunks::new(&self.path, chunk_size) } pub fn upload_chunk( &self, http: &ureq::Agent, alias: &Alias, chunk: &Chunk, ) -> Result<(), ureq::Error> { let patch_uri = self .patch_uri .as_ref() .ok_or_else(|| Other("unset patch_uri".into()))?; debug!("upload uri: {patch_uri:?}"); let res = http .patch(patch_uri) .sharry_header(alias) .header("Upload-Offset", chunk.offset) .send(&chunk.bytes)?; if res.status() != StatusCode::NO_CONTENT { return Err(Other("unexpected response status".into())); } let offset = res .headers() .get("Upload-Offset") .ok_or_else(|| Other("Upload-Offset header not found".into()))? .to_str() .map_err(|_| Other("Upload-Offset header invalid".into()))? .parse::() .map_err(|_| Other("Upload-Offset header not an integer".into()))?; if chunk.after() != offset { return Err(Other("unexpected offset response".into())); } Ok(()) } }