Compare commits
2 commits
5643ced9d1
...
b999c35965
| Author | SHA1 | Date | |
|---|---|---|---|
| b999c35965 | |||
| 1ee56ac3da |
6 changed files with 82 additions and 55 deletions
22
src/cli.rs
22
src/cli.rs
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/main.rs
20
src/main.rs
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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>()?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue