wayle-network 0.1.2

WiFi and wired network management
Documentation
use std::sync::Arc;

use tokio_stream::StreamExt;
use tokio_util::sync::CancellationToken;
use tracing::{debug, warn};
use wayle_core::Property;
use wayle_traits::{Reactive, ServiceMonitoring};
use zbus::Connection;

use super::{
    core::settings::Settings,
    discovery::NetworkServiceDiscovery,
    error::Error,
    proxy::manager::NetworkManagerProxy,
    service::NetworkService,
    types::connectivity::ConnectionType,
    wifi::{LiveWifiParams, Wifi},
    wired::{LiveWiredParams, Wired},
};

impl ServiceMonitoring for NetworkService {
    type Error = Error;

    async fn start_monitoring(&self) -> Result<(), Self::Error> {
        spawn_primary_monitoring(
            self.zbus_connection.clone(),
            self.primary.clone(),
            self.cancellation_token.child_token(),
        )
        .await?;

        spawn_device_monitoring(
            self.zbus_connection.clone(),
            self.wifi.clone(),
            self.wired.clone(),
            self.settings.clone(),
            self.cancellation_token.child_token(),
        )
        .await
    }
}

async fn spawn_primary_monitoring(
    connection: Connection,
    primary: Property<ConnectionType>,
    cancellation_token: CancellationToken,
) -> Result<(), Error> {
    let nm_proxy = NetworkManagerProxy::new(&connection)
        .await
        .map_err(Error::DbusError)?;

    let initial_type = nm_proxy.primary_connection_type().await?;
    update_primary_connection(&initial_type, &primary);

    let mut type_changed = nm_proxy.receive_primary_connection_type_changed().await;

    tokio::spawn(async move {
        loop {
            tokio::select! {
                _ = cancellation_token.cancelled() => {
                    debug!("NetworkMonitoring primary monitoring cancelled");
                    return;
                }
                Some(change) = type_changed.next() => {
                    if let Ok(nm_type) = change.get().await {
                        debug!(nm_type = %nm_type, "Primary connection type changed");
                        update_primary_connection(&nm_type, &primary);
                    }
                }
            }
        }
    });

    Ok(())
}

async fn spawn_device_monitoring(
    connection: Connection,
    wifi: Property<Option<Arc<Wifi>>>,
    wired: Property<Option<Arc<Wired>>>,
    settings: Arc<Settings>,
    cancellation_token: CancellationToken,
) -> Result<(), Error> {
    let nm_proxy = NetworkManagerProxy::new(&connection)
        .await
        .map_err(Error::DbusError)?;

    let mut device_added = nm_proxy.receive_device_added().await?;
    let mut device_removed = nm_proxy.receive_device_removed().await?;

    tokio::spawn(async move {
        loop {
            tokio::select! {
                _ = cancellation_token.cancelled() => {
                    debug!("NetworkMonitoring device monitoring cancelled");
                    return;
                }
                Some(signal) = device_added.next() => {
                    let Ok(args) = signal.args() else { continue };
                    debug!(path = %args.device_path, "Network device added");

                    try_initialize_wifi(&connection, &wifi, &settings, &cancellation_token).await;
                    try_initialize_wired(&connection, &wired, &cancellation_token).await;
                }
                Some(signal) = device_removed.next() => {
                    let Ok(args) = signal.args() else { continue };
                    debug!(path = %args.device_path, "Network device removed");

                    handle_wifi_removed(&args.device_path, &wifi);
                    handle_wired_removed(&args.device_path, &wired);
                }
            }
        }
    });

    Ok(())
}

async fn try_initialize_wifi(
    connection: &Connection,
    wifi: &Property<Option<Arc<Wifi>>>,
    settings: &Arc<Settings>,
    cancellation_token: &CancellationToken,
) {
    if wifi.get().is_some() {
        return;
    }

    let Some(path) = NetworkServiceDiscovery::wifi_device_path(connection)
        .await
        .ok()
        .flatten()
    else {
        return;
    };

    match Wifi::get_live(LiveWifiParams {
        connection,
        device_path: path.clone(),
        cancellation_token,
        settings: settings.clone(),
    })
    .await
    {
        Ok(new_wifi) => {
            debug!(path = %path, "WiFi device initialized");
            wifi.set(Some(new_wifi));
        }
        Err(err) => {
            warn!(error = %err, path = %path, "Failed to initialize WiFi device");
        }
    }
}

async fn try_initialize_wired(
    connection: &Connection,
    wired: &Property<Option<Arc<Wired>>>,
    cancellation_token: &CancellationToken,
) {
    if wired.get().is_some() {
        return;
    }

    let Some(path) = NetworkServiceDiscovery::wired_device_path(connection)
        .await
        .ok()
        .flatten()
    else {
        return;
    };

    match Wired::get_live(LiveWiredParams {
        connection,
        device_path: path.clone(),
        cancellation_token,
    })
    .await
    {
        Ok(new_wired) => {
            debug!(path = %path, "Wired device initialized");
            wired.set(Some(new_wired));
        }
        Err(err) => {
            warn!(error = %err, path = %path, "Failed to initialize wired device");
        }
    }
}

fn handle_wifi_removed(device_path: &str, wifi: &Property<Option<Arc<Wifi>>>) {
    let Some(current) = wifi.get() else { return };

    if current.device.core.object_path.as_str() == device_path {
        debug!(path = %device_path, "WiFi device removed");
        wifi.set(None);
    }
}

fn handle_wired_removed(device_path: &str, wired: &Property<Option<Arc<Wired>>>) {
    let Some(current) = wired.get() else { return };

    if current.device.core.object_path.as_str() == device_path {
        debug!(path = %device_path, "Wired device removed");
        wired.set(None);
    }
}

fn update_primary_connection(nm_type: &str, primary: &Property<ConnectionType>) {
    let connection_type = ConnectionType::from_nm_type(nm_type);
    debug!(?connection_type, "Primary connection type resolved");
    primary.set(connection_type);
}