Compare commits

...

2 commits

6 changed files with 82 additions and 55 deletions

View file

@ -2,7 +2,7 @@ use std::time::Duration;
use clap::{Parser, builder::PossibleValuesParser};
use super::sharry::File;
use super::sharry::{File, Alias, Uri, NewShareRequest};
#[derive(Parser, Debug, Hash)]
#[command(version, about, long_about = None)]
@ -21,29 +21,29 @@ pub struct Cli {
default_value = "https", value_name = "VARIANT",
value_parser = PossibleValuesParser::new(["http", "https"]),
)]
pub protocol: String,
protocol: String,
/// Name of the new share
#[arg(short, long, default_value = "ShrUpl Upload", value_name = "TEXT")]
pub name: String,
name: String,
/// Description of the new share
#[arg(short, long, value_name = "TEXT")]
pub description: Option<String>,
description: Option<String>,
/// Maximum number of views for the new share
#[arg(short, long, default_value_t = 100, value_name = "N")]
pub max_views: u32,
max_views: u32,
/// Chunk size for uploading, in MiB
#[arg(short, long, default_value_t = 10, value_name = "N")]
pub chunk_size: usize,
/// Base URL for Sharry Instance
pub url: String,
url: String,
/// ID of a public alias to use
pub alias: String,
alias: String,
/// Files to upload to the new share
#[arg(value_name = "FILE", required = true, value_parser = parse_sharry_file)]
@ -62,4 +62,12 @@ impl Cli {
pub fn get_timeout(&self) -> Option<Duration> {
(!self.timeout.is_zero()).then_some(self.timeout)
}
pub fn get_alias(&self) -> Alias {
Alias::new(Uri::with_protocol(&self.protocol, &self.url), &self.alias)
}
pub fn get_share_request(&self) -> NewShareRequest {
NewShareRequest::new(&self.name, self.description.as_ref(), self.max_views)
}
}

View file

