game-scanner 1.0.4

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

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

#[derive(Default)]
struct Manifest {
    id: String,
    dipinstallpath: String,
    previousstate: String,
}

pub fn read(file: &Path, launcher_executable: &Path) -> Result<Game> {
    let manifest_data = std::fs::read_to_string(&file).map_err(|error| {
        Error::new(
            ErrorKind::InvalidManifest,
            format!(
                "Invalid Origin manifest: {} {}",
                file.display().to_string(),
                error.to_string()
            ),
        )
    });

    if manifest_data.is_err() {
        return Err(manifest_data.err().unwrap());
    }

    let manifest = String::from("http://mock/") + &manifest_data.unwrap();

    let manifest_url = url::Url::parse(&manifest).map_err(|error| {
        Error::new(
            ErrorKind::InvalidManifest,
            format!(
                "Error on read the Origin manifest: {} {}",
                file.display().to_string(),
                error.to_string()
            ),
        )
    });

    if manifest_url.is_err() {
        return Err(manifest_url.err().unwrap());
    }

    let manifest_url = manifest_url.unwrap();

    let manifest_lines = manifest_url
        .query()
        .map(|data| data.split("&").collect::<Vec<&str>>())
        .ok_or_else(|| {
            Error::new(
                ErrorKind::InvalidManifest,
                format!(
                    "Error on read the Origin manifest: {}",
                    file.display().to_string(),
                ),
            )
        });

    if manifest_lines.is_err() {
        return Err(manifest_lines.err().unwrap());
    }

    let mut manifest = Manifest::default();

    for manifest_line in manifest_lines.unwrap() {
        let pair = manifest_line.split("=").collect::<Vec<&str>>();

        let attr = pair.get(0).unwrap().to_string();
        let value = pair.get(1).unwrap().to_string();

        match attr.as_str() {
            "id" => {
                manifest.id = value;
            }
            "dipinstallpath" => {
                manifest.dipinstallpath =
                    make_dip_install_path(&value).map_or(String::new(), |value| value);
            }
            "previousstate" => {
                manifest.previousstate = value;
            }
            _ => {}
        }
    }

    let name = get_game_name(file).map_or(String::from("Unknown"), |value| value);

    if manifest.previousstate != "kCompleted" {
        return Err(Error::new(
            ErrorKind::IgnoredApp,
            format!("({}) {} is an invalid game", &manifest.id, &name),
        ));
    }

    return Ok(Game {
        _type: GameType::Origin.to_string(),
        id: manifest.id.clone(),
        name: get_game_name(file).unwrap(),
        path: manifest.dipinstallpath,
        commands: GameCommands {
            install: Some(vec![
                launcher_executable.display().to_string(),
                format!("origin2://game/download?offerId={}", &manifest.id),
            ]),
            launch: vec![
                launcher_executable.display().to_string(),
                format!("origin2://game/launch?offerIds={}", &manifest.id),
            ],
            uninstall: None,
        },
        state: GameState {
            installed: true,
            needs_update: false,
            downloading: false,
            total_bytes: None,
            received_bytes: None,
        },
    });
}

fn make_dip_install_path(value: &String) -> Option<String> {
    return Option::from(value.clone())
        .map(|path| path.replace("%5c", &std::path::MAIN_SEPARATOR.to_string()))
        .map(|path| path.replace("%3a", ":"))
        .map(|path| path.replace("%20", " "))
        .map(PathBuf::from)
        .map(|path| path.display().to_string());
}

fn get_game_name(file: &Path) -> Option<String> {
    return file
        .parent()
        .and_then(|path| path.file_name())
        .and_then(|path| path.to_str())
        .map(|path| path.to_string());
}