use std::sync::{Arc, OnceLock};
use crate::registry::{RecoveryGate, ServiceRegistry};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct NetworkStatus {
pub online: bool,
pub metered: bool,
}
impl Default for NetworkStatus {
fn default() -> Self {
Self {
online: true,
metered: false,
}
}
}
pub trait NetworkMonitor: Send + Sync {
fn status(&self) -> NetworkStatus;
fn is_alive(&self) -> bool;
fn reconnect(&self);
}
pub type NetworkMonitorRef = Arc<dyn NetworkMonitor>;
struct DefaultNetworkMonitor;
impl NetworkMonitor for DefaultNetworkMonitor {
fn status(&self) -> NetworkStatus {
NetworkStatus::default()
}
fn is_alive(&self) -> bool {
true
}
fn reconnect(&self) {}
}
static PLATFORM_NETWORK_MONITOR: ServiceRegistry<dyn NetworkMonitor> = ServiceRegistry::new();
static DEFAULT_NETWORK_MONITOR: OnceLock<NetworkMonitorRef> = OnceLock::new();
static NETWORK_MONITOR_HANDLE: OnceLock<NetworkMonitorRef> = OnceLock::new();
static NETWORK_RECOVERY: RecoveryGate = RecoveryGate::new();
struct PlatformNetworkMonitor;
fn registered_network_monitor() -> NetworkMonitorRef {
PLATFORM_NETWORK_MONITOR
.get_or_warn("network monitor")
.unwrap_or_else(|| {
DEFAULT_NETWORK_MONITOR
.get_or_init(|| Arc::new(DefaultNetworkMonitor))
.clone()
})
}
fn active_network_monitor() -> NetworkMonitorRef {
let monitor = registered_network_monitor();
if monitor.is_alive() {
NETWORK_RECOVERY.succeeded();
} else if NETWORK_RECOVERY.try_start() {
monitor.reconnect();
}
monitor
}
impl NetworkMonitor for PlatformNetworkMonitor {
fn status(&self) -> NetworkStatus {
active_network_monitor().status()
}
fn is_alive(&self) -> bool {
registered_network_monitor().is_alive()
}
fn reconnect(&self) {
registered_network_monitor().reconnect();
}
}
pub fn set_platform_network_monitor(monitor: NetworkMonitorRef) {
PLATFORM_NETWORK_MONITOR.set(monitor);
NETWORK_RECOVERY.succeeded();
}
pub fn clear_platform_network_monitor() {
PLATFORM_NETWORK_MONITOR.clear();
}
pub fn network_monitor() -> NetworkMonitorRef {
NETWORK_MONITOR_HANDLE
.get_or_init(|| Arc::new(PlatformNetworkMonitor))
.clone()
}
pub fn network_status() -> NetworkStatus {
let monitor = network_monitor();
if monitor.is_alive() {
NETWORK_RECOVERY.succeeded();
} else if NETWORK_RECOVERY.try_start() {
monitor.reconnect();
}
monitor.status()
}
#[cfg(test)]
#[path = "tests/network_status_tests.rs"]
mod tests;