refactored actions to own files
This commit is contained in:
11
src/actions.rs
Normal file
11
src/actions.rs
Normal 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};
|
||||
@@ -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
42
src/actions/download.rs
Normal 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
70
src/actions/include.rs
Normal 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,
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
|
||||
75
src/main.rs
75
src/main.rs
@@ -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,
|
||||
} => {
|
||||
Some(conflict) => {
|
||||
error!(
|
||||
"File conflict between {} and {} at {}",
|
||||
lhs_mod_id,
|
||||
rhs_mod_id,
|
||||
path.to_string_lossy()
|
||||
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");
|
||||
}
|
||||
instance::InststanceError::FomodRunInstaller => {
|
||||
error!("Failed to run FOMod installer");
|
||||
}
|
||||
instance::InststanceError::FomodFinalize(error) => {
|
||||
error!(
|
||||
"FOMod installer finished but failed to finalize result: {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user