diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/baas.rs | 14 | ||||
| -rw-r--r-- | src/contentdb.rs | 9 | ||||
| -rw-r--r-- | src/download.rs | 35 | ||||
| -rw-r--r-- | src/error.rs | 48 | ||||
| -rw-r--r-- | src/game.rs | 7 | ||||
| -rw-r--r-- | src/lib.rs | 51 | ||||
| -rw-r--r-- | src/minemod.rs | 28 | ||||
| -rw-r--r-- | src/util.rs | 1 | ||||
| -rw-r--r-- | src/world.rs | 4 | 
9 files changed, 178 insertions, 19 deletions
diff --git a/src/baas.rs b/src/baas.rs index 36a7aad..938b4c4 100644 --- a/src/baas.rs +++ b/src/baas.rs @@ -1,4 +1,4 @@ -//! Functions to manipulate the global Minetest installation. +//! This module contains functions to query & manipulate the global Minetest installation.  use std::{collections::HashMap, path::PathBuf};  use dirs; @@ -191,7 +191,8 @@ impl Baas {  /// Snapshot of a [`Baas`] scan.  /// -/// Every item is indexed by its ID/name. +/// A snapshot is created through the [`Baas::snapshot`] method and gives a frozen view on the +/// installed objects.  #[derive(Debug, Clone)]  pub struct Snapshot {      worlds: HashMap<String, World>, @@ -200,14 +201,23 @@ pub struct Snapshot {  }  impl Snapshot { +    /// Return all worlds that were found. +    /// +    /// The map maps the world's name to the [`World`] object.      pub fn worlds(&self) -> &HashMap<String, World> {          &self.worlds      } +    /// Return all games that were found. +    /// +    /// The map maps the game ID to the [`Game`] object.      pub fn games(&self) -> &HashMap<String, Game> {          &self.games      } +    /// Return the available mods that were found. +    /// +    /// The map maps the mod name to the [`MineMod`] object.      pub fn mods(&self) -> &HashMap<String, MineMod> {          &self.mods      } diff --git a/src/contentdb.rs b/src/contentdb.rs index 467f9dc..d9c4688 100644 --- a/src/contentdb.rs +++ b/src/contentdb.rs @@ -20,16 +20,23 @@ static PROVIDES_SELECTOR: Lazy<Selector> =  static A_SELECTOR: Lazy<Selector> = Lazy::new(|| Selector::parse("a").expect("Invalid selector")); -/// (Partial) metadata of a content item, as returned by the Content DB. +/// (Partial) metadata of a content item, as returned by the Content DB API.  #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]  pub struct ContentMeta { +    /// Username of the author.      pub author: String, +    /// Name of the package.      pub name: String, +    /// A list of mods that are provided by this package.      pub provides: Vec<String>, +    /// The short description of the package.      pub short_description: String, +    /// The (human-readable) title of this package.      pub title: String, +    /// The type of the package ("mod", "game", "txp")      #[serde(rename = "type")]      pub typ: String, +    /// The download URL of the package.      pub url: Url,  } diff --git a/src/download.rs b/src/download.rs index dfce2fc..b9507b7 100644 --- a/src/download.rs +++ b/src/download.rs @@ -1,3 +1,20 @@ +//! Module to download mods from the internet. +//! +//! This module allows to download mods from various sources in the internet. Source specification +//! is done through the [`Source`] enum: +//! +//! * [`Source::Http`]: Download straight from an URL. It is expected that the URL points to a zip +//!   archive which contains the mod, either directly or in a subfolder. +//! * [`Source::ContentDb`]: Refers to a package on the ContentDB. The [`Downloader`] will consult +//!   the API to get the right download URL. +//! * [`Source::ModId`]: Refers to a simple mod name. Note that this specification can be +//!   ambiguous, in which case the [`Downloader`] will return an error. +//! +//! The actual download work is done by a [`Downloader`]. Each [`Downloader`] has its own temporary +//! directory, in which any mods are downloaded and extracted. If you drop the [`Downloader`], +//! those mods will be deleted and the objects pointing to the now-gone directories are no longer +//! useful. +  use std::{      fs,      io::{Cursor, Read}, @@ -54,8 +71,8 @@ impl FromStr for Source {  /// A downloader is responsible for downloading mods from various sources.  /// -/// Note that the [`MineMod`] that the [`Downloader`] creates will not work after the downloader -/// has been destroyed, as the temporary files will be lost. +/// Note that the objects that the [`Downloader`] creates will not work after the downloader has +/// been destroyed, as the temporary files will be lost.  #[derive(Debug)]  pub struct Downloader {      temp_dir: TempDir, @@ -63,10 +80,12 @@ pub struct Downloader {  }  impl Downloader { +    /// Create a new [`Downloader`], refering to the default ContentDB.      pub fn new() -> Result<Downloader> {          Downloader::with_content_db(Default::default())      } +    /// Create a new [`Downloader`] that points to a specific ContentDB instance.      pub fn with_content_db(content_db: ContentDb) -> Result<Downloader> {          let temp_dir = TempDir::new(env!("CARGO_PKG_NAME"))?;          Ok(Downloader { @@ -75,6 +94,15 @@ impl Downloader {          })      } +    /// Download a mod from the given source. +    /// +    /// This function may download either a mod ([`minemod::MineMod`]) or a modpack +    /// ([`minemod::Modpack`]), therefore it returns a trait object that can be queried for the +    /// required information. +    /// +    /// Note that the object will be useless when the [`Downloader`] is dropped, as the temporary +    /// directory containing the downloaded data will be lost. Use [`ModContainer::install_to`] to +    /// copy the mod content to a different directory.      pub fn download(&self, source: &Source) -> Result<Box<dyn ModContainer>> {          match *source {              Source::Http(ref url) => self.download_http(url), @@ -96,8 +124,7 @@ impl Downloader {      ///      /// The [`Downloader`] expects to receive a zipfile containing the mod directory on this link.      /// -    /// The mod is extracted to a temporary directory and has to be copied using -    /// [`MineMod::copy_to`]. +    /// Refer to the module level documentation and [`Downloader::download`] for more information.      pub fn download_http(&self, url: &Url) -> Result<Box<dyn ModContainer>> {          let mut reader = ureq::request_url("GET", url).call()?.into_reader();          let mut data = Vec::new(); diff --git a/src/error.rs b/src/error.rs index 86d758f..5dbd6b6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,40 +1,64 @@ +//! Error definitions for ModderBaas. +//! +//! The type alias [`Result`] can be used, which defaults the error type to [`enum@Error`]. Any function +//! that introduces errors should return a [`Result`] — unless it is clear that a more narrow error +//! will suffice, such as [`crate::util::copy_recursive`].  use std::path::PathBuf;  use thiserror::Error; +/// The main error type.  #[derive(Error, Debug)]  pub enum Error { +    /// A malformed or otherwise invalid mod ID has been given.      #[error("invalid mod id '{0}'")]      InvalidModId(String), -    #[error("underlying HTTP error")] -    UreqError(#[from] ureq::Error), -    #[error("underlying I/O error")] -    IoError(#[from] std::io::Error), +    /// The ContentDB website returned invalid data.      #[error("the website returned unexpected data")]      InvalidScrape, +    /// The directory does not contain a valid Minetest mod.      #[error("'{0}' is not a valid mod directory")]      InvalidModDir(PathBuf), -    #[error("ZIP error")] -    ZipError(#[from] zip::result::ZipError), -    #[error("the downloaded file was empty")] -    EmptyArchive, +    /// The directory does not contain a valid Minetest game.      #[error("'{0}' is not a valid game directory")]      InvalidGameDir(PathBuf), +    /// The directory does not contain a valid Minetest world.      #[error("'{0}' is not a valid world directory")]      InvalidWorldDir(PathBuf), +    /// The directory does not contain a valid Minetest modpack.      #[error("'{0}' is not a valid modpack directory")]      InvalidModpackDir(PathBuf), -    #[error("the world has no game ID set")] -    NoGameSet, +    /// The given source string can not be parsed into a [`crate::download::Source`].      #[error("'{0}' does not represent a valid mod source")]      InvalidSourceSpec(String), -    #[error("invalid URL")] -    UrlError(#[from] url::ParseError), + +    /// An empty ZIP archive was downloaded. +    #[error("the downloaded file was empty")] +    EmptyArchive, +    /// The given world does not have a game ID set. +    #[error("the world has no game ID set")] +    NoGameSet, +    /// ContentDB returned more than one fitting mod for the query.      #[error("the mod ID '{0}' does not point to a single mod")]      AmbiguousModId(String), + +    /// Wrapper for HTTP errors. +    #[error("underlying HTTP error")] +    UreqError(#[from] ureq::Error), +    /// Wrapper for generic I/O errors. +    #[error("underlying I/O error")] +    IoError(#[from] std::io::Error), +    /// Wrapper for ZIP errors. +    #[error("ZIP error")] +    ZipError(#[from] zip::result::ZipError), +    /// Wrapper for URL parsing errors. +    #[error("invalid URL")] +    UrlError(#[from] url::ParseError), +    /// Wrapper for Unix errors.      #[cfg(unix)]      #[error("underlying Unix error")]      NixError(#[from] nix::errno::Errno),  } +/// A [`Result`] with the error type defaulted to [`enum@Error`].  pub type Result<T, E = Error> = std::result::Result<T, E>; diff --git a/src/game.rs b/src/game.rs index f323212..0d2405d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,3 +1,10 @@ +//! Module to interact with installed games. +//! +//! The main type of this module is a [`Game`], which can be constructed by opening installed +//! Minetest games through [`Game::open`]. +//! +//! Valid game directories have a `game.conf` configuration file which contains some metadata about +//! the game.  use std::{      fmt,      path::{Path, PathBuf}, @@ -1,3 +1,51 @@ +//! ModderBaas is a library that allows you to manage Minetest mods. +//! +//! The main point of the library is to be used by the `modderbaas` CLI application, however it can +//! also be used in other Rust programs that want a (limited) API to interact with Minetest +//! content. +//! +//! # Representation +//! +//! Most objects ([`Game`], [`World`], [`MineMod`], [`Modpack`]) are represented by the path to the +//! on-disk directory that contains the corresponding game/world/mod/modpack. The initializers take +//! care that the directory contains a valid object. +//! +//! Many of the query operations do not do any caching, so avoid calling them in tight loops. If +//! you want to do a lot of name queries (e.g. to find installed mods), consider using a +//! [`Snapshot`]. +//! +//! # Racing +//! +//! ModderBaas expects that during the time of its access, no other application meddles with the +//! directories. It will *not* lead to crashes, but you will get a lot more error return values. +//! +//! # Mutability +//! +//! Some objects support mutating their state. This is usually done by directly writing the data +//! into the corresponding file. Therefore, those methods (even though they mutate state) only take +//! a shared reference (`&self`) — keep that in mind! +//! +//! # Crate Structure +//! +//! Most game objects are implemented in their own module and re-exported at the crate root: +//! +//! * [`game`] +//! * [`minemod`] +//! * [`world`] +//! +//! Interacting with mods from the internet/ContentDB is done through two modules: +//! +//! * [`contentdb`] +//! * [`download`] +//! +//! Some modules contain auxiliary functions: +//! +//! * [`kvstore`] +//! * [`util`] +//! +//! Error definitions are in [`error`]. +//! +//! [`baas`] contains functions and data to interact with the global Minetest installation.  use std::{fs, path::Path};  use log::debug; @@ -22,6 +70,7 @@ pub mod world;  pub use baas::{Baas, Snapshot};  pub use contentdb::ContentDb;  pub use download::{Downloader, Source}; +pub use error::Error;  pub use game::Game;  pub use minemod::{MineMod, Modpack};  pub use world::World; @@ -33,6 +82,8 @@ use error::Result;  /// Files for which `scanner` returns `Ok(..)` will be collected and returned. Files for which  /// `scanner` returns `Err(..)` will be silently discarded.  /// +/// This function is useful to iterate through the items in a directory and find fitting objects: +///  /// ```rust  /// use modderbaas::minemod::MineMod;  /// let mods = scan("/tmp", |p| MineMod::open(p))?; diff --git a/src/minemod.rs b/src/minemod.rs index f45b897..456d1c6 100644 --- a/src/minemod.rs +++ b/src/minemod.rs @@ -1,3 +1,27 @@ +//! Module to interact with installed mods and modpacks. +//! +//! Due to technical reasons (`mod` being a Rust keyword), this module is called `minemod` and the +//! mod objects are called [`MineMod`]. +//! +//! Simple mods are represented by [`MineMod`], which can be opened by [`MineMod::open`]. Valid +//! mods are identified by their `mod.conf`, which contains metadata about the mod. +//! +//! Modpacks can be represented by [`Modpack`] and loaded through [`Modpack::open`]. A modpack is +//! just a collection of mods grouped together, and the modpack directory needs to have a +//! `modpack.conf` in its directory. +//! +//! # Mods and Packs United +//! +//! In some cases, we cannot know in advance whether we are dealing with a mod or a modpack (e.g. +//! when downloading content from ContentDB). Therefore, the trait [`ModContainer`] exists, which +//! can be used as a trait object (`Box<dyn ModContainer>`). It provides the most important methods +//! and allows downcasting through `Any`. +//! +//! If you want to work with the mods directly, you can use [`ModContainer::mods`], which returns +//! the mod itself for [`MineMod`]s, and all contained mods for [`Modpack`]s. +//! +//! If you want to open a directory as a [`ModContainer`], you can use [`open_mod_or_pack`]. +  use std::{      any::Any,      collections::HashMap, @@ -20,6 +44,7 @@ pub struct MineMod {  }  impl MineMod { +    /// Opens the given directory as a mod.      pub fn open<P: AsRef<Path>>(path: P) -> Result<MineMod> {          MineMod::open_path(path.as_ref())      } @@ -93,6 +118,7 @@ pub struct Modpack {  }  impl Modpack { +    /// Opens the given directory as a modpack.      pub fn open<P: AsRef<Path>>(path: P) -> Result<Modpack> {          Modpack::open_path(path.as_ref())      } @@ -154,6 +180,8 @@ impl fmt::Display for Modpack {  }  /// A thing that can contain mods. +/// +/// This is useful for code that should deal with both mods and modpacks.  pub trait ModContainer: Any + fmt::Display {      /// Returns the name of the mod container.      fn name(&self) -> Result<String>; diff --git a/src/util.rs b/src/util.rs index d5f9ec2..ea401ba 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,4 @@ +//! Utility functions.  use std::{fs, io, path::Path};  #[cfg(unix)] diff --git a/src/world.rs b/src/world.rs index 31ddbd4..5dce6d0 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,3 +1,6 @@ +//! Module to interact with Minetest worlds (savegames). +//! +//! The main object is [`World`], which represents a Minetest world on-disk.  use std::{      collections::HashMap,      fmt, @@ -17,6 +20,7 @@ pub struct World {  }  impl World { +    /// Open the given directory as a [`World`].      pub fn open<P: AsRef<Path>>(path: P) -> Result<World> {          World::open_path(path.as_ref())      }  | 
