1use std::path::Path;
2
3use anyhow::{Context, Result, anyhow};
4
5use crate::{
6 models::{
7 common::{enums::TrustMode, version::Version},
8 provider::{Asset, Release},
9 upstream::Package,
10 },
11 providers::provider_manager::ProviderManager,
12 services::{
13 packaging::{
14 InstallPreview, PackageInstaller, PackagePhase, PackageProgressEvent,
15 transaction_recorder::{PackageTransaction, failed_package, successful_package},
16 },
17 storage::{
18 package_storage::PackageStorage,
19 transaction_storage::{TransactionKind, UndoActionKind},
20 },
21 trust::TrustedSignatureKeys,
22 },
23 utils::static_paths::UpstreamPaths,
24};
25
26#[derive(Debug, Clone)]
27pub enum PackageTransactionContext {
28 Record {
29 kind: TransactionKind,
30 undo_kind: Option<UndoActionKind>,
31 },
32 CoveredByParent,
33}
34
35impl PackageTransactionContext {
36 pub fn install() -> Self {
37 Self::Record {
38 kind: TransactionKind::Install,
39 undo_kind: Some(UndoActionKind::Remove),
40 }
41 }
42
43 pub fn build() -> Self {
44 Self::Record {
45 kind: TransactionKind::Build,
46 undo_kind: Some(UndoActionKind::Remove),
47 }
48 }
49}
50
51pub struct ReleaseInstallRequest {
52 pub package: Package,
53 pub version: Option<String>,
54 pub add_entry: bool,
55 pub trust_mode: TrustMode,
56 pub transaction_context: PackageTransactionContext,
57}
58
59pub struct SelectedAssetInstallRequest<'a> {
60 pub package: Package,
61 pub release: &'a Release,
62 pub asset: &'a Asset,
63 pub add_entry: bool,
64 pub trust_mode: TrustMode,
65 pub transaction_context: PackageTransactionContext,
66}
67
68pub struct LocalArtifactInstallRequest<'a> {
69 pub package: Package,
70 pub artifact_path: &'a Path,
71 pub version: Version,
72 pub add_entry: bool,
73 pub transaction_context: PackageTransactionContext,
74}
75
76pub struct InstallOperation<'a> {
77 installer: PackageInstaller<'a>,
78 package_storage: &'a mut PackageStorage,
79 trusted_keys: TrustedSignatureKeys,
80 paths: &'a UpstreamPaths,
81}
82
83impl<'a> InstallOperation<'a> {
84 pub fn new(
85 provider_manager: &'a ProviderManager,
86 package_storage: &'a mut PackageStorage,
87 paths: &'a UpstreamPaths,
88 trusted_keys: TrustedSignatureKeys,
89 ) -> Result<Self> {
90 Ok(Self {
91 installer: PackageInstaller::new(provider_manager, paths)?,
92 package_storage,
93 trusted_keys,
94 paths,
95 })
96 }
97
98 pub async fn preview_release_install(
99 &self,
100 package: &Package,
101 version: &Option<String>,
102 ) -> Result<InstallPreview> {
103 self.installer
104 .preview_single_install(package, version)
105 .await
106 }
107
108 pub async fn install_release<F, H, P>(
109 &mut self,
110 request: ReleaseInstallRequest,
111 download_progress_callback: &mut Option<F>,
112 message_callback: &mut Option<H>,
113 progress_callback: &mut Option<P>,
114 ) -> Result<Package>
115 where
116 F: FnMut(u64, u64),
117 H: FnMut(&str),
118 P: FnMut(PackageProgressEvent),
119 {
120 let package_name = request.package.name.clone();
121 let transaction =
122 self.start_transaction(request.transaction_context, package_name.clone())?;
123
124 let result = match self
125 .installer
126 .install_release(
127 &self.trusted_keys,
128 request.package,
129 &request.version,
130 &request.add_entry,
131 request.trust_mode,
132 download_progress_callback,
133 message_callback,
134 progress_callback,
135 )
136 .await
137 {
138 Ok(installed_package) => {
139 self.save_installed_package(installed_package, message_callback, progress_callback)
140 }
141 Err(err) => Err(err),
142 };
143
144 self.finish_transaction(transaction, &package_name, result)
145 }
146
147 pub async fn install_selected_asset<F, H, P>(
148 &mut self,
149 request: SelectedAssetInstallRequest<'_>,
150 download_progress_callback: &mut Option<F>,
151 message_callback: &mut Option<H>,
152 progress_callback: &mut Option<P>,
153 ) -> Result<Package>
154 where
155 F: FnMut(u64, u64),
156 H: FnMut(&str),
157 P: FnMut(PackageProgressEvent),
158 {
159 let package_name = request.package.name.clone();
160 let transaction =
161 self.start_transaction(request.transaction_context, package_name.clone())?;
162
163 let result = match self
164 .installer
165 .install_selected_asset(
166 &self.trusted_keys,
167 request.package,
168 request.release,
169 request.asset,
170 &request.add_entry,
171 request.trust_mode,
172 download_progress_callback,
173 message_callback,
174 progress_callback,
175 )
176 .await
177 {
178 Ok(installed_package) => {
179 self.save_installed_package(installed_package, message_callback, progress_callback)
180 }
181 Err(err) => Err(err),
182 };
183
184 self.finish_transaction(transaction, &package_name, result)
185 }
186
187 pub async fn install_local_artifact<H, P>(
188 &mut self,
189 request: LocalArtifactInstallRequest<'_>,
190 message_callback: &mut Option<H>,
191 progress_callback: &mut Option<P>,
192 ) -> Result<Package>
193 where
194 H: FnMut(&str),
195 P: FnMut(PackageProgressEvent),
196 {
197 let package_name = request.package.name.clone();
198 let transaction =
199 self.start_transaction(request.transaction_context, package_name.clone())?;
200
201 let result = match self
202 .installer
203 .install_local_artifact(
204 request.package,
205 request.artifact_path,
206 request.version,
207 &request.add_entry,
208 message_callback,
209 progress_callback,
210 )
211 .await
212 {
213 Ok(installed_package) => {
214 self.save_installed_package(installed_package, message_callback, progress_callback)
215 }
216 Err(err) => Err(err),
217 };
218
219 self.finish_transaction(transaction, &package_name, result)
220 }
221
222 fn start_transaction(
223 &self,
224 context: PackageTransactionContext,
225 package_name: String,
226 ) -> Result<Option<PackageTransaction>> {
227 match context {
228 PackageTransactionContext::Record { kind, undo_kind } => Ok(Some(
229 PackageTransaction::start(self.paths, kind, vec![package_name], undo_kind)?,
230 )),
231 PackageTransactionContext::CoveredByParent => Ok(None),
232 }
233 }
234
235 fn finish_transaction(
236 &self,
237 transaction: Option<PackageTransaction>,
238 package_name: &str,
239 result: Result<Package>,
240 ) -> Result<Package> {
241 match (result, transaction) {
242 (Ok(installed_package), Some(transaction)) => {
243 transaction.complete(vec![successful_package(
244 package_name.to_string(),
245 None,
246 Some(installed_package.version.to_string()),
247 )])?;
248 Ok(installed_package)
249 }
250 (Err(err), Some(transaction)) => {
251 let summary = crate::output::error_summary(&err);
252 transaction.fail(
253 vec![failed_package(
254 package_name.to_string(),
255 None,
256 None,
257 summary.clone(),
258 )],
259 summary,
260 )?;
261 Err(err)
262 }
263 (Ok(installed_package), None) => Ok(installed_package),
264 (Err(err), None) => Err(err),
265 }
266 }
267
268 fn save_installed_package<H, P>(
269 &mut self,
270 installed_package: Package,
271 message_callback: &mut Option<H>,
272 progress_callback: &mut Option<P>,
273 ) -> Result<Package>
274 where
275 H: FnMut(&str),
276 P: FnMut(PackageProgressEvent),
277 {
278 if let Some(cb) = progress_callback.as_mut() {
279 cb(PackageProgressEvent::Phase(PackagePhase::SavingMetadata));
280 }
281
282 if let Err(err) = self
283 .package_storage
284 .add_or_update_package(installed_package.clone())
285 .context(format!(
286 "Failed to save package '{}' to storage",
287 installed_package.name
288 ))
289 {
290 return self.fail_after_metadata_error(installed_package, err, message_callback);
291 }
292
293 Ok(installed_package)
294 }
295
296 fn fail_after_metadata_error<H>(
297 &self,
298 installed_package: Package,
299 err: anyhow::Error,
300 message_callback: &mut Option<H>,
301 ) -> Result<Package>
302 where
303 H: FnMut(&str),
304 {
305 match self
306 .installer
307 .cleanup_partial_install(&installed_package, message_callback)
308 {
309 Ok(()) => Err(err.context(format!(
310 "Rolled back partial install for '{}'",
311 installed_package.name
312 ))),
313 Err(cleanup_err) => Err(anyhow!(
314 "{}. Additionally failed to roll back partial install for '{}': {}",
315 err,
316 installed_package.name,
317 cleanup_err
318 )),
319 }
320 }
321}