first usable version
This commit is contained in:
parent
5ce0b953cb
commit
b714f3b2fc
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -10,7 +10,6 @@ dependencies = [
|
|||||||
"serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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_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)",
|
"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)",
|
"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)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.2.5"
|
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-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 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 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-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-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"
|
"checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3"
|
||||||
|
@ -12,6 +12,5 @@ log = "0.3"
|
|||||||
serde = "0.9.11"
|
serde = "0.9.11"
|
||||||
serde_derive = "0.9.11"
|
serde_derive = "0.9.11"
|
||||||
serde_json = "0.9"
|
serde_json = "0.9"
|
||||||
toml = "0.3.1"
|
|
||||||
url = "1.4.0"
|
url = "1.4.0"
|
||||||
xdg = "2.1.0"
|
xdg = "2.1.0"
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std;
|
use std;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use toml;
|
|
||||||
use xdg;
|
use xdg;
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
IOError(std::io::Error),
|
IOError(std::io::Error),
|
||||||
TomlDeError(toml::de::Error),
|
SerdeJsonError(serde_json::Error),
|
||||||
TomlSeError(toml::ser::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for ConfigError {
|
impl From<std::io::Error> for ConfigError {
|
||||||
@ -19,26 +18,20 @@ impl From<std::io::Error> for ConfigError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<toml::de::Error> for ConfigError {
|
impl From<serde_json::Error> for ConfigError {
|
||||||
fn from(e: toml::de::Error) -> Self {
|
fn from(e: serde_json::Error) -> Self {
|
||||||
ConfigError::TomlDeError(e)
|
ConfigError::SerdeJsonError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<toml::ser::Error> for ConfigError {
|
pub struct ConfigFiles {
|
||||||
fn from(e: toml::ser::Error) -> Self {
|
|
||||||
ConfigError::TomlSeError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Config {
|
|
||||||
pub xdg_dirs: BaseDirectories,
|
pub xdg_dirs: BaseDirectories,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl ConfigFiles {
|
||||||
pub fn new() -> Config {
|
pub fn new() -> ConfigFiles {
|
||||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("gog-sync").unwrap();
|
let xdg_dirs = xdg::BaseDirectories::with_prefix("gog-sync").unwrap();
|
||||||
Config { xdg_dirs: xdg_dirs }
|
ConfigFiles { xdg_dirs: xdg_dirs }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load<T>(&self, name: &str) -> Result<T, ConfigError>
|
pub fn load<T>(&self, name: &str) -> Result<T, ConfigError>
|
||||||
@ -50,9 +43,9 @@ impl Config {
|
|||||||
let mut config_contents = String::new();
|
let mut config_contents = String::new();
|
||||||
config_file.read_to_string(&mut config_contents)?;
|
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),
|
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
|
where T: Serialize
|
||||||
{
|
{
|
||||||
let config_path = self.xdg_dirs.place_config_file(name)?;
|
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)?;
|
let mut config_file = File::create(&config_path)?;
|
||||||
match config_file.write_all(content_toml.as_bytes()) {
|
match config_file.write_all(content_toml.as_bytes()) {
|
73
src/gog.rs
73
src/gog.rs
@ -1,10 +1,12 @@
|
|||||||
use config::{Config, ConfigError};
|
use configfiles::{ConfigFiles, ConfigError};
|
||||||
use http::{Http, HttpError};
|
use http::{Http, HttpError};
|
||||||
use models;
|
use models;
|
||||||
use models::{Token, Game, Installer};
|
use models::{Token, Game, Installer, Config};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@ -62,8 +64,8 @@ impl<'a> Gog<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn login(&mut self) -> Result<(), GogError> {
|
pub fn login(&mut self) -> Result<(), GogError> {
|
||||||
let config = Config::new();
|
let config = ConfigFiles::new();
|
||||||
let mut token: Token = match config.load("token.toml") {
|
let mut token: Token = match config.load("token.json") {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let code = self.get_code()?;
|
let code = self.get_code()?;
|
||||||
@ -75,7 +77,7 @@ impl<'a> Gog<'a> {
|
|||||||
token = self.refresh_token(token.refresh_token.as_str())?;
|
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);
|
let auth_header = format!("Authorization: Bearer {token}", token = token.access_token);
|
||||||
self.http_client.add_header(auth_header.as_str())?;
|
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> {
|
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()?;
|
let game_ids = self.get_game_ids()?;
|
||||||
|
|
||||||
for game_id in 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 = self.get_game(game_id)?;
|
||||||
let game_hash = models::get_hash(&game);
|
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);
|
let game_root = Path::new(storage_path).join(&game.title);
|
||||||
fs::create_dir_all(&game_root)?;
|
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 {
|
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);
|
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);
|
let installer_uri = format!("https://embed.gog.com{}", installer.manual_url);
|
||||||
|
|
||||||
info!("downloading {} for {}...",
|
info!("downloading {} for {}...",
|
||||||
&installer.manual_url,
|
&installer.manual_url,
|
||||||
&game.title);
|
&game.title);
|
||||||
self.http_client.download(installer_uri.as_str(), &game_root)?;
|
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 {
|
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);
|
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);
|
let extra_uri = format!("https://embed.gog.com{}", extra.manual_url);
|
||||||
|
|
||||||
info!("downloading {} for {}...", &extra.name, &game.title);
|
info!("downloading {} for {}...", &extra.name, &game.title);
|
||||||
self.http_client.download(extra_uri.as_str(), &game_root)?;
|
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(())
|
Ok(())
|
||||||
@ -186,6 +245,10 @@ impl<'a> Gog<'a> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if [1, 2, 3, 4, 5].contains(&game_id_parsed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
game_ids.push(game_id_parsed);
|
game_ids.push(game_id_parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ impl From<url::ParseError> for HttpError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<HttpError> for WriteError {
|
impl From<HttpError> for WriteError {
|
||||||
fn from(e: HttpError) -> Self {
|
fn from(_: HttpError) -> Self {
|
||||||
WriteError::__Nonexhaustive
|
WriteError::__Nonexhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
src/main.rs
25
src/main.rs
@ -8,19 +8,20 @@ extern crate serde;
|
|||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate toml;
|
|
||||||
extern crate url;
|
extern crate url;
|
||||||
extern crate xdg;
|
extern crate xdg;
|
||||||
|
|
||||||
mod config;
|
mod configfiles;
|
||||||
mod gog;
|
mod gog;
|
||||||
mod http;
|
mod http;
|
||||||
mod models;
|
mod models;
|
||||||
|
|
||||||
use config::Config;
|
use configfiles::ConfigFiles;
|
||||||
use clap::{Arg, App};
|
use clap::{Arg, App};
|
||||||
use gog::Gog;
|
use gog::Gog;
|
||||||
use http::Http;
|
use http::Http;
|
||||||
|
use models::Config;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init().unwrap();
|
env_logger::init().unwrap();
|
||||||
@ -37,9 +38,23 @@ fn main() {
|
|||||||
.takes_value(true))
|
.takes_value(true))
|
||||||
.get_matches();
|
.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 http_client = Http::new();
|
||||||
let mut gog = Gog::new(&mut http_client);
|
let mut gog = Gog::new(&mut http_client);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use chrono::UTC;
|
use chrono::UTC;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -28,6 +29,21 @@ fn timestamp() -> i64 {
|
|||||||
UTC::now().timestamp()
|
UTC::now().timestamp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub storage: String,
|
||||||
|
#[serde(default = "default_map")]
|
||||||
|
pub games: HashMap<String, u64>,
|
||||||
|
#[serde(default = "default_map")]
|
||||||
|
pub installers: HashMap<String, u64>,
|
||||||
|
#[serde(default = "default_map")]
|
||||||
|
pub extras: HashMap<String, u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_map() -> HashMap<String, u64> {
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
Loading…
Reference in New Issue
Block a user