Skip to main content

upstream_rs/application/operations/
install_operation.rs

1use anyhow::{Context, Result};
2use console::style;
3use std::time::{Duration, Instant};
4
5use crate::{
6    models::common::enums::TrustMode,
7    models::upstream::Package,
8    providers::provider_manager::ProviderManager,
9    services::{
10        packaging::{
11            InstallPreview, PackageInstaller, PackageProgressEvent, PackageTransactionContext,
12        },
13        storage::package_storage::PackageStorage,
14        trust::TrustedSignatureKeys,
15    },
16    utils::static_paths::UpstreamPaths,
17};
18
19const INSTALL_PROGRESS_UPDATE_INTERVAL: Duration = Duration::from_millis(100);
20
21macro_rules! message {
22    ($cb:expr, $($arg:tt)*) => {{
23        if let Some(cb) = $cb.as_mut() {
24            cb(&format!($($arg)*));
25        }
26    }};
27}
28
29pub struct InstallOperation<'a> {
30    installer: PackageInstaller<'a>,
31    package_storage: &'a mut PackageStorage,
32    trusted_keys: TrustedSignatureKeys,
33}
34
35impl<'a> InstallOperation<'a> {
36    pub fn new(
37        provider_manager: &'a ProviderManager,
38        package_storage: &'a mut PackageStorage,
39        paths: &'a UpstreamPaths,
40        trusted_keys: TrustedSignatureKeys,
41    ) -> Result<Self> {
42        Ok(Self {
43            installer: PackageInstaller::new(provider_manager, paths)?,
44            package_storage,
45            trusted_keys,
46        })
47    }
48
49    pub async fn install_bulk<F, G, H>(
50        &mut self,
51        packages: Vec<Package>,
52        trust_mode: TrustMode,
53        download_progress_callback: &mut Option<F>,
54        overall_progress_callback: &mut Option<G>,
55        message_callback: &mut Option<H>,
56    ) -> Result<()>
57    where
58        F: FnMut(u64, u64),
59        G: FnMut(u32, u32),
60        H: FnMut(&str),
61    {
62        let total = packages.len() as u32;
63        let mut completed = 0;
64        let mut failures = 0;
65
66        for package in packages {
67            let package_name = package.name.clone();
68            message!(message_callback, "Installing '{}' ...", package_name);
69
70            let use_icon = &package.icon_path.is_some();
71            let mut last_progress: Option<(u64, u64)> = None;
72            let mut last_emit: Option<Instant> = None;
73            let mut throttled_download_progress = download_progress_callback.as_mut().map(|cb| {
74                |downloaded: u64, total: u64| {
75                    last_progress = Some((downloaded, total));
76                    let should_emit = last_emit
77                        .map(|t| t.elapsed() >= INSTALL_PROGRESS_UPDATE_INTERVAL)
78                        .unwrap_or(true);
79                    if should_emit {
80                        cb(downloaded, total);
81                        last_emit = Some(Instant::now());
82                    }
83                }
84            });
85
86            match self
87                .install_single(
88                    package,
89                    &None,
90                    use_icon,
91                    trust_mode,
92                    &mut throttled_download_progress,
93                    message_callback,
94                )
95                .await
96                .context(format!("Failed to install package '{}'", package_name))
97            {
98                Ok(_) => {
99                    message!(message_callback, "{}", style("Package installed").green());
100                }
101                Err(e) => {
102                    message!(message_callback, "{} {}", style("Install failed:").red(), e);
103                    failures += 1;
104                }
105            }
106
107            if let (Some((downloaded, total)), Some(cb)) =
108                (last_progress, download_progress_callback.as_mut())
109            {
110                cb(downloaded, total);
111            }
112
113            completed += 1;
114            if let Some(cb) = overall_progress_callback.as_mut() {
115                cb(completed, total);
116            }
117        }
118
119        if failures > 0 {
120            message!(
121                message_callback,
122                "{} package(s) failed to install",
123                failures
124            );
125        }
126
127        Ok(())
128    }
129
130    pub async fn install_single<F, H>(
131        &mut self,
132        package: Package,
133        version: &Option<String>,
134        add_entry: &bool,
135        trust_mode: TrustMode,
136        download_progress_callback: &mut Option<F>,
137        message_callback: &mut Option<H>,
138    ) -> Result<()>
139    where
140        F: FnMut(u64, u64),
141        H: FnMut(&str),
142    {
143        let mut no_progress: Option<fn(PackageProgressEvent)> = None;
144        self.install_single_with_progress(
145            package,
146            version,
147            add_entry,
148            trust_mode,
149            download_progress_callback,
150            message_callback,
151            &mut no_progress,
152        )
153        .await
154    }
155
156    #[allow(clippy::too_many_arguments)]
157    pub async fn install_single_with_context<F, H>(
158        &mut self,
159        package: Package,
160        version: &Option<String>,
161        add_entry: &bool,
162        trust_mode: TrustMode,
163        transaction_context: PackageTransactionContext,
164        download_progress_callback: &mut Option<F>,
165        message_callback: &mut Option<H>,
166    ) -> Result<()>
167    where
168        F: FnMut(u64, u64),
169        H: FnMut(&str),
170    {
171        let mut no_progress: Option<fn(PackageProgressEvent)> = None;
172        self.install_release_with_context(
173            package,
174            version,
175            add_entry,
176            trust_mode,
177            transaction_context,
178            download_progress_callback,
179            message_callback,
180            &mut no_progress,
181        )
182        .await
183    }
184
185    #[allow(clippy::too_many_arguments)]
186    pub async fn install_single_with_progress<F, H, P>(
187        &mut self,
188        package: Package,
189        version: &Option<String>,
190        add_entry: &bool,
191        trust_mode: TrustMode,
192        download_progress_callback: &mut Option<F>,
193        message_callback: &mut Option<H>,
194        progress_callback: &mut Option<P>,
195    ) -> Result<()>
196    where
197        F: FnMut(u64, u64),
198        H: FnMut(&str),
199        P: FnMut(PackageProgressEvent),
200    {
201        self.install_release_with_context(
202            package,
203            version,
204            add_entry,
205            trust_mode,
206            PackageTransactionContext::install(),
207            download_progress_callback,
208            message_callback,
209            progress_callback,
210        )
211        .await
212    }
213
214    #[allow(clippy::too_many_arguments)]
215    async fn install_release_with_context<F, H, P>(
216        &mut self,
217        package: Package,
218        version: &Option<String>,
219        add_entry: &bool,
220        trust_mode: TrustMode,
221        transaction_context: PackageTransactionContext,
222        download_progress_callback: &mut Option<F>,
223        message_callback: &mut Option<H>,
224        progress_callback: &mut Option<P>,
225    ) -> Result<()>
226    where
227        F: FnMut(u64, u64),
228        H: FnMut(&str),
229        P: FnMut(PackageProgressEvent),
230    {
231        self.installer
232            .install_release_with_progress(
233                self.package_storage,
234                &self.trusted_keys,
235                package,
236                version,
237                add_entry,
238                trust_mode,
239                transaction_context,
240                download_progress_callback,
241                message_callback,
242                progress_callback,
243            )
244            .await
245            .map(|_| ())
246    }
247
248    pub async fn install_local_artifact<H>(
249        &mut self,
250        package: Package,
251        artifact_path: &std::path::Path,
252        version: crate::models::common::version::Version,
253        add_entry: &bool,
254        transaction_context: PackageTransactionContext,
255        message_callback: &mut Option<H>,
256    ) -> Result<Package>
257    where
258        H: FnMut(&str),
259    {
260        self.installer
261            .install_local_artifact(
262                self.package_storage,
263                package,
264                artifact_path,
265                version,
266                add_entry,
267                transaction_context,
268                message_callback,
269            )
270            .await
271    }
272
273    pub async fn preview_single_install(
274        &self,
275        package: &Package,
276        version: &Option<String>,
277    ) -> Result<InstallPreview> {
278        self.installer
279            .preview_single_install(package, version)
280            .await
281    }
282}