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}