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}