game-scanner 1.1.0

Game Scanner for any launcher and OS
use std::{
    convert::identity,
    path::{Path, PathBuf},
};

use crate::{
    error::{Error, ErrorKind, Result},
    prelude::{Game, GameCommands, GameState, GameType},
    utils::path::{fix_path_separator, get_filename},
};

use self::super::{
    proto::{product::ProductInstall, read_product},
    types::BlizzardGames,
};

pub fn read_all(file: &Path, launcher_executable: &Path) -> Result<Vec<Game>> {
    let database = read_product(file).map_err(|error| {
        Error::new(
            ErrorKind::InvalidManifest,
            format!("Invalid Blizzard manifest: {}", error.to_string()),
        )
    })?;

    let manifests = database
        .product_installs
        .into_iter()
        .filter(get_manifest_predicate)
        .collect::<Vec<ProductInstall>>();

    let mut games = Vec::new();

    for manifest in manifests {
        games.push(parse_manifest(&manifest, launcher_executable))
    }

    return Ok(games);
}

fn get_manifest_predicate(item: &ProductInstall) -> bool {
    item.product_code.ne("agent") && item.product_code.ne("bna")
}

pub fn read(id: &str, file: &Path, launcher_executable: &Path) -> Result<Game> {
    let database = read_product(file).map_err(|error| {
        Error::new(
            ErrorKind::InvalidManifest,
            format!("Invalid Blizzard manifest: {}", error.to_string()),
        )
    })?;

    let manifest = database
        .product_installs
        .into_iter()
        .find(|item| item.uid == id)
        .ok_or(Error::new(
            ErrorKind::GameNotFound,
            format!("Blizzard game with id ({}) does not exist", id),
        ))?;

    Ok(parse_manifest(&manifest, launcher_executable))
}

fn parse_manifest(manifest: &ProductInstall, launcher_executable: &Path) -> Game {
    let mut game_path = manifest
        .settings
        .clone()
        .map(|settings| settings.install_path)
        .map_or(PathBuf::new(), PathBuf::from);

    if cfg!(target_os = "windows") {
        game_path = fix_path_separator(&game_path);
    }

    let launch_code = BlizzardGames::from_uid(&manifest.uid).get_code();

    let installed = manifest
        .cached_product_state
        .clone()
        .map_or(true, |cached_product_state| {
            cached_product_state
                .base_product_state
                .map_or(true, |state| state.installed || state.playable)
        });

    let needs_update =
        manifest
            .cached_product_state
            .clone()
            .map_or(false, |cached_product_state| {
                cached_product_state
                    .base_product_state
                    .map_or(false, |state| !state.update_complete)
            });

    let downloading = manifest
        .cached_product_state
        .clone()
        .map_or(false, |cached_product_state| {
            cached_product_state
                .update_progress
                .map_or(false, |update_progress| {
                    update_progress.download_remaining > 0
                })
        });

    let total_bytes = manifest
        .cached_product_state
        .clone()
        .map_or(None, |product| {
            product.update_progress.map_or(None, |update_progress| {
                Some(update_progress.total_to_download)
            })
        });

    let received_bytes = manifest
        .cached_product_state
        .clone()
        .map_or(None, |product| {
            product.update_progress.map_or(None, |update_progress| {
                let total_to_download: i64 =
                    i64::try_from(update_progress.total_to_download).map_or(0, identity);

                let download_remaining: i64 =
                    i64::try_from(update_progress.download_remaining).map_or(0, identity);

                let mut received = total_to_download - download_remaining;

                if received < 0 {
                    received = download_remaining;
                }

                u64::try_from(received).map_or(None, Some)
            })
        });

    Game {
        _type: GameType::Blizzard.to_string(),
        id: String::from(&manifest.uid),
        name: get_filename(&game_path),
        path: Some(game_path),
        commands: GameCommands {
            install: Some(vec![
                launcher_executable.display().to_string(),
                format!("--game={}", launch_code),
            ]),
            launch: Some(vec![
                launcher_executable.display().to_string(),
                format!("--exec=\"launch {}\"", launch_code),
            ]),
            uninstall: None,
        },
        state: GameState {
            installed,
            needs_update,
            downloading,
            total_bytes,
            received_bytes,
        },
    }
}