//! Functions to manipulate the global Minetest installation. use std::{collections::HashMap, path::PathBuf}; use dirs; use log::debug; use super::{ error::Result, game::Game, minemod::{self, MineMod}, scan, world::World, }; /// Returns a list of folders in which worlds are expected. /// /// Note that not all of these folders need to exist. /// /// This returns the following locations: /// /// * `$HOME/.minetest/worlds` /// * `/var/games/minetest-server/.minetest/worlds` pub fn world_dirs() -> Result> { let mut paths = vec!["/var/games/minetest-server/.minetest/worlds".into()]; if let Some(home) = dirs::home_dir() { paths.push(home.join(".minetest").join("worlds")) } Ok(paths) } /// Returns a list of folders in which games are expected. /// /// Note that not all of these folders need to exist. /// /// This returns the following locations: /// /// * `$HOME/.minetest/games` /// * `/var/games/minetest-server/.minetest/games` /// * `/usr/share/minetest/games` /// * `/usr/share/games/minetest/games` pub fn game_dirs() -> Result> { let mut paths = vec![ "/var/games/minetest-server/.minetest/games".into(), "/usr/share/minetest/games".into(), "/usr/share/games/minetest/games".into(), ]; if let Some(home) = dirs::home_dir() { paths.push(home.join(".minetest").join("games")) } Ok(paths) } /// Returns a list of folders in which mods are expected. /// /// Note that not all of these folders need to exist. /// /// This returns the following locations: /// /// * `$HOME/.minetest/mods` /// * `/var/games/minetest-server/.minetest/mods` /// * `/usr/share/games/minetest/mods` /// * `/usr/share/minetest/mods` pub fn mod_dirs() -> Result> { let mut paths = vec![ "/var/games/minetest-server/.minetest/mods".into(), "/usr/share/games/minetest/mods".into(), "/usr/share/minetest/mods".into(), ]; if let Some(home) = dirs::home_dir() { paths.push(home.join(".minetest").join("mods")) } Ok(paths) } /// The [`Baas`] provides a way to list all worlds, games and mods on the system and allows access /// via the [`World`], [`Game`] and [`MineMod`] wrappers. #[derive(Debug, Default, Clone)] pub struct Baas { world_dirs: Vec, game_dirs: Vec, mod_dirs: Vec, } impl Baas { /// Create a [`Baas`] with the standard dirs. pub fn with_standard_dirs() -> Result { Ok(Baas::default() .with_world_dirs(world_dirs()?) .with_game_dirs(game_dirs()?) .with_mod_dirs(mod_dirs()?)) } /// Replace the world dirs with the given list of world dirs. pub fn with_world_dirs(self, world_dirs: Vec) -> Baas { Baas { world_dirs, ..self } } /// The list of directories which are searched for worlds. #[inline] pub fn world_dirs(&self) -> &[PathBuf] { self.world_dirs.as_slice() } /// Replace the game dirs with the given list of game dirs. pub fn with_game_dirs(self, game_dirs: Vec) -> Baas { Baas { game_dirs, ..self } } /// The list of directories which are searched for games. #[inline] pub fn game_dirs(&self) -> &[PathBuf] { self.game_dirs.as_slice() } /// Replace the mod dirs with the given list of mod dirs. pub fn with_mod_dirs(self, mod_dirs: Vec) -> Baas { Baas { mod_dirs, ..self } } /// The list of directories which are searched for mods. #[inline] pub fn mod_dirs(&self) -> &[PathBuf] { self.mod_dirs.as_slice() } /// Returns a vector of all words that were found in the world dirs. pub fn worlds(&self) -> Result> { let mut worlds = vec![]; for dir in self.world_dirs() { match scan(&dir, |p| World::open(p)) { Ok(w) => worlds.extend(w), Err(e) => debug!("Cannot scan {:?}: {}", dir, e), } } Ok(worlds) } /// Returns a vector of all games that were found in the game dirs. pub fn games(&self) -> Result> { let mut games = vec![]; for dir in self.game_dirs() { match scan(&dir, |p| Game::open(p)) { Ok(g) => games.extend(g), Err(e) => debug!("Cannot scan {:?}: {}", dir, e), } } Ok(games) } /// Returns a vector of all mods that were found in the mod dirs. /// /// Note that modpacks are flattened into mods. pub fn mods(&self) -> Result> { let mut mods = vec![]; for dir in self.mod_dirs() { match scan(&dir, |p| minemod::open_mod_or_pack(p)) { Ok(m) => { for container in m { mods.extend(container.mods()?); } } Err(e) => debug!("Cannot scan {:?}: {}", dir, e), } } Ok(mods) } /// Return a snapshot of the current state. /// /// A snapshot "freezes" the lists of worlds, mods and games in time. It is useful to avoid /// unnecessary I/O when it is known that the state should not have changed. It also allows /// fast searching of items by their name. pub fn snapshot(&self) -> Result { let worlds = self.worlds()?; let games = self.games()?; let mods = self.mods()?; Ok(Snapshot { worlds: worlds .into_iter() .map(|w| Ok((w.world_name()?, w))) .collect::>()?, games: games.into_iter().map(|g| (g.technical_name(), g)).collect(), mods: mods .into_iter() .map(|m| Ok((m.mod_id()?, m))) .collect::>()?, }) } } /// Snapshot of a [`Baas`] scan. /// /// Every item is indexed by its ID/name. #[derive(Debug, Clone)] pub struct Snapshot { worlds: HashMap, games: HashMap, mods: HashMap, } impl Snapshot { pub fn worlds(&self) -> &HashMap { &self.worlds } pub fn games(&self) -> &HashMap { &self.games } pub fn mods(&self) -> &HashMap { &self.mods } }