Skip to main content

romm_cli/core/download/manager/
rom.rs

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