Skip to main content

net_kit/
net.rs

1use std::sync::RwLock;
2
3use tokio::runtime::Handle;
4use vibe_ready::VibeLogListener;
5
6use crate::inner::inner_net::InnerNet;
7use crate::net_error::NetError;
8use crate::network_status::NetworkStatus;
9
10pub type NetworkStatusListener = Box<dyn Fn(NetworkStatus) + Send + Sync + 'static>;
11
12/// Listener for the engine's internal diagnostic log records.
13///
14/// This is the log callback accepted by [`Net::set_log_listener`]; it is a
15/// re-export of the underlying engine listener type.
16pub type LogListener = VibeLogListener;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct NetworkStatusListenerHandle(u64);
20
21impl NetworkStatusListenerHandle {
22    pub(crate) fn from_raw(id: u64) -> Self {
23        Self(id)
24    }
25}
26
27/// The single public API facade exposed by `net-kit`.
28///
29/// `Net` itself is only responsible for lifecycle management and forwarding;
30/// all of the actual runtime and network-monitoring logic lives in the
31/// internal [`InnerNet`], ensuring isolation.
32pub struct Net {
33    inner: RwLock<Option<InnerNet>>,
34}
35
36impl Default for Net {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl Net {
43    /// Create an instance only; does not start network monitoring.
44    pub fn new() -> Net {
45        Net {
46            inner: RwLock::new(None),
47        }
48    }
49
50    /// Start network monitoring. Redundant calls are ignored; `start` may be
51    /// called again after `shutdown`.
52    ///
53    /// Internally creates and owns a runtime engine owned by `net-kit`.
54    pub async fn start(&self) -> Result<(), NetError> {
55        self.start_inner(InnerNet::new).await
56    }
57
58    /// Start network monitoring. Redundant calls are ignored; `start` may be
59    /// called again after `shutdown`.
60    ///
61    /// Uses a Tokio runtime supplied by the developer. The caller is
62    /// responsible for keeping that runtime alive; `shutdown` does not close
63    /// it.
64    pub async fn start_with_tokio_rt(&self, runtime_handle: Handle) -> Result<(), NetError> {
65        self.start_inner(move || InnerNet::new_with_tokio_rt(runtime_handle))
66            .await
67    }
68
69    /// Install the internal engine, start monitoring, then wait for the initial
70    /// network state to become ready.
71    ///
72    /// `factory` is only invoked when a new engine actually needs to be
73    /// created — i.e. when not already started. This avoids needlessly creating
74    /// an engine (and its owned runtime) on a redundant `start` only to drop it
75    /// immediately; dropping an owned runtime inside an async context panics, so
76    /// that path must never be taken.
77    async fn start_inner<F>(&self, factory: F) -> Result<(), NetError>
78    where
79        F: FnOnce() -> Result<InnerNet, NetError>,
80    {
81        // Decide whether a new engine is needed while holding the write lock;
82        // only invoke `factory` when not started, so no surplus engine is ever
83        // created that would have to be dropped in an async context. Release the
84        // lock as soon as the initial-state receiver is obtained, to avoid
85        // holding it across an await.
86        let initial_state = {
87            let mut guard = self.inner.write().map_err(NetError::from_poison)?;
88            if guard.is_none() {
89                let candidate = factory()?;
90                *guard = Some(candidate);
91            }
92            // Whether freshly installed or already present, trigger/reuse the
93            // monitor on the current inner instance.
94            match guard.as_ref() {
95                Some(inner) => Some(inner.begin()?),
96                None => None,
97            }
98        };
99
100        if let Some(initial_state) = initial_state {
101            InnerNet::wait_for_initial_state(initial_state).await;
102        }
103
104        Ok(())
105    }
106
107    /// Stop network monitoring and destroy all resources created by `start`.
108    /// Redundant calls are ignored.
109    pub fn shutdown(&self) -> Result<(), NetError> {
110        let inner = self.inner.write().map_err(NetError::from_poison)?.take();
111        if let Some(inner) = inner {
112            inner.shutdown()?;
113        }
114        Ok(())
115    }
116
117    /// Query whether the network is currently available.
118    ///
119    /// Returns [`NetworkStatus::Unavailable`] when not started; returns
120    /// [`NetError::Lock`] if an internal lock is poisoned, leaving recovery to
121    /// the developer.
122    pub fn local_network_reachability(&self) -> Result<NetworkStatus, NetError> {
123        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
124            Some(inner) => inner.local_network_reachability(),
125            None => Ok(NetworkStatus::default()),
126        }
127    }
128
129    /// Register a network notification listener; multiple may be registered.
130    ///
131    /// Returns `Ok(None)` when not started; `Ok(Some(handle))` on success after
132    /// start; returns [`NetError::Lock`] if an internal lock is poisoned.
133    pub fn register(
134        &self,
135        listener: NetworkStatusListener,
136    ) -> Result<Option<NetworkStatusListenerHandle>, NetError> {
137        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
138            Some(inner) => inner.register(listener).map(Some),
139            None => Ok(None),
140        }
141    }
142
143    /// Unregister a network notification listener by handle.
144    ///
145    /// Returns `Ok(false)` when not started; `Ok(true)` when a listener was
146    /// found and removed; returns [`NetError::Lock`] if an internal lock is
147    /// poisoned.
148    pub fn unregister(&self, handle: NetworkStatusListenerHandle) -> Result<bool, NetError> {
149        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
150            Some(inner) => inner.unregister(handle),
151            None => Ok(false),
152        }
153    }
154
155    /// Clear all registered network listeners. Returns [`NetError::NotStarted`]
156    /// when not started.
157    pub fn clear_all_listener(&self) -> Result<(), NetError> {
158        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
159            Some(inner) => inner.clear_all_listener(),
160            None => Err(NetError::NotStarted),
161        }
162    }
163
164    /// Query whether network monitoring is currently started.
165    ///
166    /// Returns `true` while an engine is installed (i.e. after `start` and
167    /// before `shutdown`); returns `false` otherwise. A poisoned internal lock
168    /// is treated as not started, so this method never panics and never returns
169    /// an error.
170    pub fn is_started(&self) -> bool {
171        match self.inner.read() {
172            Ok(guard) => guard.is_some(),
173            Err(_) => false,
174        }
175    }
176
177    /// Query whether network monitoring is currently shut down (i.e. not
178    /// started).
179    ///
180    /// This is the logical inverse of [`Net::is_started`]: it returns `true`
181    /// before the first `start` and after every `shutdown`. A poisoned internal
182    /// lock is treated as not started, so this method never panics and never
183    /// returns an error.
184    pub fn is_shutdown(&self) -> bool {
185        !self.is_started()
186    }
187
188    /// Query the name of the network the host is currently connected to.
189    ///
190    /// Returns `Ok(None)` when not started; `Ok(Some(name))` when started and a
191    /// connected network name could be resolved; `Ok(None)` when started but no
192    /// name is available (or the platform is unsupported); returns
193    /// [`NetError::Lock`] if an internal lock is poisoned.
194    pub fn get_current_network_name(&self) -> Result<Option<String>, NetError> {
195        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
196            Some(inner) => inner.get_current_network_name(),
197            None => Ok(None),
198        }
199    }
200
201    /// Install (or clear, with `None`) a listener for the engine's internal
202    /// diagnostic log records.
203    ///
204    /// Returns [`NetError::NotStarted`] when not started; returns
205    /// [`NetError::Lock`] if an internal lock is poisoned.
206    pub fn set_log_listener(&self, listener: Option<LogListener>) -> Result<(), NetError> {
207        match self.inner.read().map_err(NetError::from_poison)?.as_ref() {
208            Some(inner) => {
209                inner.set_log_listener(listener);
210                Ok(())
211            }
212            None => Err(NetError::NotStarted),
213        }
214    }
215}