Skip to main content

nm_wifi/
backend.rs

1use std::{
2    error::Error,
3    future::Future,
4    io,
5    pin::Pin,
6    sync::mpsc::{self, Receiver, TryRecvError},
7};
8
9use crate::{
10    app::runtime::{
11        RuntimeBackendDriver,
12        RuntimeEvent,
13        RuntimeRequest,
14        ScanSnapshot,
15    },
16    network::ConnectionRequest,
17    wifi::WifiNetwork,
18};
19
20pub type BackendFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
21
22pub trait NetworkBackend {
23    fn connected_ssid(&self) -> Result<Option<String>, Box<dyn Error>>;
24    fn adapter_name(&self) -> Result<Option<String>, Box<dyn Error>>;
25    fn scan_networks(
26        &self,
27    ) -> BackendFuture<'_, Result<Vec<WifiNetwork>, Box<dyn Error>>>;
28    fn connect(
29        &self,
30        request: ConnectionRequest<'_>,
31    ) -> Result<(), Box<dyn Error>>;
32    fn disconnect(&self, network: &WifiNetwork) -> Result<(), Box<dyn Error>>;
33}
34
35fn runtime_channel_closed_error() -> Box<dyn Error> {
36    io::Error::other("runtime backend event channel closed").into()
37}
38
39fn poll_pending_event(
40    pending_event: &mut Option<Receiver<RuntimeEvent>>,
41) -> Result<Option<RuntimeEvent>, Box<dyn Error>> {
42    match pending_event.as_mut() {
43        Some(receiver) => match receiver.try_recv() {
44            Ok(event) => {
45                *pending_event = None;
46                Ok(Some(event))
47            }
48            Err(TryRecvError::Empty) => Ok(None),
49            Err(TryRecvError::Disconnected) => {
50                *pending_event = None;
51                Err(runtime_channel_closed_error())
52            }
53        },
54        None => Ok(None),
55    }
56}
57
58#[cfg(feature = "demo")]
59#[derive(Debug, Default, Clone, Copy)]
60pub struct DemoNetworkBackend;
61
62#[cfg(feature = "demo")]
63impl NetworkBackend for DemoNetworkBackend {
64    fn connected_ssid(&self) -> Result<Option<String>, Box<dyn Error>> {
65        crate::network::demo::get_connected_ssid()
66    }
67
68    fn adapter_name(&self) -> Result<Option<String>, Box<dyn Error>> {
69        crate::network::demo::get_wifi_adapter_name()
70    }
71
72    fn scan_networks(
73        &self,
74    ) -> BackendFuture<'_, Result<Vec<WifiNetwork>, Box<dyn Error>>> {
75        Box::pin(crate::network::demo::scan_wifi_networks())
76    }
77
78    fn connect(
79        &self,
80        request: ConnectionRequest<'_>,
81    ) -> Result<(), Box<dyn Error>> {
82        crate::network::demo::connect_to_network(request)
83    }
84
85    fn disconnect(&self, network: &WifiNetwork) -> Result<(), Box<dyn Error>> {
86        crate::network::demo::disconnect_from_network(network)
87    }
88}
89
90#[cfg(feature = "demo")]
91#[derive(Default)]
92struct DemoRuntimeDriver {
93    pending_event: Option<Receiver<RuntimeEvent>>,
94}
95
96#[cfg(feature = "demo")]
97impl RuntimeBackendDriver for DemoRuntimeDriver {
98    fn begin(&mut self, request: RuntimeRequest) {
99        let (sender, receiver) = mpsc::channel();
100        let event = match request {
101            RuntimeRequest::Scan => RuntimeEvent::Scan(Ok(ScanSnapshot {
102                networks: crate::network::demo::demo_networks(),
103                adapter_name: crate::network::demo::get_wifi_adapter_name()
104                    .ok()
105                    .flatten(),
106            })),
107            RuntimeRequest::Connect {
108                network,
109                passphrase,
110            } => {
111                let result = match passphrase.as_deref() {
112                    Some(passphrase) => {
113                        crate::network::demo::connect_to_network(
114                            ConnectionRequest::Secured {
115                                network: &network,
116                                passphrase,
117                            },
118                        )
119                    }
120                    None => crate::network::demo::connect_to_network(
121                        ConnectionRequest::Open { network: &network },
122                    ),
123                };
124                RuntimeEvent::Connect(result.map_err(|error| error.to_string()))
125            }
126            RuntimeRequest::Disconnect { network } => RuntimeEvent::Disconnect(
127                crate::network::demo::disconnect_from_network(&network)
128                    .map_err(|error| error.to_string()),
129            ),
130        };
131        let _ = sender.send(event);
132        self.pending_event = Some(receiver);
133    }
134
135    fn poll_event(&mut self) -> Result<Option<RuntimeEvent>, Box<dyn Error>> {
136        poll_pending_event(&mut self.pending_event)
137    }
138}
139
140#[cfg(feature = "demo")]
141pub(crate) fn default_runtime_driver() -> Box<dyn RuntimeBackendDriver> {
142    Box::new(DemoRuntimeDriver::default())
143}
144
145#[cfg(not(feature = "demo"))]
146#[derive(Debug, Default, Clone, Copy)]
147pub struct NetworkManagerBackend;
148
149#[cfg(not(feature = "demo"))]
150impl NetworkBackend for NetworkManagerBackend {
151    fn connected_ssid(&self) -> Result<Option<String>, Box<dyn Error>> {
152        crate::network::networkmanager::get_connected_ssid()
153    }
154
155    fn adapter_name(&self) -> Result<Option<String>, Box<dyn Error>> {
156        crate::network::networkmanager::get_wifi_adapter_name()
157    }
158
159    fn scan_networks(
160        &self,
161    ) -> BackendFuture<'_, Result<Vec<WifiNetwork>, Box<dyn Error>>> {
162        Box::pin(crate::network::networkmanager::scan_wifi_networks())
163    }
164
165    fn connect(
166        &self,
167        request: ConnectionRequest<'_>,
168    ) -> Result<(), Box<dyn Error>> {
169        crate::network::networkmanager::connect_to_network(request)
170    }
171
172    fn disconnect(&self, network: &WifiNetwork) -> Result<(), Box<dyn Error>> {
173        crate::network::networkmanager::disconnect_from_network(network)
174    }
175}
176
177#[cfg(not(feature = "demo"))]
178#[derive(Default)]
179struct NetworkManagerRuntimeDriver {
180    pending_event: Option<Receiver<RuntimeEvent>>,
181}
182
183#[cfg(not(feature = "demo"))]
184impl RuntimeBackendDriver for NetworkManagerRuntimeDriver {
185    fn begin(&mut self, request: RuntimeRequest) {
186        let (sender, receiver) = mpsc::channel();
187
188        match request {
189            RuntimeRequest::Scan => {
190                tokio::spawn(async move {
191                    let event = match tokio::task::spawn_blocking(|| {
192                        let networks = crate::network::networkmanager::scan_wifi_networks_blocking();
193                        let adapter_name = crate::network::networkmanager::get_wifi_adapter_name()
194                            .ok()
195                            .flatten();
196
197                        match networks {
198                            Ok(networks) => RuntimeEvent::Scan(Ok(ScanSnapshot {
199                                networks,
200                                adapter_name,
201                            })),
202                            Err(error) => RuntimeEvent::Scan(Err(error.to_string())),
203                        }
204                    })
205                    .await
206                    {
207                        Ok(event) => event,
208                        Err(error) => RuntimeEvent::Scan(Err(format!(
209                            "runtime scan task failed: {error}"
210                        ))),
211                    };
212
213                    let _ = sender.send(event);
214                });
215            }
216            RuntimeRequest::Connect {
217                network,
218                passphrase,
219            } => {
220                tokio::spawn(async move {
221                    let event = match tokio::task::spawn_blocking(move || {
222                        let result = match passphrase.as_deref() {
223                            Some(passphrase) => crate::network::networkmanager::connect_to_network(
224                                ConnectionRequest::Secured {
225                                    network: &network,
226                                    passphrase,
227                                },
228                            ),
229                            None => crate::network::networkmanager::connect_to_network(
230                                ConnectionRequest::Open { network: &network },
231                            ),
232                        };
233
234                        RuntimeEvent::Connect(result.map_err(|error| error.to_string()))
235                    })
236                    .await
237                    {
238                        Ok(event) => event,
239                        Err(error) => RuntimeEvent::Connect(Err(format!(
240                            "runtime connect task failed: {error}"
241                        ))),
242                    };
243
244                    let _ = sender.send(event);
245                });
246            }
247            RuntimeRequest::Disconnect { network } => {
248                tokio::spawn(async move {
249                    let event = match tokio::task::spawn_blocking(move || {
250                        RuntimeEvent::Disconnect(
251                            crate::network::networkmanager::disconnect_from_network(&network)
252                                .map_err(|error| error.to_string()),
253                        )
254                    })
255                    .await
256                    {
257                        Ok(event) => event,
258                        Err(error) => RuntimeEvent::Disconnect(Err(format!(
259                            "runtime disconnect task failed: {error}"
260                        ))),
261                    };
262
263                    let _ = sender.send(event);
264                });
265            }
266        }
267
268        self.pending_event = Some(receiver);
269    }
270
271    fn poll_event(&mut self) -> Result<Option<RuntimeEvent>, Box<dyn Error>> {
272        poll_pending_event(&mut self.pending_event)
273    }
274}
275
276#[cfg(not(feature = "demo"))]
277pub(crate) fn default_runtime_driver() -> Box<dyn RuntimeBackendDriver> {
278    Box::new(NetworkManagerRuntimeDriver::default())
279}
280
281#[cfg(feature = "demo")]
282pub fn default_backend() -> Box<dyn NetworkBackend> {
283    Box::new(DemoNetworkBackend)
284}
285
286#[cfg(not(feature = "demo"))]
287pub fn default_backend() -> Box<dyn NetworkBackend> {
288    Box::new(NetworkManagerBackend)
289}