sharry::file reimplementation apparently works
This commit is contained in:
parent
88c6ce94de
commit
cb0d8a3d9a
6 changed files with 18 additions and 241 deletions
25
src/main.rs
25
src/main.rs
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue