Skip to main content

cranpose_services/
network_status.rs

1//! Network status (connectivity + whether the connection is metered). Apps use
2//! it to defer or confirm large transfers on cellular/metered links.
3//!
4//! Each part is an observation or [`Unknown`](Connectivity::Unknown): with no
5//! platform monitor installed via [`set_platform_network_monitor`], or one
6//! that stopped, both parts are unknown. Android observes both through
7//! `ConnectivityManager`; web observes connectivity through `navigator.onLine`
8//! and cannot tell metering. An app that defers large transfers on metered
9//! links starts one on its own only when the status says
10//! [`Unmetered`](Metering::Unmetered).
11
12use std::sync::{Arc, OnceLock};
13
14use crate::registry::{RecoveryGate, ServiceRegistry};
15
16/// Whether the device has a usable network path.
17#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
18pub enum Connectivity {
19    /// A path that can reach the internet.
20    Online,
21    /// No usable path.
22    Offline,
23    /// Nothing observes connectivity: no platform monitor, or one that
24    /// stopped. A request may still succeed.
25    #[default]
26    Unknown,
27}
28
29/// Whether the active network path costs the user by the byte.
30#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
31pub enum Metering {
32    /// Cellular, a hotspot, or a network the user marked as metered.
33    Metered,
34    /// Wi-Fi or Ethernet the user did not mark as metered.
35    Unmetered,
36    /// The platform cannot tell, nothing observes the network, or there is
37    /// no active path to meter.
38    #[default]
39    Unknown,
40}
41
42/// A snapshot of the current network state. The default knows nothing.
43#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
44pub struct NetworkStatus {
45    pub connectivity: Connectivity,
46    pub metering: Metering,
47}
48
49/// Reports the current network status.
50pub trait NetworkMonitor: Send + Sync {
51    fn status(&self) -> NetworkStatus;
52    fn is_alive(&self) -> bool;
53    fn reconnect(&self);
54}
55
56pub type NetworkMonitorRef = Arc<dyn NetworkMonitor>;
57
58struct DefaultNetworkMonitor;
59
60impl NetworkMonitor for DefaultNetworkMonitor {
61    fn status(&self) -> NetworkStatus {
62        NetworkStatus::default()
63    }
64
65    fn is_alive(&self) -> bool {
66        true
67    }
68
69    fn reconnect(&self) {}
70}
71
72static PLATFORM_NETWORK_MONITOR: ServiceRegistry<dyn NetworkMonitor> = ServiceRegistry::new();
73static DEFAULT_NETWORK_MONITOR: OnceLock<NetworkMonitorRef> = OnceLock::new();
74static NETWORK_MONITOR_HANDLE: OnceLock<NetworkMonitorRef> = OnceLock::new();
75static NETWORK_RECOVERY: RecoveryGate = RecoveryGate::new();
76
77struct PlatformNetworkMonitor;
78
79fn registered_network_monitor() -> NetworkMonitorRef {
80    PLATFORM_NETWORK_MONITOR
81        .get_or_warn("network monitor")
82        .unwrap_or_else(|| {
83            DEFAULT_NETWORK_MONITOR
84                .get_or_init(|| Arc::new(DefaultNetworkMonitor))
85                .clone()
86        })
87}
88
89fn active_network_monitor() -> NetworkMonitorRef {
90    let monitor = registered_network_monitor();
91    if monitor.is_alive() {
92        NETWORK_RECOVERY.succeeded();
93    } else if NETWORK_RECOVERY.try_start() {
94        monitor.reconnect();
95    }
96    monitor
97}
98
99impl NetworkMonitor for PlatformNetworkMonitor {
100    /// The monitor's observation, or nothing known while it stays stopped.
101    fn status(&self) -> NetworkStatus {
102        let monitor = active_network_monitor();
103        if monitor.is_alive() {
104            monitor.status()
105        } else {
106            NetworkStatus::default()
107        }
108    }
109
110    fn is_alive(&self) -> bool {
111        registered_network_monitor().is_alive()
112    }
113
114    fn reconnect(&self) {
115        registered_network_monitor().reconnect();
116    }
117}
118
119/// Installs a platform network monitor, replacing any previous one.
120pub fn set_platform_network_monitor(monitor: NetworkMonitorRef) {
121    PLATFORM_NETWORK_MONITOR.set(monitor);
122    NETWORK_RECOVERY.succeeded();
123}
124
125/// Removes any registered platform network monitor (tests and teardown).
126pub fn clear_platform_network_monitor() {
127    PLATFORM_NETWORK_MONITOR.clear();
128}
129
130/// The active network monitor: the platform one if installed, else a default
131/// that knows nothing.
132pub fn network_monitor() -> NetworkMonitorRef {
133    NETWORK_MONITOR_HANDLE
134        .get_or_init(|| Arc::new(PlatformNetworkMonitor))
135        .clone()
136}
137
138/// The current network status. A stopped platform monitor gets a reconnect
139/// attempt first.
140pub fn network_status() -> NetworkStatus {
141    network_monitor().status()
142}
143
144#[cfg(test)]
145#[path = "tests/network_status_tests.rs"]
146mod tests;