use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeviceInfo {
pub address: String,
pub name: String,
pub approved: bool,
pub paired: bool,
pub last_seen: Option<i64>,
pub connection_type: Option<String>,
pub backend_device_id: Option<String>,
#[serde(default)]
pub device_class: u32,
#[serde(default)]
pub appearance: u16,
#[serde(default)]
pub connected: bool,
}
impl DeviceInfo {
pub fn new(address: String, name: String) -> Self {
Self {
address,
name,
approved: false,
paired: false,
last_seen: None,
connection_type: None,
backend_device_id: None,
device_class: 0,
appearance: 0,
connected: false,
}
}
pub fn with_type(address: String, name: String, connection_type: String) -> Self {
Self {
address,
name,
approved: false,
paired: false,
last_seen: None,
connection_type: Some(connection_type),
backend_device_id: None,
device_class: 0,
appearance: 0,
connected: false,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum WorkingMode {
Any,
Approved,
}
impl Default for WorkingMode {
fn default() -> Self {
WorkingMode::Approved
}
}
#[derive(Debug)]
pub struct DeviceMemory {
devices: Arc<RwLock<HashMap<String, DeviceInfo>>>,
working_mode: Arc<RwLock<WorkingMode>>,
known_devices_file: Option<PathBuf>,
}
impl DeviceMemory {
pub fn new(known_devices_file: Option<PathBuf>) -> Self {
Self {
devices: Arc::new(RwLock::new(HashMap::new())),
working_mode: Arc::new(RwLock::new(WorkingMode::default())),
known_devices_file,
}
}
pub async fn load_devices(&self) -> Result<()> {
if let Some(file_path) = &self.known_devices_file {
if file_path.exists() {
let content = tokio::fs::read_to_string(file_path).await?;
let devices: HashMap<String, DeviceInfo> = serde_json::from_str(&content)?;
let mut device_lock = self.devices.write().await;
*device_lock = devices;
}
}
Ok(())
}
pub async fn save_devices(&self) -> Result<()> {
if let Some(file_path) = &self.known_devices_file {
if let Some(parent) = file_path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
let devices = self.devices.read().await;
let content = serde_json::to_string_pretty(&*devices)?;
tokio::fs::write(file_path, content).await?;
}
Ok(())
}
pub async fn add_device(&self, device: DeviceInfo) -> Result<()> {
let mut devices = self.devices.write().await;
let address = device.address.clone();
let is_new = !devices.contains_key(&address);
if is_new {
devices.insert(address.clone(), device);
drop(devices);
if let Err(e) = self.save_devices().await {
tracing::warn!("Failed to save devices to file: {}", e);
}
tracing::debug!("Added new device to memory: {}", address);
} else {
if let Some(existing_device) = devices.get_mut(&address) {
let original_approved = existing_device.approved;
let original_paired = existing_device.paired;
let original_backend_id = existing_device.backend_device_id.clone();
let original_last_seen = existing_device.last_seen;
*existing_device = device;
existing_device.approved = original_approved;
existing_device.paired = original_paired;
existing_device.backend_device_id = original_backend_id;
existing_device.last_seen = original_last_seen;
}
drop(devices);
tracing::trace!("Updated existing device (preserved approval/pairing): {}", address);
}
Ok(())
}
pub async fn remove_device(&self, address: &str) -> Result<bool> {
let mut devices = self.devices.write().await;
let removed = devices.remove(address).is_some();
drop(devices);
if removed {
self.save_devices().await?;
}
Ok(removed)
}
pub async fn get_device(&self, address: &str) -> Option<DeviceInfo> {
let devices = self.devices.read().await;
devices.get(address).cloned()
}
pub async fn get_all_devices(&self) -> Vec<DeviceInfo> {
let devices = self.devices.read().await;
devices.values().cloned().collect()
}
pub async fn get_approved_devices(&self) -> Vec<DeviceInfo> {
let devices = self.devices.read().await;
devices.values()
.filter(|device| device.approved)
.cloned()
.collect()
}
pub async fn approve_device(&self, address: &str) -> Result<bool> {
let mut devices = self.devices.write().await;
if let Some(device) = devices.get_mut(address) {
device.approved = true;
drop(devices);
self.save_devices().await?;
Ok(true)
} else {
Ok(false)
}
}
pub async fn set_device_paired(&self, address: &str, paired: bool) -> Result<bool> {
let mut devices = self.devices.write().await;
if let Some(device) = devices.get_mut(address) {
device.paired = paired;
drop(devices);
self.save_devices().await?;
Ok(true)
} else {
Ok(false)
}
}
pub async fn is_device_paired(&self, address: &str) -> bool {
let devices = self.devices.read().await;
devices.get(address)
.map(|device| device.paired)
.unwrap_or(false)
}
pub async fn update_device_from_discovery(
&self,
address: String,
name: String,
timestamp: i64,
device_class: u32,
appearance: u16,
) -> Result<()> {
let mut devices = self.devices.write().await;
let is_new = !devices.contains_key(&address);
if is_new {
let device = DeviceInfo {
address: address.clone(),
name: name.clone(),
approved: false,
paired: false,
last_seen: Some(timestamp),
connection_type: None,
backend_device_id: None,
device_class,
appearance,
connected: false,
};
devices.insert(address.clone(), device);
drop(devices);
if let Err(e) = self.save_devices().await {
tracing::warn!("Failed to save devices to file: {}", e);
}
tracing::debug!("Discovered new device: {} ({})", name, address);
} else {
if let Some(device) = devices.get_mut(&address) {
device.name = name; device.last_seen = Some(timestamp);
device.device_class = device_class;
device.appearance = appearance;
}
drop(devices);
tracing::trace!("Updated discovery info for existing device: {}", address);
}
Ok(())
}
pub async fn update_last_seen(&self, address: &str, timestamp: i64) -> Result<()> {
let mut devices = self.devices.write().await;
if let Some(device) = devices.get_mut(address) {
device.last_seen = Some(timestamp);
}
Ok(())
}
pub async fn set_device_connected(&self, address: &str, connected: bool) -> Result<bool> {
let mut devices = self.devices.write().await;
if let Some(device) = devices.get_mut(address) {
device.connected = connected;
Ok(true)
} else {
Ok(false)
}
}
pub async fn get_working_mode(&self) -> WorkingMode {
*self.working_mode.read().await
}
pub async fn set_working_mode(&self, mode: WorkingMode) -> Result<()> {
let mut working_mode = self.working_mode.write().await;
*working_mode = mode;
Ok(())
}
pub async fn should_connect_to_device(&self, address: &str) -> bool {
let mode = self.get_working_mode().await;
match mode {
WorkingMode::Any => true,
WorkingMode::Approved => {
if let Some(device) = self.get_device(address).await {
device.approved
} else {
false
}
}
}
}
pub async fn get_devices_filtered(&self, only_approved: bool) -> Vec<DeviceInfo> {
let all_devices = self.get_all_devices().await;
tracing::debug!("get_devices_filtered: only_approved={}, total_devices={}", only_approved, all_devices.len());
if only_approved {
let approved = self.get_approved_devices().await;
tracing::debug!(" Returning {} approved devices", approved.len());
approved
} else {
tracing::debug!(" Returning {} total devices", all_devices.len());
all_devices
}
}
pub async fn set_backend_device_id(&self, address: &str, backend_id: String) -> Result<bool> {
let mut devices = self.devices.write().await;
if let Some(device) = devices.get_mut(address) {
device.backend_device_id = Some(backend_id);
drop(devices);
self.save_devices().await?;
Ok(true)
} else {
Ok(false)
}
}
pub async fn get_registered_devices(&self) -> Vec<crate::api::RegisteredDeviceInfo> {
let devices = self.devices.read().await;
devices.values()
.filter_map(|device| {
device.backend_device_id.as_ref().map(|backend_id| {
crate::api::RegisteredDeviceInfo {
address: device.address.clone(),
backend_device_id: Some(backend_id.clone()),
approved: device.approved,
}
})
})
.collect()
}
pub async fn get_all_devices_as_registered(&self) -> Vec<crate::api::RegisteredDeviceInfo> {
let devices = self.devices.read().await;
devices.values()
.map(|device| crate::api::RegisteredDeviceInfo {
address: device.address.clone(),
backend_device_id: device.backend_device_id.clone(),
approved: device.approved,
})
.collect()
}
pub async fn add_device_with_auto_approval(&self, address: &str, name: &str, connection_type: Option<String>) -> Result<()> {
let mut device = if let Some(connection_type) = connection_type {
DeviceInfo::with_type(address.to_string(), name.to_string(), connection_type)
} else {
DeviceInfo::new(address.to_string(), name.to_string())
};
device.approved = true;
self.add_device(device).await
}
}
impl Default for DeviceMemory {
fn default() -> Self {
Self::new(None)
}
}