shrupl/src/main.rs
Jörn-Michael Miehe 783346c888 major refactoring
- `sharry::Client` fn signatures
- `sharry::ClientError` rework
- `file::Uploading` saves `file_id` instead of `patch_uri`
- `impl sharry::Client for ureq::Agent` into separate file
2025-06-18 14:21:25 +00:00

182 lines
4.5 KiB
Rust

mod appstate;
mod cachefile;
mod cli;
mod file;
mod impl_ureq;
mod sharry;
use std::{
process,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
use clap::Parser;
use console::style;
use dialoguer::{Select, theme::ColorfulTheme};
use log::{error, info, trace};
use appstate::AppState;
use cli::Cli;
use sharry::ClientError;
fn prompt_continue() -> bool {
let prompt = format!(
"This operation has previously been stopped. {}",
style("How to proceed?").cyan()
);
let choices = [
format!("Load and {}", style("continue operation").green().bold()),
format!("Start a {}", style("new operation").cyan().bold()),
format!("Quit {}", style("ShrUpl").yellow().bold()),
];
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt(prompt)
.default(0)
.items(&choices)
.interact()
.unwrap_or(2);
if selection == 2 {
process::exit(255);
}
selection == 0
}
fn handle_error(e: &ClientError) {
if e.is_fatal() {
// react to fatal error
error!("fatal error: {e:?}");
eprintln!(
"{} {}",
style("Error!").red().bold(),
style(e.to_string()).cyan().italic(),
);
process::exit(1);
}
// handle recoverable error
info!("recoverable error: {e:?}");
}
fn main() {
env_logger::init();
let check_ctrlc = {
let stop = Arc::new(AtomicBool::new(false));
let stop_ctrlc = stop.clone();
ctrlc::set_handler(move || {
stop_ctrlc.store(true, Ordering::SeqCst);
info!("stopping as soon as possible ...");
})
.expect("Error setting Ctrl-C handler");
move || {
if stop.load(Ordering::SeqCst) {
process::exit(255);
}
}
};
let args = Cli::parse();
info!("args: {args:#?}");
println!(
"{} to {}!",
style("Welcome").magenta().bold(),
style("ShrUpl").yellow().bold(),
);
let mut state = AppState::try_resume(&args)
.and_then(|state| prompt_continue().then_some(state))
.unwrap_or_else(|| {
check_ctrlc();
match AppState::from_args(&args) {
Ok(state) => {
state.save().unwrap_or_else(|e| {
eprintln!(
"{} Failed to save {} state: {e}",
style("Warning:").red().bold(),
style("ShrUpl").yellow().bold(),
);
});
state
}
Err(e) => {
handle_error(&e);
process::exit(1);
}
}
});
info!("continuing with state: {state:#?}");
let fns_magenta = state
.file_names()
.iter()
.map(|&n| style(n).magenta().to_string())
.collect::<Vec<_>>()
.join(", ");
println!(
"{} is uploading: {fns_magenta}",
style("ShrUpl").yellow().bold(),
);
let mut buffer = vec![0; args.chunk_size * 1024 * 1024];
loop {
match state.upload_chunk(&mut buffer) {
Err(e) => {
// TODO better error handling (this will just retry endlessly)
// Error 404: Share might have been deleted
handle_error(&e);
if let Some(s) = state.rewind() {
trace!("State rewound, retrying last chunk");
state = s;
} else {
eprintln!("{} Failed to retry chunk!", style("Error:").red().bold());
process::exit(1);
}
}
Ok(false) => {
trace!("chunk uploaded");
}
Ok(true) => {
info!("all uploads done");
break;
}
}
state.save().unwrap_or_else(|e| {
eprintln!(
"{} Failed to save {} state: {e}",
style("Warning:").red().bold(),
style("ShrUpl").yellow().bold(),
);
});
check_ctrlc();
}
state.clear().unwrap_or_else(|e| {
eprintln!(
"{} Failed to remove {} state: {e}",
style("Warning:").red().bold(),
style("ShrUpl").yellow().bold(),
);
});
println!(
"{} finished {}",
style("ShrUpl").yellow().bold(),
style("successfully!").green()
);
}