use std::{ fs::File, io::{self, Read, Seek, SeekFrom}, path::PathBuf, }; use log::debug; use serde::{Deserialize, Serialize}; use ureq::{ Error::Other, http::{HeaderValue, StatusCode}, }; use super::{Alias, SharryAlias}; #[derive(Serialize, Deserialize, Debug)] pub struct FileUploading { path: PathBuf, size: usize, uri: String, offset: usize, } #[derive(Debug)] pub enum UploadError { FileIO(io::Error), Request, ResponseStatus, ResponseOffset, } pub enum ChunkState { Ok(FileUploading), Err(FileUploading, UploadError), Finished(PathBuf), } impl FileUploading { pub(super) fn new(path: PathBuf, size: usize, uri: String) -> Self { Self { path, size, uri, offset: 0, } } fn read_chunk(&self, chunk_size: usize) -> io::Result> { let offset = u64::try_from(self.offset).map_err(io::Error::other)?; let mut f = File::open(&self.path)?; f.seek(SeekFrom::Start(offset))?; let mut bytes = vec![0; chunk_size]; let read_len = f.read(&mut bytes)?; bytes.truncate(read_len); Ok(bytes) } pub fn upload_chunk( mut self, http: &ureq::Agent, alias: &Alias, chunk_size: usize, ) -> ChunkState { let chunk = match self.read_chunk(chunk_size) { Err(e) => return ChunkState::Err(self, UploadError::FileIO(e)), Ok(value) => value, }; let Ok(res) = (http.patch(&self.uri)) .sharry_header(alias) .header("Upload-Offset", self.offset) .send(&chunk) else { return ChunkState::Err(self, UploadError::Request); }; if res.status() != StatusCode::NO_CONTENT { return ChunkState::Err(self, UploadError::ResponseStatus); } let Some(Ok(Ok(res_offset))) = (res.headers().get("Upload-Offset")) .map(HeaderValue::to_str) .map(|v| v.map(str::parse::)) else { return ChunkState::Err(self, UploadError::ResponseOffset); }; if self.offset + chunk.len() != res_offset { return ChunkState::Err(self, UploadError::ResponseOffset); } self.offset = res_offset; if self.offset == self.size { return ChunkState::Finished(self.path); } ChunkState::Ok(self) } }