use std::io;
use std::sync::Arc;
use crate::config::{LuaVersion, LuaVersionUnset};
use crate::lockfile::{LocalPackage, LocalPackageId};
use crate::progress::{MultiProgress, Progress, ProgressBar};
use crate::tree::TreeError;
use crate::{config::Config, tree::Tree};
use futures::future::join_all;
use itertools::Itertools;
use thiserror::Error;
#[derive(Error, Debug)]
#[error(transparent)]
pub enum RemoveError {
LuaVersionUnset(#[from] LuaVersionUnset),
Io(#[from] io::Error),
#[error(transparent)]
Tree(#[from] TreeError),
}
pub struct Remove<'a> {
config: &'a Config,
packages: Vec<LocalPackageId>,
progress: Option<Arc<Progress<MultiProgress>>>,
}
impl<'a> Remove<'a> {
pub fn new(config: &'a Config) -> Self {
Self {
config,
packages: Vec::new(),
progress: None,
}
}
pub fn packages<I>(self, packages: I) -> Self
where
I: IntoIterator<Item = LocalPackageId>,
{
Self {
packages: self.packages.into_iter().chain(packages).collect_vec(),
..self
}
}
pub fn package(self, package: LocalPackageId) -> Self {
self.packages(std::iter::once(package))
}
pub fn progress(self, progress: Arc<Progress<MultiProgress>>) -> Self {
Self {
progress: Some(progress),
..self
}
}
pub async fn remove(self) -> Result<(), RemoveError> {
let progress = match self.progress {
Some(p) => p,
None => MultiProgress::new_arc(),
};
let tree = self
.config
.user_tree(LuaVersion::from(self.config)?.clone())?;
remove(self.packages, tree, &Arc::clone(&progress)).await
}
}
async fn remove(
package_ids: Vec<LocalPackageId>,
tree: Tree,
progress: &Progress<MultiProgress>,
) -> Result<(), RemoveError> {
let lockfile = tree.lockfile()?;
let packages = package_ids
.iter()
.filter_map(|id| lockfile.get(id))
.cloned()
.collect_vec();
join_all(packages.into_iter().map(|package| {
let bar = progress.map(|p| p.new_bar());
let tree = tree.clone();
tokio::spawn(remove_package(package, tree, bar))
}))
.await;
lockfile.map_then_flush(|lockfile| {
package_ids
.iter()
.for_each(|package| lockfile.remove_by_id(package));
Ok::<_, io::Error>(())
})?;
Ok(())
}
async fn remove_package(
package: LocalPackage,
tree: Tree,
bar: Progress<ProgressBar>,
) -> Result<(), RemoveError> {
bar.map(|p| {
p.set_message(format!(
"🗑️ Removing {}@{}",
package.name(),
package.version()
))
});
let rock_layout = tree.installed_rock_layout(&package)?;
tokio::fs::remove_dir_all(&rock_layout.etc).await?;
tokio::fs::remove_dir_all(&rock_layout.rock_path).await?;
for relative_binary_path in package.spec.binaries() {
let binary_file_name = relative_binary_path
.file_name()
.expect("malformed lockfile");
let binary_path = tree.bin().join(binary_file_name);
if binary_path.is_file() {
tokio::fs::remove_file(binary_path).await?;
}
let unwrapped_binary_path = tree.unwrapped_bin().join(binary_file_name);
if unwrapped_binary_path.is_file() {
tokio::fs::remove_file(unwrapped_binary_path).await?;
}
}
bar.map(|p| p.finish_and_clear());
Ok(())
}