Compare commits

...

2 Commits

Author SHA1 Message Date
bdeebfee4f changed the way files get resolved on mod install
to better handle fomod and it's required interactivity
2026-03-30 22:07:46 +02:00
7e20cd370c removed callback function from fomod installer 2026-03-29 22:48:21 +02:00
6 changed files with 175 additions and 92 deletions

View File

@@ -7,5 +7,5 @@ mod load_order;
pub use activate::{ActivationError, activate_instance};
pub use download::handle_nxm;
pub use include::insert_mod_to_instance;
pub use install::resolve_files_for_install;
pub use install::{resolve_files_for_install, ResolveFileResult};
pub use load_order::{LoadOrderError, create_loadorder};

View File

@@ -4,30 +4,34 @@ use std::{
};
use globset::{Glob, GlobSet, GlobSetBuilder};
use log::{debug, trace};
use crate::{
fomod, install_prompt,
mod_config_installer::run_fomod_installer,
types::{ModConfig, ModFile, ModdedInstance, RootConfig},
fomod,
types::{ModConfig, ModFile, RootConfig},
utils::{resolve_case_insensitive, walk_all_files},
};
pub fn resolve_files_for_install(
root_config: &RootConfig,
instance: &ModdedInstance,
mod_to_install: &ModConfig,
) -> anyhow::Result<Vec<ModFile>> {
) -> anyhow::Result<ResolveFileResult> {
let mod_location = root_config.mod_location().join(mod_to_install.path());
let files = match determain_mod_kind(mod_to_install, &mod_location)? {
ModKind::Fomod(xml_path) => install_fomod(instance, xml_path, &mod_location)?,
ModKind::EmbeddedData(_data_path) => install_from_dir(mod_to_install, mod_location)?,
ModKind::Root => install_root(mod_to_install, mod_location)?,
ModKind::Unkown => install_from_dir_to_data(mod_to_install, mod_location)?,
let result = match determain_mod_kind(mod_to_install, &mod_location)? {
ModKind::Fomod(xml_path) => {
let module_config = fomod::Config::load_from_file(xml_path)?;
ResolveFileResult::Fomod(module_config)
}
ModKind::EmbeddedData(_data_path) => {
ResolveFileResult::Files(install_from_dir(mod_to_install, mod_location)?)
}
ModKind::Root => ResolveFileResult::Files(install_root(mod_to_install, mod_location)?),
ModKind::Unkown => {
ResolveFileResult::Files(install_from_dir_to_data(mod_to_install, mod_location)?)
}
};
Ok(files)
Ok(result)
}
fn determain_mod_kind(
@@ -51,32 +55,38 @@ fn determain_mod_kind(
}
}
fn install_fomod(
instance: &ModdedInstance,
module_config_path: impl AsRef<Path>,
mod_root: impl AsRef<Path>,
) -> anyhow::Result<Vec<ModFile>> {
debug!("Running FOmod installer");
let module_config = fomod::Config::load_from_file(module_config_path)?;
let active_plugins: Vec<_> = instance
.active_plugins()
.map(|e| e.to_string_lossy())
.map(|e| e.to_string())
.collect();
trace!("Current loded plugins: {:?}", active_plugins);
let files = run_fomod_installer(module_config, &active_plugins, install_prompt::prompt)?;
let mod_files: Vec<_> = files
.iter()
.map(|f| ModFile::from_installer(f.clone(), &mod_root))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect();
Ok(mod_files)
}
// fn install_fomod(
// instance: &ModdedInstance,
// module_config_path: impl AsRef<Path>,
// mod_root: impl AsRef<Path>,
// ) -> anyhow::Result<Vec<ModFile>> {
// debug!("Running FOmod installer");
// let module_config = fomod::Config::load_from_file(module_config_path)?;
//
// let active_plugins: Vec<_> = instance
// .active_plugins()
// .map(|e| e.to_string_lossy())
// .map(|e| e.to_string())
// .collect();
//
// trace!("Current loded plugins: {:?}", active_plugins);
//
// let mut installer = FomodInstaller::new(&module_config, &active_plugins);
// let mut selection: Option<Vec<usize>> = None;
// while let Some(prompt) = installer.run_step(selection.as_deref()) {
// selection = Some(install_prompt::prompt(prompt));
// }
// let files = installer.finalize();
//
// let mod_files: Vec<_> = files
// .iter()
// .map(|f| ModFile::from_installer(f.clone(), &mod_root))
// .collect::<Result<Vec<_>, _>>()?
// .into_iter()
// .flatten()
// .collect();
// Ok(mod_files)
// }
fn install_from_dir(
mod_config: &ModConfig,
@@ -143,6 +153,11 @@ enum ModKind {
Unkown,
}
pub enum ResolveFileResult {
Files(Vec<ModFile>),
Fomod(fomod::Config),
}
fn should_be_included(path: impl AsRef<Path>) -> bool {
matches!(
path.as_ref().extension().and_then(|e| e.to_str()),

View File

@@ -5,9 +5,14 @@ use std::{error::Error, path::Path};
use fomod_manager::{
actions::{
activate_instance, create_loadorder, handle_nxm, insert_mod_to_instance,
ResolveFileResult, activate_instance, create_loadorder, handle_nxm, insert_mod_to_instance,
resolve_files_for_install,
}, cli::{self, Args}, nexus::NexusAPI, tui, types::RootConfig
},
cli::{self, Args},
mod_config_installer::FomodInstaller,
nexus::NexusAPI,
tui,
types::RootConfig,
};
fn command_activate(
@@ -26,7 +31,19 @@ fn command_add(root_config: &RootConfig, instance_id: &str, mod_id: &str) -> any
.mod_by_id(mod_id)
.ok_or(anyhow!("Can't find mod in config"))?;
let files = resolve_files_for_install(root_config, &instance, &mod_to_install)?;
let files = match resolve_files_for_install(root_config, &mod_to_install)? {
ResolveFileResult::Files(mod_files) => mod_files,
ResolveFileResult::Fomod(module_config) => {
let mod_location = root_config.mod_location().join(mod_to_install.path());
let active_plugins: Vec<String> = instance.active_plugins().collect();
let mut installer = FomodInstaller::new(&module_config, &active_plugins);
let mut selection: Option<Vec<usize>> = None;
while let Some(prompt) = installer.run_step(selection.as_deref()) {
selection = Some(fomod_manager::install_prompt::prompt(prompt));
}
installer.finalize(mod_location)?
}
};
match insert_mod_to_instance(&mut instance, &mod_to_install, &files, 0) {
None => {
@@ -101,7 +118,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
cli::Commands::Tui => {
tui::run(&mut root_config)?;
},
}
}
Ok(())

View File

@@ -1,10 +1,13 @@
use std::{collections::HashMap, fmt::Display};
use std::{collections::HashMap, fmt::Display, io, path::Path};
use log::{debug, warn};
use crate::fomod::{
use crate::{
fomod::{
CompositeDependency, Config, DependencyOperator, DependencyState, FileList, FileTypeEnum,
Group, GroupType, ModuleDependency, Plugin, PluginTypeDescriptorEnum, PluginTypeEnum,
},
types::ModFile,
};
#[derive(Debug)]
@@ -117,6 +120,7 @@ fn evaluate_module_depbendecy(
}
}
#[derive(Debug)]
pub struct GroupPrompt {
pub name: String,
pub select_type: GroupType,
@@ -146,6 +150,7 @@ impl GroupPrompt {
}
}
#[derive(Debug)]
pub struct InstallOption {
pub name: String,
pub option_type: PluginTypeEnum,
@@ -191,66 +196,90 @@ fn resolve_plugin_type(
}
}
pub fn run_fomod_installer(
fomod_config: Config,
installed_plugins: &[String],
group_prompt: fn(GroupPrompt) -> Vec<usize>,
) -> anyhow::Result<Vec<FileTypeEnum>> {
let mut state = InstallerState::new();
pub struct FomodInstaller<'a> {
state: InstallerState,
current_step: (usize, usize),
config: &'a Config,
installed_plugins: &'a [String],
}
// Always-installed files first
impl<'a> FomodInstaller<'a> {
pub fn new(fomod_config: &'a Config, installed_plugins: &'a [String]) -> Self {
let mut state = InstallerState::new();
if let Some(required) = &fomod_config.required_install_files {
state.add_files(required);
}
if let Some(install_steps) = fomod_config.install_steps {
let steps = &install_steps.install_step;
Self {
state,
current_step: (0, 0),
config: fomod_config,
installed_plugins,
}
}
pub fn run_step(&mut self, selection: Option<&[usize]>) -> Option<GroupPrompt> {
let Some(install_steps) = &self.config.install_steps else {
return None;
};
let step = install_steps.install_step.get(self.current_step.0)?;
for step in steps {
// Check if the step should be visible
if step
.visible
.as_ref()
.is_some_and(|v| !evaluate_module_depbendecy(v, &state, installed_plugins))
.is_some_and(|v| !evaluate_module_depbendecy(v, &self.state, self.installed_plugins))
{
// Dependency to show the step not meet. Skipping.
continue;
self.current_step = (self.current_step.0 + 1, 0);
return self.run_step(selection);
}
for group in &step.optional_file_groups.group {
let Some(group) = step.optional_file_groups.group.get(self.current_step.1) else {
self.current_step = (self.current_step.0 + 1, 0);
return self.run_step(selection);
};
// TODO: Skip groups where all plugins are NotUsable
let prompt = GroupPrompt::new(group, &state, installed_plugins);
let selected_plugins = (group_prompt)(prompt);
match selection {
Some(selected_plugins) => {
for i in selected_plugins {
let plugin = &group.plugins.plugin[i];
let plugin = &group.plugins.plugin[*i];
// Add files from selected plugin
if let Some(files) = &plugin.files {
state.add_files(files);
self.state.add_files(files);
}
// Set condition flags
if let Some(condition_flags) = &plugin.condition_flags {
for flag in &condition_flags.flag {
state.set_flag(&flag.name, &flag.flag_value);
}
}
}
self.state.set_flag(&flag.name, &flag.flag_value);
}
}
}
// Evaluate conditional file installs based on final flag state
if let Some(conditional) = &fomod_config.conditional_file_installs {
for pattern in &conditional.patterns.pattern {
if evaluate_module_depbendecy(&pattern.dependencies, &state, installed_plugins) {
state.add_files(&pattern.files);
// Next step
self.current_step = (self.current_step.0, self.current_step.1 + 1);
self.run_step(None)
}
None => Some(GroupPrompt::new(group, &self.state, self.installed_plugins)),
}
}
Ok(state.into_file_list())
pub fn finalize(self, mod_root: impl AsRef<Path>) -> Result<Vec<ModFile>, io::Error> {
let files: Vec<_> = self
.state
.into_file_list()
.iter()
.map(|f| ModFile::from_installer(f.clone(), &mod_root))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect();
Ok(files)
}
}

View File

@@ -1,5 +1,4 @@
use std::{
ffi::OsStr,
fs::{self, read_to_string},
io::Write,
path::{Path, PathBuf},
@@ -106,8 +105,12 @@ impl ModdedInstance {
&self.mods
}
pub fn active_plugins(&self) -> impl Iterator<Item = &OsStr> {
self.mods.iter().flat_map(|e| e.active_plugins())
pub fn active_plugins(&self) -> impl Iterator<Item = String> {
self.mods
.iter()
.flat_map(|e| e.active_plugins())
.map(|e| e.to_string_lossy())
.map(|e| e.to_string())
}
}
@@ -156,6 +159,5 @@ mod tests {
let new_mod = InstalledMod::new("mod1", 1);
cfg.update_or_create_mod(&new_mod);
}
}

View File

@@ -20,7 +20,12 @@ fn add_plain() -> Result<(), Box<dyn Error>> {
let mod_to_install = root_config
.mod_by_id("add_test_plain")
.expect("Mod not found");
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
let files_to_add = match resolve_files_for_install(&root_config, &mod_to_install)? {
fomod_manager::actions::ResolveFileResult::Files(mod_files) => mod_files,
_ => {
panic!("Resolved files have wrong type");
}
};
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
@@ -53,7 +58,12 @@ fn add_nested() -> Result<(), Box<dyn Error>> {
let mod_to_install = root_config
.mod_by_id("add_test_nested")
.expect("Mod not found");
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
let files_to_add = match resolve_files_for_install(&root_config, &mod_to_install)? {
fomod_manager::actions::ResolveFileResult::Files(mod_files) => mod_files,
_ => {
panic!("Resolved files have wrong type");
}
};
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
@@ -86,7 +96,12 @@ fn add_root() -> Result<(), Box<dyn Error>> {
let mod_to_install = root_config
.mod_by_id("add_test_root")
.expect("Mod not found");
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
let files_to_add = match resolve_files_for_install(&root_config, &mod_to_install)? {
fomod_manager::actions::ResolveFileResult::Files(mod_files) => mod_files,
_ => {
panic!("Resolved files have wrong type");
}
};
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
@@ -117,7 +132,12 @@ fn add_filter() -> Result<(), Box<dyn Error>> {
let mod_to_install = root_config
.mod_by_id("add_test_filter")
.expect("Mod not found");
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
let files_to_add = match resolve_files_for_install(&root_config, &mod_to_install)? {
fomod_manager::actions::ResolveFileResult::Files(mod_files) => mod_files,
_ => {
panic!("Resolved files have wrong type");
}
};
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);