diff --git a/src/main.rs b/src/main.rs index 0488d9a..9451c97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,12 +22,15 @@ fn main() { let share = Share::create(&agent, &alias, share).unwrap(); info!("share: {share:?}"); - let file = File::create(&agent, &share, "/lib/x86_64-linux-gnu/liblldb-14.so.1").unwrap(); + let file = File::new("/lib/x86_64-linux-gnu/liblldb-14.so.1") + .unwrap() + .create(&agent, &share) + .unwrap(); info!("file: {file:?}"); for chunk in file.chunked(10 * 1024 * 1024) { println!("chunk len: {}", chunk.bytes.len()); - file.upload_chunk(&agent, &chunk) + file.upload_chunk(&agent, &alias, &chunk) .inspect_err(|e| error!("error: {e}")) .unwrap(); } diff --git a/src/sharry/file/chunkedfile.rs b/src/sharry/file/chunks.rs similarity index 93% rename from src/sharry/file/chunkedfile.rs rename to src/sharry/file/chunks.rs index b135e6f..c3e10e7 100644 --- a/src/sharry/file/chunkedfile.rs +++ b/src/sharry/file/chunks.rs @@ -6,13 +6,13 @@ use std::{ use log::error; -pub struct ChunkedFile<'t> { +pub struct FileChunks<'t> { path: &'t PathBuf, cnum: u64, csize: usize, } -impl<'t> ChunkedFile<'t> { +impl<'t> FileChunks<'t> { pub(super) fn new(path: &'t PathBuf, chunk_size: usize) -> Self { Self { path, @@ -22,7 +22,7 @@ impl<'t> ChunkedFile<'t> { } } -impl Iterator for ChunkedFile<'_> { +impl Iterator for FileChunks<'_> { type Item = Chunk; fn next(&mut self) -> Option { diff --git a/src/sharry/file/file.rs b/src/sharry/file/file.rs deleted file mode 100644 index 3877861..0000000 --- a/src/sharry/file/file.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::{ - ffi::OsStr, - fs::metadata, - io::{Read, Seek}, - path::PathBuf, -}; - -use log::debug; -use ureq::{Error::Other, http::StatusCode}; - -use super::super::{ - alias::{Alias, SharryAlias}, - share::Share, -}; -use super::chunkedfile::{Chunk, ChunkedFile}; - -#[derive(Debug)] -pub struct File<'t> { - alias: &'t Alias, - path: PathBuf, - patch_uri: String, -} - -impl<'t> File<'t> { - pub fn create( - http: &ureq::Agent, - share: &'t Share, - path: impl Into, - ) -> Result { - let path: PathBuf = path.into(); - let filename = 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", metadata(&path)?.len()) - .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()))?; - - debug!("location: {location}"); - - Ok(Self { - alias: share.alias, - path, - patch_uri: location.into(), - }) - } - - pub fn chunked(&self, chunk_size: usize) -> ChunkedFile { - ChunkedFile::new(&self.path, chunk_size) - } - - pub fn upload_chunk(&self, http: &ureq::Agent, chunk: &Chunk) -> Result<(), ureq::Error> { - debug!("upload uri: {:?}", self.patch_uri); - - let res = http - .patch(&self.patch_uri) - .sharry_header(self.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(()) - } -} diff --git a/src/sharry/file/mod.rs b/src/sharry/file/mod.rs index e8f309f..2ab36a0 100644 --- a/src/sharry/file/mod.rs +++ b/src/sharry/file/mod.rs @@ -1,7 +1,122 @@ -#![allow(unused_imports)] +mod chunks; -mod chunkedfile; -mod file; +use std::{ffi::OsStr, fs::metadata, io, path::PathBuf}; -pub use chunkedfile::{Chunk, ChunkedFile}; -pub use file::File; +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(()) + } +}