1use flume::{Receiver, Sender};
2use smallvec::SmallVec;
3use std::path::PathBuf;
4use stormdl_core::{DownloadId, DownloadOptions, DownloadState, SegmentState};
5use url::Url;
6
7#[derive(Debug, Clone)]
8pub enum OrchestratorCommand {
9 AddDownload { url: Url, options: DownloadOptions },
10 PauseDownload(DownloadId),
11 ResumeDownload(DownloadId),
12 CancelDownload(DownloadId),
13 SetBandwidthLimit(Option<u64>),
14}
15
16#[derive(Debug, Clone)]
17pub enum DownloadEvent {
18 DownloadAdded {
19 id: DownloadId,
20 url: Url,
21 filename: String,
22 total_size: Option<u64>,
23 },
24 ProgressUpdate {
25 id: DownloadId,
26 downloaded: u64,
27 segments: Vec<SegmentState>,
28 },
29 SpeedUpdate {
30 id: DownloadId,
31 speed: f64,
32 },
33 StateChange {
34 id: DownloadId,
35 state: DownloadState,
36 },
37 SegmentRebalanced {
38 id: DownloadId,
39 old_count: usize,
40 new_count: usize,
41 },
42 Error {
43 id: DownloadId,
44 error: String,
45 },
46 Complete {
47 id: DownloadId,
48 path: PathBuf,
49 hash: String,
50 },
51}
52
53#[derive(Debug, Clone)]
54pub struct Download {
55 pub id: DownloadId,
56 pub url: Url,
57 pub filename: String,
58 pub total_bytes: Option<u64>,
59 pub downloaded_bytes: u64,
60 pub state: DownloadState,
61 pub segments: Vec<SegmentState>,
62 pub speed_samples: SmallVec<[f64; 30]>,
63 pub error: Option<String>,
64}
65
66impl Download {
67 pub fn new(id: DownloadId, url: Url, filename: String, total_bytes: Option<u64>) -> Self {
68 Self {
69 id,
70 url,
71 filename,
72 total_bytes,
73 downloaded_bytes: 0,
74 state: DownloadState::Pending,
75 segments: Vec::new(),
76 speed_samples: SmallVec::new(),
77 error: None,
78 }
79 }
80
81 pub fn progress(&self) -> f64 {
82 match self.total_bytes {
83 Some(total) if total > 0 => self.downloaded_bytes as f64 / total as f64,
84 _ => 0.0,
85 }
86 }
87
88 pub fn current_speed(&self) -> f64 {
89 self.speed_samples.last().copied().unwrap_or(0.0)
90 }
91
92 pub fn average_speed(&self) -> f64 {
93 if self.speed_samples.is_empty() {
94 return 0.0;
95 }
96 self.speed_samples.iter().sum::<f64>() / self.speed_samples.len() as f64
97 }
98
99 pub fn add_speed_sample(&mut self, speed: f64) {
100 if self.speed_samples.len() >= 30 {
101 self.speed_samples.remove(0);
102 }
103 self.speed_samples.push(speed);
104 }
105}
106
107pub struct AppState {
108 pub downloads: Vec<Download>,
109 pub selected_download_id: Option<DownloadId>,
110 pub command_tx: Sender<OrchestratorCommand>,
111 pub event_rx: Receiver<DownloadEvent>,
112 pub settings: Settings,
113}
114
115#[derive(Debug, Clone)]
116pub struct Settings {
117 pub download_dir: PathBuf,
118 pub max_concurrent: usize,
119 pub max_segments: usize,
120 pub bandwidth_limit: Option<u64>,
121 pub turbo_mode: bool,
122}
123
124impl Default for Settings {
125 fn default() -> Self {
126 Self {
127 download_dir: dirs::download_dir().unwrap_or_else(|| PathBuf::from(".")),
128 max_concurrent: 3,
129 max_segments: 32,
130 bandwidth_limit: None,
131 turbo_mode: false,
132 }
133 }
134}
135
136impl AppState {
137 pub fn new(command_tx: Sender<OrchestratorCommand>, event_rx: Receiver<DownloadEvent>) -> Self {
138 Self {
139 downloads: Vec::new(),
140 selected_download_id: None,
141 command_tx,
142 event_rx,
143 settings: Settings::default(),
144 }
145 }
146
147 pub fn add_download(
148 &mut self,
149 id: DownloadId,
150 url: Url,
151 filename: String,
152 total_bytes: Option<u64>,
153 ) {
154 if let Some(existing) = self.get_download_mut(id) {
155 existing.filename = filename;
156 if total_bytes.is_some() {
157 existing.total_bytes = total_bytes;
158 }
159 } else {
160 let download = Download::new(id, url, filename, total_bytes);
161 self.downloads.push(download);
162 }
163 }
164
165 pub fn get_download(&self, id: DownloadId) -> Option<&Download> {
166 self.downloads.iter().find(|d| d.id == id)
167 }
168
169 pub fn get_download_mut(&mut self, id: DownloadId) -> Option<&mut Download> {
170 self.downloads.iter_mut().find(|d| d.id == id)
171 }
172
173 pub fn remove_download(&mut self, id: DownloadId) {
174 self.downloads.retain(|d| d.id != id);
175 if self.selected_download_id == Some(id) {
176 self.selected_download_id = None;
177 }
178 }
179}