ludusavi 0.31.0

Game save backup tool
Documentation
mod generic;
pub mod heroic;
mod legendary;
mod lutris;

use std::collections::{HashMap, HashSet};

use crate::{
    prelude::StrictPath,
    resource::{
        config::Root,
        manifest::{Manifest, Os},
    },
    scan::TitleFinder,
};

#[derive(Clone, Default, Debug)]
pub struct Launchers {
    games: HashMap<Root, HashMap<String, HashSet<LauncherGame>>>,
    empty: HashSet<LauncherGame>,
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct LauncherGame {
    pub install_dir: Option<StrictPath>,
    pub prefix: Option<StrictPath>,
    pub platform: Option<Os>,
}

impl LauncherGame {
    pub fn is_empty(&self) -> bool {
        self.install_dir.is_none() && self.prefix.is_none() && self.platform.is_none()
    }

    pub fn replace_in_paths(&self, old: &StrictPath, new: &StrictPath) -> Self {
        Self {
            install_dir: self.install_dir.as_ref().map(|x| x.replace(old, new)),
            prefix: self.prefix.as_ref().map(|x| x.replace(old, new)),
            platform: self.platform,
        }
    }
}

impl Launchers {
    pub fn get_game(&self, root: &Root, game: &str) -> impl Iterator<Item = &LauncherGame> {
        self.games
            .get(root)
            .and_then(|root| root.get(game))
            .unwrap_or(&self.empty)
            .iter()
    }

    pub fn scan(
        roots: &[Root],
        manifest: &Manifest,
        subjects: &[String],
        title_finder: &TitleFinder,
        legendary: Option<StrictPath>,
    ) -> Self {
        let mut instance = Self::default();

        for root in roots {
            if root.is_game_specific() {
                log::trace!("Skipping launcher info for game-specific root: {:?}", root);
                continue;
            }

            log::debug!("Scanning launcher info: {:?}", &root);
            let mut found = match root {
                Root::Heroic(root) => heroic::scan(root, title_finder, legendary.as_ref()),
                Root::Legendary(root) => legendary::scan(root, title_finder),
                Root::Lutris(root) => lutris::scan(root, title_finder),
                _ => generic::scan(root, manifest, subjects),
            };
            found.retain(|_k, v| {
                v.retain(|x| !x.is_empty());
                !v.is_empty()
            });
            log::debug!("launcher games found ({:?}): {:#?}", &root, &found);
            if !found.is_empty() {
                instance.games.entry(root.clone()).or_default().extend(found);
            }
        }

        instance
    }

    #[cfg(test)]
    pub fn scan_dirs(roots: &[Root], manifest: &Manifest, subjects: &[String]) -> Self {
        Self::scan(roots, manifest, subjects, &TitleFinder::default(), None)
    }
}