gog-sync 0.3.4

Synchronizes a GOG library with a local folder.
use configfiles::ConfigFiles;
use gog::{Gog, GogError};
use http::{Http, HttpError};
use models;
use models::content::Content;
use models::contentinfo::ContentInfo;
use models::data::Data;
use models::datainfo::DataInfo;
use models::extra::Extra;
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::io;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::fs;
use std::fs::File;

pub fn sync_content(content: Content,
                    storage_path_games: &str,
                    storage_path_movies: &str,
                    os_filters: &Vec<String>,
                    language_filters: &Vec<String>,
                    resolution_filters: &Vec<String>,
                    skip_movies: bool,
                    skip_games: bool)
                    -> Result<(), GogError> {

    let content_root = if content.is_movie {
        Path::new(storage_path_movies).join(&content.title)
    } else {
        Path::new(storage_path_games).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);
        return Ok(());
    }

    let content_hash = models::get_hash(&content);

    if content_info_saved.hash == content_hash {
        info!("{} already up to date.", &content.title);
        return Ok(());
    }

    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(),
    };

    ConfigFiles::save_to_path(&content_info_path, &content_info)?;

    save_keys(content.cd_keys, &content_root)?;

    for data in content.data {
        save_data(content.title.as_str(),
                  data,
                  &content_root,
                  &content_info_path,
                  &content_info_saved,
                  &mut content_info);
    }

    Ok(())
}

fn save_data(content_title: &str,
             data: Data,
             content_root: &PathBuf,
             content_info_path: &PathBuf,
             content_info_saved: &ContentInfo,
             content_info: &mut ContentInfo)
             -> Result<(), GogError> {
    let data_hash_saved = match content_info_saved.data.get(data.manual_url.as_str()) {
        Some(value) => value.hash,
        None => u64::min_value(),
    };

    let data_hash = models::get_hash(&data);
    if data_hash_saved == data_hash {
        info!("{} already up to date.", &data.manual_url);
        return Ok(());
    }

    let data_root = Path::new(&content_root).join(&data.language);
    fs::create_dir_all(&data_root)?;

    let data_uri = format!("https://embed.gog.com{}", data.manual_url);

    info!("downloading {} for {}...", &data.manual_url, content_title);
    let filename = self.api_request_download(data_uri.as_str(), &data_root)?;

    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)?;

    Ok(())
}

fn save_keys(cd_keys: BTreeMap<String, String>, content_root: &PathBuf) -> Result<(), GogError> {
    let key_root = content_root.join("keys");

    if cd_keys.len() > 0 {
        fs::create_dir_all(&key_root)?;
    }

    for (key, value) in cd_keys {
        let key_path = key_root.join(format!("{}.txt", key));
        let mut key_file = File::create(&key_path)?;
        key_file.write_all(value.as_bytes())?
    }

    Ok(())
}

/*
/// Syncs the contents of a gog account with a local folder.
/// Uses a hash to figure out whether something has changed.
pub fn syncold(storage_path: &str,
               storage_path_movies: &str,
               os_filters: &Vec<String>,
               language_filters: &Vec<String>,
               resolution_filters: &Vec<String>,
               skip_movies: bool,
               skip_games: bool)
               -> Result<(), GogError> {
    let content_ids = self.get_content_ids()?;

    for content_id in content_ids {
        let content = match self.get_content(content_id,
                                             &os_filters,
                                             &language_filters,
                                             &resolution_filters) {
            Ok(value) => value,
            Err(error) => {
                error!("{}: {}", &content_id, error);
                continue;
            }
        };

        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;
        }

        let content_hash = models::get_hash(&content);

        if content_info_saved.hash == content_hash {
            info!("{} already up to date.", &content.title);
            continue;
        }

        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(),
        };

        ConfigFiles::save_to_path(&content_info_path, &content_info)?;

        let key_root = content_root.join("keys");

        if content.cd_keys.len() > 0 {
            fs::create_dir_all(&key_root)?;
        }

        for (key, value) in content.cd_keys {
            let key_path = key_root.join(format!("{}.txt", key));
            let mut key_file = File::create(&key_path)?;
            key_file.write_all(value.as_bytes())?
        }

        for data in content.data {
            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 {
                info!("{} already up to date.", &data.manual_url);
                continue;
            }

            let data_root = Path::new(&content_root).join(&data.language);
            fs::create_dir_all(&data_root)?;

            let data_uri = format!("https://embed.gog.com{}", data.manual_url);

            info!("downloading {} for {}...", &data.manual_url, &content.title);
            let filename = self.api_request_download(data_uri.as_str(), &data_root)?;

            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 content_info_saved.extras.get(&content_id.to_string()) {
                Some(value) => value.hash,
                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, &content.title);
            let filename = self.api_request_download(extra_uri.as_str(), &content_root)?;

            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)?;
        }
    }

    Ok(())
}*/