shrupl/src/sharry/file/mod.rs

122 lines
3.1 KiB
Rust

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<String>,
}
impl File {
pub fn new(path: impl Into<PathBuf>) -> io::Result<Self> {
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<Self, ureq::Error> {
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::<u64>()
.map_err(|_| Other("Upload-Offset header not an integer".into()))?;
if chunk.after() != offset {
return Err(Other("unexpected offset response".into()));
}
Ok(())
}
}