From dcbd44a752891ea3c52c49046093ec67ed392127 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Thu, 4 May 2017 16:19:06 +0200 Subject: [PATCH] use seperate info files --- Cargo.lock | 2 +- src/configfiles.rs | 27 +++++++++++++ src/gog.rs | 97 ++++++++++++++++++++++++---------------------- src/http.rs | 6 +-- src/models.rs | 43 ++++++++++++++++++++ 5 files changed, 125 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aba6141..b4de2ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "gog-sync" -version = "0.2.3" +version = "0.2.4" dependencies = [ "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.22.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/configfiles.rs b/src/configfiles.rs index 2915a32..1d9123b 100644 --- a/src/configfiles.rs +++ b/src/configfiles.rs @@ -13,6 +13,7 @@ use std; use std::io::{Read, Write}; use xdg; use xdg::BaseDirectories; +use std::path::Path; /// Wraps `std::io::Error` and `serde_json::Error`. #[derive(Debug)] @@ -116,4 +117,30 @@ impl ConfigFiles { Err(error) => Err(ConfigError::IOError(error)), } } + + pub fn save_to_path(path: &Path, content: &T) -> Result<(), ConfigError> + where T: Serialize + { + let content_toml = serde_json::to_string_pretty(content)?; + + let mut config_file = File::create(path)?; + match config_file.write_all(content_toml.as_bytes()) { + Ok(_) => Ok(()), + Err(error) => Err(ConfigError::IOError(error)), + } + } + + pub fn load_from_path(path: &Path) -> Result + where T: Deserialize + { + let mut file = File::open(path)?; + + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + match serde_json::from_str(contents.as_str()) { + Ok(value) => Ok(value), + Err(error) => Err(ConfigError::SerdeJsonError(error)), + } + } } diff --git a/src/gog.rs b/src/gog.rs index 1fe3075..cb9b137 100644 --- a/src/gog.rs +++ b/src/gog.rs @@ -13,7 +13,7 @@ use configfiles::{ConfigFiles, ConfigError}; use http::{Http, HttpError}; use models; -use models::{Token, Content, Data, Config}; +use models::{Token, Content, ContentInfo, DataInfo, ExtraInfo, Data}; use serde_json; use serde_json::Value; use std::collections::HashMap; @@ -130,35 +130,9 @@ impl<'a> Gog<'a> { skip_movies: bool, skip_games: bool) -> 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 { - game_storage: String::from(storage_path), - movie_storage: String::from(storage_path_movies), - content: HashMap::new(), - data: HashMap::new(), - extras: HashMap::new(), - os_filters: os_filters.clone(), - language_filters: language_filters.clone(), - resolution_filters: resolution_filters.clone(), - skip_movies: skip_movies, - skip_games: skip_games, - } - } - }; - let content_ids = self.get_content_ids()?; for content_id in content_ids { - let content_hash_saved = match config.content.get(&content_id.to_string()) { - Some(value) => value.clone(), - None => u64::min_value(), - }; - let content = match self.get_content(content_id, &os_filters, &language_filters, @@ -170,6 +144,20 @@ impl<'a> Gog<'a> { } }; + let content_root = if content.is_movie { + Path::new(storage_path_movies).join(&content.title) + } else { + Path::new(storage_path).join(&content.title) + }; + + let content_info_path = Path::new(&content_root).join("info.json"); + + let content_info_saved = + match ConfigFiles::load_from_path::(&content_info_path) { + Ok(value) => value, + Err(_) => ContentInfo::new(), + }; + if (content.is_movie && skip_movies) || (!content.is_movie && skip_games) { info!("filtering {}", content.title); continue; @@ -177,18 +165,23 @@ impl<'a> Gog<'a> { let content_hash = models::get_hash(&content); - if content_hash_saved == content_hash { + if content_info_saved.hash == content_hash { info!("{} already up to date.", &content.title); continue; } - let content_root = if content.is_movie { - Path::new(storage_path_movies).join(&content.title) - } else { - Path::new(storage_path).join(&content.title) + fs::create_dir_all(&content_root)?; + + let mut content_info = ContentInfo { + hash: content_hash, + id: content_id, + title: content.title.clone(), + cd_keys: content.cd_keys.clone(), + data: HashMap::new(), + extras: HashMap::new(), }; - fs::create_dir_all(&content_root)?; + ConfigFiles::save_to_path(&content_info_path, &content_info)?; let key_root = content_root.join("keys"); @@ -203,10 +196,11 @@ impl<'a> Gog<'a> { } for data in content.data { - let data_hash_saved = match config.data.get(&data.manual_url) { - Some(value) => value.clone(), + let data_hash_saved = match content_info_saved.data.get(&content_id.to_string()) { + Some(value) => value.hash, None => u64::min_value(), }; + let data_hash = models::get_hash(&data); if data_hash_saved == data_hash { @@ -220,15 +214,22 @@ impl<'a> Gog<'a> { let data_uri = format!("https://embed.gog.com{}", data.manual_url); info!("downloading {} for {}...", &data.manual_url, &content.title); - self.http_client.download(data_uri.as_str(), &data_root)?; + let filename = self.http_client.download(data_uri.as_str(), &data_root)?; - config.data.insert(data.manual_url, data_hash); - configfiles.save("config.json", &config)?; + let data_info = DataInfo { + hash: data_hash, + filename: filename, + language: data.language, + }; + + content_info.data.insert(data.manual_url.clone(), data_info); + ConfigFiles::save_to_path(&content_info_path, &content_info)?; } for extra in content.extras { - let extra_hash_saved = match config.extras.get(&extra.manual_url) { - Some(value) => value.clone(), + let extra_hash_saved = match content_info_saved.extras + .get(&content_id.to_string()) { + Some(value) => value.hash, None => u64::min_value(), }; @@ -242,14 +243,17 @@ impl<'a> Gog<'a> { let extra_uri = format!("https://embed.gog.com{}", extra.manual_url); info!("downloading {} for {}...", &extra.name, &content.title); - self.http_client.download(extra_uri.as_str(), &content_root)?; + let filename = self.http_client.download(extra_uri.as_str(), &content_root)?; - config.extras.insert(extra.manual_url, extra_hash); - configfiles.save("config.json", &config)?; + let extra_info = ExtraInfo { + hash: extra_hash, + filename: filename, + name: extra.name, + }; + + content_info.extras.insert(extra.manual_url.clone(), extra_info); + ConfigFiles::save_to_path(&content_info_path, &content_info)?; } - - config.content.insert(content_id.to_string(), content_hash); - configfiles.save("config.json", &config)?; } Ok(()) @@ -390,6 +394,7 @@ impl<'a> Gog<'a> { debug!("found {:?}", &content_raw); let mut content: Content = serde_json::from_str(&response)?; + content.id = content_id; let downloads = &content_raw["downloads"]; diff --git a/src/http.rs b/src/http.rs index a4b40ca..61335ff 100644 --- a/src/http.rs +++ b/src/http.rs @@ -131,7 +131,7 @@ impl Http { /// Download a file to the specified folder without creating the folder. /// - /// The filename is taken from the last URI segment. + /// The filename is taken from the last URI segment and returned in the result. /// # Example /// ``` /// use http::Http; @@ -145,7 +145,7 @@ impl Http { pub fn download(&mut self, download_uri: &str, download_dir: &PathBuf) - -> Result<(), HttpError> { + -> Result { let download_path_tmp = Path::new(download_dir.as_os_str()).join(".progress"); let mut file_download = File::create(&download_path_tmp)?; @@ -185,6 +185,6 @@ impl Http { Err(error) => Err(HttpError::IOError(error)), }?; - Ok(()) + Ok(file_name.to_owned()) } } diff --git a/src/models.rs b/src/models.rs index b4e68ca..f6e0989 100644 --- a/src/models.rs +++ b/src/models.rs @@ -84,6 +84,8 @@ impl Config { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Content { + #[serde(skip_deserializing)] + pub id: u64, pub title: String, #[serde(skip_deserializing)] #[serde(rename(deserialize = "cdKey"))] @@ -101,6 +103,47 @@ impl fmt::Display for Content { } } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ContentInfo { + #[serde(skip_deserializing)] + pub id: u64, + pub hash: u64, + pub title: String, + pub cd_keys: BTreeMap, + pub data: HashMap, + pub extras: HashMap, +} + +impl ContentInfo { + pub fn new() -> ContentInfo { + ContentInfo { + id: 0, + hash: 0, + title: String::new(), + cd_keys: BTreeMap::new(), + data: HashMap::new(), + extras: HashMap::new(), + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DataInfo { + pub hash: u64, + pub filename: String, + pub language: String, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExtraInfo { + pub hash: u64, + pub filename: String, + pub name: String, +} + #[derive(Hash)] #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")]