refactored actions to own files

This commit is contained in:
2026-03-18 13:23:58 +01:00
parent 9e3bdeacc6
commit 281327d69c
9 changed files with 161 additions and 162 deletions

11
src/actions.rs Normal file
View File

@@ -0,0 +1,11 @@
mod activate;
mod download;
mod include;
mod install;
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 load_order::{LoadOrderError, create_loadorder};

View File

@@ -7,6 +7,7 @@ use std::io::Write;
use std::path::PathBuf;
use std::{fs, io, os::unix, path::Path};
/// Create the symlinks for a instance in a given directory
pub fn activate_instance(
root_config: &RootConfig,
instance: &ModdedInstance,

42
src/actions/download.rs Normal file
View File

@@ -0,0 +1,42 @@
use anyhow::anyhow;
use log::error;
use crate::{
nexus::{NXMUrl, download_nxm},
types::RootConfig,
unpacker::unpack,
};
/// Handles a nexus mod url. Downloads, unpacks and adds the mod to the config
pub fn handle_nxm(root_config: &mut RootConfig, raw_url: &str) -> anyhow::Result<()> {
let Some(dl_location) = root_config.download_location() else {
return Err(anyhow!("No download location set"));
};
let Some(api_key) = root_config.nexus_api_key() else {
return Err(anyhow!("No API key provided"));
};
let Some(nxm_url) = NXMUrl::parse_url(raw_url) else {
return Err(anyhow!("Failed to parse URL"));
};
let (dl_file, mod_info) = download_nxm(api_key, &nxm_url, dl_location)?;
let mod_id = format!("{}-{}", mod_info.generate_id(), nxm_url.file);
if root_config.game_by_id(&mod_id).is_some() {
error!(
"Generated mod id already exists. Pleas install downloaded mod manually. Downloaded at {}",
&dl_file.to_string_lossy()
);
return Err(anyhow!("Mod with generated id already exists"));
}
let new_mod = unpack(root_config, &mod_id, dl_file)?;
root_config.add_mod(&new_mod);
root_config.save_to_file()?;
Ok(())
}

70
src/actions/include.rs Normal file
View File

@@ -0,0 +1,70 @@
use log::warn;
use std::{collections::HashMap, path::PathBuf};
use crate::{
file_conflict_solver::ConflictSolver,
types::{InstalledMod, ModConfig, ModFile, ModdedInstance},
};
pub fn insert_mod_to_instance(
instance: &mut ModdedInstance,
from_mod: &ModConfig,
files_to_add: &[ModFile],
priority: isize,
) -> Option<FileConflict> {
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!("File conflict on already added file: {:?}", conflict);
}
}
let new_mod = InstalledMod::new(from_mod.id(), priority);
for file in files_to_add {
if let Some(conflict) = solver.add_file(file, &new_mod) {
return Some(FileConflict {
lhs_mod_id: conflict.lhs_mod.mod_id().to_owned(),
rhs_mod_id: conflict.rhs_mod.mod_id().to_owned(),
path: conflict.rhs_file.dst().to_owned(),
});
}
}
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().to_owned(), new_mod);
}
}
}
for (_, installed_mod) in map {
instance.update_or_create_mod(&installed_mod);
}
None
}
pub struct FileConflict {
pub lhs_mod_id: String,
pub rhs_mod_id: String,
pub path: PathBuf,
}

View File

@@ -1,79 +1,19 @@
use std::{
collections::HashMap,
io,
path::{Path, PathBuf},
};
use globset::{Glob, GlobSet, GlobSetBuilder};
use log::{debug, trace, warn};
use thiserror::Error;
use log::{debug, trace};
use crate::{
file_conflict_solver::ConflictSolver,
fomod, install_prompt,
mod_config_installer::run_fomod_installer,
types::{InstalledMod, ModConfig, ModFile, ModdedInstance, RootConfig},
types::{ModConfig, ModFile, ModdedInstance, RootConfig},
utils::{resolve_case_insensitive, walk_all_files},
};
pub fn insert_mod_to_instance(
instance: &mut ModdedInstance,
from_mod: &ModConfig,
files: &[ModFile],
priority: isize,
) -> Result<(), InststanceError> {
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!("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) {
return Err(InststanceError::FileConflict {
lhs_mod_id: conflict.lhs_mod.mod_id().to_owned(),
rhs_mod_id: conflict.rhs_mod.mod_id().to_owned(),
path: conflict.rhs_file.dst().to_owned(),
});
}
}
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().to_owned(), new_mod);
}
}
}
for (_, installed_mod) in map {
instance.update_or_create_mod(&installed_mod);
}
Ok(())
}
pub fn files_to_install_mod(
pub fn resolve_files_for_install(
root_config: &RootConfig,
instance: &ModdedInstance,
mod_to_install: &ModConfig,
@@ -126,14 +66,11 @@ fn install_fomod(
.collect();
trace!("Current loded plugins: {:?}", active_plugins);
let files = run_fomod_installer(module_config, &active_plugins, install_prompt::prompt)
.map_err(|_| InststanceError::FomodRunInstaller)?;
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).map_err(InststanceError::FomodFinalize)
})
.map(|f| ModFile::from_installer(f.clone(), &mod_root))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
@@ -226,19 +163,3 @@ fn should_be_included(path: impl AsRef<Path>) -> bool {
)
)
}
#[derive(Debug, Error)]
pub enum InststanceError {
#[error("Two mods write the same file")]
FileConflict {
lhs_mod_id: String,
rhs_mod_id: String,
path: PathBuf,
},
#[error("Failed to run fomod installer")]
FomodRunInstaller,
#[error("Failed to handle results of fomod installer")]
FomodFinalize(io::Error),
}

