1use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
8pub struct ManagedDownload {
9 pub id: usize,
10 pub url: String,
11 pub dest: PathBuf,
12 pub mod_name: Option<String>,
13 pub state: DownloadState,
14}
15
16#[derive(Debug, Clone)]
18pub enum DownloadState {
19 Queued,
20 Active { progress: f64 },
21 Paused { bytes: u64 },
22 Complete { path: PathBuf },
23 Failed { error: String },
24}
25
26pub struct DownloadManager {
31 downloads: Vec<ManagedDownload>,
32 max_concurrent: usize,
33 next_id: usize,
34}
35
36impl DownloadManager {
37 #[must_use]
39 pub fn new(max_concurrent: usize) -> Self {
40 Self {
41 downloads: Vec::new(),
42 max_concurrent,
43 next_id: 0,
44 }
45 }
46
47 pub fn enqueue(&mut self, url: String, dest: PathBuf, mod_name: Option<String>) -> usize {
49 let id = self.next_id;
50 self.next_id += 1;
51 self.downloads.push(ManagedDownload {
52 id,
53 url,
54 dest,
55 mod_name,
56 state: DownloadState::Queued,
57 });
58 id
59 }
60
61 pub fn pause(&mut self, id: usize, bytes_so_far: u64) {
63 if let Some(dl) = self.get_mut(id)
64 && matches!(dl.state, DownloadState::Active { .. })
65 {
66 dl.state = DownloadState::Paused {
67 bytes: bytes_so_far,
68 };
69 }
70 }
71
72 pub fn resume(&mut self, id: usize) {
74 if let Some(dl) = self.get_mut(id)
75 && matches!(dl.state, DownloadState::Paused { .. })
76 {
77 dl.state = DownloadState::Queued;
78 }
79 }
80
81 pub fn cancel(&mut self, id: usize) {
83 self.downloads.retain(|dl| dl.id != id);
84 }
85
86 #[must_use]
88 pub fn active_count(&self) -> usize {
89 self.downloads
90 .iter()
91 .filter(|dl| matches!(dl.state, DownloadState::Active { .. }))
92 .count()
93 }
94
95 #[must_use]
97 pub fn max_concurrent(&self) -> usize {
98 self.max_concurrent
99 }
100
101 #[must_use]
103 pub fn all(&self) -> &[ManagedDownload] {
104 &self.downloads
105 }
106
107 pub fn get_mut(&mut self, id: usize) -> Option<&mut ManagedDownload> {
109 self.downloads.iter_mut().find(|dl| dl.id == id)
110 }
111
112 #[must_use]
114 pub fn get(&self, id: usize) -> Option<&ManagedDownload> {
115 self.downloads.iter().find(|dl| dl.id == id)
116 }
117
118 #[must_use]
120 pub fn can_start_more(&self) -> bool {
121 self.active_count() < self.max_concurrent
122 }
123
124 #[must_use]
127 pub fn next_queued(&self) -> Vec<usize> {
128 let budget = self.max_concurrent.saturating_sub(self.active_count());
129 self.downloads
130 .iter()
131 .filter(|dl| matches!(dl.state, DownloadState::Queued))
132 .take(budget)
133 .map(|dl| dl.id)
134 .collect()
135 }
136}