romm_api/core/download/manager/
extras.rs1use std::sync::Arc;
2
3use crate::client::RommClient;
4use crate::core::extras::DownloadTarget;
5use crate::error::DownloadError;
6use crate::types::Rom;
7
8use super::super::extras_job::{finalize_extras_job_status, ExtrasItemResult, ExtrasJob};
9use super::super::paths::resolve_download_directory;
10use super::super::transfer::{download_target_with_fallback, prepare_download_target_destination};
11use super::DownloadManager;
12
13impl DownloadManager {
14 pub fn start_extras_download(
15 &self,
16 rom: &Rom,
17 selected: Vec<DownloadTarget>,
18 client: RommClient,
19 configured_download_dir: Option<&str>,
20 ) -> Result<(), DownloadError> {
21 if selected.is_empty() {
22 return Err(DownloadError::NoExtrasTargets);
23 }
24
25 let _ = resolve_download_directory(configured_download_dir)?;
26
27 let platform = rom
28 .platform_display_name
29 .as_deref()
30 .or(rom.platform_custom_name.as_deref())
31 .unwrap_or("—")
32 .to_string();
33
34 let total_items = selected.len();
35 let job = ExtrasJob::new(rom.id, rom.name.clone(), platform, total_items);
36 let job_id = job.id;
37
38 match self.extras_jobs.lock() {
39 Ok(mut jobs) => jobs.push(job),
40 Err(err) => {
41 eprintln!("warning: extras job list lock poisoned: {}", err);
42 return Err(DownloadError::ExtrasJobListPoisoned(err.to_string()));
43 }
44 }
45
46 let extras_jobs = self.extras_jobs.clone();
47 tokio::spawn(async move {
48 let semaphore = Arc::new(tokio::sync::Semaphore::new(4));
49 let mut handles = Vec::new();
50
51 for target in selected {
52 let permit = match semaphore.clone().acquire_owned().await {
53 Ok(p) => p,
54 Err(_) => break,
55 };
56 let client = client.clone();
57 let extras_jobs = extras_jobs.clone();
58 handles.push(tokio::spawn(async move {
59 let mut on_progress = |_r: u64, _t: u64| {};
60 let download_result = match prepare_download_target_destination(&target).await {
61 Ok(true) => Ok(()),
62 Ok(false) => {
63 download_target_with_fallback(
64 &client,
65 &target,
66 |_, _| false,
67 &mut on_progress,
68 )
69 .await
70 }
71 Err(err) => Err(err),
72 };
73
74 drop(permit);
75
76 let (ok, err) = match download_result {
77 Ok(()) => (true, None),
78 Err(e) => (false, Some(e.to_string())),
79 };
80
81 let item = ExtrasItemResult {
82 title: target.title.clone(),
83 kind: target.kind,
84 ok,
85 error: err,
86 };
87
88 if let Ok(mut list) = extras_jobs.lock() {
89 if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
90 j.completed_items = j.completed_items.saturating_add(1);
91 j.item_results.push(item);
92 if j.completed_items >= j.total_items {
93 j.status = finalize_extras_job_status(&j.item_results);
94 }
95 }
96 }
97 }));
98 }
99
100 for h in handles {
101 let _ = h.await;
102 }
103 });
104
105 Ok(())
106 }
107}