Skip to main content

stormdl_gui/
state.rs

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}