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