sharry::file reimplementation apparently works

This commit is contained in:
Jörn-Michael Miehe 2025-06-04 16:44:16 +00:00
parent 88c6ce94de
commit cb0d8a3d9a
6 changed files with 18 additions and 241 deletions

View file

@ -3,12 +3,12 @@ mod cli;
mod sharry; mod sharry;
use clap::Parser; use clap::Parser;
use log::{error, info}; use log::{debug, error, info};
use ureq::Agent; use ureq::Agent;
use appstate::AppState; use appstate::AppState;
use cli::Cli; use cli::Cli;
use sharry::Share; use sharry::{ChunkState, Share};
fn main() { fn main() {
env_logger::init(); env_logger::init();
@ -31,18 +31,21 @@ fn main() {
info!("share: {share:?}"); info!("share: {share:?}");
for file in args.files { 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:?}"); info!("file: {file:?}");
// for chunk in file.chunked(args.chunk_size * 1024 * 1024).seek(0) { loop {
// info!("chunk: {chunk:?}"); 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) debug!("file: {file:?}");
// .unwrap_or_else(|e| { }
// error!("error: {e}");
// panic!("{e}");
// });
// }
} }
share.notify(&agent, &alias).unwrap(); share.notify(&agent, &alias).unwrap();

View file

@ -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<Self::Item> {
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<u8>,
}
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
}
}

View file

@ -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<String>,
}
impl Hash for File {
fn hash<H: Hasher>(&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<Path>) -> io::Result<Self> {
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<Self, ureq::Error> {
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::<u64>()
.map_err(|e| Other(e.into()))?;
if chunk.after() != offset {
return Err(Other("unexpected offset response".into()));
}
Ok(())
}
}

View file

@ -2,6 +2,6 @@ mod checked;
mod uploading; mod uploading;
pub use checked::FileChecked; pub use checked::FileChecked;
pub use uploading::FileUploading; pub use uploading::{ChunkState, FileUploading};
use super::{Alias, Share, alias::SharryAlias}; use super::{Alias, Share, alias::SharryAlias};

View file

@ -21,6 +21,7 @@ pub struct FileUploading {
pub(super) offset: usize, pub(super) offset: usize,
} }
#[derive(Debug)]
pub enum UploadError { pub enum UploadError {
FileIO(io::Error), FileIO(io::Error),
Request, Request,
@ -82,7 +83,7 @@ impl FileUploading {
return ChunkState::Err(self, UploadError::ResponseOffset); return ChunkState::Err(self, UploadError::ResponseOffset);
} }
self.offset += res_offset; self.offset = res_offset;
if self.offset == self.size { if self.offset == self.size {
return ChunkState::Finished; return ChunkState::Finished;

View file

@ -7,5 +7,5 @@ mod share;
pub use alias::Alias; pub use alias::Alias;
pub use api::{NewShareRequest, Uri}; pub use api::{NewShareRequest, Uri};
pub use file::{FileChecked, FileUploading}; pub use file::{ChunkState, FileChecked, FileUploading};
pub use share::Share; pub use share::Share;