romm_api/core/download/manager/
rom.rs1use 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}