1use crate::DownloadOptions;
7use crate::advanced_download::AdvancedDownloader;
8use crate::config::Config;
9use crate::download::download as simple_download;
10use crate::optimization::Optimizer;
11use crate::torrent::{TorrentCallbacks, download_magnet};
12use std::error::Error;
13use std::fmt;
14use std::sync::Arc;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::mpsc::{Receiver as MpscReceiver, Sender as MpscSender};
17use std::thread;
18use std::time::Duration;
19
20#[derive(Debug, Clone)]
22pub enum DownloadCommand {
23 Start {
24 url: String,
25 output_path: String,
26 is_advanced: bool,
27 verify_iso: bool,
28 expected_sha256: Option<String>,
29 },
30 Cancel,
31}
32
33#[derive(Debug, Clone)]
35pub enum WorkerToGuiMessage {
36 Progress(f32),
37 StatusUpdate(String),
38 Completed(String),
39 Error(String),
40}
41
42impl fmt::Display for WorkerToGuiMessage {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 WorkerToGuiMessage::Progress(p) => write!(f, "Progress({:.2})", p),
46 WorkerToGuiMessage::StatusUpdate(s) => write!(f, "Status: {}", s),
47 WorkerToGuiMessage::Completed(s) => write!(f, "Completed: {}", s),
48 WorkerToGuiMessage::Error(e) => write!(f, "Error: {}", e),
49 }
50 }
51}
52
53pub fn spawn_download_worker(
55 config: Config,
56 download_rx: MpscReceiver<DownloadCommand>,
57 status_tx: MpscSender<WorkerToGuiMessage>,
58) -> thread::JoinHandle<()> {
59 thread::spawn(move || {
60 download_worker(config, download_rx, status_tx);
61 })
62}
63
64fn download_worker(
65 config: Config,
66 download_rx: MpscReceiver<DownloadCommand>,
67 status_tx: MpscSender<WorkerToGuiMessage>,
68) {
69 let cancel_token = Arc::new(AtomicBool::new(false));
70 let mut download_handle: Option<thread::JoinHandle<()>> = None;
71
72 loop {
73 match download_rx.recv_timeout(Duration::from_millis(50)) {
74 Ok(command) => match command {
75 DownloadCommand::Start {
76 url,
77 output_path,
78 is_advanced,
79 verify_iso,
80 expected_sha256,
81 } => {
82 cancel_token.store(false, Ordering::SeqCst);
83 let _ = status_tx.send(WorkerToGuiMessage::StatusUpdate(format!(
84 "Initializing: {}",
85 url
86 )));
87
88 let optimizer = Optimizer::from_config(config.optimization.clone());
89 let proxy = config.proxy.clone();
90 let cancel_token_clone = cancel_token.clone();
91 let status_tx_clone = status_tx.clone();
92
93 download_handle = Some(thread::spawn(move || {
94 let result = if url.starts_with("magnet:?") {
95 let callbacks = TorrentCallbacks {
96 status: Some(Arc::new({
97 let status_tx = status_tx_clone.clone();
98 move |msg| {
99 status_tx.send(WorkerToGuiMessage::StatusUpdate(msg)).ok();
100 }
101 })),
102 progress: Some(Arc::new({
103 let status_tx = status_tx_clone.clone();
104 move |p| {
105 status_tx.send(WorkerToGuiMessage::Progress(p)).ok();
106 }
107 })),
108 };
109
110 download_magnet(&url, &output_path, true, proxy, optimizer, callbacks)
111 } else if is_advanced {
112 AdvancedDownloader::new(
113 url.clone(),
114 output_path.clone(),
115 true,
116 proxy,
117 optimizer,
118 )
119 .and_then(|mut downloader| {
120 downloader.set_cancel_token(cancel_token_clone.clone());
121 if let Some(expected_sha256) = expected_sha256.clone() {
122 downloader.set_expected_sha256(expected_sha256);
123 }
124
125 let status_tx_cb = status_tx_clone.clone();
126 downloader.set_progress_callback(move |p| {
127 status_tx_cb.send(WorkerToGuiMessage::Progress(p)).ok();
128 });
129
130 let status_tx_cb = status_tx_clone.clone();
131 downloader.set_status_callback(move |msg| {
132 status_tx_cb
133 .send(WorkerToGuiMessage::StatusUpdate(msg))
134 .ok();
135 });
136
137 downloader.download()
138 })
139 } else {
140 let options = DownloadOptions {
141 quiet_mode: true,
142 output_path: Some(output_path.clone()),
143 verify_iso,
144 expected_sha256: expected_sha256.clone(),
145 extra_headers: Vec::new(),
146 };
147
148 let status_tx_cb = status_tx_clone.clone();
149 let status_cb = move |msg: String| {
150 status_tx_cb
151 .send(WorkerToGuiMessage::StatusUpdate(msg))
152 .ok();
153 };
154
155 simple_download(&url, proxy, optimizer, options, Some(&status_cb))
156 };
157
158 report_download_result(
159 result,
160 output_path,
161 &cancel_token_clone,
162 &status_tx_clone,
163 );
164 }));
165 }
166 DownloadCommand::Cancel => {
167 cancel_token.store(true, Ordering::SeqCst);
168 let _ = status_tx.send(WorkerToGuiMessage::StatusUpdate(
169 "Cancelling download...".into(),
170 ));
171 }
172 },
173 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
174 if let Some(handle) = download_handle.take() {
175 if handle.is_finished() {
176 let _ = handle.join();
177 } else {
178 download_handle = Some(handle);
179 }
180 }
181 }
182 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
183 }
184 }
185}
186
187fn report_download_result(
188 result: Result<(), Box<dyn Error + Send + Sync>>,
189 output_path: String,
190 cancel_token: &AtomicBool,
191 status_tx: &MpscSender<WorkerToGuiMessage>,
192) {
193 if cancel_token.load(Ordering::SeqCst) {
194 let _ = status_tx.send(WorkerToGuiMessage::StatusUpdate(
195 "Download cancelled".into(),
196 ));
197 return;
198 }
199
200 match result {
201 Ok(_) => {
202 let _ = status_tx.send(WorkerToGuiMessage::Completed(output_path));
203 }
204 Err(e) => {
205 let err_msg = e.to_string();
206 if err_msg.contains("cancelled") {
207 let _ = status_tx.send(WorkerToGuiMessage::StatusUpdate(
208 "Download cancelled".into(),
209 ));
210 } else {
211 let _ = status_tx.send(WorkerToGuiMessage::Error(err_msg));
212 }
213 }
214 }
215}