aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/baas.rs14
-rw-r--r--src/contentdb.rs9
-rw-r--r--src/download.rs35
-rw-r--r--src/error.rs48
-rw-r--r--src/game.rs7
-rw-r--r--src/lib.rs51
-rw-r--r--src/minemod.rs28
-rw-r--r--src/util.rs1
-rw-r--r--src/world.rs4
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},
diff --git a/src/lib.rs b/src/lib.rs
index bf9e3c9..f9b1c8b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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())
}