From b714f3b2fca3ea8d3c85785614bb9244355faee8 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Mon, 20 Mar 2017 16:20:37 +0100 Subject: [PATCH] first usable version --- Cargo.lock | 10 ----- Cargo.toml | 1 - src/{config.rs => configfiles.rs} | 31 +++++-------- src/gog.rs | 73 ++++++++++++++++++++++++++++--- src/http.rs | 2 +- src/main.rs | 25 ++++++++--- src/models.rs | 16 +++++++ 7 files changed, 117 insertions(+), 41 deletions(-) rename src/{config.rs => configfiles.rs} (67%) diff --git a/Cargo.lock b/Cargo.lock index 9a69ca3..ecaac91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,6 @@ dependencies = [ "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -353,14 +352,6 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "toml" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "unicode-bidi" version = "0.2.5" @@ -475,7 +466,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" "checksum time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "211b63c112206356ef1ff9b19355f43740fc3f85960c598a93d3a3d3ba7beade" -"checksum toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3474f3c6eaf32eedb4f4a66a26214f020f828a6d96c37e38a35e3a379bbcfd11" "checksum unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a078ebdd62c0e71a709c3d53d2af693fe09fe93fbff8344aebe289b78f9032" "checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" diff --git a/Cargo.toml b/Cargo.toml index b8fbb0b..015c155 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,5 @@ log = "0.3" serde = "0.9.11" serde_derive = "0.9.11" serde_json = "0.9" -toml = "0.3.1" url = "1.4.0" xdg = "2.1.0" diff --git a/src/config.rs b/src/configfiles.rs similarity index 67% rename from src/config.rs rename to src/configfiles.rs index b6c71d2..d07c2e4 100644 --- a/src/config.rs +++ b/src/configfiles.rs @@ -1,16 +1,15 @@ use serde::{Deserialize, Serialize}; +use serde_json; use std::fs::File; use std; use std::io::{Read, Write}; -use toml; use xdg; use xdg::BaseDirectories; #[derive(Debug)] pub enum ConfigError { IOError(std::io::Error), - TomlDeError(toml::de::Error), - TomlSeError(toml::ser::Error), + SerdeJsonError(serde_json::Error), } impl From for ConfigError { @@ -19,26 +18,20 @@ impl From for ConfigError { } } -impl From for ConfigError { - fn from(e: toml::de::Error) -> Self { - ConfigError::TomlDeError(e) +impl From for ConfigError { + fn from(e: serde_json::Error) -> Self { + ConfigError::SerdeJsonError(e) } } -impl From for ConfigError { - fn from(e: toml::ser::Error) -> Self { - ConfigError::TomlSeError(e) - } -} - -pub struct Config { +pub struct ConfigFiles { pub xdg_dirs: BaseDirectories, } -impl Config { - pub fn new() -> Config { +impl ConfigFiles { + pub fn new() -> ConfigFiles { let xdg_dirs = xdg::BaseDirectories::with_prefix("gog-sync").unwrap(); - Config { xdg_dirs: xdg_dirs } + ConfigFiles { xdg_dirs: xdg_dirs } } pub fn load(&self, name: &str) -> Result @@ -50,9 +43,9 @@ impl Config { let mut config_contents = String::new(); config_file.read_to_string(&mut config_contents)?; - match toml::from_str(config_contents.as_str()) { + match serde_json::from_str(config_contents.as_str()) { Ok(value) => Ok(value), - Err(error) => Err(ConfigError::TomlDeError(error)), + Err(error) => Err(ConfigError::SerdeJsonError(error)), } } @@ -60,7 +53,7 @@ impl Config { where T: Serialize { let config_path = self.xdg_dirs.place_config_file(name)?; - let content_toml = toml::to_string(content)?; + let content_toml = serde_json::to_string_pretty(content)?; let mut config_file = File::create(&config_path)?; match config_file.write_all(content_toml.as_bytes()) { diff --git a/src/gog.rs b/src/gog.rs index fefe81a..68fa503 100644 --- a/src/gog.rs +++ b/src/gog.rs @@ -1,10 +1,12 @@ -use config::{Config, ConfigError}; +use configfiles::{ConfigFiles, ConfigError}; use http::{Http, HttpError}; use models; -use models::{Token, Game, Installer}; +use models::{Token, Game, Installer, Config}; use serde_json; use serde_json::Value; +use std::collections::HashMap; use std::fs; +use std::fs::File; use std::io; use std::io::Write; use std::path::Path; @@ -62,8 +64,8 @@ impl<'a> Gog<'a> { } pub fn login(&mut self) -> Result<(), GogError> { - let config = Config::new(); - let mut token: Token = match config.load("token.toml") { + let config = ConfigFiles::new(); + let mut token: Token = match config.load("token.json") { Ok(value) => value, Err(_) => { let code = self.get_code()?; @@ -75,7 +77,7 @@ impl<'a> Gog<'a> { token = self.refresh_token(token.refresh_token.as_str())?; } - config.save("token.toml", &token)?; + config.save("token.json", &token)?; let auth_header = format!("Authorization: Bearer {token}", token = token.access_token); self.http_client.add_header(auth_header.as_str())?; @@ -84,34 +86,91 @@ impl<'a> Gog<'a> { } pub fn sync(&mut self, storage_path: &str) -> Result<(), GogError> { + let configfiles = ConfigFiles::new(); + let mut config: Config = match configfiles.load("config.json") { + Ok(value) => value, + Err(_) => { + error!("Configuration error, generating new one..."); + + Config { + storage: String::from(storage_path), + games: HashMap::new(), + installers: HashMap::new(), + extras: HashMap::new(), + } + } + }; + let game_ids = self.get_game_ids()?; for game_id in game_ids { + let game_hash_saved = match config.games.get(&game_id.to_string()) { + Some(value) => value.clone(), + None => u64::min_value(), + }; + let game = self.get_game(game_id)?; let game_hash = models::get_hash(&game); + if game_hash_saved == game_hash { + info!("{} already up to date.", &game.title); + continue; + } + let game_root = Path::new(storage_path).join(&game.title); fs::create_dir_all(&game_root)?; + if !game.cd_key.is_empty() { + let key_path = Path::new(game_root.as_os_str()).join("key.txt"); + let mut key_file = File::create(&key_path)?; + key_file.write_all(game.cd_key.as_bytes())? + } for installer in game.installers { + let installer_hash_saved = match config.installers.get(&installer.manual_url) { + Some(value) => value.clone(), + None => u64::min_value(), + }; let installer_hash = models::get_hash(&installer); + if installer_hash_saved == installer_hash { + info!("{} already up to date.", &installer.manual_url); + continue; + } + let installer_uri = format!("https://embed.gog.com{}", installer.manual_url); info!("downloading {} for {}...", &installer.manual_url, &game.title); self.http_client.download(installer_uri.as_str(), &game_root)?; + + config.installers.insert(installer.manual_url, installer_hash); + configfiles.save("config.json", &config)?; } for extra in game.extras { + let extra_hash_saved = match config.installers.get(&extra.manual_url) { + Some(value) => value.clone(), + None => u64::min_value(), + }; let extra_hash = models::get_hash(&extra); + if extra_hash_saved == extra_hash { + info!("{} already up to date.", &extra.manual_url); + continue; + } + let extra_uri = format!("https://embed.gog.com{}", extra.manual_url); info!("downloading {} for {}...", &extra.name, &game.title); self.http_client.download(extra_uri.as_str(), &game_root)?; + + config.extras.insert(extra.manual_url, extra_hash); + configfiles.save("config.json", &config)?; } + + config.games.insert(game_id.to_string(), game_hash); + configfiles.save("config.json", &config)?; } Ok(()) @@ -186,6 +245,10 @@ impl<'a> Gog<'a> { continue; } + if [1, 2, 3, 4, 5].contains(&game_id_parsed) { + continue; + } + game_ids.push(game_id_parsed); } diff --git a/src/http.rs b/src/http.rs index 1ee3ed4..22a4041 100644 --- a/src/http.rs +++ b/src/http.rs @@ -45,7 +45,7 @@ impl From for HttpError { } impl From for WriteError { - fn from(e: HttpError) -> Self { + fn from(_: HttpError) -> Self { WriteError::__Nonexhaustive } } diff --git a/src/main.rs b/src/main.rs index e7126da..41f5757 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,19 +8,20 @@ extern crate serde; extern crate serde_json; #[macro_use] extern crate serde_derive; -extern crate toml; extern crate url; extern crate xdg; -mod config; +mod configfiles; mod gog; mod http; mod models; -use config::Config; +use configfiles::ConfigFiles; use clap::{Arg, App}; use gog::Gog; use http::Http; +use models::Config; +use std::collections::HashMap; fn main() { env_logger::init().unwrap(); @@ -37,9 +38,23 @@ fn main() { .takes_value(true)) .get_matches(); - let download_folder = matches.value_of("storage").unwrap_or("."); + let configfiles = ConfigFiles::new(); + let config: Config = match configfiles.load("config.json") { + Ok(value) => value, + Err(_) => { + Config { + storage: String::from("."), + games: HashMap::new(), + installers: HashMap::new(), + extras: HashMap::new(), + } + } + }; - let config = Config::new(); + let download_folder: &str = match matches.value_of("storage") { + Some(value) => value, + None => config.storage.as_str(), + }; let mut http_client = Http::new(); let mut gog = Gog::new(&mut http_client); diff --git a/src/models.rs b/src/models.rs index 30c4e19..c41289a 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,6 +1,7 @@ use chrono::UTC; use std::fmt; use std::hash::{Hash, Hasher}; +use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; #[derive(Serialize, Deserialize)] @@ -28,6 +29,21 @@ fn timestamp() -> i64 { UTC::now().timestamp() } +#[derive(Serialize, Deserialize)] +pub struct Config { + pub storage: String, + #[serde(default = "default_map")] + pub games: HashMap, + #[serde(default = "default_map")] + pub installers: HashMap, + #[serde(default = "default_map")] + pub extras: HashMap, +} + +fn default_map() -> HashMap { + HashMap::new() +} + #[derive(Hash)] #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")]