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::>() .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() ); }