use seperate info files

This commit is contained in:
Sebastian Hugentobler 2017-05-04 16:19:06 +02:00
parent a8be23508c
commit dcbd44a752
5 changed files with 125 additions and 50 deletions

2
Cargo.lock generated
View File

@ -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)",

View File

@ -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<T>(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<T>(path: &Path) -> Result<T, ConfigError>
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)),
}
}
}

View File

@ -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::<ContentInfo>(&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"];

View File

@ -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<String, HttpError> {
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())
}
}

View File

@ -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<String, String>,
pub data: HashMap<String, DataInfo>,
pub extras: HashMap<String, ExtraInfo>,
}
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")]