View File

@@ -10,7 +10,10 @@ use std::{
use thiserror::Error;
use walkdir::WalkDir;
use crate::{types::{self, ModdedInstance, RootConfig}, utils::is_plugin_file};
use crate::{
types::{self, ModdedInstance, RootConfig},
utils::is_plugin_file,
};
pub fn create_loadorder(
root_config: &RootConfig,

View File

@@ -1,12 +1,10 @@
pub mod activator;
pub mod cli;
pub mod file_conflict_solver;
pub mod fomod;
pub mod install_prompt;
pub mod instance;
pub mod load_order;
pub mod mod_config_installer;
pub mod nexus;
pub mod types;
pub mod unpacker;
pub mod utils;
pub mod actions;

View File

@@ -4,11 +4,12 @@ use log::{debug, error, info};
use std::{error::Error, path::Path};
use fomod_manager::{
activator::activate_instance,
actions::{
activate_instance, create_loadorder, handle_nxm, insert_mod_to_instance,
resolve_files_for_install,
},
cli::{self, Args},
instance::{self, files_to_install_mod, insert_mod_to_instance},
load_order,
nexus::{NXMUrl, NexusAPI, download_nxm},
nexus::NexusAPI,
types::RootConfig,
unpacker::unpack,
};
@@ -29,40 +30,23 @@ 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 = files_to_install_mod(root_config, &instance, &mod_to_install)?;
let files = resolve_files_for_install(root_config, &instance, &mod_to_install)?;
match insert_mod_to_instance(&mut instance, &mod_to_install, &files, 0) {
Ok(_) => {
None => {
instance.save_to_file()?;
Ok(())
}
Err(err) => {
match &err {
instance::InststanceError::FileConflict {
lhs_mod_id,
rhs_mod_id,
path,
} => {
error!(
"File conflict between {} and {} at {}",
lhs_mod_id,
rhs_mod_id,
path.to_string_lossy()
);
info!("To resolve file conflicts give one mod a higher priority in the config");
}
instance::InststanceError::FomodRunInstaller => {
error!("Failed to run FOMod installer");
}
instance::InststanceError::FomodFinalize(error) => {
error!(
"FOMod installer finished but failed to finalize result: {}",
error
);
}
};
Some(conflict) => {
error!(
"File conflict between {} and {} at {}",
conflict.lhs_mod_id,
conflict.rhs_mod_id,
conflict.path.to_string_lossy()
);
info!("To resolve file conflicts give one mod a higher priority in the config");
Err(err.into())
Err(anyhow!("File conflict"))
}
}
}
@@ -71,46 +55,15 @@ fn command_order(root_config: &RootConfig, instance_id: &str) -> anyhow::Result<
let mut instance = root_config.load_instance_by_id(instance_id)?;
let game = root_config.game_by_id(instance.game_id()).unwrap();
let new_load_order = load_order::create_loadorder(root_config, &game, &instance)?;
let new_load_order = create_loadorder(root_config, &game, &instance)?;
instance.set_load_order(new_load_order);
instance.save_to_file()?;
Ok(())
}
fn command_download(root_config: &mut RootConfig, raw_url: &str) -> anyhow::Result<()> {
let Some(dl_location) = root_config.download_location() else {
return Err(anyhow!("No download location set"));
};
let Some(api_key) = root_config.nexus_api_key() else {
return Err(anyhow!("No API key provided"));
};
let Some(nxm_url) = NXMUrl::parse_url(raw_url) else {
return Err(anyhow!("Failed to parse URL"));
};
let (dl_file, mod_info) = download_nxm(api_key, &nxm_url, dl_location)?;
let mod_id = format!("{}-{}", mod_info.generate_id(), nxm_url.file);
if root_config.game_by_id(&mod_id).is_some() {
error!(
"Generated mod id already exists. Pleas install downloaded mod manually. Downloaded at {}",
&dl_file.to_string_lossy()
);
return Err(anyhow!("Mod with generated id already exists"));
}
let new_mod = unpack(root_config, &mod_id, dl_file)?;
root_config.add_mod(&new_mod);
root_config.save_to_file()?;
Ok(())
handle_nxm(root_config, raw_url)
}
fn command_unpack(

View File

@@ -1,7 +1,7 @@
use std::{collections::HashSet, error::Error, path::PathBuf};
use fomod_manager::{
instance::{files_to_install_mod, insert_mod_to_instance},
actions::{insert_mod_to_instance, resolve_files_for_install},
types::{Link, RootConfig},
};
@@ -20,9 +20,9 @@ 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 = files_to_install_mod(&root_config, &instance, &mod_to_install)?;
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0)?;
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
let installed_mods = instance.mods();
@@ -53,9 +53,9 @@ 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 = files_to_install_mod(&root_config, &instance, &mod_to_install)?;
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0)?;
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
let installed_mods = instance.mods();
@@ -86,9 +86,9 @@ 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 = files_to_install_mod(&root_config, &instance, &mod_to_install)?;
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0)?;
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
let installed_mods = instance.mods();
@@ -117,9 +117,9 @@ 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 = files_to_install_mod(&root_config, &instance, &mod_to_install)?;
let files_to_add = resolve_files_for_install(&root_config, &instance, &mod_to_install)?;
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0)?;
insert_mod_to_instance(&mut instance, &mod_to_install, &files_to_add, 0);
let installed_mods = instance.mods();