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