aria2_core/engine/
http_tracker_client.rs1use std::time::{Duration, Instant};
15
16use tracing::{debug, info};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TrackerEvent {
23 Started,
25 Stopped,
27 Completed,
29 None,
31}
32
33impl TrackerEvent {
34 pub fn as_str(&self) -> &'static str {
36 match self {
37 TrackerEvent::Started => "started",
38 TrackerEvent::Stopped => "stopped",
39 TrackerEvent::Completed => "completed",
40 TrackerEvent::None => "",
41 }
42 }
43}
44
45#[derive(Debug)]
50pub struct TrackerState {
51 pub current_event: TrackerEvent,
53 pub last_announce_time: Option<Instant>,
55 pub min_interval_secs: u64,
57 pub interval_secs: u64,
59 pub announce_count: u64,
61 completed_sent: bool,
63 stopped_sent: bool,
65}
66
67impl Default for TrackerState {
68 fn default() -> Self {
69 Self {
70 current_event: TrackerEvent::Started, last_announce_time: None,
72 min_interval_secs: 300, interval_secs: 1800, announce_count: 0,
75 completed_sent: false,
76 stopped_sent: false,
77 }
78 }
79}
80
81impl TrackerState {
82 pub fn new() -> Self {
84 Self::default()
85 }
86
87 pub fn should_announce(&self) -> bool {
92 match self.last_announce_time {
93 Some(last) => {
94 let elapsed = last.elapsed().as_secs();
95 elapsed >= self.min_interval_secs
96 }
97 None => true, }
99 }
100
101 pub fn secs_until_next_announce(&self) -> u64 {
103 match self.last_announce_time {
104 Some(last) => {
105 let elapsed = last.elapsed().as_secs();
106 if elapsed >= self.min_interval_secs {
107 0
108 } else {
109 self.min_interval_secs.saturating_sub(elapsed)
110 }
111 }
112 None => 0,
113 }
114 }
115
116 pub fn record_announce(&mut self, event: TrackerEvent) {
123 self.current_event = match event {
124 TrackerEvent::Started => {
125 debug!("TrackerState: Recorded Started event");
126 TrackerEvent::None }
128 TrackerEvent::Completed => {
129 self.completed_sent = true;
130 info!("TrackerState: Recorded Completed event");
131 TrackerEvent::None }
133 TrackerEvent::Stopped => {
134 self.stopped_sent = true;
135 info!("TrackerState: Recorded Stopped event (terminal)");
136 TrackerEvent::Stopped }
138 TrackerEvent::None => TrackerEvent::None,
139 };
140
141 self.last_announce_time = Some(Instant::now());
142 self.announce_count += 1;
143 }
144
145 pub fn update_intervals(&mut self, interval: Option<u64>, min_interval: Option<u64>) {
150 if let Some(iv) = interval {
151 self.interval_secs = iv.max(60);
153 debug!("TrackerState: Updated interval to {}s", self.interval_secs);
154 }
155 if let Some(mi) = min_interval {
156 self.min_interval_secs = mi.max(30);
158 debug!(
159 "TrackerState: Updated min_interval to {}s",
160 self.min_interval_secs
161 );
162 }
163 }
164
165 pub fn mark_completed(&mut self) {
167 if !self.completed_sent {
168 self.current_event = TrackerEvent::Completed;
169 info!("TrackerState: Transitioning to Completed event");
170 }
171 }
172
173 pub fn mark_stopped(&mut self) {
175 if !self.stopped_sent {
176 self.current_event = TrackerEvent::Stopped;
177 info!("TrackerState: Transitioning to Stopped event");
178 }
179 }
180
181 pub fn reset(&mut self) {
183 *self = Self::default();
184 }
185
186 pub fn is_stopped(&self) -> bool {
188 self.stopped_sent
189 }
190}
191
192pub fn is_https_tracker(url: &str) -> bool {
203 url.to_lowercase().starts_with("https://")
204}
205
206pub fn build_tracker_client(timeout_secs: u64) -> Result<reqwest::Client, String> {
214 reqwest::Client::builder()
215 .timeout(Duration::from_secs(timeout_secs))
216 .build()
217 .map_err(|e| format!("Failed to build HTTP client: {}", e))
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_https_tracker_url_detected() {
226 assert!(is_https_tracker("https://tracker.example.com/announce"));
227 assert!(is_https_tracker("HTTPS://TRACKER.EXAMPLE.COM/ANNOUNCE"));
228 assert!(!is_https_tracker("http://tracker.example.com/announce"));
229 assert!(!is_https_tracker("udp://tracker.example.com:1337/announce"));
230 }
231
232 #[test]
233 fn test_tracker_event_as_str() {
234 assert_eq!(TrackerEvent::Started.as_str(), "started");
235 assert_eq!(TrackerEvent::Stopped.as_str(), "stopped");
236 assert_eq!(TrackerEvent::Completed.as_str(), "completed");
237 assert_eq!(TrackerEvent::None.as_str(), "");
238 }
239
240 #[test]
241 fn test_tracker_state_default() {
242 let state = TrackerState::new();
243 assert_eq!(state.current_event, TrackerEvent::Started);
244 assert!(state.last_announce_time.is_none());
245 assert_eq!(state.announce_count, 0);
246 assert!(!state.completed_sent);
247 assert!(!state.stopped_sent);
248 }
249
250 #[test]
251 fn test_tracker_state_should_announce_initially() {
252 let state = TrackerState::new();
253 assert!(
254 state.should_announce(),
255 "Should be able to announce immediately on fresh state"
256 );
257 }
258
259 #[test]
260 fn test_tracker_state_sequence_started_to_completed() {
261 let mut state = TrackerState::new();
262
263 assert_eq!(state.current_event, TrackerEvent::Started);
265
266 state.record_announce(TrackerEvent::Started);
268 assert_eq!(state.announce_count, 1);
269 assert_eq!(state.current_event, TrackerEvent::None); state.mark_completed();
273 assert_eq!(state.current_event, TrackerEvent::Completed);
274 assert!(!state.completed_sent); state.record_announce(TrackerEvent::Completed);
278 assert_eq!(state.announce_count, 2);
279 assert!(state.completed_sent);
280 assert_eq!(state.current_event, TrackerEvent::None); }
282
283 #[test]
284 fn test_tracker_event_stopped_on_cancel() {
285 let mut state = TrackerState::new();
286
287 state.record_announce(TrackerEvent::Started);
289
290 state.mark_stopped();
292 assert_eq!(state.current_event, TrackerEvent::Stopped);
293 assert!(!state.stopped_sent); state.record_announce(TrackerEvent::Stopped);
297 assert!(state.stopped_sent);
298 assert_eq!(state.current_event, TrackerEvent::Stopped);
300 }
301
302 #[test]
303 fn test_min_interval_respected() {
304 let mut state = TrackerState::new();
305 state.min_interval_secs = 10;
306
307 assert!(state.should_announce());
309 state.record_announce(TrackerEvent::Started);
310
311 assert!(
313 !state.should_announce(),
314 "Should not re-announce before min_interval"
315 );
316
317 let wait = state.secs_until_next_announce();
319 assert!(wait > 0, "Should need to wait at least some seconds");
320 assert!(wait <= 10, "Should not wait more than min_interval");
321 }
322
323 #[test]
324 fn test_update_intervals() {
325 let mut state = TrackerState::new();
326 assert_eq!(state.interval_secs, 1800); assert_eq!(state.min_interval_secs, 300); state.update_intervals(Some(900), Some(300));
330 assert_eq!(state.interval_secs, 900);
331 assert_eq!(state.min_interval_secs, 300);
332
333 state.update_intervals(Some(10), Some(5));
335 assert_eq!(state.interval_secs, 60); assert_eq!(state.min_interval_secs, 30); }
338
339 #[test]
340 fn test_build_tracker_client_succeeds() {
341 let client = build_tracker_client(30);
342 assert!(client.is_ok(), "Should build client with valid timeout");
343 }
344
345 #[test]
346 fn test_is_stopped_terminal_state() {
347 let mut state = TrackerState::new();
348 assert!(!state.is_stopped());
349
350 state.mark_stopped();
351 state.record_announce(TrackerEvent::Stopped);
352 assert!(state.is_stopped());
353 }
354
355 #[test]
356 fn test_reset_clears_state() {
357 let mut state = TrackerState::new();
358 state.record_announce(TrackerEvent::Started);
359 state.mark_completed();
360 state.record_announce(TrackerEvent::Completed);
361
362 state.reset();
363 assert_eq!(state.current_event, TrackerEvent::Started);
364 assert_eq!(state.announce_count, 0);
365 assert!(!state.completed_sent);
366 assert!(!state.stopped_sent);
367 }
368
369 #[test]
370 fn test_multiple_completes_only_one_event() {
371 let mut state = TrackerState::new();
372 state.record_announce(TrackerEvent::Started);
373
374 state.mark_completed();
376 assert_eq!(state.current_event, TrackerEvent::Completed);
377 state.record_announce(TrackerEvent::Completed);
378
379 state.mark_completed();
381 assert_eq!(
382 state.current_event,
383 TrackerEvent::None,
384 "Second mark_completed should not change state"
385 );
386 }
387}