conduit_cli/core/
remover.rs1use crate::core::filesystem::config::ConduitConfig;
2use crate::core::error::CoreResult;
3use crate::core::events::{CoreCallbacks, CoreEvent};
4use crate::core::filesystem::lock::ConduitLock;
5use crate::core::paths::CorePaths;
6use std::collections::HashSet;
7use std::fs;
8
9pub struct RemoveReport {
10 pub removed_slug: String,
11 pub purged_slugs: Vec<String>,
12}
13
14pub fn remove_mod(
15 paths: &CorePaths,
16 slug: &str,
17 config: &mut ConduitConfig,
18 lock: &mut ConduitLock,
19 callbacks: &mut dyn CoreCallbacks,
20) -> CoreResult<Option<RemoveReport>> {
21 if !config.mods.contains_key(slug) {
22 return Ok(None);
23 }
24
25 config.mods.remove(slug);
26
27 let mut mods_to_keep = HashSet::new();
28 for root_slug in config.mods.keys() {
29 collect_dependencies(root_slug, lock, &mut mods_to_keep);
30 }
31
32 let mut purged = Vec::new();
33 let all_locked_slugs: Vec<String> = lock.locked_mods.keys().cloned().collect();
34 for locked_slug in all_locked_slugs {
35 if !mods_to_keep.contains(&locked_slug)
36 && let Some(mod_data) = lock.locked_mods.remove(&locked_slug) {
37 let dest_path = paths.mods_dir().join(&mod_data.filename);
38 if dest_path.exists() {
39 fs::remove_file(dest_path)?;
40 }
41 purged.push(locked_slug.clone());
42 callbacks.on_event(CoreEvent::Purged { slug: locked_slug });
43 }
44 }
45
46 Ok(Some(RemoveReport {
47 removed_slug: slug.to_string(),
48 purged_slugs: purged,
49 }))
50}
51
52fn collect_dependencies(slug: &str, lock: &ConduitLock, kept: &mut HashSet<String>) {
53 if kept.contains(slug) {
54 return;
55 }
56 if let Some(mod_data) = lock.locked_mods.get(slug) {
57 kept.insert(slug.to_string());
58 for dep_id in &mod_data.dependencies {
59 if let Some((dep_slug, _)) = lock.locked_mods.iter().find(|(_, m)| &m.id == dep_id) {
60 collect_dependencies(dep_slug, lock, kept);
61 }
62 }
63 }
64}