Skip to main content

upstream_rs/application/operations/
install_operation.rs

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}