winbrew_app\operations\remove/
execution.rs1use anyhow::Context;
18use tracing::{debug, warn};
19
20use std::path::{Path, PathBuf};
21
22use crate::core::fs::cleanup_path;
23use crate::core::paths::{install_root_from_package_dir, package_journal_file_at};
24use crate::database;
25use crate::database::package_journal_key;
26use crate::engines::{EngineKind, PackageEngine};
27use crate::operations::shims;
28
29use super::{RemovalError, RemovalPlan, Result};
30use crate::models::domains::installed::InstalledPackage;
31
32pub fn execute_removal(plan: &RemovalPlan, force: bool) -> Result<()> {
37 let conn = database::get_conn()?;
38 let shims_root =
39 install_root_from_package_dir(Path::new(&plan.package.install_dir)).join("shims");
40
41 execute_removal_with_conn(plan, force, &shims_root, &conn)
42}
43
44fn execute_removal_with_conn(
50 plan: &RemovalPlan,
51 force: bool,
52 shims_root: &std::path::Path,
53 conn: &database::DbConnection,
54) -> Result<()> {
55 debug!(
56 package = plan.package.name.as_str(),
57 force, "starting remove"
58 );
59
60 if !force && !plan.dependents.is_empty() {
61 return Err(RemovalError::DependentPackagesBlocked {
66 name: plan.package.name.clone(),
67 dependents: plan.dependents.join(", "),
68 });
69 }
70
71 let install_dir = PathBuf::from(&plan.package.install_dir);
72 let engine_kind = plan.package.engine_kind;
73 let commands = match database::list_commands_for_package(conn, &plan.package.name) {
74 Ok(commands) => commands,
75 Err(err) => {
76 warn!(
77 package = plan.package.name.as_str(),
78 error = %err,
79 "failed to read package commands for shim cleanup"
80 );
81 Vec::new()
82 }
83 };
84
85 match engine_kind {
86 EngineKind::Msix | EngineKind::Msi | EngineKind::NativeExe | EngineKind::Font => {
87 engine_kind.remove(&plan.package)?;
88
89 if install_dir.exists()
90 && let Err(err) = cleanup_path(&install_dir)
91 {
92 warn!(
93 "failed to remove package directory for {}: {err}",
94 plan.package.name
95 );
96 }
97
98 database::delete_package(conn, &plan.package.name)?;
99 if !commands.is_empty()
100 && let Err(err) = shims::remove_shim_files(shims_root, &commands)
101 {
102 warn!(
103 package = plan.package.name.as_str(),
104 error = %err,
105 "failed to remove package shims"
106 );
107 }
108
109 if let Err(err) =
110 cleanup_committed_journal(&install_dir, &plan.package.name, &plan.package.version)
111 {
112 warn!(
113 package = plan.package.name.as_str(),
114 error = %err,
115 "failed to remove committed package journal"
116 );
117 }
118 }
119 EngineKind::Zip | EngineKind::Portable => {
120 if install_dir.exists() {
121 let trash_dir = install_dir.with_extension("trash");
122
123 cleanup_path(&trash_dir).context("failed to clean up old trash directory")?;
124
125 std::fs::rename(&install_dir, &trash_dir)
126 .context("failed to stage package for removal")?;
127
128 let trash_package = InstalledPackage {
129 install_dir: trash_dir.to_string_lossy().into_owned(),
130 ..plan.package.clone()
131 };
132
133 if let Err(err) = database::delete_package(conn, &plan.package.name) {
134 let _ = std::fs::rename(&trash_dir, &install_dir);
135 return Err(RemovalError::Unexpected(err));
136 }
137
138 if !commands.is_empty()
139 && let Err(err) = shims::remove_shim_files(shims_root, &commands)
140 {
141 warn!(
142 package = plan.package.name.as_str(),
143 error = %err,
144 "failed to remove package shims"
145 );
146 }
147
148 if let Err(err) = cleanup_committed_journal(
149 &install_dir,
150 &plan.package.name,
151 &plan.package.version,
152 ) {
153 warn!(
154 package = plan.package.name.as_str(),
155 error = %err,
156 "failed to remove committed package journal"
157 );
158 }
159
160 if let Err(err) = engine_kind.remove(&trash_package) {
161 warn!(
162 "failed to completely remove trash for {}: {err}",
163 plan.package.name
164 );
165 }
166 } else {
167 database::delete_package(conn, &plan.package.name)?;
168 if !commands.is_empty()
169 && let Err(err) = shims::remove_shim_files(shims_root, &commands)
170 {
171 warn!(
172 package = plan.package.name.as_str(),
173 error = %err,
174 "failed to remove package shims"
175 );
176 }
177
178 if let Err(err) = cleanup_committed_journal(
179 &install_dir,
180 &plan.package.name,
181 &plan.package.version,
182 ) {
183 warn!(
184 package = plan.package.name.as_str(),
185 error = %err,
186 "failed to remove committed package journal"
187 );
188 }
189 }
190 }
191 }
192
193 debug!(
194 package = plan.package.name.as_str(),
195 force, "remove completed"
196 );
197
198 Ok(())
199}
200
201fn cleanup_committed_journal(
202 install_dir: &Path,
203 package_name: &str,
204 package_version: &str,
205) -> anyhow::Result<()> {
206 let install_root = install_root_from_package_dir(install_dir);
207 let package_key = package_journal_key(package_name, package_version);
208 let journal_path = package_journal_file_at(&install_root, &package_key);
209
210 cleanup_path(&journal_path).with_context(|| {
211 format!(
212 "failed to remove committed journal at {}",
213 journal_path.display()
214 )
215 })?;
216
217 Ok(())
218}