diff options
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)) - } -} |