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