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()) } |