use std::env; use std::error::Error; use std::fs::{create_dir_all, File, OpenOptions}; use std::io::{stdout, BufReader, Write}; use std::path::Path; use std::process::exit; use clap::{App, Arg}; use curl::easy::{Easy, WriteError}; use serde_json::from_reader; use url::Url; use crate::config::{Config, ConfigManager}; use crossterm::{cursor, QueueableCommand}; use lazy_static::lazy_static; const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); lazy_static! { static ref COMMANDS: Vec<&'static str> = vec!["download", "favorites", "print", "searches"]; } mod config; mod model; fn read_animeboxes_backup>( path: P, ) -> Result> { let file = File::open(path)?; let reader = BufReader::new(file); let result: model::anime_boxes::Backup = from_reader(reader)?; Ok(result) } fn write_to_file(data: &[u8], path: &Path) -> std::io::Result<()> { if !path.exists() { let mut file = File::create(path)?; file.write_all(data)?; } else { let mut file = OpenOptions::new().append(true).open(path)?; file.write_all(data)?; } Ok(()) } fn download_command(backup: model::anime_boxes::Backup, mut config: config::ConfigManager) { let temp_directory = env::current_dir().unwrap().join( config .config .save_directory .as_ref() .unwrap_or(&String::from("tmp")), ); create_dir_all(&temp_directory); let favorites: Vec = backup .favorites .iter() .map(|f| String::from(&f.file.url)) .collect(); let mut favorites_to_download: Vec = Vec::new(); for favorite in favorites { if !config.config.downloaded.contains(&favorite) { favorites_to_download.push(favorite); } } let to_download_count = favorites_to_download.len(); let mut count = 0; let mut favorites_downloaded = Vec::new(); for favorite in favorites_to_download { count += 1; let mut easy = Easy::new(); let favorite_url = Url::parse(&favorite); match favorite_url { Err(e) => { println!("Error downloading {}. Not a valid URL", favorite); continue; } Ok(url) => { let path_segments = url.path_segments().unwrap(); let filename = path_segments.last().unwrap(); let target_path = temp_directory.as_path().join(&filename); let progress_count = format!("{}/{}", count, to_download_count); let progress_filename = String::from(filename); easy.url(url.as_str()); easy.progress(true); easy.progress_function( move |total_bytes_to_download, bytes_downloaded, total_bytes_to_upload, bytes_uploaded| { let percentage = (bytes_downloaded / total_bytes_to_download) * 100.0; let mut stdout = stdout(); stdout.queue(cursor::SavePosition); stdout.write( format!( " {} {}: {:.2}", &progress_count, progress_filename, percentage ) .as_bytes(), ); stdout.queue(cursor::RestorePosition); stdout.flush(); true }, ); match easy.write_function(move |data| match write_to_file(data, &target_path) { Err(e) => { println!("Error writing to {:#?}: {}%", target_path, e); Err(WriteError::Pause) } Ok(d) => Ok(data.len()), }) { Err(e) => { println!("Error downloading {}", &favorite); } Ok(_) => favorites_downloaded.push(String::from(&favorite)), } easy.perform().unwrap(); } } } let favorites_downloaded_count = favorites_downloaded.len(); let mut all_downloaded: Vec = Vec::new(); for downloaded in &config.config.downloaded { all_downloaded.push(String::from(downloaded)); } for downloaded in favorites_downloaded { println!("{}", downloaded); all_downloaded.push(downloaded); } let all_downloaded_count = all_downloaded.len(); let new_config = Config { save_directory: Some(String::from(config.config.save_directory.as_ref().unwrap())), downloaded: all_downloaded, }; config.save(new_config); println!( "Downloaded {:#?}/{:#?} images", favorites_downloaded_count, all_downloaded_count ) } fn favorites_command(backup: model::anime_boxes::Backup) { let favorites: Vec = backup .favorites .iter() .map(|f| String::from(&f.file.url)) .collect(); for favorite in favorites { println!("{}", favorite); } } fn searches_command(backup: model::anime_boxes::Backup) { let mut searches: Vec = backup .search_history .iter() .map(|s| String::from(&s.search_text)) .collect(); searches.sort(); for search in searches { println!("{}", search); } } fn main() { let matches = App::new("AnimeBoxes Sync") .version(VERSION.unwrap_or("UNKNOWN")) .author("Drew Short ") .about("Parses AnimeBoxes backup files") .arg( Arg::with_name("config") .short("c") .value_name("FILE") .help("Set a custom config file") .takes_value(true), ) .arg( Arg::with_name("INPUT") .help("The AnimeBoxes file to process") .required(true) .index(1), ) .arg( Arg::with_name("COMMAND") .help("The command to run on the backup") .required(true) .index(2) .possible_values(&COMMANDS), ) .get_matches(); let config = matches.value_of("config").unwrap_or("config.json"); let config_path = env::current_dir().unwrap().join(config); let mut config_manager = ConfigManager::new(config_path.as_path()); let path = matches.value_of("INPUT").unwrap(); let result = read_animeboxes_backup(path).unwrap(); let command = matches.value_of("COMMAND").unwrap(); match command { "download" => download_command(result, config_manager), "favorites" => favorites_command(result), "print" => println!("{:#?}", result), "searches" => searches_command(result), _ => { println!("{} is unrecognized", command); exit(1) } } }