1use parking_lot::RwLock;
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10use thiserror::Error;
11use tracing::{debug, info};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum BackgroundState {
16 Active,
18 Paused,
20 Pausing,
22 Resuming,
24}
25
26#[derive(Debug, Clone)]
28pub struct BackgroundModeConfig {
29 pub pause_dht_queries: bool,
31 pub pause_provider_announcements: bool,
33 pub close_idle_connections: bool,
35 pub idle_connection_threshold: Duration,
37 pub keep_minimal_connections: bool,
39 pub minimal_connection_count: usize,
41 pub reduce_dht_frequency: bool,
43 pub background_dht_interval: Duration,
45}
46
47impl Default for BackgroundModeConfig {
48 fn default() -> Self {
49 Self {
50 pause_dht_queries: false,
51 pause_provider_announcements: true,
52 close_idle_connections: true,
53 idle_connection_threshold: Duration::from_secs(300), keep_minimal_connections: true,
55 minimal_connection_count: 3,
56 reduce_dht_frequency: true,
57 background_dht_interval: Duration::from_secs(300), }
59 }
60}
61
62impl BackgroundModeConfig {
63 pub fn mobile() -> Self {
65 Self {
66 pause_dht_queries: true, pause_provider_announcements: true,
68 close_idle_connections: true,
69 idle_connection_threshold: Duration::from_secs(60), keep_minimal_connections: true,
71 minimal_connection_count: 2, reduce_dht_frequency: true,
73 background_dht_interval: Duration::from_secs(600), }
75 }
76
77 pub fn server() -> Self {
79 Self {
80 pause_dht_queries: false,
81 pause_provider_announcements: false,
82 close_idle_connections: false,
83 idle_connection_threshold: Duration::from_secs(3600), keep_minimal_connections: false,
85 minimal_connection_count: 0,
86 reduce_dht_frequency: false,
87 background_dht_interval: Duration::from_secs(60),
88 }
89 }
90
91 pub fn balanced() -> Self {
93 Self::default()
94 }
95}
96
97pub struct BackgroundModeManager {
99 config: BackgroundModeConfig,
100 state: Arc<RwLock<BackgroundState>>,
101 stats: Arc<RwLock<BackgroundModeStats>>,
102 last_transition: Arc<RwLock<Option<Instant>>>,
104 time_in_active: Arc<RwLock<Duration>>,
106 time_in_paused: Arc<RwLock<Duration>>,
107}
108
109#[derive(Debug, Clone, Default)]
111pub struct BackgroundModeStats {
112 pub pause_count: usize,
114 pub resume_count: usize,
116 pub total_background_time: Duration,
118 pub total_foreground_time: Duration,
120 pub connections_closed_on_pause: usize,
122 pub dht_queries_skipped: usize,
124}
125
126#[derive(Debug, Error)]
128pub enum BackgroundModeError {
129 #[error("Invalid state transition from {from:?} to {to:?}")]
130 InvalidStateTransition {
131 from: BackgroundState,
132 to: BackgroundState,
133 },
134
135 #[error("Operation not allowed in current state: {0:?}")]
136 OperationNotAllowed(BackgroundState),
137}
138
139impl BackgroundModeManager {
140 pub fn new(config: BackgroundModeConfig) -> Self {
142 Self {
143 config,
144 state: Arc::new(RwLock::new(BackgroundState::Active)),
145 stats: Arc::new(RwLock::new(BackgroundModeStats::default())),
146 last_transition: Arc::new(RwLock::new(Some(Instant::now()))),
147 time_in_active: Arc::new(RwLock::new(Duration::ZERO)),
148 time_in_paused: Arc::new(RwLock::new(Duration::ZERO)),
149 }
150 }
151
152 pub fn state(&self) -> BackgroundState {
154 *self.state.read()
155 }
156
157 pub fn is_paused(&self) -> bool {
159 matches!(self.state(), BackgroundState::Paused)
160 }
161
162 pub fn is_active(&self) -> bool {
164 matches!(self.state(), BackgroundState::Active)
165 }
166
167 pub fn pause(&self) -> Result<(), BackgroundModeError> {
169 let current_state = *self.state.read();
170
171 match current_state {
172 BackgroundState::Active => {
173 info!("Pausing network for background mode");
174 *self.state.write() = BackgroundState::Pausing;
175
176 self.update_time_tracking(current_state);
178
179 self.perform_pause_operations();
181
182 *self.state.write() = BackgroundState::Paused;
183 *self.last_transition.write() = Some(Instant::now());
184
185 let mut stats = self.stats.write();
186 stats.pause_count += 1;
187
188 debug!("Network paused successfully");
189 Ok(())
190 }
191 BackgroundState::Paused => {
192 Ok(())
194 }
195 state => Err(BackgroundModeError::InvalidStateTransition {
196 from: state,
197 to: BackgroundState::Paused,
198 }),
199 }
200 }
201
202 pub fn resume(&self) -> Result<(), BackgroundModeError> {
204 let current_state = *self.state.read();
205
206 match current_state {
207 BackgroundState::Paused => {
208 info!("Resuming network from background mode");
209 *self.state.write() = BackgroundState::Resuming;
210
211 self.update_time_tracking(current_state);
213
214 self.perform_resume_operations();
216
217 *self.state.write() = BackgroundState::Active;
218 *self.last_transition.write() = Some(Instant::now());
219
220 let mut stats = self.stats.write();
221 stats.resume_count += 1;
222
223 debug!("Network resumed successfully");
224 Ok(())
225 }
226 BackgroundState::Active => {
227 Ok(())
229 }
230 state => Err(BackgroundModeError::InvalidStateTransition {
231 from: state,
232 to: BackgroundState::Active,
233 }),
234 }
235 }
236
237 fn perform_pause_operations(&self) {
239 debug!(
241 "Background mode config: pause_dht={}, pause_announcements={}, close_idle={}",
242 self.config.pause_dht_queries,
243 self.config.pause_provider_announcements,
244 self.config.close_idle_connections
245 );
246
247 }
250
251 fn perform_resume_operations(&self) {
253 debug!("Resuming network operations");
254
255 }
258
259 fn update_time_tracking(&self, old_state: BackgroundState) {
261 if let Some(last_transition) = *self.last_transition.read() {
262 let elapsed = last_transition.elapsed();
263
264 match old_state {
265 BackgroundState::Active => {
266 *self.time_in_active.write() += elapsed;
267 let mut stats = self.stats.write();
268 stats.total_foreground_time += elapsed;
269 }
270 BackgroundState::Paused => {
271 *self.time_in_paused.write() += elapsed;
272 let mut stats = self.stats.write();
273 stats.total_background_time += elapsed;
274 }
275 _ => {}
276 }
277 }
278 }
279
280 pub fn should_allow_dht_query(&self) -> bool {
282 match self.state() {
283 BackgroundState::Active | BackgroundState::Resuming => true,
284 BackgroundState::Paused | BackgroundState::Pausing => !self.config.pause_dht_queries,
285 }
286 }
287
288 pub fn should_allow_provider_announcements(&self) -> bool {
290 match self.state() {
291 BackgroundState::Active | BackgroundState::Resuming => true,
292 BackgroundState::Paused | BackgroundState::Pausing => {
293 !self.config.pause_provider_announcements
294 }
295 }
296 }
297
298 pub fn config(&self) -> &BackgroundModeConfig {
300 &self.config
301 }
302
303 pub fn stats(&self) -> BackgroundModeStats {
305 let current_state = *self.state.read();
307 self.update_time_tracking(current_state);
308
309 self.stats.read().clone()
310 }
311
312 pub fn reset_stats(&self) {
314 *self.stats.write() = BackgroundModeStats::default();
315 *self.time_in_active.write() = Duration::ZERO;
316 *self.time_in_paused.write() = Duration::ZERO;
317 *self.last_transition.write() = Some(Instant::now());
318 }
319
320 pub fn record_dht_query_skipped(&self) {
322 self.stats.write().dht_queries_skipped += 1;
323 }
324
325 pub fn record_connections_closed(&self, count: usize) {
327 self.stats.write().connections_closed_on_pause += count;
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_background_mode_creation() {
337 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
338 assert_eq!(manager.state(), BackgroundState::Active);
339 assert!(manager.is_active());
340 assert!(!manager.is_paused());
341 }
342
343 #[test]
344 fn test_pause_resume() {
345 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
346
347 assert!(manager.pause().is_ok());
349 assert_eq!(manager.state(), BackgroundState::Paused);
350 assert!(manager.is_paused());
351 assert!(!manager.is_active());
352
353 assert!(manager.resume().is_ok());
355 assert_eq!(manager.state(), BackgroundState::Active);
356 assert!(manager.is_active());
357 assert!(!manager.is_paused());
358 }
359
360 #[test]
361 fn test_pause_when_already_paused() {
362 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
363
364 assert!(manager.pause().is_ok());
365 assert!(manager.pause().is_ok()); assert_eq!(manager.state(), BackgroundState::Paused);
367 }
368
369 #[test]
370 fn test_resume_when_already_active() {
371 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
372
373 assert!(manager.resume().is_ok()); assert_eq!(manager.state(), BackgroundState::Active);
375 }
376
377 #[test]
378 fn test_dht_query_allowed_in_active_state() {
379 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
380 assert!(manager.should_allow_dht_query());
381 }
382
383 #[test]
384 fn test_dht_query_behavior_in_paused_state() {
385 let config = BackgroundModeConfig {
386 pause_dht_queries: true,
387 ..Default::default()
388 };
389 let manager = BackgroundModeManager::new(config);
390
391 manager
392 .pause()
393 .expect("test: pause should succeed from Active state");
394 assert!(!manager.should_allow_dht_query());
395 }
396
397 #[test]
398 fn test_dht_query_allowed_when_not_paused_in_background() {
399 let config = BackgroundModeConfig {
400 pause_dht_queries: false,
401 ..Default::default()
402 };
403 let manager = BackgroundModeManager::new(config);
404
405 manager
406 .pause()
407 .expect("test: pause should succeed from Active state");
408 assert!(manager.should_allow_dht_query());
409 }
410
411 #[test]
412 fn test_provider_announcements_in_active_state() {
413 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
414 assert!(manager.should_allow_provider_announcements());
415 }
416
417 #[test]
418 fn test_provider_announcements_in_paused_state() {
419 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
420 manager
421 .pause()
422 .expect("test: pause should succeed from Active state");
423 assert!(!manager.should_allow_provider_announcements());
425 }
426
427 #[test]
428 fn test_statistics_tracking() {
429 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
430
431 manager
432 .pause()
433 .expect("test: first pause should succeed from Active state");
434 manager
435 .resume()
436 .expect("test: resume should succeed from Paused state");
437 manager
438 .pause()
439 .expect("test: second pause should succeed from Active state");
440
441 let stats = manager.stats();
442 assert_eq!(stats.pause_count, 2);
443 assert_eq!(stats.resume_count, 1);
444 }
445
446 #[test]
447 fn test_mobile_config() {
448 let config = BackgroundModeConfig::mobile();
449 assert!(config.pause_dht_queries);
450 assert!(config.pause_provider_announcements);
451 assert!(config.close_idle_connections);
452 assert_eq!(config.minimal_connection_count, 2);
453 }
454
455 #[test]
456 fn test_server_config() {
457 let config = BackgroundModeConfig::server();
458 assert!(!config.pause_dht_queries);
459 assert!(!config.pause_provider_announcements);
460 assert!(!config.close_idle_connections);
461 }
462
463 #[test]
464 fn test_record_dht_query_skipped() {
465 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
466 manager.record_dht_query_skipped();
467 manager.record_dht_query_skipped();
468
469 let stats = manager.stats();
470 assert_eq!(stats.dht_queries_skipped, 2);
471 }
472
473 #[test]
474 fn test_record_connections_closed() {
475 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
476 manager.record_connections_closed(5);
477
478 let stats = manager.stats();
479 assert_eq!(stats.connections_closed_on_pause, 5);
480 }
481
482 #[test]
483 fn test_reset_stats() {
484 let manager = BackgroundModeManager::new(BackgroundModeConfig::default());
485
486 manager
487 .pause()
488 .expect("test: pause should succeed from Active state");
489 manager
490 .resume()
491 .expect("test: resume should succeed from Paused state");
492 manager.record_dht_query_skipped();
493
494 manager.reset_stats();
495
496 let stats = manager.stats();
497 assert_eq!(stats.pause_count, 0);
498 assert_eq!(stats.resume_count, 0);
499 assert_eq!(stats.dht_queries_skipped, 0);
500 }
501}