Compare commits
1 commit
351596d56d
...
173e9c38df
| Author | SHA1 | Date | |
|---|---|---|---|
| 173e9c38df |
8 changed files with 57 additions and 163 deletions
|
|
@ -1,13 +1,13 @@
|
|||
use std::{fmt, io, time::Duration};
|
||||
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget};
|
||||
use console::style;
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use log::{debug, warn};
|
||||
|
||||
use crate::{
|
||||
cachefile::CacheFile,
|
||||
cli::Cli,
|
||||
file::{Chunk, FileTrait},
|
||||
output::new_progressbar,
|
||||
sharry::{self, Client},
|
||||
};
|
||||
|
||||
|
|
@ -46,8 +46,6 @@ impl AppState {
|
|||
.inspect_err(|e| debug!("could not resume from hash {:?}: {e}", args.get_hash()))
|
||||
.ok()?;
|
||||
|
||||
inner.peek_uploading();
|
||||
|
||||
Some(Self::new(new_http(args.get_timeout()), inner))
|
||||
}
|
||||
|
||||
|
|
@ -56,11 +54,23 @@ impl AppState {
|
|||
|
||||
let share_id = http.share_create(&args.get_uri(), &args.alias, args.get_share_request())?;
|
||||
|
||||
Ok(Self::new(http, CacheFile::from_args(args, share_id)?))
|
||||
Ok(Self::new(http, CacheFile::from_args(args, share_id)))
|
||||
}
|
||||
|
||||
fn with_progressbar(&mut self, f: impl FnOnce(&ProgressBar), drop_bar: bool) {
|
||||
let bar = &*self.progress.get_or_insert_with(new_progressbar);
|
||||
let bar = &*self.progress.get_or_insert_with(|| {
|
||||
ProgressBar::hidden().with_style(
|
||||
ProgressStyle::with_template(&format!(
|
||||
concat!(
|
||||
"{{bar:50.cyan/blue}} {{msg:.magenta}}: ",
|
||||
"{{binary_bytes:.yellow}}{}{{binary_total_bytes:.yellow}} ",
|
||||
"({{eta}})",
|
||||
),
|
||||
style("/").magenta(),
|
||||
))
|
||||
.expect("style template is not valid"),
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(upl) = self.inner.peek_uploading() {
|
||||
if bar.length().is_none() {
|
||||
|
|
@ -144,7 +154,7 @@ impl AppState {
|
|||
self.http
|
||||
.share_create(&args.get_uri(), &args.alias, args.get_share_request())?;
|
||||
|
||||
Ok(Self::new(self.http, CacheFile::from_args(args, share_id)?))
|
||||
Ok(Self::new(self.http, CacheFile::from_args(args, share_id)))
|
||||
}
|
||||
|
||||
pub fn save(&self) -> io::Result<()> {
|
||||
|
|
|
|||
|
|
@ -3,17 +3,14 @@ use std::{
|
|||
fs,
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use indicatif::ProgressDrawTarget;
|
||||
use log::{debug, trace};
|
||||
use log::trace;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
cli::Cli,
|
||||
file::{self, Chunk, FileTrait},
|
||||
output::new_progressbar,
|
||||
file::{self, Chunk},
|
||||
sharry::{self, Client, Uri},
|
||||
};
|
||||
|
||||
|
|
@ -59,35 +56,15 @@ impl CacheFile {
|
|||
Ok(Self { file_name, ..state })
|
||||
}
|
||||
|
||||
pub fn from_args(args: &Cli, share_id: String) -> io::Result<Self> {
|
||||
// TODO CLI switch begin
|
||||
|
||||
let mut files = args.files.clone();
|
||||
|
||||
let bar = new_progressbar();
|
||||
bar.set_draw_target(ProgressDrawTarget::stderr());
|
||||
// BOOKMARK assumption: total file size < 2 EiB
|
||||
bar.set_length(files.iter().map(|f| f.get_size()).sum());
|
||||
bar.enable_steady_tick(Duration::from_millis(50));
|
||||
|
||||
for chk in &mut files {
|
||||
bar.set_message(format!("hashing {:?}", chk.get_name()));
|
||||
chk.hash(|bytes| bar.inc(bytes))?;
|
||||
debug!("{chk:?}");
|
||||
}
|
||||
|
||||
bar.finish_with_message("finished hashing files");
|
||||
|
||||
// TODO CLI switch end
|
||||
|
||||
Ok(Self {
|
||||
pub fn from_args(args: &Cli, share_id: String) -> Self {
|
||||
Self {
|
||||
file_name: Self::cache_file(args),
|
||||
uri: args.get_uri(),
|
||||
alias_id: args.alias.clone(),
|
||||
share_id,
|
||||
uploading: None,
|
||||
files: files.into(),
|
||||
})
|
||||
files: args.files.clone().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_empty(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ impl Cli {
|
|||
}
|
||||
|
||||
pub fn get_hash(&self) -> String {
|
||||
let mut hasher = Blake2b::new().hash_length(16).to_state();
|
||||
let mut hasher = Blake2b::new().hash_length(64).to_state();
|
||||
|
||||
hasher.update(self.get_uri().as_ref());
|
||||
hasher.update(self.alias.as_bytes());
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ pub struct Checked {
|
|||
pub(super) path: PathBuf,
|
||||
/// size of that file
|
||||
pub(super) size: u64,
|
||||
/// hash of that file
|
||||
pub(super) hash: Option<String>,
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Checked {
|
||||
|
|
@ -43,7 +41,6 @@ impl Checked {
|
|||
Ok(Self {
|
||||
path: fs::canonicalize(&value)?,
|
||||
size: meta.len(),
|
||||
hash: None,
|
||||
})
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
|
|
@ -53,19 +50,6 @@ impl Checked {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn hash(&mut self, f: impl Fn(u64)) -> io::Result<()> {
|
||||
if self.hash.is_some() {
|
||||
return Err(io::Error::other(format!(
|
||||
"file {:?} is already hashed!",
|
||||
self.path.display()
|
||||
)));
|
||||
}
|
||||
|
||||
self.hash = Some(super::compute_file_hash(&self.path, self.size, f)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// start uploading this file
|
||||
///
|
||||
/// - tries to create a new entry in a share
|
||||
|
|
@ -84,7 +68,7 @@ impl Checked {
|
|||
) -> sharry::Result<Uploading> {
|
||||
let file_id = client.file_create(uri, alias_id, share_id, &self)?;
|
||||
|
||||
Ok(Uploading::new(self.path, self.size, self.hash, file_id))
|
||||
Ok(Uploading::new(self.path, self.size, file_id))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,8 +84,4 @@ impl<'t> FileTrait<'t> for Checked {
|
|||
fn get_size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn check_hash(&self, on_progress: impl Fn(u64)) -> io::Result<bool> {
|
||||
super::check_file_hash(&self.path, self.size, &self.hash, on_progress)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,62 +2,12 @@ mod checked;
|
|||
mod chunk;
|
||||
mod uploading;
|
||||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
io::{self, Read},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use base64ct::{Base64, Encoding};
|
||||
use blake2b_simd::Params as Blake2b;
|
||||
use std::{ffi::OsStr, path::Path};
|
||||
|
||||
pub use checked::Checked;
|
||||
pub use chunk::Chunk;
|
||||
pub use uploading::Uploading;
|
||||
|
||||
fn compute_file_hash<P>(path: P, size: u64, on_progress: impl Fn(u64)) -> io::Result<String>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut file = fs::File::open(path)?;
|
||||
let mut hasher = Blake2b::new().hash_length(64).to_state();
|
||||
|
||||
let mut buf = vec![0u8; 4 * 1024 * 1024];
|
||||
let mut bytes_read = 0;
|
||||
|
||||
loop {
|
||||
let n = file.read(&mut buf)?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
hasher.update(&buf[..n]);
|
||||
|
||||
bytes_read += n as u64;
|
||||
on_progress(n as u64);
|
||||
}
|
||||
|
||||
if bytes_read != size {
|
||||
return Err(io::Error::other(format!(
|
||||
"Hashed {bytes_read:?} bytes, known file size {:?}!",
|
||||
size
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Base64::encode_string(hasher.finalize().as_bytes()))
|
||||
}
|
||||
|
||||
fn check_file_hash(
|
||||
path: impl AsRef<Path>,
|
||||
size: u64,
|
||||
hash: &Option<String>,
|
||||
on_progress: impl Fn(u64),
|
||||
) -> io::Result<bool> {
|
||||
let Some(hash) = hash else { return Ok(false) };
|
||||
|
||||
Ok(*hash == compute_file_hash(path, size, on_progress)?)
|
||||
}
|
||||
|
||||
pub trait FileTrait<'t> {
|
||||
/// extract the filename part of a `Path` reference
|
||||
///
|
||||
|
|
@ -75,6 +25,4 @@ pub trait FileTrait<'t> {
|
|||
|
||||
/// get the file's size
|
||||
fn get_size(&self) -> u64;
|
||||
|
||||
fn check_hash(&self, on_progress: impl Fn(u64)) -> io::Result<bool>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,8 @@ use super::{Checked, Chunk, FileTrait};
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Uploading {
|
||||
/// canonical path to a regular file
|
||||
path: PathBuf,
|
||||
/// size of that file
|
||||
size: u64,
|
||||
/// hash of that file
|
||||
hash: Option<String>,
|
||||
file_id: String,
|
||||
#[serde(skip)]
|
||||
last_offset: Option<u64>,
|
||||
|
|
@ -24,11 +20,10 @@ pub struct Uploading {
|
|||
}
|
||||
|
||||
impl Uploading {
|
||||
pub(super) fn new(path: PathBuf, size: u64, hash: Option<String>, file_id: String) -> Self {
|
||||
pub(super) fn new(path: PathBuf, size: u64, file_id: String) -> Self {
|
||||
Self {
|
||||
path,
|
||||
size,
|
||||
hash,
|
||||
file_id,
|
||||
last_offset: None,
|
||||
offset: 0,
|
||||
|
|
@ -84,7 +79,6 @@ impl Uploading {
|
|||
Checked {
|
||||
path: self.path,
|
||||
size: self.size,
|
||||
hash: self.hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -100,8 +94,4 @@ impl<'t> FileTrait<'t> for Uploading {
|
|||
fn get_size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn check_hash(&self, on_progress: impl Fn(u64)) -> io::Result<bool> {
|
||||
super::check_file_hash(&self.path, self.size, &self.hash, on_progress)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
56
src/main.rs
56
src/main.rs
|
|
@ -24,32 +24,6 @@ use output::{Log, SHRUPL};
|
|||
use sharry::{ClientError, Parameter};
|
||||
|
||||
fn main() {
|
||||
let args = Cli::parse();
|
||||
|
||||
env_logger::Builder::new()
|
||||
.filter_module("shrupl", args.get_level_filter())
|
||||
.parse_default_env()
|
||||
.init();
|
||||
|
||||
info!("args: {args:#?}");
|
||||
|
||||
println!("{} to {}!", style("Welcome").magenta().bold(), *SHRUPL);
|
||||
|
||||
let mut state = AppState::try_resume(&args)
|
||||
.and_then(|state| output::prompt_continue().then_some(state))
|
||||
.unwrap_or_else(|| match AppState::from_args(&args) {
|
||||
Ok(state) => {
|
||||
state.save().unwrap_or_else(|e| {
|
||||
Log::warning(format_args!("Failed to save state: {e}"));
|
||||
});
|
||||
state
|
||||
}
|
||||
Err(e) => {
|
||||
Log::handle(&e);
|
||||
Log::error(format_args!("Failed to create state: {e}"));
|
||||
}
|
||||
});
|
||||
|
||||
let check_ctrlc = {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let stop_ctrlc = stop.clone();
|
||||
|
|
@ -67,6 +41,36 @@ fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
let args = Cli::parse();
|
||||
|
||||
env_logger::Builder::new()
|
||||
.filter_module("shrupl", args.get_level_filter())
|
||||
.parse_default_env()
|
||||
.init();
|
||||
|
||||
info!("args: {args:#?}");
|
||||
|
||||
println!("{} to {}!", style("Welcome").magenta().bold(), *SHRUPL);
|
||||
|
||||
let mut state = AppState::try_resume(&args)
|
||||
.and_then(|state| output::prompt_continue().then_some(state))
|
||||
.unwrap_or_else(|| {
|
||||
check_ctrlc();
|
||||
|
||||
match AppState::from_args(&args) {
|
||||
Ok(state) => {
|
||||
state.save().unwrap_or_else(|e| {
|
||||
Log::warning(format_args!("Failed to save state: {e}"));
|
||||
});
|
||||
state
|
||||
}
|
||||
Err(e) => {
|
||||
Log::handle(&e);
|
||||
Log::error(format_args!("Failed to create state: {e}"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
info!("continuing with state: {state:#?}");
|
||||
|
||||
let fns_magenta = output::style_all(&args.file_names(), StyledObject::magenta).join(", ");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use std::{fmt, process, sync::LazyLock};
|
|||
|
||||
use console::{StyledObject, style};
|
||||
use dialoguer::{Select, theme::ColorfulTheme};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use log::{error, info};
|
||||
|
||||
use crate::sharry;
|
||||
|
|
@ -44,20 +43,6 @@ where
|
|||
strs.iter().map(|&s| f(style(s)).to_string()).collect()
|
||||
}
|
||||
|
||||
pub fn new_progressbar() -> ProgressBar {
|
||||
ProgressBar::hidden().with_style(
|
||||
ProgressStyle::with_template(&format!(
|
||||
concat!(
|
||||
"{{bar:50.cyan/blue}} {{msg:.magenta}}: ",
|
||||
"{{binary_bytes:.yellow}}{}{{binary_total_bytes:.yellow}} ",
|
||||
"({{eta}})",
|
||||
),
|
||||
style("/").magenta(),
|
||||
))
|
||||
.expect("invalid style template"),
|
||||
)
|
||||
}
|
||||
|
||||
pub enum Log {}
|
||||
|
||||
impl Log {
|
||||
|
|
|
|||
Loading…
Reference in a new issue