117 lines
3.1 KiB
Rust
117 lines
3.1 KiB
Rust
mod chunks;
|
|
|
|
use std::{
|
|
ffi::OsStr,
|
|
fs::metadata,
|
|
io::{self, ErrorKind},
|
|
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 m = metadata(&path)?;
|
|
if !m.is_file() {
|
|
return Err(io::Error::new(ErrorKind::NotFound, "not a file"));
|
|
}
|
|
|
|
let name = (path.file_name().and_then(OsStr::to_str))
|
|
.ok_or_else(|| io::Error::new(ErrorKind::NotFound, "bad file name"))?
|
|
.to_string();
|
|
|
|
Ok(Self {
|
|
path,
|
|
name,
|
|
size: m.len(),
|
|
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(|e| Other(e.into()))?
|
|
.parse::<u64>()
|
|
.map_err(|e| Other(e.into()))?;
|
|
|
|
if chunk.after() != offset {
|
|
return Err(Other("unexpected offset response".into()));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|