173 lines
5.5 KiB
Rust
173 lines
5.5 KiB
Rust
use anyhow::Result;
|
|
use async_trait::async_trait;
|
|
use bread_shared::RawEvent;
|
|
use serde::Serialize;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use tokio::sync::{mpsc, watch, RwLock};
|
|
use tracing::info;
|
|
|
|
use crate::core::config::Config;
|
|
use crate::core::supervisor::spawn_supervised;
|
|
|
|
pub mod bluetooth;
|
|
pub mod filesystem;
|
|
pub mod git;
|
|
pub mod hyprland;
|
|
pub mod network;
|
|
pub mod network_rtnetlink;
|
|
pub mod podman;
|
|
pub mod power;
|
|
pub mod power_upower;
|
|
pub mod systemd;
|
|
pub mod udev;
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AdapterStatus {
|
|
Connected,
|
|
Disconnected,
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait Adapter: Send + Sync {
|
|
fn name(&self) -> &'static str;
|
|
async fn run(&self, tx: mpsc::Sender<RawEvent>) -> Result<()>;
|
|
async fn on_connect(&self) -> Result<()> {
|
|
Ok(())
|
|
}
|
|
async fn on_disconnect(&self) -> Result<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct Manager {
|
|
raw_tx: mpsc::Sender<RawEvent>,
|
|
config: Config,
|
|
shutdown_rx: watch::Receiver<bool>,
|
|
status: Arc<RwLock<HashMap<String, AdapterStatus>>>,
|
|
}
|
|
|
|
impl Manager {
|
|
pub fn new(
|
|
raw_tx: mpsc::Sender<RawEvent>,
|
|
config: Config,
|
|
shutdown_rx: watch::Receiver<bool>,
|
|
) -> Self {
|
|
Self {
|
|
raw_tx,
|
|
config,
|
|
shutdown_rx,
|
|
status: Arc::new(RwLock::new(HashMap::new())),
|
|
}
|
|
}
|
|
|
|
pub fn status_handle(&self) -> Arc<RwLock<HashMap<String, AdapterStatus>>> {
|
|
self.status.clone()
|
|
}
|
|
|
|
pub async fn start_all(&self) -> Result<()> {
|
|
info!("starting adapters");
|
|
|
|
if self.config.adapters.udev.enabled {
|
|
let adapter = udev::UdevAdapter::new(self.config.adapters.udev.subsystems.clone());
|
|
adapter.enumerate_existing(&self.raw_tx).await?;
|
|
self.spawn_adapter(adapter);
|
|
}
|
|
|
|
if self.config.adapters.hyprland.enabled {
|
|
self.spawn_adapter(hyprland::HyprlandAdapter);
|
|
}
|
|
|
|
if self.config.adapters.power.enabled {
|
|
// Prefer UPower D-Bus adapter; fall back to sysfs poller if D-Bus is unavailable.
|
|
match power_upower::UPowerAdapter::probe().await {
|
|
Ok(adapter) => self.spawn_adapter(adapter),
|
|
Err(e) => {
|
|
info!("upower unavailable ({e}), falling back to sysfs power poller");
|
|
self.spawn_adapter(power::PowerAdapter::new(
|
|
self.config.adapters.power.poll_interval_secs,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.config.adapters.bluetooth.enabled {
|
|
let adapter = bluetooth::BluetoothAdapter::new();
|
|
adapter.enumerate_existing(&self.raw_tx).await;
|
|
self.spawn_adapter(adapter);
|
|
}
|
|
|
|
if self.config.adapters.network.enabled {
|
|
// Prefer rtnetlink-based adapter; fall back to existing sysfs-based adapter
|
|
let rt = network_rtnetlink::RtnetlinkAdapter::new();
|
|
if let Ok(adapter) = rt {
|
|
self.spawn_adapter(adapter);
|
|
} else {
|
|
self.spawn_adapter(network::NetworkAdapter);
|
|
}
|
|
}
|
|
|
|
// Filesystem/git/systemd all default to "no roots/units configured",
|
|
// in which case they'd have nothing to do — skip spawning them
|
|
// entirely rather than running an adapter that can never emit.
|
|
if self.config.adapters.filesystem.enabled
|
|
&& !self.config.adapters.filesystem.roots.is_empty()
|
|
{
|
|
let adapter =
|
|
filesystem::FilesystemAdapter::new(self.config.adapters.filesystem.roots.clone());
|
|
adapter.enumerate_existing(&self.raw_tx).await;
|
|
self.spawn_adapter(adapter);
|
|
}
|
|
|
|
if self.config.adapters.git.enabled && !self.config.adapters.git.roots.is_empty() {
|
|
let adapter = git::GitAdapter::new(self.config.adapters.git.roots.clone());
|
|
self.spawn_adapter(adapter);
|
|
}
|
|
|
|
if self.config.adapters.systemd.enabled && !self.config.adapters.systemd.units.is_empty() {
|
|
let adapter = systemd::SystemdAdapter::new(self.config.adapters.systemd.units.clone());
|
|
self.spawn_adapter(adapter);
|
|
}
|
|
|
|
if self.config.adapters.podman.enabled {
|
|
self.spawn_adapter(podman::PodmanAdapter::new());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn spawn_adapter<A>(&self, adapter: A)
|
|
where
|
|
A: Adapter + Clone + 'static,
|
|
{
|
|
let name = adapter.name();
|
|
let tx = self.raw_tx.clone();
|
|
let shutdown_rx = self.shutdown_rx.clone();
|
|
let shutdown_for_task = shutdown_rx.clone();
|
|
let status = self.status.clone();
|
|
spawn_supervised(name, shutdown_rx, move || {
|
|
let adapter = adapter.clone();
|
|
let tx = tx.clone();
|
|
let mut shutdown_rx = shutdown_for_task.clone();
|
|
let status = status.clone();
|
|
async move {
|
|
adapter.on_connect().await?;
|
|
{
|
|
let mut guard = status.write().await;
|
|
guard.insert(adapter.name().to_string(), AdapterStatus::Connected);
|
|
}
|
|
let result = tokio::select! {
|
|
result = adapter.run(tx) => result,
|
|
_ = shutdown_rx.changed() => Ok(()),
|
|
};
|
|
adapter.on_disconnect().await?;
|
|
{
|
|
let mut guard = status.write().await;
|
|
guard.insert(adapter.name().to_string(), AdapterStatus::Disconnected);
|
|
}
|
|
result
|
|
}
|
|
});
|
|
}
|
|
}
|