the big refactor

This commit is contained in:
2026-03-04 15:17:55 +01:00
parent 7e6de5c73c
commit dc41f93ecb
10 changed files with 472 additions and 470 deletions

178
src/instance.rs Normal file
View File

@@ -0,0 +1,178 @@
use std::{
collections::{HashMap, HashSet},
io,
path::{Path, PathBuf},
};
use globset::{Glob, GlobSet, GlobSetBuilder};
use log::warn;
use crate::{
basic_types::{InstalledMod, Link, ModConfig, ModFile, ModdedInstance, RootConfig},
file_conflict_solver::ConflictSolver,
fomod, install_prompt,
mod_config_installer::run_fomod_installer,
utils::{resolve_case_insensitive, walk_files_recursive},
};
pub fn insert_mod_to_instance(
instance: &mut ModdedInstance,
from_mod: &ModConfig,
files: &[ModFile],
priority: isize,
) {
let mut solver = ConflictSolver::new();
let mut installed_files: Vec<(ModFile, &InstalledMod)> = Vec::new();
for installed_mod in &instance.mods {
for link in installed_mod.files() {
let recreated_mod_file = ModFile::new(&link.src, &link.dst, 0);
installed_files.push((recreated_mod_file, installed_mod));
}
}
for (file, from_mod) in &installed_files {
if let Some(conflict) = solver.add_file(file, from_mod) {
warn!("Got file conflict on already added file: {:?}", conflict);
}
}
let new_mod = InstalledMod::new(&from_mod.id, priority);
for file in files {
if let Some(conflict) = solver.add_file(file, &new_mod) {
// TODO: Return conflict
}
}
let new_link_tree = solver.export_files();
let mut map: HashMap<String, InstalledMod> = HashMap::new();
for (file, from_mod) in new_link_tree {
match map.get_mut(&from_mod.mod_id()) {
Some(existing) => {
existing.add_file(file);
}
None => {
let mut new_mod = InstalledMod::new(&from_mod.mod_id(), from_mod.priority());
new_mod.add_file(file);
map.insert(new_mod.mod_id(), new_mod);
}
}
}
for (_, installed_mod) in map {
instance.update_or_create_mod(&installed_mod);
}
}
pub fn files_to_install_mod(
root_config: &RootConfig,
instance: &ModdedInstance,
mod_to_install: &ModConfig,
) -> anyhow::Result<Vec<ModFile>> {
let mod_location = root_config.get_mod_location(mod_to_install);
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.join(data_path))?
}
ModKind::Root => install_from_dir(mod_to_install, mod_location)?,
ModKind::Unkown => todo!(),
};
Ok(files)
}
fn determain_mod_kind(
mod_config: &ModConfig,
mod_location: impl AsRef<Path>,
) -> Result<ModKind, io::Error> {
if mod_config.is_root_mod() {
return Ok(ModKind::Root);
}
// Check for moduleconfig.xml
let module_config_path = resolve_case_insensitive(&mod_location, "fomod/ModuleConfig.xml")?;
if let Some(path) = module_config_path {
return Ok(ModKind::Fomod(path));
};
match resolve_case_insensitive(&mod_location, "data")? {
Some(data_path) => Ok(ModKind::EmbeddedData(data_path)),
None => Ok(ModKind::Unkown),
}
}
fn install_fomod(
_instance: &ModdedInstance,
module_config_path: impl AsRef<Path>,
mod_root: impl AsRef<Path>,
) -> anyhow::Result<Vec<ModFile>> {
let module_config = fomod::Config::load_from_file(module_config_path)?;
// TODO: add active plugins from instance config
let files = run_fomod_installer(module_config, &[], install_prompt::prompt)?;
let mod_files: Vec<_> = files
.iter()
.flat_map(|f| ModFile::from_installer(f.clone(), &mod_root).unwrap())
.collect();
Ok(mod_files)
}
fn install_from_dir(
mod_config: &ModConfig,
path: impl AsRef<Path>,
) -> anyhow::Result<Vec<ModFile>> {
let glob_filter = create_glob_filter(mod_config.ignore())?;
let files: Vec<_> = walk_files_recursive(&path)?
.map(|entry| entry.path())
.map(|file_path| file_path.strip_prefix(&path).unwrap().to_owned())
.filter(|rel_path| !glob_filter.is_match(rel_path))
.map(|rel_path| ModFile::new(&rel_path, &rel_path, 0))
.collect();
Ok(files)
}
fn create_glob_filter(rules: &[String]) -> anyhow::Result<GlobSet> {
let mut builder = GlobSetBuilder::new();
for p in rules {
builder.add(Glob::new(p)?);
}
let set = builder.build()?;
Ok(set)
}
enum ModKind {
Fomod(PathBuf),
EmbeddedData(PathBuf),
Root,
Unkown,
}
fn should_be_included(path: impl AsRef<Path>) -> bool {
matches!(
path.as_ref().extension().and_then(|e| e.to_str()),
Some(
"esp"
| "esm"
| "esl"
| "bsa"
| "ba2"
| "bsl"
| "ini"
| "pex"
| "psc"
| "strings"
| "ilstrings"
| "dlstrings"
| "dll"
)
)
}