@ -6,7 +6,7 @@ use log::{error, info};
use ureq::Agent;
use cli::Cli;
use sharry::{Alias, NewShareRequest, Share, Uri};
use sharry::Share;
fn main() {
env_logger::init();
@ -20,24 +20,24 @@ fn main() {
.build()
.into();
let alias = Alias::new(Uri::with_protocol(args.protocol, args.url), args.alias);
let share = NewShareRequest::new(args.name, args.description, args.max_views);
let share = Share::create(&agent, &alias, share).unwrap();
let alias = args.get_alias();
let share = Share::create(&agent, &alias, args.get_share_request()).unwrap();
info!("share: {share:?}");
for file in args.files {
let file = file.create(&agent, &share).unwrap();
let file = file.create(&agent, &alias, &share).unwrap();
info!("file: {file:?}");
for chunk in file.chunked(args.chunk_size * 1024 * 1024) {
for chunk in file.chunked(args.chunk_size * 1024 * 1024).seek(0) {
info!("chunk len: {}", chunk.bytes.len());
file.upload_chunk(&agent, &alias, &chunk)
.inspect_err(|e| error!("error: {e}"))
.unwrap();
.unwrap_or_else(|e| {
error!("error: {e}");
panic!("{e}");
});
}
}
share.notify(&agent).unwrap();
share.notify(&agent, &alias).unwrap();
}

View file

@ -5,7 +5,7 @@ use ureq::RequestBuilder;
use super::api::Uri;
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct Alias {
pub(super) api_uri: String,
pub(super) id: String,

View file

@ -1,23 +1,31 @@
use std::{
fs::File,
io::{Read, Seek, SeekFrom},
path::PathBuf,
path::Path,
};
use log::error;
pub struct FileChunks<'t> {
path: &'t PathBuf,
cnum: u64,
csize: usize,
file_path: &'t Path,
chunk_index: u64,
chunk_size: usize,
}
impl<'t> FileChunks<'t> {
pub(super) fn new(path: &'t PathBuf, chunk_size: usize) -> Self {
pub(super) fn new(path: &'t Path, chunk_size: usize) -> Self {
Self {
path,
cnum: 0,
csize: chunk_size,
file_path: path,
chunk_index: 0,
chunk_size,
}
}
pub fn seek(self, chunk_index: u64) -> Self {
Self {
file_path: self.file_path,
chunk_index,
chunk_size: self.chunk_size,
}
}
}
@ -26,23 +34,23 @@ impl Iterator for FileChunks<'_> {
type Item = Chunk;
fn next(&mut self) -> Option<Self::Item> {
let offset = {
let csize: u64 = self.csize.try_into().unwrap();
self.cnum * csize
};
let offset = self.chunk_index
* u64::try_from(self.chunk_size)
.inspect_err(|e| error!("Error converting to u64: {e}"))
.ok()?;
let mut f = File::open(self.path)
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.csize];
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);
self.cnum += 1;
self.chunk_index += 1;
Some(Self::Item { offset, bytes }).filter(|c| !c.bytes.is_empty())
}

View file

@ -3,8 +3,9 @@ mod chunks;
use std::{
ffi::OsStr,
fs::metadata,
hash::{Hash, Hasher},
io::{self, ErrorKind},
path::PathBuf,
path::{Path, PathBuf, absolute},
};
use log::{debug, error};
@ -16,44 +17,55 @@ use super::{
};
pub use chunks::{Chunk, FileChunks};
#[derive(Debug, Clone, Hash)]
#[derive(Debug, Clone)]
pub struct File {
path: PathBuf,
abs_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();
impl Hash for File {
fn hash<H: Hasher>(&self, state: &mut H) {
self.abs_path.hash(state);
}
}
let m = metadata(&path)?;
impl File {
pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
let abs_path = absolute(path)?;
let m = metadata(&abs_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))
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 {
path,
abs_path,
name,
size: m.len(),
patch_uri: None,
})
}
pub fn create(self, http: &ureq::Agent, share: &Share) -> Result<Self, ureq::Error> {
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 = (share.alias).get_endpoint(format!("alias/upload/{}/files/tus", share.id));
let endpoint = alias.get_endpoint(format!("alias/upload/{}/files/tus", share.id));
let res = (http.post(endpoint))
.sharry_header(share.alias)
.sharry_header(alias)
.header("Sharry-File-Name", &self.name)
.header("Upload-Length", self.size)
.send_empty()?;
@ -71,7 +83,7 @@ impl File {
debug!("received uri: {location}");
Ok(Self {
path: self.path,
abs_path: self.abs_path,
name: self.name,
size: self.size,
patch_uri: Some(location),
@ -79,7 +91,7 @@ impl File {
}
pub fn chunked(&self, chunk_size: usize) -> FileChunks {
FileChunks::new(&self.path, chunk_size)
FileChunks::new(&self.abs_path, chunk_size)
}
pub fn upload_chunk(

View file

@ -6,15 +6,14 @@ use super::{
};
#[derive(Debug)]
pub struct Share<'t> {
pub(super) alias: &'t Alias,
pub struct Share {
pub(super) id: String,
}
impl<'t> Share<'t> {
impl Share {
pub fn create(
http: &ureq::Agent,
alias: &'t Alias,
alias: &Alias,
data: NewShareRequest,
) -> Result<Self, ureq::Error> {
let res = (http.post(alias.get_endpoint("alias/upload/new")))
@ -29,14 +28,14 @@ impl<'t> Share<'t> {
return Err(ureq::Error::Other("unexpected json response".into()));
}
Ok(Self { alias, id: res.id })
Ok(Self { id: res.id })
}
pub fn notify(&self, http: &ureq::Agent) -> Result<(), ureq::Error> {
let endpoint = (self.alias).get_endpoint(format!("alias/mail/notify/{}", self.id));
pub fn notify(&self, http: &ureq::Agent, alias: &Alias) -> Result<(), ureq::Error> {
let endpoint = alias.get_endpoint(format!("alias/mail/notify/{}", self.id));
let res = (http.post(endpoint))
.sharry_header(self.alias)
.sharry_header(alias)
.send_empty()?
.body_mut()
.read_json::<NotifyShareResponse>()?;