diff options
author | Daniel Schadt <kingdread@gmx.de> | 2021-11-09 12:46:43 +0100 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2021-11-09 12:50:33 +0100 |
commit | d21c5dc9c53b02620fce916ffc1a2695e9d3f698 (patch) | |
tree | fb17ba6ee4e0a1ed532ab6e570418abff5264616 /src/download.rs | |
parent | 919796ec954414ae16f113896bdf212381c96437 (diff) | |
download | modderbaas-d21c5dc9c53b02620fce916ffc1a2695e9d3f698.tar.gz modderbaas-d21c5dc9c53b02620fce916ffc1a2695e9d3f698.tar.bz2 modderbaas-d21c5dc9c53b02620fce916ffc1a2695e9d3f698.zip |
Separate the binary and library
This uses the workspace feature of cargo, with the benefit that
1) We can more cleanly group the binary (user facing) code from the
library
2) We can have dependencies that apply to the binary only
The first point could've been achieved without workspaces (Cargo
supports both binaries and libraries in a crate), but the second point
is what really makes this approach a lot better.
Diffstat (limited to 'src/download.rs')
-rw-r--r-- | src/download.rs | 161 |
1 files changed, 0 insertions, 161 deletions
diff --git a/src/download.rs b/src/download.rs deleted file mode 100644 index b9507b7..0000000 --- a/src/download.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! 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}, - str::FromStr, -}; - -use tempdir::TempDir; -use url::Url; -use uuid::Uuid; -use zip::ZipArchive; - -use super::{ - contentdb::{ContentDb, ContentId}, - error::{Error, Result}, - minemod::{self, ModContainer, ModId}, -}; - -/// A source determines where a mod should be loaded from. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Source { - /// Download a mod archive through HTTP. - Http(Url), - /// Download a mod from the Content DB, using the given user- and package name. - ContentDb(ContentId), - /// Search the Content DB for a given mod ID. - /// - /// The download may fail if there are multiple mods providing the same ID. - ModId(ModId), -} - -impl FromStr for Source { - type Err = Error; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - if s.starts_with("http://") || s.starts_with("https://") { - let url = Url::parse(s)?; - return Ok(Source::Http(url)); - } - let groups = regex!("^([^/]+)/([^/]+)$").captures(s); - if let Some(groups) = groups { - return Ok(Source::ContentDb(( - groups.get(1).unwrap().as_str().into(), - groups.get(2).unwrap().as_str().into(), - ))); - } - - if !s.contains(' ') { - return Ok(Source::ModId(s.into())); - } - - Err(Error::InvalidSourceSpec(s.into())) - } -} - -/// A downloader is responsible for downloading mods from various sources. -/// -/// 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, - content_db: ContentDb, -} - -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 { - temp_dir, - content_db, - }) - } - - /// 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), - Source::ContentDb((ref user, ref package)) => { - let url = self.content_db.download_url(user, package)?; - self.download_http(&url) - } - Source::ModId(ref id) => { - let candidates = self.content_db.resolve(id)?; - if candidates.len() != 1 { - return Err(Error::AmbiguousModId(id.into())); - } - self.download_http(&candidates[0].url) - } - } - } - - /// Downloads a mod given a HTTP link. - /// - /// The [`Downloader`] expects to receive a zipfile containing the mod directory on this link. - /// - /// 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(); - reader.read_to_end(&mut data)?; - let data = Cursor::new(data); - let mut archive = ZipArchive::new(data)?; - - let dir = self - .temp_dir - .path() - .join(&Uuid::new_v4().to_hyphenated().to_string()); - fs::create_dir(&dir)?; - - archive.extract(&dir)?; - - // Some archives contain the mod files directly, so try to open it: - if let Ok(pack) = minemod::open_mod_or_pack(&dir) { - return Ok(pack); - } - - // If the archive does not contain the mod directly, we instead try the subdirectories that - // we've extracted. - for entry in fs::read_dir(&dir)? { - let entry = entry?; - let metadata = fs::metadata(&entry.path())?; - if metadata.is_dir() { - if let Ok(pack) = minemod::open_mod_or_pack(&entry.path()) { - return Ok(pack); - } - } - } - Err(Error::InvalidModDir(dir)) - } -} |