Skip to main content

romm_api/core/download/manager/
extras.rs

1use 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}