From 1db932c9f435fb54a3ca58333495d1e24ca7400f Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Tue, 9 Nov 2021 18:38:52 +0100 Subject: split mod install logic Previously, install_mod was a huge function that did a lot of things at once. Not only did it do the actual mod copying, it also had the dependency resolution, user interaction, ... Now, we've split the code and made it more re-usable. The dependency resolution is done in Baas::install, with special "hooks" being given in baas::Installer that allow a consumer to customize the installation and embed the user interaction there. The code itself is pretty much the same, it is just split up now. --- src/main.rs | 183 +++++++++++++++++++++++++++--------------------------------- 1 file changed, 83 insertions(+), 100 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 87f8847..5b8c19c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,9 @@ use log::debug; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use modderbaas::{ - minemod::ModContainer, util, Baas, ContentDb, Downloader, MineMod, Snapshot, Source, World, + baas::{Baas, Installer}, + minemod::{self, ModContainer}, + util, ContentDb, Downloader, MineMod, Snapshot, Source, World, }; fn main() -> Result<()> { @@ -96,7 +98,7 @@ fn main() -> Result<()> { if let Some(install) = matches.subcommand_matches("install") { let mods = install.values_of("mod").unwrap().collect::>(); - install_mods(&mut stdout, &snapshot, &world, &mods, install)?; + install_mods(&mut stdout, &baas, &world, &mods, install)?; } Ok(()) @@ -207,121 +209,88 @@ fn enable_mods( /// Install the given mods, installing dependencies if needed. fn install_mods( output: &mut StandardStream, - snapshot: &Snapshot, + baas: &Baas, world: &World, mods: &[&str], matches: &ArgMatches, ) -> Result<()> { let target_dir = Path::new(matches.value_of("target").unwrap()); let dry_run = matches.is_present("dry-run"); - let chown_after = matches.is_present("chown"); + let chown = matches.is_present("chown"); let content_db = ContentDb::new(); let downloader = Downloader::new()?; - let mut wanted = mods + let sources = mods .iter() .map(|&s| Source::from_str(s)) .collect::, _>>()?; - let mut to_install = Vec::>::new(); - let mut to_enable = Vec::::new(); + let installer = InteractiveInstaller { + output, + content_db, + target_dir, + dry_run, + chown, + }; + baas.install(installer, &downloader, world, sources)?; - let game = snapshot - .games() - .get(&world.game_id()?) - .ok_or_else(|| anyhow!("The game definition was not found"))?; - let game_mods = game - .mods()? - .into_iter() - .map(|m| m.mod_id()) - .collect::, _>>()?; + writeln!(output, "Done!")?; - 'modloop: while !wanted.is_empty() { - let next_mod = wanted.remove(0); + Ok(()) +} - // Special handling for mods specified by their ID, as those could already exist. - if let Source::ModId(ref id) = next_mod { - if let Some(m) = snapshot.mods().get(id) { - // We have that one, just enable it and its dependencies! - to_enable.push(m.clone()); - wanted.extend(m.dependencies()?.into_iter().map(Source::ModId)); - continue; - } else if game_mods.contains(id) { - // This mod is already contained in the game, nothing for us to do - continue; - } +/// The installer that interactively asks the user about choices. +struct InteractiveInstaller<'o, 'p> { + output: &'o mut StandardStream, + content_db: ContentDb, + target_dir: &'p Path, + dry_run: bool, + chown: bool, +} - // Is this a mod that is already queued for installation? - for mod_or_pack in &to_install { - for m in mod_or_pack.mods()? { - if &m.name()? == id { - continue 'modloop; - } - } - } +impl<'o, 'p> Installer for InteractiveInstaller<'o, 'p> { + type Error = anyhow::Error; - // This mod is not available, so we search the content DB - writeln!(output, "Searching for candidates: {}", id)?; - let candidates = content_db.resolve(id)?; - if candidates.is_empty() { - bail!("Could not find a suitable mod for '{}'", id); - } else if candidates.len() == 1 { - wanted.push(Source::Http(candidates.into_iter().next().unwrap().url)); - } else { - let items = candidates - .into_iter() - .map(|c| { - ( - format!("{} by {} - {}", c.title, c.author, c.short_description), - c, - ) - }) - .collect::>(); - writeln!( - output, - "{} candidates found, please select one:", - items.len() - )?; - let candidate = user_choice(&items, output)?; - wanted.push(Source::Http(candidate.url.clone())); - } + fn resolve(&mut self, mod_id: &str) -> Result { + writeln!(&mut self.output, "Searching for candidates: {}", mod_id)?; + + let candidates = self.content_db.resolve(mod_id)?; + if candidates.is_empty() { + bail!("Could not find a suitable mod for '{}'", mod_id); + } else if candidates.len() == 1 { + Ok(Source::Http(candidates.into_iter().next().unwrap().url)) } else { - let downloaded = downloader - .download(&next_mod) - .context("Failed to download mod")?; - for m in downloaded.mods()? { - wanted.extend(m.dependencies()?.into_iter().map(Source::ModId)); - } - to_install.push(downloaded); + let items = candidates + .into_iter() + .map(|c| { + ( + format!("{} by {} - {}", c.title, c.author, c.short_description), + c, + ) + }) + .collect::>(); + writeln!( + &mut self.output, + "{} candidates found, please select one:", + items.len() + )?; + let candidate = user_choice(&items, self.output)?; + Ok(Source::Http(candidate.url.clone())) } } - writeln!(output, "Installing {} new mods:", to_install.len())?; - writeln!(output, "{}", to_install.iter().join(", "))?; - - ask_continue(output)?; + fn install_mod(&mut self, mod_or_pack: &dyn ModContainer) -> Result> { + let mod_id = mod_or_pack.name()?; + writeln!(&mut self.output, "Installing {}", mod_id)?; - if dry_run { - for m in to_install { - let mod_id = m.name()?; - let mod_dir = target_dir.join(&mod_id); - writeln!(output, "Installing {} to {:?}", m, mod_dir)?; - to_enable.extend(m.mods()?); + if self.dry_run { + // Re-open so we get a fresh Box<> + return Ok(minemod::open_mod_or_pack(mod_or_pack.path())?); } - for m in to_enable { - let mod_id = m.mod_id()?; - writeln!(output, "Enabling {}", mod_id)?; - } - return Ok(()); - } - for m in to_install { - let mod_id = m.name()?; - writeln!(output, "Installing {}", mod_id)?; - let installed = m - .install_to(target_dir) + let installed = mod_or_pack + .install_to(self.target_dir) .context(format!("Error installing '{}'", mod_id))?; - to_enable.extend(installed.mods()?); #[cfg(unix)] { @@ -329,24 +298,38 @@ fn install_mods( sys::stat, unistd::{Gid, Uid}, }; - if chown_after { - let perms = stat::stat(target_dir)?; + if self.chown { + let perms = stat::stat(self.target_dir)?; let (uid, gid) = (Uid::from_raw(perms.st_uid), Gid::from_raw(perms.st_gid)); util::chown_recursive(installed.path(), Some(uid), Some(gid))?; } } + + Ok(installed) } - for m in to_enable { - let mod_id = m.mod_id()?; - world - .enable_mod(&mod_id) - .context(format!("Error enabling '{}'", mod_id))?; + fn display_changes(&mut self, to_install: &[Box]) -> Result<()> { + writeln!( + &mut self.output, + "Installing {} new mods:", + to_install.len() + )?; + writeln!(&mut self.output, "{}", to_install.iter().join(", "))?; + + ask_continue(self.output) } - writeln!(output, "Done!")?; + fn enable_mod(&mut self, world: &World, minemod: &MineMod) -> Result<()> { + let mod_id = minemod.mod_id()?; + writeln!(&mut self.output, "Enabling {}", mod_id)?; - Ok(()) + if !self.dry_run { + world + .enable_mod(&mod_id) + .context(format!("Error enabling '{}'", mod_id))?; + } + Ok(()) + } } /// Presents the user with a choice of items and awaits a selection. -- cgit v1.2.3