lux_lib/operations/
remove.rs1use std::io;
2use std::sync::Arc;
3
4use crate::config::{LuaVersion, LuaVersionUnset};
5use crate::lockfile::{LocalPackage, LocalPackageId};
6use crate::progress::{MultiProgress, Progress, ProgressBar};
7use crate::tree::TreeError;
8use crate::{config::Config, tree::Tree};
9use futures::future::join_all;
10use itertools::Itertools;
11use thiserror::Error;
12
13#[derive(Error, Debug)]
14#[error(transparent)]
15pub enum RemoveError {
16 LuaVersionUnset(#[from] LuaVersionUnset),
17 Io(#[from] io::Error),
18 #[error(transparent)]
19 Tree(#[from] TreeError),
20}
21
22pub struct Remove<'a> {
23 config: &'a Config,
24 packages: Vec<LocalPackageId>,
25 progress: Option<Arc<Progress<MultiProgress>>>,
26}
27
28impl<'a> Remove<'a> {
31 pub fn new(config: &'a Config) -> Self {
33 Self {
34 config,
35 packages: Vec::new(),
36 progress: None,
37 }
38 }
39
40 pub fn packages<I>(self, packages: I) -> Self
42 where
43 I: IntoIterator<Item = LocalPackageId>,
44 {
45 Self {
46 packages: self.packages.into_iter().chain(packages).collect_vec(),
47 ..self
48 }
49 }
50
51 pub fn package(self, package: LocalPackageId) -> Self {
53 self.packages(std::iter::once(package))
54 }
55
56 pub fn progress(self, progress: Arc<Progress<MultiProgress>>) -> Self {
59 Self {
60 progress: Some(progress),
61 ..self
62 }
63 }
64
65 pub async fn remove(self) -> Result<(), RemoveError> {
67 let progress = match self.progress {
68 Some(p) => p,
69 None => MultiProgress::new_arc(),
70 };
71 let tree = self
72 .config
73 .user_tree(LuaVersion::from(self.config)?.clone())?;
74 remove(self.packages, tree, &Arc::clone(&progress)).await
75 }
76}
77
78async fn remove(
80 package_ids: Vec<LocalPackageId>,
81 tree: Tree,
82 progress: &Progress<MultiProgress>,
83) -> Result<(), RemoveError> {
84 let lockfile = tree.lockfile()?;
85
86 let packages = package_ids
87 .iter()
88 .filter_map(|id| lockfile.get(id))
89 .cloned()
90 .collect_vec();
91
92 join_all(packages.into_iter().map(|package| {
93 let bar = progress.map(|p| p.new_bar());
94
95 let tree = tree.clone();
96 tokio::spawn(remove_package(package, tree, bar))
97 }))
98 .await;
99
100 lockfile.map_then_flush(|lockfile| {
101 package_ids
102 .iter()
103 .for_each(|package| lockfile.remove_by_id(package));
104
105 Ok::<_, io::Error>(())
106 })?;
107
108 Ok(())
109}
110
111async fn remove_package(
112 package: LocalPackage,
113 tree: Tree,
114 bar: Progress<ProgressBar>,
115) -> Result<(), RemoveError> {
116 bar.map(|p| {
117 p.set_message(format!(
118 "🗑️ Removing {}@{}",
119 package.name(),
120 package.version()
121 ))
122 });
123
124 let rock_layout = tree.installed_rock_layout(&package)?;
125 tokio::fs::remove_dir_all(&rock_layout.etc).await?;
126 tokio::fs::remove_dir_all(&rock_layout.rock_path).await?;
127
128 for relative_binary_path in package.spec.binaries() {
130 let binary_file_name = relative_binary_path
131 .file_name()
132 .expect("malformed lockfile");
133
134 let binary_path = tree.bin().join(binary_file_name);
135 if binary_path.is_file() {
136 tokio::fs::remove_file(binary_path).await?;
137 }
138
139 let unwrapped_binary_path = tree.unwrapped_bin().join(binary_file_name);
140 if unwrapped_binary_path.is_file() {
141 tokio::fs::remove_file(unwrapped_binary_path).await?;
142 }
143 }
144
145 bar.map(|p| p.finish_and_clear());
146 Ok(())
147}