//! Implementation of the `modderbaas install` subcommand. use std::{io::Write, path::Path, str::FromStr}; use anyhow::{bail, Context, Result}; use clap::ArgMatches; use itertools::Itertools; use termcolor::StandardStream; use modderbaas::{ baas::{Baas, Installer}, minemod::{self, ModContainer}, util, ContentDb, Downloader, MineMod, Source, World, }; use super::uiutil::{ask_continue, user_choice}; /// Install the given mods, installing dependencies if needed. pub fn install_mods( output: &mut StandardStream, 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 = matches.is_present("chown"); let content_db = ContentDb::new(); let downloader = Downloader::new()?; let sources = mods .iter() .map(|&s| Source::from_str(s)) .collect::, _>>()?; let installer = InteractiveInstaller { output, content_db, target_dir, dry_run, chown, }; baas.install(installer, &downloader, world, sources)?; writeln!(output, "Done!")?; Ok(()) } /// 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, } impl<'o, 'p> Installer for InteractiveInstaller<'o, 'p> { type Error = anyhow::Error; 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 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())) } } 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 self.dry_run { // Re-open so we get a fresh Box<> return Ok(minemod::open_mod_or_pack(mod_or_pack.path())?); } let installed = mod_or_pack .install_to(self.target_dir) .context(format!("Error installing '{}'", mod_id))?; #[cfg(unix)] { use nix::{ sys::stat, unistd::{Gid, Uid}, }; 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) } 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) } fn enable_mod(&mut self, world: &World, minemod: &MineMod) -> Result<()> { let mod_id = minemod.mod_id()?; writeln!(&mut self.output, "Enabling {}", mod_id)?; if !self.dry_run { world .enable_mod(&mod_id) .context(format!("Error enabling '{}'", mod_id))?; } Ok(()) } }