diff --git a/src/main.rs b/src/main.rs index 6d594f8..2701bcd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,12 @@ mod cli; mod sharry; use clap::Parser; -use log::{error, info}; +use log::{debug, error, info}; use ureq::Agent; use appstate::AppState; use cli::Cli; -use sharry::Share; +use sharry::{ChunkState, Share}; fn main() { env_logger::init(); @@ -31,18 +31,21 @@ fn main() { info!("share: {share:?}"); for file in args.files { - let file = file.start_upload(&agent, &alias, &share).unwrap(); + let mut file = file.start_upload(&agent, &alias, &share).unwrap(); info!("file: {file:?}"); - // for chunk in file.chunked(args.chunk_size * 1024 * 1024).seek(0) { - // info!("chunk: {chunk:?}"); + loop { + match file.upload_chunk(&agent, &alias, args.chunk_size * 1024 * 1024) { + ChunkState::Ok(upl) => file = upl, + ChunkState::Err(upl, e) => { + error!("error: {e:?}"); + file = upl; + } + ChunkState::Finished => break, + }; - // file.upload_chunk(&agent, &alias, &chunk) - // .unwrap_or_else(|e| { - // error!("error: {e}"); - // panic!("{e}"); - // }); - // } + debug!("file: {file:?}"); + } } share.notify(&agent, &alias).unwrap(); diff --git a/src/sharry/file/_chunks.rs b/src/sharry/file/_chunks.rs deleted file mode 100644 index de484cd..0000000 --- a/src/sharry/file/_chunks.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::{ - fmt::Debug, - fs::File, - io::{Read, Seek, SeekFrom}, - path::Path, -}; - -use log::error; -// use serde::{Deserialize, Serialize}; - -#[derive(Debug)] -pub struct FileChunks<'t> { - file_path: &'t Path, - offset: u64, - chunk_size: usize, -} - -impl<'t> FileChunks<'t> { - pub(super) fn new(path: &'t Path, chunk_size: usize) -> Self { - Self { - file_path: path, - offset: 0, - chunk_size, - } - } - - pub fn seek(self, offset: u64) -> Self { - Self { - file_path: self.file_path, - offset, - chunk_size: self.chunk_size, - } - } -} - -impl Iterator for FileChunks<'_> { - type Item = Chunk; - - fn next(&mut self) -> Option { - let offset = self.offset; - - let bytes = { - let mut f = File::open(self.file_path) - .inspect_err(|e| error!("Error opening file: {e}")) - .ok()?; - f.seek(SeekFrom::Start(offset)).ok()?; - - let mut bytes = vec![0; self.chunk_size]; - let read_len = (f.read(&mut bytes)) - .inspect_err(|e| error!("Error reading file: {e}")) - .ok()?; - bytes.truncate(read_len); - - bytes - }; - - let read_len: u64 = (bytes.len().try_into()) - .inspect_err(|e| error!("Error converting to u64: {e}")) - .ok()?; - self.offset += read_len; - - Some(Self::Item { offset, bytes }).filter(|c| !c.bytes.is_empty()) - } -} - -pub struct Chunk { - pub offset: u64, - pub bytes: Vec, -} - -impl Debug for Chunk { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Chunk") - .field("offset", &self.offset) - .field("len", &self.bytes.len()) - .finish() - } -} - -impl Chunk { - pub fn after(&self) -> u64 { - let len: u64 = self.bytes.len().try_into().unwrap(); - self.offset + len - } -} diff --git a/src/sharry/file/_mod.rs b/src/sharry/file/_mod.rs deleted file mode 100644 index e230f0c..0000000 --- a/src/sharry/file/_mod.rs +++ /dev/null @@ -1,142 +0,0 @@ -mod chunks; - -use std::{ - ffi::OsStr, - fs::{canonicalize, metadata}, - hash::{Hash, Hasher}, - io::{self, ErrorKind}, - path::{Path, PathBuf}, -}; - -use log::{debug, error}; -use serde::{Deserialize, Serialize}; -use ureq::{Error::Other, http::StatusCode}; - -use super::{ - alias::{Alias, SharryAlias}, - share::Share, -}; -pub use chunks::{Chunk, FileChunks}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct File { - abs_path: PathBuf, - name: String, - size: u64, - patch_uri: Option, -} - -impl Hash for File { - fn hash(&self, state: &mut H) { - self.abs_path.hash(state); - } -} - -impl PartialEq for File { - fn eq(&self, other: &Self) -> bool { - self.abs_path == other.abs_path - } -} - -impl Eq for File {} - -impl File { - pub fn new(path: impl AsRef) -> io::Result { - let abs_path = canonicalize(path)?; - - let m = metadata(&abs_path)?; - if !m.is_file() { - return Err(io::Error::new(ErrorKind::NotFound, "not a file")); - } - - let name = (abs_path.file_name().and_then(OsStr::to_str)) - .ok_or_else(|| io::Error::new(ErrorKind::NotFound, "bad file name"))? - .to_string(); - - Ok(Self { - abs_path, - name, - size: m.len(), - patch_uri: None, - }) - } - - pub fn get_path(&self) -> &Path { - &self.abs_path - } - - pub fn create( - self, - http: &ureq::Agent, - alias: &Alias, - share: &Share, - ) -> Result { - if self.patch_uri.is_some() { - return Err(Other("patch_uri already set".into())); - } - - let endpoint = alias.get_endpoint(format!("alias/upload/{}/files/tus", share.id)); - - let res = (http.post(endpoint)) - .sharry_header(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 { - abs_path: self.abs_path, - name: self.name, - size: self.size, - patch_uri: Some(location), - }) - } - - pub fn chunked(&self, chunk_size: usize) -> FileChunks { - FileChunks::new(&self.abs_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(|e| Other(e.into()))? - .parse::() - .map_err(|e| Other(e.into()))?; - - if chunk.after() != offset { - return Err(Other("unexpected offset response".into())); - } - - Ok(()) - } -} diff --git a/src/sharry/file/mod.rs b/src/sharry/file/mod.rs index ffae74c..f9d6c3b 100644 --- a/src/sharry/file/mod.rs +++ b/src/sharry/file/mod.rs @@ -2,6 +2,6 @@ mod checked; mod uploading; pub use checked::FileChecked; -pub use uploading::FileUploading; +pub use uploading::{ChunkState, FileUploading}; use super::{Alias, Share, alias::SharryAlias}; diff --git a/src/sharry/file/uploading.rs b/src/sharry/file/uploading.rs index 6652d74..e53e236 100644 --- a/src/sharry/file/uploading.rs +++ b/src/sharry/file/uploading.rs @@ -21,6 +21,7 @@ pub struct FileUploading { pub(super) offset: usize, } +#[derive(Debug)] pub enum UploadError { FileIO(io::Error), Request, @@ -82,7 +83,7 @@ impl FileUploading { return ChunkState::Err(self, UploadError::ResponseOffset); } - self.offset += res_offset; + self.offset = res_offset; if self.offset == self.size { return ChunkState::Finished; diff --git a/src/sharry/mod.rs b/src/sharry/mod.rs index 5da98a5..98d7643 100644 --- a/src/sharry/mod.rs +++ b/src/sharry/mod.rs @@ -7,5 +7,5 @@ mod share; pub use alias::Alias; pub use api::{NewShareRequest, Uri}; -pub use file::{FileChecked, FileUploading}; +pub use file::{ChunkState, FileChecked, FileUploading}; pub use share::Share;