refactor state refresh mechanism.

This commit is contained in:
Vixalie 2025-02-26 06:38:13 +08:00
parent 4f5420b658
commit d9a4f92c04
4 changed files with 90 additions and 52 deletions

View File

@ -27,7 +27,7 @@ pub async fn handle_bluetooth_events(
while let Some(event) = event_stream.next().await { while let Some(event) = event_stream.next().await {
match event { match event {
btleplug::api::CentralEvent::DeviceDiscovered(_id) => { btleplug::api::CentralEvent::DeviceDiscovered(_id) => {
app.emit("app_state_updated", ()).unwrap(); app.emit("peripheral_found", ()).unwrap();
} }
btleplug::api::CentralEvent::DeviceConnected(_id) => { btleplug::api::CentralEvent::DeviceConnected(_id) => {
let state = app_state.write().await; let state = app_state.write().await;
@ -36,7 +36,7 @@ pub async fn handle_bluetooth_events(
for peripheral in peripherals { for peripheral in peripherals {
if peripheral.id() == _id { if peripheral.id() == _id {
state.set_connected_peripheral(peripheral).await; state.set_connected_peripheral(peripheral).await;
app.emit("app_state_updated", ()).unwrap(); app.emit("peripheral_connected", ()).unwrap();
break; break;
} }
} }
@ -45,14 +45,14 @@ pub async fn handle_bluetooth_events(
btleplug::api::CentralEvent::DeviceDisconnected(_id) => { btleplug::api::CentralEvent::DeviceDisconnected(_id) => {
let state = app_state.write().await; let state = app_state.write().await;
state.clear_connected_peripheral().await; state.clear_connected_peripheral().await;
app.emit("app_state_updated", ()).unwrap(); app.emit("peripheral_disconnected", ()).unwrap();
} }
_ => {} _ => {}
} }
} }
})) }))
} else { } else {
app.emit("app_state_updated", ()).unwrap(); app.emit("central_state_updated", ()).unwrap();
Err(errors::AppError::BluetoothNotReady) Err(errors::AppError::BluetoothNotReady)
} }
} }
@ -91,8 +91,8 @@ pub async fn start_scan(
.start_scan(ScanFilter::default()) .start_scan(ScanFilter::default())
.await .await
.map_err(|_| errors::AppError::UnableToStartScan)?; .map_err(|_| errors::AppError::UnableToStartScan)?;
app_handle.emit("app_state_updated", ()).unwrap();
state.set_scanning(true).await; state.set_scanning(true).await;
app_handle.emit("scanning_started", ()).unwrap();
Ok(()) Ok(())
} else { } else {
Err(errors::AppError::NoAvailableBluetoothAdapter) Err(errors::AppError::NoAvailableBluetoothAdapter)
@ -110,8 +110,8 @@ pub async fn stop_scan(
.stop_scan() .stop_scan()
.await .await
.map_err(|_| errors::AppError::UnableToStopScan)?; .map_err(|_| errors::AppError::UnableToStopScan)?;
app_handle.emit("app_state_updated", ()).unwrap();
state.set_scanning(false).await; state.set_scanning(false).await;
app_handle.emit("scanning_stopped", ()).unwrap();
Ok(()) Ok(())
} else { } else {
Err(errors::AppError::NoAvailableBluetoothAdapter) Err(errors::AppError::NoAvailableBluetoothAdapter)

View File

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use btleplug::api::{Central as _, Peripheral as _}; use btleplug::api::{Central as _, Peripheral as _};
use state::{ApplicationState, CentralState, ChannelState, PeripheralItem}; use state::{CentralState, ChannelState, PeripheralItem};
use tauri::{async_runtime::RwLock, AppHandle, Emitter, State}; use tauri::{async_runtime::RwLock, AppHandle, Emitter, State};
use crate::{bluetooth, errors, state::AppState}; use crate::{bluetooth, errors, state::AppState};
@ -23,28 +23,81 @@ pub async fn activate_central_adapter(
let handle = let handle =
bluetooth::handle_bluetooth_events(Arc::clone(&app), Arc::clone(&app_state)).await?; bluetooth::handle_bluetooth_events(Arc::clone(&app), Arc::clone(&app_state)).await?;
{ {
let state = app_state.write().await; // Changed from lock() to write() let state = app_state.write().await;
state.set_central_event_handler(handle).await; state.set_central_event_handler(handle).await;
} }
app.emit("app_state_updated", ()).unwrap(); app.emit("central_state_updated", ()).unwrap();
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command]
pub async fn refresh_application_state( pub async fn central_device_state(
app_state: State<'_, Arc<RwLock<AppState>>>, app_state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<ApplicationState, errors::AppError> { ) -> Result<CentralState, errors::AppError> {
let state = app_state.read().await; let state = app_state.read().await;
let mut peripherals: Vec<PeripheralItem> = vec![];
let mut connected_peripheral = None;
let central = state.get_central_adapter().await; let central = state.get_central_adapter().await;
let central_state = if let Some(central) = central { if let Some(central) = central {
let central_device_state = central let central_device_state = central
.adapter_state() .adapter_state()
.await .await
.map_err(|_| errors::AppError::BluetoothNotReady)?; .map_err(|_| errors::AppError::BluetoothNotReady)?;
Ok(CentralState {
is_ready: central_device_state == btleplug::api::CentralState::PoweredOn,
is_scanning: *state.scanning.lock().await,
connected: state
.connected_peripheral
.lock()
.await
.clone()
.map(|p| p.id()),
})
} else {
Ok(CentralState::default())
}
}
#[tauri::command]
pub async fn connected_peripheral_state(
app_state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<Option<PeripheralItem>, errors::AppError> {
let state = app_state.read().await;
let connected_peripheral = state.connected_peripheral.lock().await.clone();
if let Some(peripheral) = connected_peripheral {
let properties = peripheral
.properties()
.await
.map_err(|_| errors::AppError::UnableToRetrievePeripheralProperties)?;
if let Some(properties) = properties {
let represent = properties
.local_name
.unwrap_or_else(|| properties.address.to_string());
Ok(Some(PeripheralItem {
id: peripheral.id(),
address: properties.address.to_string(),
represent,
is_connected: peripheral
.is_connected()
.await
.map_err(|_| errors::AppError::UnableToRetrievePeripheralProperties)?,
rssi: properties.rssi,
battery: properties.tx_power_level,
}))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
#[tauri::command]
pub async fn found_peripherals(
app_state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<Vec<PeripheralItem>, errors::AppError> {
let state = app_state.read().await;
let central = state.get_central_adapter().await;
let mut peripherals: Vec<PeripheralItem> = vec![];
if let Some(central) = central {
let found_peripherals = central let found_peripherals = central
.peripherals() .peripherals()
.await .await
@ -69,38 +122,25 @@ pub async fn refresh_application_state(
rssi: properties.rssi, rssi: properties.rssi,
battery: properties.tx_power_level, battery: properties.tx_power_level,
}; };
if peripheral
.is_connected()
.await
.map_err(|_| errors::AppError::UnableToRetrievePeripheralState)?
{
connected_peripheral = Some(item.clone());
}
peripherals.push(item); peripherals.push(item);
} }
} }
}
Ok(peripherals)
}
CentralState { #[tauri::command]
is_ready: central_device_state == btleplug::api::CentralState::PoweredOn, pub async fn channel_a_state(
is_scanning: *state.scanning.lock().await, _app_state: State<'_, Arc<RwLock<AppState>>>,
connected: state ) -> Result<ChannelState, errors::AppError> {
.connected_peripheral Ok(ChannelState::default())
.lock() }
.await
.clone()
.map(|p| p.id()),
}
} else {
CentralState::default()
};
Ok(ApplicationState { #[tauri::command]
central: central_state, pub async fn channel_b_state(
peripherals, _app_state: State<'_, Arc<RwLock<AppState>>>,
connected_peripheral, ) -> Result<ChannelState, errors::AppError> {
channel_a: ChannelState::default(), Ok(ChannelState::default())
channel_b: ChannelState::default(),
})
} }
#[tauri::command] #[tauri::command]

View File

@ -4,6 +4,7 @@ use serde::Serialize;
use crate::playlist::PlayMode; use crate::playlist::PlayMode;
#[derive(Debug, Clone, Default, Serialize)] #[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CentralState { pub struct CentralState {
pub is_ready: bool, pub is_ready: bool,
pub is_scanning: bool, pub is_scanning: bool,
@ -11,6 +12,7 @@ pub struct CentralState {
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PeripheralItem { pub struct PeripheralItem {
pub id: PeripheralId, pub id: PeripheralId,
pub address: String, pub address: String,
@ -21,6 +23,7 @@ pub struct PeripheralItem {
} }
#[derive(Debug, Clone, Default, Serialize)] #[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChannelState { pub struct ChannelState {
pub is_playing: bool, pub is_playing: bool,
pub strength: u32, pub strength: u32,
@ -30,12 +33,3 @@ pub struct ChannelState {
pub boost_limit: u32, pub boost_limit: u32,
pub play_mode: PlayMode, pub play_mode: PlayMode,
} }
#[derive(Debug, Clone, Serialize)]
pub struct ApplicationState {
pub central: CentralState,
pub peripherals: Vec<PeripheralItem>,
pub connected_peripheral: Option<PeripheralItem>,
pub channel_a: ChannelState,
pub channel_b: ChannelState,
}

View File

@ -53,7 +53,11 @@ pub fn run() {
Ok(()) Ok(())
}) })
.invoke_handler(generate_handler![ .invoke_handler(generate_handler![
cmd::refresh_application_state, cmd::central_device_state,
cmd::connected_peripheral_state,
cmd::found_peripherals,
cmd::channel_a_state,
cmd::channel_b_state,
cmd::activate_central_adapter, cmd::activate_central_adapter,
cmd::start_scan_devices, cmd::start_scan_devices,
cmd::stop_scan_devices cmd::stop_scan_devices