game-scanner 1.1.2

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

use rusqlite::{Connection, OpenFlags, Row};

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

pub fn read_all(file: &Path, launcher_executable: &Path) -> Result<Vec<Game>> {
    let conn =
        Connection::open_with_flags(&file, OpenFlags::SQLITE_OPEN_READ_ONLY).map_err(|error| {
            Error::new(
                ErrorKind::InvalidManifest,
                format!("Invalid Amazon Games manifest: {}", error.to_string()),
            )
        })?;

    let mut stmt = conn.prepare("SELECT * FROM DbSet").map_err(|error| {
        Error::new(
            ErrorKind::InvalidManifest,
            format!(
                "Error to read the Amazon Games manifest: {}",
                error.to_string()
            ),
        )
    })?;

    let columns = stmt
        .column_names()
        .into_iter()
        .map(String::from)
        .collect::<Vec<String>>();

    let rows = stmt
        .query_map([], |row| parse_row(&columns, row, launcher_executable))
        .map_err(|error| match error {
            rusqlite::Error::QueryReturnedNoRows => Error::new(
                ErrorKind::LibraryNotFound,
                format!("Amazon library could be empty"),
            ),
            _ => Error::new(
                ErrorKind::InvalidManifest,
                format!(
                    "Error to read the Amazon Games manifest: {}",
                    error.to_string()
                ),
            ),
        })?;

    let mut games = Vec::<Game>::new();

    for game in rows {
        games.push(game?);
    }

    return Ok(games);
}

pub fn read(id: &str, file: &Path, launcher_executable: &Path) -> Result<Game> {
    let conn =
        Connection::open_with_flags(&file, OpenFlags::SQLITE_OPEN_READ_ONLY).map_err(|error| {
            Error::new(
                ErrorKind::InvalidManifest,
                format!("Invalid Amazon Games manifest: {}", error.to_string()),
            )
        })?;

    let mut stmt = conn
        .prepare("SELECT * FROM DbSet WHERE Id = :id")
        .map_err(|error| {
            Error::new(
                ErrorKind::InvalidManifest,
                format!(
                    "Error to read the Amazon Games manifest: {}",
                    error.to_string()
                ),
            )
        })?;

    let columns = stmt
        .column_names()
        .into_iter()
        .map(String::from)
        .collect::<Vec<String>>();

    return stmt
        .query_row(&[(":id", id)], |row| {
            parse_row(&columns, row, launcher_executable)
        })
        .map_err(|error| match error {
            rusqlite::Error::QueryReturnedNoRows => Error::new(
                ErrorKind::GameNotFound,
                format!("Amazon game with id ({}) does not exist", id),
            ),
            _ => Error::new(
                ErrorKind::InvalidManifest,
                format!(
                    "Error to read the Amazon Games manifest: {}",
                    error.to_string()
                ),
            ),
        });
}

fn parse_row(
    columns: &Vec<String>,
    row: &Row,
    launcher_executable: &Path,
) -> rusqlite::Result<Game> {
    let mut game = Game::default();
    game._type = GameType::AmazonGames.to_string();

    for col in 0..columns.len() {
        let name = columns.get(col).unwrap();

        match name.as_str() {
            "Id" => game.id = row.get(col)?,
            "ProductTitle" => game.name = row.get(col)?,
            "InstallDirectory" => game.path = row.get::<_, String>(col).map(PathBuf::from).ok(),
            "Installed" => game.state.installed = row.get(col)?,
            _ => {}
        }
    }

    game.commands.launch = Some(vec![
        launcher_executable.display().to_string(),
        format!("amazon-games://play/{}", game.id),
    ]);

    Ok(game)
}