From 2b7fcbcfc3ae6fca5e78f49d3e95ed67579e13b9 Mon Sep 17 00:00:00 2001 From: Drew Short Date: Mon, 25 May 2020 14:17:42 -0500 Subject: [PATCH] Added config and download function --- Cargo.lock | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +- src/config.rs | 70 ++++++++++++++++++++ src/main.rs | 116 +++++++++++++++++++++++++++------ 4 files changed, 342 insertions(+), 21 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 678a066..bf3083e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,9 +6,11 @@ version = "0.1.0" dependencies = [ "chrono", "clap", + "curl", "lazy_static", "serde", "serde_json", + "url", ] [[package]] @@ -43,6 +45,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "cc" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "chrono" version = "0.4.11" @@ -70,6 +84,36 @@ dependencies = [ "vec_map", ] +[[package]] +name = "curl" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "762e34611d2d5233a506a79072be944fddd057db2f18e04c0d6fa79e3fd466fd" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.31+curl-7.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcd62757cc4f5ab9404bc6ca9f0ae447e729a1403948ce5106bd588ceac6a3b0" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + [[package]] name = "hermit-abi" version = "0.1.13" @@ -79,6 +123,17 @@ dependencies = [ "libc", ] +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itoa" version = "0.4.5" @@ -97,6 +152,24 @@ version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +[[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + [[package]] name = "num-integer" version = "0.1.42" @@ -116,6 +189,37 @@ dependencies = [ "autocfg", ] +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7410fef80af8ac071d4f63755c0ab89ac3df0fd1ea91f1d1f37cf5cec4395990" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" + [[package]] name = "proc-macro2" version = "1.0.17" @@ -134,12 +238,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + [[package]] name = "ryu" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + [[package]] name = "serde" version = "1.0.110" @@ -171,6 +291,24 @@ dependencies = [ "serde", ] +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "strsim" version = "0.8.0" @@ -207,6 +345,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +dependencies = [ + "smallvec", +] + [[package]] name = "unicode-width" version = "0.1.7" @@ -219,6 +375,23 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index fe7ed0c..08630bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,9 @@ incremental = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] chrono = { version = "0.4.11", features = ["serde"] } +curl = "0.4.29" clap = "2.33.1" lazy_static = "1.4.0" serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" \ No newline at end of file +serde_json = "1.0" +url = "2.1.1" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..9e89bed --- /dev/null +++ b/src/config.rs @@ -0,0 +1,70 @@ +use std::error::Error; +use std::ffi::OsString; +use std::fs::File; +use std::io::{BufReader, BufWriter}; +use std::path::Path; +use std::process::exit; + +use serde::ser::Error as SerdeError; +use serde::{Deserialize, Serialize}; +use serde_json::{from_reader, to_writer_pretty}; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub save_directory: Option, + pub downloaded: Vec, +} + +pub struct ConfigManager { + config_path: OsString, + pub config: Config, +} + +fn read_config>(path: P) -> Result> { + let file = File::open(path)?; + let reader = BufReader::new(file); + let result: Config = from_reader(reader)?; + Ok(result) +} + +fn save_config>(path: P, config: &Config) -> serde_json::Result<()> { + match File::create(path) { + Err(e) => Err(serde_json::Error::custom(e)), + Ok(file) => to_writer_pretty(file, config), + } +} + +impl ConfigManager { + pub fn new(config_path: &Path) -> ConfigManager { + let mut config = ConfigManager { + config_path: OsString::from(config_path.as_os_str()), + config: Config { + save_directory: None, + downloaded: vec![], + }, + }; + config.load(); + config + } + + fn load(&mut self) { + match read_config(&self.config_path) { + Err(e) => { + println!("Failed to read config file {:#?}", &self.config_path); + exit(1); + } + Ok(config) => self.config = config, + } + } + + pub fn save(&mut self, config: Config) { + match save_config(&self.config_path, &config) { + Err(e) => { + println!("Unexpected error saving config: {}", e); + } + Ok(_) => (), + } + self.config = config; + } +} diff --git a/src/main.rs b/src/main.rs index 89f141a..192cdf0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,24 @@ -use clap::{App, Arg}; -use lazy_static::lazy_static; -use serde_json::from_reader; +use std::env; use std::error::Error; use std::fs::File; -use std::io::BufReader; +use std::io::{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 lazy_static::lazy_static; + const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); lazy_static! { - static ref COMMANDS: Vec<&'static str> = vec!["print", "searches", "favorites"]; + static ref COMMANDS: Vec<&'static str> = vec!["download", "favorites", "print", "searches"]; } +mod config; mod model; fn read_animeboxes_backup>( @@ -23,20 +30,78 @@ fn read_animeboxes_backup>( Ok(result) } -fn searches_command(backup: model::anime_boxes::Backup) { - let mut searches: Vec = backup - .search_history +fn write_to_file(data: &[u8], path: &Path) -> std::io::Result<()> { + let mut file = File::create(path)?; + file.write_all(data)?; + Ok(()) +} + +fn download_command(backup: model::anime_boxes::Backup, mut config: config::ConfigManager) { + let favorites: Vec = backup + .favorites .iter() - .map(|s| String::from(&s.search_text)) + .map(|f| String::from(&f.file.url)) .collect(); - searches.sort(); - for search in searches { - println!("{}", search); + let mut favorites_to_download: Vec = Vec::new(); + for favorite in favorites { + if !config.config.downloaded.contains(&favorite) { + favorites_to_download.push(favorite); + } + } + let mut favorites_downloaded = Vec::new(); + for favorite in favorites_to_download { + 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 target_path = env::current_dir().unwrap().join( + config + .config + .save_directory + .as_ref() + .unwrap_or(&String::from("tmp")), + ); + easy.url(url.as_str()); + match easy.write_function(move |data| match write_to_file(data, &target_path) { + Err(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 mut favorites: Vec = backup + let favorites: Vec = backup .favorites .iter() .map(|f| String::from(&f.file.url)) @@ -46,6 +111,18 @@ fn favorites_command(backup: model::anime_boxes::Backup) { } } +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")) @@ -73,19 +150,18 @@ fn main() { ) .get_matches(); - let config = matches.value_of("config").unwrap_or("abs-default.conf"); + 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 { - "favorites" => { - favorites_command(result); - } + "download" => download_command(result, config_manager), + "favorites" => favorites_command(result), "print" => println!("{:#?}", result), - "searches" => { - searches_command(result); - } + "searches" => searches_command(result), _ => { println!("{} is unrecognized", command); exit(1)