Skip to main content

romm_api/core/download/manager/
rom.rs

1use crate::client::RommClient;
2use crate::config::RomsLayoutConfig;
3use crate::core::extras::build_base_rom_file_targets;
4use crate::core::interrupt::is_cancelled_download;
5use crate::core::utils;
6use crate::error::DownloadError;
7use crate::types::Rom;
8
9use super::super::job::{DownloadJob, DownloadStatus};
10use super::super::paths::{resolve_console_roms_dir, resolve_download_directory};
11use super::super::transfer::{
12    download_target_with_fallback, finalize_download, prepare_download_target_destination,
13    sanitized_final_filename, FinalizeResult,
14};
15use super::DownloadManager;
16
17impl DownloadManager {
18    pub fn start_download(
19        &self,
20        rom: &Rom,
21        client: RommClient,
22        layout: &RomsLayoutConfig,
23        configured_download_dir: Option<&str>,
24    ) -> Result<(), DownloadError> {
25        let platform = rom
26            .platform_display_name
27            .as_deref()
28            .or(rom.platform_custom_name.as_deref())
29            .unwrap_or("—")
30            .to_string();
31
32        let job = DownloadJob::new(rom.id, rom.name.clone(), platform);
33        let job_id = job.id;
34        let rom_id = rom.id;
35        let fs_name = rom.fs_name.clone();
36        let final_name = sanitized_final_filename(&rom.fs_name, rom.id);
37        let rom_for_targets = rom.clone();
38        let layout = layout.clone();
39        match self.jobs.lock() {
40            Ok(mut jobs) => jobs.push(job),
41            Err(err) => {
42                eprintln!("warning: download job list lock poisoned: {}", err);
43                return Err(DownloadError::JobListPoisoned(err.to_string()));
44            }
45        }
46
47        let save_dir = resolve_download_directory(configured_download_dir)?;
48        let console_dir = resolve_console_roms_dir(&layout, &save_dir, rom)?;
49        let base_targets = build_base_rom_file_targets(&rom_for_targets, &layout, &save_dir)?;
50        let jobs = self.jobs.clone();
51        tokio::spawn(async move {
52            let temp_root = save_dir.join(".tmp");
53            if let Err(err) = tokio::fs::create_dir_all(&temp_root).await {
54                if let Ok(mut list) = jobs.lock() {
55                    if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
56                        j.status = DownloadStatus::Error(format!(
57                            "Could not create temp directory {}: {err}",
58                            temp_root.display()
59                        ));
60                    }
61                }
62                return;
63            }
64
65            let final_path = console_dir.join(final_name.clone());
66            if let Err(err) = tokio::fs::create_dir_all(&console_dir).await {
67                if let Ok(mut list) = jobs.lock() {
68                    if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
69                        j.status = DownloadStatus::Error(format!(
70                            "Could not create console directory {}: {err}",
71                            console_dir.display()
72                        ));
73                    }
74                }
75                return;
76            }
77
78            let base_targets = base_targets;
79            if !base_targets.is_empty() {
80                let total_targets = base_targets.len() as f64;
81                for (idx, target) in base_targets.iter().enumerate() {
82                    let client = client.clone();
83                    let mut progress = {
84                        let jobs = jobs.clone();
85                        move |received: u64, total: u64| {
86                            let file_ratio = if total > 0 {
87                                received as f64 / total as f64
88                            } else {
89                                0.0
90                            };
91                            let total_ratio = ((idx as f64) + file_ratio) / total_targets;
92                            if let Ok(mut list) = jobs.lock() {
93                                if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
94                                    j.progress = total_ratio.min(1.0);
95                                }
96                            }
97                        }
98                    };
99                    match prepare_download_target_destination(target).await {
100                        Ok(true) => {
101                            progress(
102                                target.expected_size_bytes.unwrap_or(0),
103                                target.expected_size_bytes.unwrap_or(0),
104                            );
105                            continue;
106                        }
107                        Ok(false) => {}
108                        Err(err) => {
109                            if let Ok(mut list) = jobs.lock() {
110                                if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
111                                    j.status = DownloadStatus::Error(err.to_string());
112                                }
113                            }
114                            return;
115                        }
116                    }
117                    if let Err(final_err) =
118                        download_target_with_fallback(&client, target, |_, _| false, &mut progress)
119                            .await
120                    {
121                        if let Ok(mut list) = jobs.lock() {
122                            if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
123                                j.status = DownloadStatus::Error(final_err.to_string());
124                            }
125                        }
126                        return;
127                    }
128                }
129                if let Ok(mut list) = jobs.lock() {
130                    if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
131                        j.status = DownloadStatus::Done;
132                        j.progress = 1.0;
133                    }
134                }
135                return;
136            }
137
138            if final_path.exists() {
139                if let Ok(mut list) = jobs.lock() {
140                    if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
141                        j.status = DownloadStatus::SkippedAlreadyExists;
142                        j.progress = 1.0;
143                    }
144                }
145                return;
146            }
147
148            let temp_name = format!(
149                "rom-{}-{}-{}.part",
150                rom_id,
151                utils::sanitize_filename(&fs_name),
152                job_id
153            );
154            let temp_path = temp_root.join(temp_name);
155
156            let on_progress = |received: u64, total: u64| {
157                let p = if total > 0 {
158                    received as f64 / total as f64
159                } else {
160                    0.0
161                };
162
163                if let Ok(mut list) = jobs.lock() {
164                    if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
165                        j.progress = p;
166                    }
167                }
168            };
169
170            let download_result = client.download_rom(rom_id, &temp_path, on_progress).await;
171            if download_result.is_err() {
172                let _ = tokio::fs::remove_file(&temp_path).await;
173            }
174            match download_result {
175                Ok(()) => match finalize_download(&temp_path, &final_path).await {
176                    Ok(FinalizeResult::Done) => {
177                        if let Ok(mut list) = jobs.lock() {
178                            if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
179                                j.status = DownloadStatus::Done;
180                                j.progress = 1.0;
181                            }
182                        }
183                    }
184                    Ok(FinalizeResult::SkippedAlreadyExists) => {
185                        if let Ok(mut list) = jobs.lock() {
186                            if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
187                                j.status = DownloadStatus::SkippedAlreadyExists;
188                                j.progress = 1.0;
189                            }
190                        }
191                    }
192                    Err(err) => {
193                        let _ = tokio::fs::remove_file(&temp_path).await;
194                        if let Ok(mut list) = jobs.lock() {
195                            if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
196                                j.status = DownloadStatus::FinalizeFailed(err.to_string());
197                            }
198                        }
199                    }
200                },
201                Err(e) => {
202                    if let Ok(mut list) = jobs.lock() {
203                        if let Some(j) = list.iter_mut().find(|j| j.id == job_id) {
204                            if is_cancelled_download(&e) {
205                                j.status = DownloadStatus::Cancelled;
206                            } else {
207                                j.status = DownloadStatus::Error(e.to_string());
208                            }
209                        }
210                    }
211                }
212            }
213        });
214        Ok(())
215    }
216}