use std::io;
use crate::lockfile::{FlushLockfileError, LocalPackage, LocalPackageId};
use crate::lua_version::{LuaVersion, LuaVersionUnset};
use crate::tree::{InstallTree, TreeError};
use crate::{config::Config, tree::Tree};
use bon::Builder;
use futures::StreamExt;
use itertools::Itertools;
use miette::Diagnostic;
use thiserror::Error;
use tracing::{span, Instrument};
#[derive(Error, Debug, Diagnostic)]
#[error(transparent)]
pub enum RemoveError {
#[diagnostic(transparent)]
LuaVersionUnset(#[from] LuaVersionUnset),
Io(#[from] io::Error),
#[error(transparent)]
#[diagnostic(transparent)]
Tree(#[from] TreeError),
#[error(transparent)]
#[diagnostic(transparent)]
FlushLockfile(#[from] FlushLockfileError),
}
#[derive(Builder)]
#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
pub struct Uninstall<'a> {
#[builder(field)]
packages: Vec<LocalPackageId>,
config: &'a Config,
tree: Option<Tree>,
}
impl<'a, State> UninstallBuilder<'a, State>
where
State: uninstall_builder::State,
{
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))
}
}
impl<'a, State> UninstallBuilder<'a, State>
where
State: uninstall_builder::State + uninstall_builder::IsComplete,
{
pub async fn remove(self) -> Result<(), RemoveError> {
let args = self._build();
let tree = args.tree.unwrap_or(
args.config
.user_tree(LuaVersion::from(args.config)?.clone())?,
);
remove(args.packages, tree, args.config).await
}
}
async fn remove(
package_ids: Vec<LocalPackageId>,
tree: Tree,
config: &Config,
) -> Result<(), RemoveError> {
let lockfile = tree.lockfile()?;
let packages = package_ids
.iter()
.filter_map(|id| lockfile.get(id))
.cloned()
.collect_vec();
futures::stream::iter(packages.into_iter().map(|package| {
let tree = tree.clone();
tokio::spawn(
remove_package(package, tree).instrument(tracing::trace_span!("remove_worker")),
)
}))
.buffered(config.max_jobs())
.collect::<Vec<_>>()
.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) -> Result<(), RemoveError> {
let span = span!(
tracing::Level::INFO,
"Removing",
package = package.name().to_string(),
version = package.version().to_string(),
);
let _enter = span.enter();
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() {
if let Some(binary_file_name) = relative_binary_path.file_name() {
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?;
}
}
}
Ok(())
}