use std::{ ffi::OsStr, fs, io::{self, Read, Seek}, os::unix::fs::FileExt, path::{Path, PathBuf}, str::FromStr, }; use log::{debug, error}; use ureq::http::StatusCode; use super::{ alias::{Alias, SharryAlias}, share::Share, }; #[derive(Debug)] pub struct File<'t> { alias: &'t Alias, file_path: PathBuf, patch_uri: String, } impl<'t> File<'t> { pub fn create( http: &ureq::Agent, share: &'t Share, file_path: impl Into, ) -> Result { let file_path: PathBuf = file_path.into(); let filename = file_path .file_name() .and_then(OsStr::to_str) .unwrap_or("file.bin"); 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", filename) .header("Upload-Length", fs::metadata(&file_path)?.len()) .send_empty()?; if res.status() != StatusCode::CREATED { return Err(ureq::Error::Other("unexpected response status".into())); } let location = res .headers() .get("Location") .ok_or_else(|| ureq::Error::Other("Location header not found".into()))? .to_str() .map_err(|_| ureq::Error::Other("Location header invalid".into()))?; debug!("location: {}", location); Ok(Self { alias: share.alias, file_path, patch_uri: location.into(), }) } pub fn chunks(&self, chunk_size: usize) -> FileChunks { FileChunks::new(&self.file_path, chunk_size) } // pub fn upload(&mut self) -> io::Result<()> { // let mut f = fs::File::open(&self.path)?; // f.seek(io::SeekFrom::Start(0)); // let patch_size = f.read(&mut self.buffer)?; // let patch_data = &self.buffer[0..patch_size]; // Ok(()) // } } pub struct FileChunks<'t> { num: u64, path: &'t PathBuf, buffer: Vec, data_len: Option, } impl<'t> FileChunks<'t> { fn new(path: &'t PathBuf, chunk_size: usize) -> Self { Self { num: 0, path, buffer: vec![0; chunk_size], data_len: None, } } fn get_offset(&self) -> Option { let buffer_len: u64 = self .buffer .len() .try_into() .inspect_err(|e| error!("Error converting length: {}", e)) .ok()?; Some(self.num * buffer_len) } } impl<'t> Iterator for FileChunks<'t> { type Item = &'t [u8]; fn next(&mut self) -> Option { let mut f = fs::File::open(&self.path) .inspect_err(|e| error!("Error opening file: {}", e)) .ok()?; f.seek(io::SeekFrom::Start(self.get_offset()?)).ok()?; self.data_len = { let read_len = f .read(&mut self.buffer) .inspect_err(|e| error!("Error reading file: {}", e)) .ok()?; Some(read_len).filter(|l| *l > 0) }; self.num += 1; Some(unsafe { std::mem::transmute(&self.buffer[0..self.data_len?]) }) } }