From 036f4a7f64b1783a2e050b8f05af8a7108567957 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Wed, 30 Oct 2024 22:11:36 +0800 Subject: [PATCH 01/38] feat: add `ThreadingType` and conditionally spawn blocking --- .../providers/komorebi/komorebi_provider.rs | 30 ++++++----- packages/desktop/src/providers/provider.rs | 25 ++++++++- .../desktop/src/providers/provider_ref.rs | 53 +++++++++++-------- 3 files changed, 69 insertions(+), 39 deletions(-) diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index 900dc7cb..6ab21aef 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -40,7 +40,7 @@ impl KomorebiProvider { KomorebiProvider { _config: config } } - async fn create_socket( + fn create_socket( &self, emit_result_tx: Sender, ) -> anyhow::Result<()> { @@ -68,7 +68,7 @@ impl KomorebiProvider { .is_err() { debug!("Attempting to reconnect to Komorebi."); - time::sleep(Duration::from_secs(15)).await; + std::thread::sleep(Duration::from_secs(15)); } } @@ -78,19 +78,17 @@ impl KomorebiProvider { &String::from_utf8(buffer).unwrap(), ) { - emit_result_tx - .send( - Ok(ProviderOutput::Komorebi(Self::transform_response( - notification.state, - ))) - .into(), - ) - .await; + emit_result_tx.try_send( + Ok(ProviderOutput::Komorebi(Self::transform_response( + notification.state, + ))) + .into(), + ) } } Err(_) => { emit_result_tx - .send( + .try_send( Err(anyhow::anyhow!("Failed to read Komorebi stream.")) .into(), ) @@ -185,9 +183,13 @@ impl KomorebiProvider { #[async_trait] impl Provider for KomorebiProvider { - async fn run(&self, emit_result_tx: Sender) { - if let Err(err) = self.create_socket(emit_result_tx.clone()).await { - emit_result_tx.send(Err(err).into()).await; + fn threading_type(&self) -> ThreadingType { + ThreadingType::Sync + } + + fn run_sync(&self, emit_result_tx: Sender) { + if let Err(err) = self.create_socket(emit_result_tx.clone()) { + emit_result_tx.try_send(Err(err).into()); } } } diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index 0a391ccf..d504b7c9 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -5,8 +5,19 @@ use super::ProviderResult; #[async_trait] pub trait Provider: Send + Sync { + fn threading_type(&self) -> ThreadingType; + /// Callback for when the provider is started. - async fn run(&self, emit_result_tx: Sender); + async fn run_async(&self, _emit_result_tx: Sender) { + // TODO: Change to not implemented exception. + todo!() + } + + /// Callback for when the provider is started. + fn run_sync(&self, _emit_result_tx: Sender) { + // TODO: Change to not implemented exception. + todo!() + } /// Callback for when the provider is stopped. async fn on_stop(&self) { @@ -14,6 +25,12 @@ pub trait Provider: Send + Sync { } } +/// Determines whether `run_sync` or `run_async` is called.` +pub enum ThreadingType { + Sync, + Async, +} + /// Implements the `Provider` trait for the given struct. /// /// Expects that the struct has a `refresh_interval_ms` and `run_interval` @@ -23,7 +40,11 @@ macro_rules! impl_interval_provider { ($type:ty, $allow_identical_emits:expr) => { #[async_trait::async_trait] impl crate::providers::Provider for $type { - async fn run( + fn threading_type(&self) -> crate::providers::ThreadingType { + crate::providers::ThreadingType::Sync + } + + async fn run_async( &self, emit_result_tx: tokio::sync::mpsc::Sender< crate::providers::ProviderResult, diff --git a/packages/desktop/src/providers/provider_ref.rs b/packages/desktop/src/providers/provider_ref.rs index b8b94cae..92a67c36 100644 --- a/packages/desktop/src/providers/provider_ref.rs +++ b/packages/desktop/src/providers/provider_ref.rs @@ -19,7 +19,7 @@ use super::{ battery::BatteryProvider, cpu::CpuProvider, disk::DiskProvider, host::HostProvider, ip::IpProvider, memory::MemoryProvider, network::NetworkProvider, weather::WeatherProvider, Provider, - ProviderConfig, ProviderOutput, SharedProviderState, + ProviderConfig, ProviderOutput, SharedProviderState, ThreadingType, }; /// Reference to an active provider. @@ -132,29 +132,36 @@ impl ProviderRef { ) -> anyhow::Result<()> { let provider = Self::create_provider(config, shared_state)?; - task::spawn(async move { - // TODO: Add arc `should_stop` to be passed to `run`. - - let run = provider.run(emit_result_tx); - tokio::pin!(run); - - // Ref: https://tokio.rs/tokio/tutorial/select#resuming-an-async-operation - loop { - tokio::select! { - // Default match arm which continuously runs the provider. - _ = run => break, - - // On stop, perform any necessary clean up and exit the loop. - Some(_) = stop_rx.recv() => { - info!("Stopping provider: {}", config_hash); - _ = provider.on_stop().await; - break; - }, - } + let _ = match provider.threading_type() { + ThreadingType::Async => { + task::spawn(async move { + // TODO: Add arc `should_stop` to be passed to `run`. + + let run = provider.run_async(emit_result_tx); + tokio::pin!(run); + + // Ref: https://tokio.rs/tokio/tutorial/select#resuming-an-async-operation + loop { + tokio::select! { + // Default match arm which continuously runs the provider. + _ = run => break, + + // On stop, perform any necessary clean up and exit the loop. + Some(_) = stop_rx.recv() => { + info!("Stopping provider: {}", config_hash); + _ = provider.on_stop().await; + break; + }, + } + } + + info!("Provider stopped: {}", config_hash); + }) } - - info!("Provider stopped: {}", config_hash); - }); + ThreadingType::Sync => task::spawn_blocking(move || { + let run = provider.run_sync(emit_result_tx); + }), + }; Ok(()) } From 6833aa4485fed68c2820961b6a5703feadb766d2 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Fri, 15 Nov 2024 19:14:05 +0800 Subject: [PATCH 02/38] wip --- .../providers/komorebi/komorebi_provider.rs | 2 +- packages/desktop/src/providers/mod.rs | 2 ++ packages/desktop/src/providers/provider.rs | 21 ++++++++---- .../src/providers/provider_function.rs | 13 +++++++ .../desktop/src/providers/provider_ref.rs | 34 ++++--------------- 5 files changed, 38 insertions(+), 34 deletions(-) create mode 100644 packages/desktop/src/providers/provider_function.rs diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index 6ab21aef..bfcdcb3a 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -187,7 +187,7 @@ impl Provider for KomorebiProvider { ThreadingType::Sync } - fn run_sync(&self, emit_result_tx: Sender) { + fn run_sync(&mut self, emit_result_tx: Sender) { if let Err(err) = self.create_socket(emit_result_tx.clone()) { emit_result_tx.try_send(Err(err).into()); } diff --git a/packages/desktop/src/providers/mod.rs b/packages/desktop/src/providers/mod.rs index bad74540..65a5b5fc 100644 --- a/packages/desktop/src/providers/mod.rs +++ b/packages/desktop/src/providers/mod.rs @@ -15,6 +15,7 @@ mod memory; mod network; mod provider; mod provider_config; +mod provider_function; mod provider_manager; mod provider_output; mod provider_ref; @@ -22,6 +23,7 @@ mod weather; pub use provider::*; pub use provider_config::*; +pub use provider_function::*; pub use provider_manager::*; pub use provider_output::*; pub use provider_ref::*; diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index d504b7c9..5666d709 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use tokio::sync::mpsc::Sender; +use tokio::sync::mpsc; use super::ProviderResult; @@ -8,13 +8,21 @@ pub trait Provider: Send + Sync { fn threading_type(&self) -> ThreadingType; /// Callback for when the provider is started. - async fn run_async(&self, _emit_result_tx: Sender) { - // TODO: Change to not implemented exception. + async fn run_async( + &mut self, + _emit_result_tx: mpsc::Sender, + _stop_rx: mpsc::Receiver<()>, + ) { + // TODO: mpsc::Change to not implemented exception. todo!() } /// Callback for when the provider is started. - fn run_sync(&self, _emit_result_tx: Sender) { + fn run_sync( + &mut self, + _emit_result_tx: mpsc::Sender, + _stop_rx: mpsc::Receiver<()>, + ) { // TODO: Change to not implemented exception. todo!() } @@ -41,14 +49,15 @@ macro_rules! impl_interval_provider { #[async_trait::async_trait] impl crate::providers::Provider for $type { fn threading_type(&self) -> crate::providers::ThreadingType { - crate::providers::ThreadingType::Sync + crate::providers::ThreadingType::Async } async fn run_async( - &self, + &mut self, emit_result_tx: tokio::sync::mpsc::Sender< crate::providers::ProviderResult, >, + _stop_rx: tokio::sync::mpsc::Receiver<()>, ) { let mut interval = tokio::time::interval( std::time::Duration::from_millis(self.refresh_interval_ms()), diff --git a/packages/desktop/src/providers/provider_function.rs b/packages/desktop/src/providers/provider_function.rs new file mode 100644 index 00000000..0138f122 --- /dev/null +++ b/packages/desktop/src/providers/provider_function.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ProviderFunction { + Media(MediaFunction), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MediaFunction { + PlayPause, + Next, + Previous, +} diff --git a/packages/desktop/src/providers/provider_ref.rs b/packages/desktop/src/providers/provider_ref.rs index 92a67c36..c1f1fcff 100644 --- a/packages/desktop/src/providers/provider_ref.rs +++ b/packages/desktop/src/providers/provider_ref.rs @@ -130,36 +130,16 @@ impl ProviderRef { emit_result_tx: mpsc::Sender, mut stop_rx: mpsc::Receiver<()>, ) -> anyhow::Result<()> { - let provider = Self::create_provider(config, shared_state)?; + let mut provider = Self::create_provider(config, shared_state)?; let _ = match provider.threading_type() { - ThreadingType::Async => { - task::spawn(async move { - // TODO: Add arc `should_stop` to be passed to `run`. - - let run = provider.run_async(emit_result_tx); - tokio::pin!(run); - - // Ref: https://tokio.rs/tokio/tutorial/select#resuming-an-async-operation - loop { - tokio::select! { - // Default match arm which continuously runs the provider. - _ = run => break, - - // On stop, perform any necessary clean up and exit the loop. - Some(_) = stop_rx.recv() => { - info!("Stopping provider: {}", config_hash); - _ = provider.on_stop().await; - break; - }, - } - } - - info!("Provider stopped: {}", config_hash); - }) - } + ThreadingType::Async => task::spawn(async move { + provider.run_async(emit_result_tx, stop_rx).await; + info!("Provider stopped: {}", config_hash); + }), ThreadingType::Sync => task::spawn_blocking(move || { - let run = provider.run_sync(emit_result_tx); + provider.run_sync(emit_result_tx, stop_rx); + info!("Provider stopped: {}", config_hash); }), }; From f87f3df010bfbc35307ec595ad99c8f94cd404f4 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 16 Nov 2024 17:22:21 +0800 Subject: [PATCH 03/38] feat: rename ThreadingType -> RuntimeType; add `call_function` to `Provider` trait --- .../providers/komorebi/komorebi_provider.rs | 6 +- packages/desktop/src/providers/provider.rs | 81 ++++++++++++++++--- .../desktop/src/providers/provider_ref.rs | 12 +-- 3 files changed, 77 insertions(+), 22 deletions(-) diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index bfcdcb3a..cd84a431 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -183,11 +183,11 @@ impl KomorebiProvider { #[async_trait] impl Provider for KomorebiProvider { - fn threading_type(&self) -> ThreadingType { - ThreadingType::Sync + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync } - fn run_sync(&mut self, emit_result_tx: Sender) { + fn start_sync(&mut self, emit_result_tx: Sender) { if let Err(err) = self.create_socket(emit_result_tx.clone()) { emit_result_tx.try_send(Err(err).into()); } diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index 5666d709..0ca2c58d 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -5,36 +5,91 @@ use super::ProviderResult; #[async_trait] pub trait Provider: Send + Sync { - fn threading_type(&self) -> ThreadingType; + fn runtime_type(&self) -> RuntimeType; /// Callback for when the provider is started. - async fn run_async( + /// + /// # Panics + /// + /// Panics if wrong runtime type is used. + fn start_sync( &mut self, _emit_result_tx: mpsc::Sender, _stop_rx: mpsc::Receiver<()>, ) { - // TODO: mpsc::Change to not implemented exception. - todo!() + match self.runtime_type() { + RuntimeType::Sync => { + unreachable!("Sync providers must implement `start_sync`.") + } + RuntimeType::Async => { + panic!("Cannot call sync function on async provider.") + } + } } /// Callback for when the provider is started. - fn run_sync( + /// + /// # Panics + /// + /// Panics if wrong runtime type is used. + async fn start_async( &mut self, _emit_result_tx: mpsc::Sender, _stop_rx: mpsc::Receiver<()>, ) { - // TODO: Change to not implemented exception. - todo!() + match self.runtime_type() { + RuntimeType::Async => { + unreachable!("Async providers must implement `start_async`.") + } + RuntimeType::Sync => { + panic!("Cannot call async function on sync provider.") + } + } + } + + /// Runs the given function. + /// + /// # Panics + /// + /// Panics if wrong runtime type is used. + fn call_function_sync( + &self, + function: ProviderFunction, + ) -> anyhow::Result { + match self.runtime_type() { + RuntimeType::Sync => { + unreachable!("Sync providers must implement `call_function_sync`.") + } + RuntimeType::Async => { + panic!("Cannot call sync function on async provider.") + } + } } - /// Callback for when the provider is stopped. - async fn on_stop(&self) { - // No-op by default. + /// Runs the given function. + /// + /// # Panics + /// + /// Panics if wrong runtime type is used. + async fn call_function_async( + &self, + function: ProviderFunction, + ) -> anyhow::Result { + match self.runtime_type() { + RuntimeType::Async => { + unreachable!( + "Async providers must implement `call_function_async`." + ) + } + RuntimeType::Sync => { + panic!("Cannot call async function on sync provider.") + } + } } } /// Determines whether `run_sync` or `run_async` is called.` -pub enum ThreadingType { +pub enum RuntimeType { Sync, Async, } @@ -48,8 +103,8 @@ macro_rules! impl_interval_provider { ($type:ty, $allow_identical_emits:expr) => { #[async_trait::async_trait] impl crate::providers::Provider for $type { - fn threading_type(&self) -> crate::providers::ThreadingType { - crate::providers::ThreadingType::Async + fn runtime_type(&self) -> crate::providers::RuntimeType { + crate::providers::RuntimeType::Async } async fn run_async( diff --git a/packages/desktop/src/providers/provider_ref.rs b/packages/desktop/src/providers/provider_ref.rs index c1f1fcff..59b7ef45 100644 --- a/packages/desktop/src/providers/provider_ref.rs +++ b/packages/desktop/src/providers/provider_ref.rs @@ -19,7 +19,7 @@ use super::{ battery::BatteryProvider, cpu::CpuProvider, disk::DiskProvider, host::HostProvider, ip::IpProvider, memory::MemoryProvider, network::NetworkProvider, weather::WeatherProvider, Provider, - ProviderConfig, ProviderOutput, SharedProviderState, ThreadingType, + ProviderConfig, ProviderOutput, RuntimeType, SharedProviderState, }; /// Reference to an active provider. @@ -132,13 +132,13 @@ impl ProviderRef { ) -> anyhow::Result<()> { let mut provider = Self::create_provider(config, shared_state)?; - let _ = match provider.threading_type() { - ThreadingType::Async => task::spawn(async move { - provider.run_async(emit_result_tx, stop_rx).await; + let _ = match provider.runtime_type() { + RuntimeType::Async => task::spawn(async move { + provider.start_async(emit_result_tx, stop_rx).await; info!("Provider stopped: {}", config_hash); }), - ThreadingType::Sync => task::spawn_blocking(move || { - provider.run_sync(emit_result_tx, stop_rx); + RuntimeType::Sync => task::spawn_blocking(move || { + provider.start_sync(emit_result_tx, stop_rx); info!("Provider stopped: {}", config_hash); }), }; From a148da0027686420338136bf01acfb77b259608b Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 16 Nov 2024 18:05:07 +0800 Subject: [PATCH 04/38] wip add `call_function` on provider manager --- packages/desktop/src/providers/provider.rs | 14 ++----- .../desktop/src/providers/provider_manager.rs | 38 ++++++++++++++----- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index 0ca2c58d..b266ad93 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -12,11 +12,7 @@ pub trait Provider: Send + Sync { /// # Panics /// /// Panics if wrong runtime type is used. - fn start_sync( - &mut self, - _emit_result_tx: mpsc::Sender, - _stop_rx: mpsc::Receiver<()>, - ) { + fn start_sync(&mut self) { match self.runtime_type() { RuntimeType::Sync => { unreachable!("Sync providers must implement `start_sync`.") @@ -32,11 +28,7 @@ pub trait Provider: Send + Sync { /// # Panics /// /// Panics if wrong runtime type is used. - async fn start_async( - &mut self, - _emit_result_tx: mpsc::Sender, - _stop_rx: mpsc::Receiver<()>, - ) { + async fn start_async(&mut self) { match self.runtime_type() { RuntimeType::Async => { unreachable!("Async providers must implement `start_async`.") @@ -88,7 +80,7 @@ pub trait Provider: Send + Sync { } } -/// Determines whether `run_sync` or `run_async` is called.` +/// Determines whether `start_sync` or `start_async` is called. pub enum RuntimeType { Sync, Async, diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 73b61d35..fbceeea8 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, sync::Arc}; +use anyhow::Context; use sysinfo::{Disks, Networks, System}; use tauri::AppHandle; use tokio::sync::Mutex; @@ -11,14 +12,12 @@ use super::{ProviderConfig, ProviderRef}; #[derive(Clone)] pub struct SharedProviderState { pub sysinfo: Arc>, - pub netinfo: Arc>, - pub diskinfo: Arc>, } /// Manages the creation and cleanup of providers. pub struct ProviderManager { app_handle: AppHandle, - providers: Arc>>, + provider_refs: Arc>>, shared_state: SharedProviderState, } @@ -26,11 +25,9 @@ impl ProviderManager { pub fn new(app_handle: &AppHandle) -> Self { Self { app_handle: app_handle.clone(), - providers: Arc::new(Mutex::new(HashMap::new())), + provider_refs: Arc::new(Mutex::new(HashMap::new())), shared_state: SharedProviderState { sysinfo: Arc::new(Mutex::new(System::new_all())), - netinfo: Arc::new(Mutex::new(Networks::new_with_refreshed_list())), - diskinfo: Arc::new(Mutex::new(Disks::new_with_refreshed_list())), }, } } @@ -42,7 +39,7 @@ impl ProviderManager { config: ProviderConfig, ) -> anyhow::Result<()> { { - let mut providers = self.providers.lock().await; + let mut providers = self.provider_refs.lock().await; // If a provider with the given config already exists, refresh it // and return early. @@ -63,15 +60,38 @@ impl ProviderManager { ) .await?; - let mut providers = self.providers.lock().await; + let mut providers = self.provider_refs.lock().await; providers.insert(config_hash, provider_ref); Ok(()) } + /// Calls the given function on the provider with the given config hash. + async fn call_function( + &self, + config_hash: &str, + function: ProviderFunction, + ) -> anyhow::Result { + let mut providers = self.provider_refs.lock().await; + let found_provider = providers + .get_mut(&config_hash) + .context("No provider found with config.")?; + + match found_provider.runtime_type() { + RuntimeType::Async => { + found_provider.call_async_function(function).await + } + RuntimeType::Sync => task::spawn_blocking(move || { + found_provider.call_sync_function(function) + }) + .await + .map_err(|err| format!("Function execution failed: {}", err))?, + } + } + /// Destroys and cleans up the provider with the given config. pub async fn destroy(&self, config_hash: String) -> anyhow::Result<()> { - let mut providers = self.providers.lock().await; + let mut providers = self.provider_refs.lock().await; if let Some(found_provider) = providers.get_mut(&config_hash) { if let Err(err) = found_provider.stop().await { From 9a104374cde0055939c6c3b50da7b20707075754 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 16 Nov 2024 19:51:51 +0800 Subject: [PATCH 05/38] feat: move provider emit listener to event listener loop --- packages/desktop/src/main.rs | 24 +++++- .../src/providers/disk/disk_provider.rs | 14 +-- .../providers/komorebi/komorebi_provider.rs | 9 +- .../src/providers/media/media_provider.rs | 21 +++-- .../src/providers/network/network_provider.rs | 10 +-- packages/desktop/src/providers/provider.rs | 13 +-- .../src/providers/provider_function.rs | 12 +++ .../desktop/src/providers/provider_manager.rs | 86 ++++++++++++------- .../desktop/src/providers/provider_ref.rs | 69 ++++----------- packages/desktop/src/sys_tray.rs | 4 +- 10 files changed, 139 insertions(+), 123 deletions(-) diff --git a/packages/desktop/src/main.rs b/packages/desktop/src/main.rs index c5f4be96..e7baee5a 100644 --- a/packages/desktop/src/main.rs +++ b/packages/desktop/src/main.rs @@ -173,8 +173,8 @@ async fn start_app(app: &mut tauri::App, cli: Cli) -> anyhow::Result<()> { app.handle().plugin(tauri_plugin_dialog::init())?; // Initialize `ProviderManager` in Tauri state. - let manager = Arc::new(ProviderManager::new(app.handle())); - app.manage(manager); + let (manager, emit_rx) = ProviderManager::new(app.handle()); + app.manage(manager.clone()); // Open widgets based on CLI command. open_widgets_by_cli_command(cli, widget_factory.clone()).await?; @@ -184,7 +184,15 @@ async fn start_app(app: &mut tauri::App, cli: Cli) -> anyhow::Result<()> { SysTray::new(app.handle(), config.clone(), widget_factory.clone()) .await?; - listen_events(app.handle(), config, monitor_state, widget_factory, tray); + listen_events( + app.handle(), + config, + monitor_state, + widget_factory, + tray, + manager, + emit_rx, + ); Ok(()) } @@ -194,7 +202,9 @@ fn listen_events( config: Arc, monitor_state: Arc, widget_factory: Arc, - tray: Arc, + tray: SysTray, + manager: Arc, + emit_rx: mpsc::Receiver, ) { let app_handle = app_handle.clone(); let mut widget_open_rx = widget_factory.open_tx.subscribe(); @@ -231,6 +241,12 @@ fn listen_events( info!("Widget configs changed."); widget_factory.relaunch_by_paths(&changed_configs.keys().cloned().collect()).await }, + Ok(provider_emit) = emit_rx.recv() => { + info!("Provider emission: {:?}", provider_emit); + app_handle.emit("provider-emit", provider_emit); + manager.update_cache(provider_emit).await; + Ok(()) + }, }; if let Err(err) = res { diff --git a/packages/desktop/src/providers/disk/disk_provider.rs b/packages/desktop/src/providers/disk/disk_provider.rs index 19f86153..320d6349 100644 --- a/packages/desktop/src/providers/disk/disk_provider.rs +++ b/packages/desktop/src/providers/disk/disk_provider.rs @@ -36,7 +36,7 @@ pub struct Disk { pub struct DiskProvider { config: DiskProviderConfig, - system: Arc>, + disks: Arc>, } #[derive(Debug, Clone, PartialEq, Serialize)] @@ -50,11 +50,11 @@ pub struct DiskSizeMeasure { } impl DiskProvider { - pub fn new( - config: DiskProviderConfig, - system: Arc>, - ) -> DiskProvider { - DiskProvider { config, system } + pub fn new(config: DiskProviderConfig) -> DiskProvider { + DiskProvider { + config, + disks: Arc::new(Mutex::new(Disks::new_with_refreshed_list())), + } } fn refresh_interval_ms(&self) -> u64 { @@ -62,7 +62,7 @@ impl DiskProvider { } async fn run_interval(&self) -> anyhow::Result { - let mut disks = self.system.lock().await; + let mut disks = self.disks.lock().await; disks.refresh(); let disks = disks diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index cd84a431..745131af 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -16,7 +16,7 @@ use super::{ KomorebiContainer, KomorebiLayout, KomorebiLayoutFlip, KomorebiMonitor, KomorebiWindow, KomorebiWorkspace, }; -use crate::providers::{Provider, ProviderOutput, ProviderResult}; +use crate::providers::{Provider, ProviderEmission, ProviderOutput}; const SOCKET_NAME: &str = "zebar.sock"; @@ -42,7 +42,7 @@ impl KomorebiProvider { fn create_socket( &self, - emit_result_tx: Sender, + emit_result_tx: mpsc::UnboundedSender, ) -> anyhow::Result<()> { let socket = komorebi_client::subscribe(SOCKET_NAME) .context("Failed to initialize Komorebi socket.")?; @@ -187,7 +187,10 @@ impl Provider for KomorebiProvider { RuntimeType::Sync } - fn start_sync(&mut self, emit_result_tx: Sender) { + fn start_sync( + &mut self, + emit_result_tx: mpsc::UnboundedSender, + ) { if let Err(err) = self.create_socket(emit_result_tx.clone()) { emit_result_tx.try_send(Err(err).into()); } diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index e3f80974..a1fb03bf 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -5,7 +5,10 @@ use std::{ use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use tokio::{sync::mpsc::Sender, task}; +use tokio::{ + sync::mpsc::{self, Sender}, + task, +}; use tracing::{debug, error}; use windows::{ Foundation::{EventRegistrationToken, TypedEventHandler}, @@ -16,7 +19,9 @@ use windows::{ }, }; -use crate::providers::{Provider, ProviderOutput, ProviderResult}; +use crate::providers::{ + Provider, ProviderEmission, ProviderOutput, RuntimeType, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -60,7 +65,7 @@ impl MediaProvider { fn emit_media_info( session: Option<&GsmtcSession>, - emit_result_tx: Sender, + emit_result_tx: mpsc::UnboundedSender, ) { let _ = match Self::media_output(session) { Ok(media_output) => emit_result_tx @@ -124,7 +129,7 @@ impl MediaProvider { } fn create_session_manager( - emit_result_tx: Sender, + emit_result_tx: mpsc::UnboundedSender, ) -> anyhow::Result<()> { debug!("Creating media session manager."); @@ -216,7 +221,7 @@ impl MediaProvider { fn add_session_listeners( session: &GsmtcSession, - emit_result_tx: Sender, + emit_result_tx: mpsc::UnboundedSender, ) -> windows::core::Result { debug!("Adding session listeners."); @@ -281,7 +286,11 @@ impl MediaProvider { #[async_trait] impl Provider for MediaProvider { - async fn run(&self, emit_result_tx: Sender) { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Async + } + + async fn start_async(&mut self) { task::spawn_blocking(move || { if let Err(err) = Self::create_session_manager(emit_result_tx.clone()) diff --git a/packages/desktop/src/providers/network/network_provider.rs b/packages/desktop/src/providers/network/network_provider.rs index 48c36176..29734ef5 100644 --- a/packages/desktop/src/providers/network/network_provider.rs +++ b/packages/desktop/src/providers/network/network_provider.rs @@ -36,11 +36,11 @@ pub struct NetworkProvider { } impl NetworkProvider { - pub fn new( - config: NetworkProviderConfig, - netinfo: Arc>, - ) -> NetworkProvider { - NetworkProvider { config, netinfo } + pub fn new(config: NetworkProviderConfig) -> NetworkProvider { + NetworkProvider { + config, + netinfo: Arc::new(Mutex::new(Networks::new_with_refreshed_list())), + } } fn refresh_interval_ms(&self) -> u64 { diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index b266ad93..526bd1c9 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -1,7 +1,6 @@ use async_trait::async_trait; -use tokio::sync::mpsc; -use super::ProviderResult; +use super::{ProviderFunction, ProviderFunctionResult}; #[async_trait] pub trait Provider: Send + Sync { @@ -99,13 +98,7 @@ macro_rules! impl_interval_provider { crate::providers::RuntimeType::Async } - async fn run_async( - &mut self, - emit_result_tx: tokio::sync::mpsc::Sender< - crate::providers::ProviderResult, - >, - _stop_rx: tokio::sync::mpsc::Receiver<()>, - ) { + async fn start_async(&mut self) { let mut interval = tokio::time::interval( std::time::Duration::from_millis(self.refresh_interval_ms()), ); @@ -116,7 +109,7 @@ macro_rules! impl_interval_provider { .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); let mut last_interval_res: Option< - crate::providers::ProviderResult, + crate::providers::ProviderEmission, > = None; loop { diff --git a/packages/desktop/src/providers/provider_function.rs b/packages/desktop/src/providers/provider_function.rs index 0138f122..2c53d0ce 100644 --- a/packages/desktop/src/providers/provider_function.rs +++ b/packages/desktop/src/providers/provider_function.rs @@ -11,3 +11,15 @@ pub enum MediaFunction { Next, Previous, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ProviderFunctionResult { + Media(MediaFunctionResult), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MediaFunctionResult { + PlayPause(bool), + Next(bool), + Previous(bool), +} diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index fbceeea8..9eb8ca85 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -1,12 +1,18 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::Context; -use sysinfo::{Disks, Networks, System}; -use tauri::AppHandle; -use tokio::sync::Mutex; +use sysinfo::System; +use tauri::{AppHandle, Emitter}; +use tokio::{ + sync::{mpsc, Mutex}, + task, +}; use tracing::warn; -use super::{ProviderConfig, ProviderRef}; +use super::{ + ProviderConfig, ProviderEmission, ProviderFunction, + ProviderFunctionResult, ProviderRef, RuntimeType, +}; /// State shared between providers. #[derive(Clone)] @@ -18,18 +24,33 @@ pub struct SharedProviderState { pub struct ProviderManager { app_handle: AppHandle, provider_refs: Arc>>, + emit_cache: Arc>>, shared_state: SharedProviderState, + emit_tx: mpsc::UnboundedSender, } impl ProviderManager { - pub fn new(app_handle: &AppHandle) -> Self { - Self { - app_handle: app_handle.clone(), - provider_refs: Arc::new(Mutex::new(HashMap::new())), - shared_state: SharedProviderState { - sysinfo: Arc::new(Mutex::new(System::new_all())), - }, - } + /// Creates a new provider manager. + /// + /// Returns a tuple containing the manager and a channel for provider + /// emissions. + pub fn new( + app_handle: &AppHandle, + ) -> (Arc, mpsc::UnboundedReceiver) { + let (emit_tx, emit_rx) = mpsc::unbounded_channel::(); + + ( + Arc::new(Self { + app_handle: app_handle.clone(), + provider_refs: Arc::new(Mutex::new(HashMap::new())), + emit_cache: Arc::new(Mutex::new(HashMap::new())), + shared_state: SharedProviderState { + sysinfo: Arc::new(Mutex::new(System::new_all())), + }, + emit_tx, + }), + emit_rx, + ) } /// Creates a provider with the given config. @@ -38,22 +59,19 @@ impl ProviderManager { config_hash: String, config: ProviderConfig, ) -> anyhow::Result<()> { + // If a provider with the given config already exists, re-emit its + // latest emission and return early. { - let mut providers = self.provider_refs.lock().await; - - // If a provider with the given config already exists, refresh it - // and return early. - if let Some(found_provider) = providers.get_mut(&config_hash) { - if let Err(err) = found_provider.refresh().await { - warn!("Error refreshing provider: {:?}", err); - } - + if let Some(found_emit) = + self.emit_cache.lock().await.get(&config_hash) + { + self.app_handle.emit("provider-emit", found_emit); return Ok(()); }; } let provider_ref = ProviderRef::new( - &self.app_handle, + self.emit_tx.clone(), config, config_hash.clone(), self.shared_state.clone(), @@ -73,19 +91,17 @@ impl ProviderManager { function: ProviderFunction, ) -> anyhow::Result { let mut providers = self.provider_refs.lock().await; - let found_provider = providers - .get_mut(&config_hash) + let provider = providers + .get_mut(config_hash) .context("No provider found with config.")?; - match found_provider.runtime_type() { - RuntimeType::Async => { - found_provider.call_async_function(function).await + match provider.runtime_type() { + RuntimeType::Async => provider.call_async_function(function).await, + RuntimeType::Sync => { + task::spawn_blocking(move || provider.call_sync_function(function)) + .await + .map_err(|err| format!("Function execution failed: {}", err))? } - RuntimeType::Sync => task::spawn_blocking(move || { - found_provider.call_sync_function(function) - }) - .await - .map_err(|err| format!("Function execution failed: {}", err))?, } } @@ -103,4 +119,10 @@ impl ProviderManager { Ok(()) } + + /// Updates the cache with the given provider emission. + pub async fn update_cache(&self, emit: ProviderEmission) { + let mut cache = self.emit_cache.lock().await; + cache.insert(emit.config_hash, emit); + } } diff --git a/packages/desktop/src/providers/provider_ref.rs b/packages/desktop/src/providers/provider_ref.rs index 59b7ef45..7b0cb2cf 100644 --- a/packages/desktop/src/providers/provider_ref.rs +++ b/packages/desktop/src/providers/provider_ref.rs @@ -5,7 +5,10 @@ use serde::Serialize; use serde_json::json; use tauri::{AppHandle, Emitter}; use tokio::{ - sync::{mpsc, Mutex}, + sync::{ + mpsc::{self, UnboundedSender}, + Mutex, + }, task, }; use tracing::{info, warn}; @@ -25,11 +28,11 @@ use super::{ /// Reference to an active provider. pub struct ProviderRef { /// Cache for provider output. - cache: Arc>>>, + cache: Arc>>>, /// Sender channel for emitting provider output/error to frontend /// clients. - emit_result_tx: mpsc::Sender, + emit_result_tx: mpsc::UnboundedSender, /// Sender channel for stopping the provider. stop_tx: mpsc::Sender<()>, @@ -41,17 +44,17 @@ pub struct ProviderRef { /// in a nicer way. #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub enum ProviderResult { +pub enum ProviderEmission { Output(ProviderOutput), Error(String), } /// Implements conversion from `anyhow::Result`. -impl From> for ProviderResult { +impl From> for ProviderEmission { fn from(result: anyhow::Result) -> Self { match result { - Ok(output) => ProviderResult::Output(output), - Err(err) => ProviderResult::Error(err.to_string()), + Ok(output) => ProviderEmission::Output(output), + Err(err) => ProviderEmission::Error(err.to_string()), } } } @@ -59,7 +62,7 @@ impl From> for ProviderResult { impl ProviderRef { /// Creates a new `ProviderRef` instance. pub async fn new( - app_handle: &AppHandle, + emit_result_tx: UnboundedSender, config: ProviderConfig, config_hash: String, shared_state: SharedProviderState, @@ -67,15 +70,6 @@ impl ProviderRef { let cache = Arc::new(Mutex::new(None)); let (stop_tx, stop_rx) = mpsc::channel::<()>(1); - let (emit_result_tx, emit_result_rx) = - mpsc::channel::(1); - - Self::start_output_listener( - app_handle.clone(), - config_hash.clone(), - cache.clone(), - emit_result_rx, - ); Self::start_provider( config, @@ -92,42 +86,12 @@ impl ProviderRef { }) } - fn start_output_listener( - app_handle: AppHandle, - config_hash: String, - cache: Arc>>>, - mut emit_result_rx: mpsc::Receiver, - ) { - task::spawn(async move { - while let Some(output) = emit_result_rx.recv().await { - info!("Emitting for provider: {}", config_hash); - - let output = Box::new(output); - let payload = json!({ - "configHash": config_hash.clone(), - "result": *output.clone(), - }); - - if let Err(err) = app_handle.emit("provider-emit", payload) { - warn!("Error emitting provider output: {:?}", err); - } - - // Update the provider's output cache. - if let Ok(mut providers) = cache.try_lock() { - *providers = Some(output); - } else { - warn!("Failed to update provider output cache."); - } - } - }); - } - /// Starts the provider in a separate task. fn start_provider( config: ProviderConfig, config_hash: String, shared_state: SharedProviderState, - emit_result_tx: mpsc::Sender, + emit_result_tx: mpsc::UnboundedSender, mut stop_rx: mpsc::Receiver<()>, ) -> anyhow::Result<()> { let mut provider = Self::create_provider(config, shared_state)?; @@ -174,13 +138,10 @@ impl ProviderRef { ProviderConfig::Memory(config) => { Box::new(MemoryProvider::new(config, shared_state.sysinfo.clone())) } - ProviderConfig::Disk(config) => { - Box::new(DiskProvider::new(config, shared_state.diskinfo.clone())) + ProviderConfig::Disk(config) => Box::new(DiskProvider::new(config)), + ProviderConfig::Network(config) => { + Box::new(NetworkProvider::new(config)) } - ProviderConfig::Network(config) => Box::new(NetworkProvider::new( - config, - shared_state.netinfo.clone(), - )), ProviderConfig::Weather(config) => { Box::new(WeatherProvider::new(config)) } diff --git a/packages/desktop/src/sys_tray.rs b/packages/desktop/src/sys_tray.rs index 8e2ec538..d83d580b 100644 --- a/packages/desktop/src/sys_tray.rs +++ b/packages/desktop/src/sys_tray.rs @@ -116,7 +116,7 @@ impl SysTray { app_handle: &AppHandle, config: Arc, widget_factory: Arc, - ) -> anyhow::Result> { + ) -> anyhow::Result { let mut sys_tray = Self { app_handle: app_handle.clone(), config, @@ -126,7 +126,7 @@ impl SysTray { sys_tray.tray_icon = Some(sys_tray.create_tray_icon().await?); - Ok(Arc::new(sys_tray)) + Ok(sys_tray) } async fn create_tray_icon(&self) -> anyhow::Result { From fe13508673cbf93ec8c2f747bbf479fd9d4b2da0 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 16 Nov 2024 20:24:10 +0800 Subject: [PATCH 06/38] feat: change destroy and call_function to use channels --- packages/desktop/src/commands.rs | 4 +- .../desktop/src/providers/provider_manager.rs | 53 ++++++++----------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/packages/desktop/src/commands.rs b/packages/desktop/src/commands.rs index 01ffb595..b54a2c6a 100644 --- a/packages/desktop/src/commands.rs +++ b/packages/desktop/src/commands.rs @@ -87,7 +87,7 @@ pub async fn listen_provider( provider_manager: State<'_, Arc>, ) -> anyhow::Result<(), String> { provider_manager - .create(config_hash, config) + .create(&config_hash, config) .await .map_err(|err| err.to_string()) } @@ -98,7 +98,7 @@ pub async fn unlisten_provider( provider_manager: State<'_, Arc>, ) -> anyhow::Result<(), String> { provider_manager - .destroy(config_hash) + .destroy(&config_hash) .await .map_err(|err| err.to_string()) } diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 9eb8ca85..81a5c943 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -3,15 +3,11 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::Context; use sysinfo::System; use tauri::{AppHandle, Emitter}; -use tokio::{ - sync::{mpsc, Mutex}, - task, -}; -use tracing::warn; +use tokio::sync::{mpsc, oneshot, Mutex}; use super::{ ProviderConfig, ProviderEmission, ProviderFunction, - ProviderFunctionResult, ProviderRef, RuntimeType, + ProviderFunctionResult, ProviderRef, }; /// State shared between providers. @@ -56,14 +52,14 @@ impl ProviderManager { /// Creates a provider with the given config. pub async fn create( &self, - config_hash: String, + config_hash: &str, config: ProviderConfig, ) -> anyhow::Result<()> { // If a provider with the given config already exists, re-emit its // latest emission and return early. { if let Some(found_emit) = - self.emit_cache.lock().await.get(&config_hash) + self.emit_cache.lock().await.get(config_hash) { self.app_handle.emit("provider-emit", found_emit); return Ok(()); @@ -84,40 +80,35 @@ impl ProviderManager { Ok(()) } - /// Calls the given function on the provider with the given config hash. + /// Sends a function call through a channel to be executed by the + /// provider. + /// + /// Returns the result of the function execution. async fn call_function( &self, config_hash: &str, function: ProviderFunction, ) -> anyhow::Result { - let mut providers = self.provider_refs.lock().await; - let provider = providers - .get_mut(config_hash) + let mut provider_refs = self.provider_refs.lock().await; + let provider_ref = provider_refs + .get(config_hash) .context("No provider found with config.")?; - match provider.runtime_type() { - RuntimeType::Async => provider.call_async_function(function).await, - RuntimeType::Sync => { - task::spawn_blocking(move || provider.call_sync_function(function)) - .await - .map_err(|err| format!("Function execution failed: {}", err))? - } - } + let (tx, rx) = oneshot::channel(); + provider_ref.function_tx.send((function, tx))?; + rx.await? } /// Destroys and cleans up the provider with the given config. - pub async fn destroy(&self, config_hash: String) -> anyhow::Result<()> { - let mut providers = self.provider_refs.lock().await; - - if let Some(found_provider) = providers.get_mut(&config_hash) { - if let Err(err) = found_provider.stop().await { - warn!("Error stopping provider: {:?}", err); - } - } - - providers.remove(&config_hash); + pub async fn destroy(&self, config_hash: &str) -> anyhow::Result<()> { + let mut provider_refs = self.provider_refs.lock().await; + let provider_ref = provider_refs + .remove(config_hash) + .context("No provider found with config.")?; - Ok(()) + let (tx, rx) = oneshot::channel(); + provider_ref.stop_tx.send(tx)?; + rx.await? } /// Updates the cache with the given provider emission. From cd27f156d80be5ce1f0570b78d661414e503282d Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 16 Nov 2024 21:03:31 +0800 Subject: [PATCH 07/38] wip moving out logic from `ProviderRef` --- packages/desktop/src/commands.rs | 2 +- .../desktop/src/providers/provider_manager.rs | 110 ++++++++++++++++-- .../desktop/src/providers/provider_ref.rs | 4 +- 3 files changed, 100 insertions(+), 16 deletions(-) diff --git a/packages/desktop/src/commands.rs b/packages/desktop/src/commands.rs index b54a2c6a..43cf0adf 100644 --- a/packages/desktop/src/commands.rs +++ b/packages/desktop/src/commands.rs @@ -98,7 +98,7 @@ pub async fn unlisten_provider( provider_manager: State<'_, Arc>, ) -> anyhow::Result<(), String> { provider_manager - .destroy(&config_hash) + .stop(&config_hash) .await .map_err(|err| err.to_string()) } diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 81a5c943..a118be66 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -1,15 +1,39 @@ use std::{collections::HashMap, sync::Arc}; -use anyhow::Context; +use anyhow::{bail, Context}; use sysinfo::System; use tauri::{AppHandle, Emitter}; -use tokio::sync::{mpsc, oneshot, Mutex}; +use tokio::{ + sync::{mpsc, oneshot, Mutex}, + task, +}; +use tracing::info; use super::{ + battery::BatteryProvider, cpu::CpuProvider, disk::DiskProvider, + host::HostProvider, ip::IpProvider, memory::MemoryProvider, + network::NetworkProvider, weather::WeatherProvider, Provider, ProviderConfig, ProviderEmission, ProviderFunction, - ProviderFunctionResult, ProviderRef, + ProviderFunctionResult, RuntimeType, +}; +#[cfg(windows)] +use super::{ + keyboard::KeyboardProvider, komorebi::KomorebiProvider, + media::MediaProvider, }; +/// Reference to an active provider. +pub struct ProviderRef { + /// Sender channel for stopping the provider. + destroy_tx: mpsc::Sender<()>, + + /// Sender channel for sending function calls to the provider. + function_tx: mpsc::Sender<( + ProviderFunction, + oneshot::Sender, + )>, +} + /// State shared between providers. #[derive(Clone)] pub struct SharedProviderState { @@ -18,10 +42,19 @@ pub struct SharedProviderState { /// Manages the creation and cleanup of providers. pub struct ProviderManager { + /// Handle to the Tauri application. app_handle: AppHandle, + + /// Map of active provider refs. provider_refs: Arc>>, + + /// Cache of provider emissions. emit_cache: Arc>>, + + /// Shared state between providers. shared_state: SharedProviderState, + + /// Sender channel for provider emissions. emit_tx: mpsc::UnboundedSender, } @@ -66,20 +99,73 @@ impl ProviderManager { }; } - let provider_ref = ProviderRef::new( - self.emit_tx.clone(), - config, - config_hash.clone(), - self.shared_state.clone(), - ) - .await?; + let (stop_tx, stop_rx) = mpsc::channel::<()>(1); + let mut provider = self.create_provider(config, stop_rx)?; + + let task_handle = match provider.runtime_type() { + RuntimeType::Async => task::spawn(async move { + provider.start_async().await; + info!("Provider stopped: {}", config_hash); + }), + RuntimeType::Sync => task::spawn_blocking(move || { + provider.start_sync(); + info!("Provider stopped: {}", config_hash); + }), + }; + + let provider_ref = ProviderRef { + destroy_tx: self.emit_tx.clone(), + function_tx: self.emit_tx.clone(), + }; let mut providers = self.provider_refs.lock().await; - providers.insert(config_hash, provider_ref); + providers.insert(config_hash.to_string(), provider_ref); Ok(()) } + fn create_provider( + &self, + config: ProviderConfig, + ) -> anyhow::Result> { + let provider: Box = match config { + ProviderConfig::Battery(config) => { + Box::new(BatteryProvider::new(config)) + } + ProviderConfig::Cpu(config) => { + Box::new(CpuProvider::new(config, shared_state.sysinfo.clone())) + } + ProviderConfig::Host(config) => Box::new(HostProvider::new(config)), + ProviderConfig::Ip(config) => Box::new(IpProvider::new(config)), + #[cfg(windows)] + ProviderConfig::Komorebi(config) => { + Box::new(KomorebiProvider::new(config)) + } + #[cfg(windows)] + ProviderConfig::Media(config) => { + Box::new(MediaProvider::new(config)) + } + ProviderConfig::Memory(config) => { + Box::new(MemoryProvider::new(config, shared_state.sysinfo.clone())) + } + ProviderConfig::Disk(config) => Box::new(DiskProvider::new(config)), + ProviderConfig::Network(config) => { + Box::new(NetworkProvider::new(config)) + } + ProviderConfig::Weather(config) => { + Box::new(WeatherProvider::new(config)) + } + #[cfg(windows)] + ProviderConfig::Keyboard(config) => { + Box::new(KeyboardProvider::new(config)) + } + #[allow(unreachable_patterns)] + _ => bail!("Provider not supported on this operating system."), + }; + + Ok(provider) + } + /// Sends a function call through a channel to be executed by the /// provider. /// @@ -100,7 +186,7 @@ impl ProviderManager { } /// Destroys and cleans up the provider with the given config. - pub async fn destroy(&self, config_hash: &str) -> anyhow::Result<()> { + pub async fn stop(&self, config_hash: &str) -> anyhow::Result<()> { let mut provider_refs = self.provider_refs.lock().await; let provider_ref = provider_refs .remove(config_hash) diff --git a/packages/desktop/src/providers/provider_ref.rs b/packages/desktop/src/providers/provider_ref.rs index 7b0cb2cf..a0678a20 100644 --- a/packages/desktop/src/providers/provider_ref.rs +++ b/packages/desktop/src/providers/provider_ref.rs @@ -2,8 +2,6 @@ use std::sync::Arc; use anyhow::bail; use serde::Serialize; -use serde_json::json; -use tauri::{AppHandle, Emitter}; use tokio::{ sync::{ mpsc::{self, UnboundedSender}, @@ -11,7 +9,7 @@ use tokio::{ }, task, }; -use tracing::{info, warn}; +use tracing::info; #[cfg(windows)] use super::{ From 4d4a58d586d3c404463faaa1076d302419febe5c Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 16 Nov 2024 22:24:40 +0800 Subject: [PATCH 08/38] feat: remove `provider_ref.rs`; add all logic to provider manager --- packages/desktop/src/commands.rs | 4 +- .../src/providers/media/media_provider.rs | 2 +- .../desktop/src/providers/provider_manager.rs | 97 ++++++---- .../desktop/src/providers/provider_ref.rs | 179 ------------------ 4 files changed, 66 insertions(+), 216 deletions(-) delete mode 100644 packages/desktop/src/providers/provider_ref.rs diff --git a/packages/desktop/src/commands.rs b/packages/desktop/src/commands.rs index 43cf0adf..5500ce75 100644 --- a/packages/desktop/src/commands.rs +++ b/packages/desktop/src/commands.rs @@ -87,7 +87,7 @@ pub async fn listen_provider( provider_manager: State<'_, Arc>, ) -> anyhow::Result<(), String> { provider_manager - .create(&config_hash, config) + .create(config_hash, config) .await .map_err(|err| err.to_string()) } @@ -98,7 +98,7 @@ pub async fn unlisten_provider( provider_manager: State<'_, Arc>, ) -> anyhow::Result<(), String> { provider_manager - .stop(&config_hash) + .stop(config_hash) .await .map_err(|err| err.to_string()) } diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index a1fb03bf..c6e29faf 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -6,7 +6,7 @@ use std::{ use async_trait::async_trait; use serde::{Deserialize, Serialize}; use tokio::{ - sync::mpsc::{self, Sender}, + sync::mpsc::{self}, task, }; use tracing::{debug, error}; diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index a118be66..3c372b25 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::{bail, Context}; +use serde::Serialize; use sysinfo::System; use tauri::{AppHandle, Emitter}; use tokio::{ @@ -13,8 +14,8 @@ use super::{ battery::BatteryProvider, cpu::CpuProvider, disk::DiskProvider, host::HostProvider, ip::IpProvider, memory::MemoryProvider, network::NetworkProvider, weather::WeatherProvider, Provider, - ProviderConfig, ProviderEmission, ProviderFunction, - ProviderFunctionResult, RuntimeType, + ProviderConfig, ProviderFunction, ProviderFunctionResult, + ProviderOutput, RuntimeType, }; #[cfg(windows)] use super::{ @@ -25,19 +26,37 @@ use super::{ /// Reference to an active provider. pub struct ProviderRef { /// Sender channel for stopping the provider. - destroy_tx: mpsc::Sender<()>, + stop_tx: mpsc::Sender<()>, /// Sender channel for sending function calls to the provider. function_tx: mpsc::Sender<( ProviderFunction, oneshot::Sender, )>, + + /// Handle to the provider's task. + task_handle: task::JoinHandle<()>, +} + +/// Provider output/error emitted to frontend clients. +/// +/// This is used instead of a normal `Result` type in order to serialize it +/// in a nicer way. +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum ProviderEmission { + Output(ProviderOutput), + Error(String), } -/// State shared between providers. -#[derive(Clone)] -pub struct SharedProviderState { - pub sysinfo: Arc>, +/// Implements conversion from `anyhow::Result`. +impl From> for ProviderEmission { + fn from(result: anyhow::Result) -> Self { + match result { + Ok(output) => ProviderEmission::Output(output), + Err(err) => ProviderEmission::Error(err.to_string()), + } + } } /// Manages the creation and cleanup of providers. @@ -51,18 +70,18 @@ pub struct ProviderManager { /// Cache of provider emissions. emit_cache: Arc>>, - /// Shared state between providers. - shared_state: SharedProviderState, - /// Sender channel for provider emissions. emit_tx: mpsc::UnboundedSender, + + /// Shared `sysinfo` instance. + sysinfo: Arc>, } impl ProviderManager { /// Creates a new provider manager. /// - /// Returns a tuple containing the manager and a channel for provider - /// emissions. + /// Returns a tuple containing the `ProviderManager` instance and a + /// channel for provider emissions. pub fn new( app_handle: &AppHandle, ) -> (Arc, mpsc::UnboundedReceiver) { @@ -73,9 +92,7 @@ impl ProviderManager { app_handle: app_handle.clone(), provider_refs: Arc::new(Mutex::new(HashMap::new())), emit_cache: Arc::new(Mutex::new(HashMap::new())), - shared_state: SharedProviderState { - sysinfo: Arc::new(Mutex::new(System::new_all())), - }, + sysinfo: Arc::new(Mutex::new(System::new_all())), emit_tx, }), emit_rx, @@ -85,22 +102,25 @@ impl ProviderManager { /// Creates a provider with the given config. pub async fn create( &self, - config_hash: &str, + config_hash: String, config: ProviderConfig, ) -> anyhow::Result<()> { // If a provider with the given config already exists, re-emit its // latest emission and return early. { if let Some(found_emit) = - self.emit_cache.lock().await.get(config_hash) + self.emit_cache.lock().await.get(&config_hash) { self.app_handle.emit("provider-emit", found_emit); return Ok(()); }; } - let (stop_tx, stop_rx) = mpsc::channel::<()>(1); - let mut provider = self.create_provider(config, stop_rx)?; + let (stop_tx, stop_rx) = mpsc::channel(1); + let (function_tx, function_rx) = mpsc::channel(1); + + let mut provider = + self.create_instance(config, stop_rx, function_rx)?; let task_handle = match provider.runtime_type() { RuntimeType::Async => task::spawn(async move { @@ -114,26 +134,33 @@ impl ProviderManager { }; let provider_ref = ProviderRef { - destroy_tx: self.emit_tx.clone(), - function_tx: self.emit_tx.clone(), + stop_tx, + function_tx, + task_handle, }; let mut providers = self.provider_refs.lock().await; - providers.insert(config_hash.to_string(), provider_ref); + providers.insert(config_hash.clone(), provider_ref); Ok(()) } - fn create_provider( + /// Creates a new provider instance. + fn create_instance( &self, config: ProviderConfig, + stop_rx: mpsc::Receiver<()>, + function_rx: mpsc::Receiver<( + ProviderFunction, + oneshot::Sender, + )>, ) -> anyhow::Result> { let provider: Box = match config { ProviderConfig::Battery(config) => { Box::new(BatteryProvider::new(config)) } ProviderConfig::Cpu(config) => { - Box::new(CpuProvider::new(config, shared_state.sysinfo.clone())) + Box::new(CpuProvider::new(config, self.sysinfo.clone())) } ProviderConfig::Host(config) => Box::new(HostProvider::new(config)), ProviderConfig::Ip(config) => Box::new(IpProvider::new(config)), @@ -146,7 +173,7 @@ impl ProviderManager { Box::new(MediaProvider::new(config)) } ProviderConfig::Memory(config) => { - Box::new(MemoryProvider::new(config, shared_state.sysinfo.clone())) + Box::new(MemoryProvider::new(config, self.sysinfo.clone())) } ProviderConfig::Disk(config) => Box::new(DiskProvider::new(config)), ProviderConfig::Network(config) => { @@ -172,29 +199,31 @@ impl ProviderManager { /// Returns the result of the function execution. async fn call_function( &self, - config_hash: &str, + config_hash: String, function: ProviderFunction, ) -> anyhow::Result { - let mut provider_refs = self.provider_refs.lock().await; + let provider_refs = self.provider_refs.lock().await; let provider_ref = provider_refs - .get(config_hash) + .get(&config_hash) .context("No provider found with config.")?; let (tx, rx) = oneshot::channel(); - provider_ref.function_tx.send((function, tx))?; - rx.await? + provider_ref.function_tx.send((function, tx)).await?; + + Ok(rx.await?) } /// Destroys and cleans up the provider with the given config. - pub async fn stop(&self, config_hash: &str) -> anyhow::Result<()> { + pub async fn stop(&self, config_hash: String) -> anyhow::Result<()> { let mut provider_refs = self.provider_refs.lock().await; let provider_ref = provider_refs - .remove(config_hash) + .remove(&config_hash) .context("No provider found with config.")?; let (tx, rx) = oneshot::channel(); - provider_ref.stop_tx.send(tx)?; - rx.await? + provider_ref.stop_tx.send(tx).await?; + + Ok(rx.await?) } /// Updates the cache with the given provider emission. diff --git a/packages/desktop/src/providers/provider_ref.rs b/packages/desktop/src/providers/provider_ref.rs deleted file mode 100644 index a0678a20..00000000 --- a/packages/desktop/src/providers/provider_ref.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::sync::Arc; - -use anyhow::bail; -use serde::Serialize; -use tokio::{ - sync::{ - mpsc::{self, UnboundedSender}, - Mutex, - }, - task, -}; -use tracing::info; - -#[cfg(windows)] -use super::{ - audio::AudioProvider, keyboard::KeyboardProvider, - komorebi::KomorebiProvider, media::MediaProvider, -}; -use super::{ - battery::BatteryProvider, cpu::CpuProvider, disk::DiskProvider, - host::HostProvider, ip::IpProvider, memory::MemoryProvider, - network::NetworkProvider, weather::WeatherProvider, Provider, - ProviderConfig, ProviderOutput, RuntimeType, SharedProviderState, -}; - -/// Reference to an active provider. -pub struct ProviderRef { - /// Cache for provider output. - cache: Arc>>>, - - /// Sender channel for emitting provider output/error to frontend - /// clients. - emit_result_tx: mpsc::UnboundedSender, - - /// Sender channel for stopping the provider. - stop_tx: mpsc::Sender<()>, -} - -/// Provider output/error emitted to frontend clients. -/// -/// This is used instead of a normal `Result` type in order to serialize it -/// in a nicer way. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum ProviderEmission { - Output(ProviderOutput), - Error(String), -} - -/// Implements conversion from `anyhow::Result`. -impl From> for ProviderEmission { - fn from(result: anyhow::Result) -> Self { - match result { - Ok(output) => ProviderEmission::Output(output), - Err(err) => ProviderEmission::Error(err.to_string()), - } - } -} - -impl ProviderRef { - /// Creates a new `ProviderRef` instance. - pub async fn new( - emit_result_tx: UnboundedSender, - config: ProviderConfig, - config_hash: String, - shared_state: SharedProviderState, - ) -> anyhow::Result { - let cache = Arc::new(Mutex::new(None)); - - let (stop_tx, stop_rx) = mpsc::channel::<()>(1); - - Self::start_provider( - config, - config_hash, - shared_state, - emit_result_tx.clone(), - stop_rx, - )?; - - Ok(Self { - cache, - emit_result_tx, - stop_tx, - }) - } - - /// Starts the provider in a separate task. - fn start_provider( - config: ProviderConfig, - config_hash: String, - shared_state: SharedProviderState, - emit_result_tx: mpsc::UnboundedSender, - mut stop_rx: mpsc::Receiver<()>, - ) -> anyhow::Result<()> { - let mut provider = Self::create_provider(config, shared_state)?; - - let _ = match provider.runtime_type() { - RuntimeType::Async => task::spawn(async move { - provider.start_async(emit_result_tx, stop_rx).await; - info!("Provider stopped: {}", config_hash); - }), - RuntimeType::Sync => task::spawn_blocking(move || { - provider.start_sync(emit_result_tx, stop_rx); - info!("Provider stopped: {}", config_hash); - }), - }; - - Ok(()) - } - - fn create_provider( - config: ProviderConfig, - shared_state: SharedProviderState, - ) -> anyhow::Result> { - let provider: Box = match config { - #[cfg(windows)] - ProviderConfig::Audio(config) => { - Box::new(AudioProvider::new(config)) - } - ProviderConfig::Battery(config) => { - Box::new(BatteryProvider::new(config)) - } - ProviderConfig::Cpu(config) => { - Box::new(CpuProvider::new(config, shared_state.sysinfo.clone())) - } - ProviderConfig::Host(config) => Box::new(HostProvider::new(config)), - ProviderConfig::Ip(config) => Box::new(IpProvider::new(config)), - #[cfg(windows)] - ProviderConfig::Komorebi(config) => { - Box::new(KomorebiProvider::new(config)) - } - #[cfg(windows)] - ProviderConfig::Media(config) => { - Box::new(MediaProvider::new(config)) - } - ProviderConfig::Memory(config) => { - Box::new(MemoryProvider::new(config, shared_state.sysinfo.clone())) - } - ProviderConfig::Disk(config) => Box::new(DiskProvider::new(config)), - ProviderConfig::Network(config) => { - Box::new(NetworkProvider::new(config)) - } - ProviderConfig::Weather(config) => { - Box::new(WeatherProvider::new(config)) - } - #[cfg(windows)] - ProviderConfig::Keyboard(config) => { - Box::new(KeyboardProvider::new(config)) - } - #[allow(unreachable_patterns)] - _ => bail!("Provider not supported on this operating system."), - }; - - Ok(provider) - } - - /// Re-emits the latest provider output. - /// - /// No-ops if the provider hasn't outputted yet, since the provider will - /// anyways emit its output after initialization. - pub async fn refresh(&self) -> anyhow::Result<()> { - let cache = { self.cache.lock().await.clone() }; - - if let Some(cache) = cache { - self.emit_result_tx.send(*cache).await?; - } - - Ok(()) - } - - /// Stops the given provider. - /// - /// This triggers any necessary cleanup. - pub async fn stop(&self) -> anyhow::Result<()> { - self.stop_tx.send(()).await?; - - Ok(()) - } -} From d2bdb66e0b7879fe7feac04711b9552e6772fd8c Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 16 Nov 2024 23:47:00 +0800 Subject: [PATCH 09/38] feat: add `CommonProviderState`; replace `ProviderEmission` with type --- packages/desktop/src/main.rs | 9 +- .../desktop/src/providers/cpu/cpu_provider.rs | 4 +- .../providers/komorebi/komorebi_provider.rs | 15 +-- .../src/providers/memory/memory_provider.rs | 4 +- packages/desktop/src/providers/provider.rs | 4 +- .../desktop/src/providers/provider_manager.rs | 102 ++++++++++-------- 6 files changed, 71 insertions(+), 67 deletions(-) diff --git a/packages/desktop/src/main.rs b/packages/desktop/src/main.rs index e7baee5a..740f6332 100644 --- a/packages/desktop/src/main.rs +++ b/packages/desktop/src/main.rs @@ -8,10 +8,11 @@ use std::{env, sync::Arc}; use clap::Parser; use cli::MonitorType; use config::{MonitorSelection, WidgetPlacement}; +use providers::ProviderEmission; use tauri::{ async_runtime::block_on, AppHandle, Emitter, Manager, RunEvent, }; -use tokio::task; +use tokio::{sync::mpsc, task}; use tracing::{error, info, level_filters::LevelFilter}; use tracing_subscriber::EnvFilter; use widget_factory::WidgetOpenOptions; @@ -204,7 +205,7 @@ fn listen_events( widget_factory: Arc, tray: SysTray, manager: Arc, - emit_rx: mpsc::Receiver, + mut emit_rx: mpsc::UnboundedReceiver, ) { let app_handle = app_handle.clone(); let mut widget_open_rx = widget_factory.open_tx.subscribe(); @@ -241,9 +242,9 @@ fn listen_events( info!("Widget configs changed."); widget_factory.relaunch_by_paths(&changed_configs.keys().cloned().collect()).await }, - Ok(provider_emit) = emit_rx.recv() => { + Some(provider_emit) = emit_rx.recv() => { info!("Provider emission: {:?}", provider_emit); - app_handle.emit("provider-emit", provider_emit); + app_handle.emit("provider-emit", provider_emit.clone()); manager.update_cache(provider_emit).await; Ok(()) }, diff --git a/packages/desktop/src/providers/cpu/cpu_provider.rs b/packages/desktop/src/providers/cpu/cpu_provider.rs index 5f9e6bdc..e257d46b 100644 --- a/packages/desktop/src/providers/cpu/cpu_provider.rs +++ b/packages/desktop/src/providers/cpu/cpu_provider.rs @@ -24,13 +24,13 @@ pub struct CpuOutput { pub struct CpuProvider { config: CpuProviderConfig, - sysinfo: Arc>, + sysinfo: Arc>, } impl CpuProvider { pub fn new( config: CpuProviderConfig, - sysinfo: Arc>, + sysinfo: Arc>, ) -> CpuProvider { CpuProvider { config, sysinfo } } diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index 745131af..34893ed9 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -9,7 +9,7 @@ use komorebi_client::{ Container, Monitor, SocketMessage, Window, Workspace, }; use serde::{Deserialize, Serialize}; -use tokio::{sync::mpsc::Sender, time}; +use tokio::sync::mpsc::{self}; use tracing::debug; use super::{ @@ -78,7 +78,7 @@ impl KomorebiProvider { &String::from_utf8(buffer).unwrap(), ) { - emit_result_tx.try_send( + emit_result_tx.send( Ok(ProviderOutput::Komorebi(Self::transform_response( notification.state, ))) @@ -86,14 +86,9 @@ impl KomorebiProvider { ) } } - Err(_) => { - emit_result_tx - .try_send( - Err(anyhow::anyhow!("Failed to read Komorebi stream.")) - .into(), - ) - .await; - } + Err(_) => emit_result_tx.send( + Err(anyhow::anyhow!("Failed to read Komorebi stream.")).into(), + ), } } diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index 077c8f4d..c76d8d3d 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -26,13 +26,13 @@ pub struct MemoryOutput { pub struct MemoryProvider { config: MemoryProviderConfig, - sysinfo: Arc>, + sysinfo: Arc>, } impl MemoryProvider { pub fn new( config: MemoryProviderConfig, - sysinfo: Arc>, + sysinfo: Arc>, ) -> MemoryProvider { MemoryProvider { config, sysinfo } } diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index 526bd1c9..7bc4a705 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -45,7 +45,7 @@ pub trait Provider: Send + Sync { /// Panics if wrong runtime type is used. fn call_function_sync( &self, - function: ProviderFunction, + #[allow(unused_variables)] function: ProviderFunction, ) -> anyhow::Result { match self.runtime_type() { RuntimeType::Sync => { @@ -64,7 +64,7 @@ pub trait Provider: Send + Sync { /// Panics if wrong runtime type is used. async fn call_function_async( &self, - function: ProviderFunction, + #[allow(unused_variables)] function: ProviderFunction, ) -> anyhow::Result { match self.runtime_type() { RuntimeType::Async => { diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 3c372b25..5fc697ba 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -1,8 +1,6 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::{bail, Context}; -use serde::Serialize; -use sysinfo::System; use tauri::{AppHandle, Emitter}; use tokio::{ sync::{mpsc, oneshot, Mutex}, @@ -23,10 +21,28 @@ use super::{ media::MediaProvider, }; +/// Common fields for a provider. +pub struct CommonProviderState { + /// Receiver channel for stopping the provider. + stop_rx: oneshot::Receiver<()>, + + /// Receiver channel for sending function calls to the provider. + function_rx: mpsc::Receiver<( + ProviderFunction, + oneshot::Sender, + )>, + + /// Hash of the provider's config. + config_hash: String, + + /// Shared `sysinfo` instance. + sysinfo: Arc>, +} + /// Reference to an active provider. pub struct ProviderRef { /// Sender channel for stopping the provider. - stop_tx: mpsc::Sender<()>, + stop_tx: oneshot::Sender<()>, /// Sender channel for sending function calls to the provider. function_tx: mpsc::Sender<( @@ -38,26 +54,8 @@ pub struct ProviderRef { task_handle: task::JoinHandle<()>, } -/// Provider output/error emitted to frontend clients. -/// -/// This is used instead of a normal `Result` type in order to serialize it -/// in a nicer way. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum ProviderEmission { - Output(ProviderOutput), - Error(String), -} - -/// Implements conversion from `anyhow::Result`. -impl From> for ProviderEmission { - fn from(result: anyhow::Result) -> Self { - match result { - Ok(output) => ProviderEmission::Output(output), - Err(err) => ProviderEmission::Error(err.to_string()), - } - } -} +/// A thread-safe `Result` type for provider outputs and errors. +pub type ProviderEmission = Result; /// Manages the creation and cleanup of providers. pub struct ProviderManager { @@ -74,7 +72,7 @@ pub struct ProviderManager { emit_tx: mpsc::UnboundedSender, /// Shared `sysinfo` instance. - sysinfo: Arc>, + sysinfo: Arc>, } impl ProviderManager { @@ -92,7 +90,7 @@ impl ProviderManager { app_handle: app_handle.clone(), provider_refs: Arc::new(Mutex::new(HashMap::new())), emit_cache: Arc::new(Mutex::new(HashMap::new())), - sysinfo: Arc::new(Mutex::new(System::new_all())), + sysinfo: Arc::new(Mutex::new(sysinfo::System::new_all())), emit_tx, }), emit_rx, @@ -116,20 +114,28 @@ impl ProviderManager { }; } - let (stop_tx, stop_rx) = mpsc::channel(1); + let (stop_tx, stop_rx) = oneshot::channel(); let (function_tx, function_rx) = mpsc::channel(1); - let mut provider = - self.create_instance(config, stop_rx, function_rx)?; + let common = CommonProviderState { + stop_rx, + function_rx, + config_hash: config_hash.clone(), + sysinfo: self.sysinfo.clone(), + }; + let mut provider = self.create_instance(config, common)?; + + // Spawn the provider's task based on its runtime type. + let config_hash_clone = config_hash.clone(); let task_handle = match provider.runtime_type() { RuntimeType::Async => task::spawn(async move { provider.start_async().await; - info!("Provider stopped: {}", config_hash); + info!("Provider stopped: {}", config_hash_clone); }), RuntimeType::Sync => task::spawn_blocking(move || { provider.start_sync(); - info!("Provider stopped: {}", config_hash); + info!("Provider stopped: {}", config_hash_clone); }), }; @@ -140,7 +146,7 @@ impl ProviderManager { }; let mut providers = self.provider_refs.lock().await; - providers.insert(config_hash.clone(), provider_ref); + providers.insert(config_hash, provider_ref); Ok(()) } @@ -149,42 +155,44 @@ impl ProviderManager { fn create_instance( &self, config: ProviderConfig, - stop_rx: mpsc::Receiver<()>, - function_rx: mpsc::Receiver<( - ProviderFunction, - oneshot::Sender, - )>, + common: CommonProviderState, ) -> anyhow::Result> { let provider: Box = match config { ProviderConfig::Battery(config) => { - Box::new(BatteryProvider::new(config)) + Box::new(BatteryProvider::new(config, common)) } ProviderConfig::Cpu(config) => { - Box::new(CpuProvider::new(config, self.sysinfo.clone())) + Box::new(CpuProvider::new(config, common)) + } + ProviderConfig::Host(config) => { + Box::new(HostProvider::new(config, common)) + } + ProviderConfig::Ip(config) => { + Box::new(IpProvider::new(config, common)) } - ProviderConfig::Host(config) => Box::new(HostProvider::new(config)), - ProviderConfig::Ip(config) => Box::new(IpProvider::new(config)), #[cfg(windows)] ProviderConfig::Komorebi(config) => { - Box::new(KomorebiProvider::new(config)) + Box::new(KomorebiProvider::new(config, common)) } #[cfg(windows)] ProviderConfig::Media(config) => { - Box::new(MediaProvider::new(config)) + Box::new(MediaProvider::new(config, common)) } ProviderConfig::Memory(config) => { - Box::new(MemoryProvider::new(config, self.sysinfo.clone())) + Box::new(MemoryProvider::new(config, common)) + } + ProviderConfig::Disk(config) => { + Box::new(DiskProvider::new(config, common)) } - ProviderConfig::Disk(config) => Box::new(DiskProvider::new(config)), ProviderConfig::Network(config) => { - Box::new(NetworkProvider::new(config)) + Box::new(NetworkProvider::new(config, common)) } ProviderConfig::Weather(config) => { - Box::new(WeatherProvider::new(config)) + Box::new(WeatherProvider::new(config, common)) } #[cfg(windows)] ProviderConfig::Keyboard(config) => { - Box::new(KeyboardProvider::new(config)) + Box::new(KeyboardProvider::new(config, common)) } #[allow(unreachable_patterns)] _ => bail!("Provider not supported on this operating system."), From 825448a424b3055e91a69a1605124d4f3bf9e3fc Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 02:49:56 +0800 Subject: [PATCH 10/38] refactor: return task handle from `create_instance` --- .../providers/komorebi/komorebi_provider.rs | 2 +- .../desktop/src/providers/provider_manager.rs | 33 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index 34893ed9..048cf23f 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -83,7 +83,7 @@ impl KomorebiProvider { notification.state, ))) .into(), - ) + ); } } Err(_) => emit_result_tx.send( diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 5fc697ba..393873f0 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -124,20 +124,8 @@ impl ProviderManager { sysinfo: self.sysinfo.clone(), }; - let mut provider = self.create_instance(config, common)?; - - // Spawn the provider's task based on its runtime type. - let config_hash_clone = config_hash.clone(); - let task_handle = match provider.runtime_type() { - RuntimeType::Async => task::spawn(async move { - provider.start_async().await; - info!("Provider stopped: {}", config_hash_clone); - }), - RuntimeType::Sync => task::spawn_blocking(move || { - provider.start_sync(); - info!("Provider stopped: {}", config_hash_clone); - }), - }; + let task_handle = + self.create_instance(config, config_hash.clone(), common)?; let provider_ref = ProviderRef { stop_tx, @@ -155,8 +143,9 @@ impl ProviderManager { fn create_instance( &self, config: ProviderConfig, + config_hash: String, common: CommonProviderState, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let provider: Box = match config { ProviderConfig::Battery(config) => { Box::new(BatteryProvider::new(config, common)) @@ -198,7 +187,19 @@ impl ProviderManager { _ => bail!("Provider not supported on this operating system."), }; - Ok(provider) + // Spawn the provider's task based on its runtime type. + let task_handle = match provider.runtime_type() { + RuntimeType::Async => task::spawn(async move { + provider.start_async().await; + info!("Provider stopped: {}", config_hash); + }), + RuntimeType::Sync => task::spawn_blocking(move || { + provider.start_sync(); + info!("Provider stopped: {}", config_hash); + }), + }; + + Ok(task_handle) } /// Sends a function call through a channel to be executed by the From f9a2a6442cc3d7ec0a4550bfd41dbcf66dbaa505 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 04:42:00 +0800 Subject: [PATCH 11/38] feat: pass `common` to all providers --- .../src/providers/battery/battery_provider.rs | 13 ++++++++++--- .../desktop/src/providers/cpu/cpu_provider.rs | 17 ++++++++--------- .../src/providers/disk/disk_provider.rs | 9 +++++++-- .../src/providers/host/host_provider.rs | 13 ++++++++++--- .../desktop/src/providers/ip/ip_provider.rs | 12 ++++++++++-- .../providers/keyboard/keyboard_provider.rs | 13 ++++++++++--- .../providers/komorebi/komorebi_provider.rs | 13 +++++++++---- .../src/providers/media/media_provider.rs | 12 ++++++++---- .../src/providers/memory/memory_provider.rs | 13 +++++++++++-- .../src/providers/network/network_provider.rs | 9 +++++++-- .../src/providers/weather/weather_provider.rs | 18 +++++++++++++----- 11 files changed, 103 insertions(+), 39 deletions(-) diff --git a/packages/desktop/src/providers/battery/battery_provider.rs b/packages/desktop/src/providers/battery/battery_provider.rs index 21316295..972558e0 100644 --- a/packages/desktop/src/providers/battery/battery_provider.rs +++ b/packages/desktop/src/providers/battery/battery_provider.rs @@ -8,7 +8,10 @@ use starship_battery::{ Manager, State, }; -use crate::{impl_interval_provider, providers::ProviderOutput}; +use crate::{ + impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -32,11 +35,15 @@ pub struct BatteryOutput { pub struct BatteryProvider { config: BatteryProviderConfig, + common: CommonProviderState, } impl BatteryProvider { - pub fn new(config: BatteryProviderConfig) -> BatteryProvider { - BatteryProvider { config } + pub fn new( + config: BatteryProviderConfig, + common: CommonProviderState, + ) -> BatteryProvider { + BatteryProvider { config, common } } fn refresh_interval_ms(&self) -> u64 { diff --git a/packages/desktop/src/providers/cpu/cpu_provider.rs b/packages/desktop/src/providers/cpu/cpu_provider.rs index e257d46b..ef630fd9 100644 --- a/packages/desktop/src/providers/cpu/cpu_provider.rs +++ b/packages/desktop/src/providers/cpu/cpu_provider.rs @@ -1,10 +1,9 @@ -use std::sync::Arc; - use serde::{Deserialize, Serialize}; -use sysinfo::System; -use tokio::sync::Mutex; -use crate::{impl_interval_provider, providers::ProviderOutput}; +use crate::{ + impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -24,15 +23,15 @@ pub struct CpuOutput { pub struct CpuProvider { config: CpuProviderConfig, - sysinfo: Arc>, + common: CommonProviderState, } impl CpuProvider { pub fn new( config: CpuProviderConfig, - sysinfo: Arc>, + common: CommonProviderState, ) -> CpuProvider { - CpuProvider { config, sysinfo } + CpuProvider { config, common } } fn refresh_interval_ms(&self) -> u64 { @@ -40,7 +39,7 @@ impl CpuProvider { } async fn run_interval(&self) -> anyhow::Result { - let mut sysinfo = self.sysinfo.lock().await; + let mut sysinfo = self.common.sysinfo.lock().await; sysinfo.refresh_cpu(); Ok(ProviderOutput::Cpu(CpuOutput { diff --git a/packages/desktop/src/providers/disk/disk_provider.rs b/packages/desktop/src/providers/disk/disk_provider.rs index 320d6349..c997ac5b 100644 --- a/packages/desktop/src/providers/disk/disk_provider.rs +++ b/packages/desktop/src/providers/disk/disk_provider.rs @@ -7,7 +7,7 @@ use tokio::sync::Mutex; use crate::{ common::{to_iec_bytes, to_si_bytes}, impl_interval_provider, - providers::ProviderOutput, + providers::{CommonProviderState, ProviderOutput}, }; #[derive(Deserialize, Debug)] @@ -36,6 +36,7 @@ pub struct Disk { pub struct DiskProvider { config: DiskProviderConfig, + common: CommonProviderState, disks: Arc>, } @@ -50,9 +51,13 @@ pub struct DiskSizeMeasure { } impl DiskProvider { - pub fn new(config: DiskProviderConfig) -> DiskProvider { + pub fn new( + config: DiskProviderConfig, + common: CommonProviderState, + ) -> DiskProvider { DiskProvider { config, + common, disks: Arc::new(Mutex::new(Disks::new_with_refreshed_list())), } } diff --git a/packages/desktop/src/providers/host/host_provider.rs b/packages/desktop/src/providers/host/host_provider.rs index 9b26a4b2..46b66c3d 100644 --- a/packages/desktop/src/providers/host/host_provider.rs +++ b/packages/desktop/src/providers/host/host_provider.rs @@ -1,7 +1,10 @@ use serde::{Deserialize, Serialize}; use sysinfo::System; -use crate::{impl_interval_provider, providers::ProviderOutput}; +use crate::{ + impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -22,11 +25,15 @@ pub struct HostOutput { pub struct HostProvider { config: HostProviderConfig, + common: CommonProviderState, } impl HostProvider { - pub fn new(config: HostProviderConfig) -> HostProvider { - HostProvider { config } + pub fn new( + config: HostProviderConfig, + common: CommonProviderState, + ) -> HostProvider { + HostProvider { config, common } } fn refresh_interval_ms(&self) -> u64 { diff --git a/packages/desktop/src/providers/ip/ip_provider.rs b/packages/desktop/src/providers/ip/ip_provider.rs index 37b1cdac..894ef7a6 100644 --- a/packages/desktop/src/providers/ip/ip_provider.rs +++ b/packages/desktop/src/providers/ip/ip_provider.rs @@ -3,7 +3,10 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use super::ipinfo_res::IpinfoRes; -use crate::{impl_interval_provider, providers::ProviderOutput}; +use crate::{ + impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -23,13 +26,18 @@ pub struct IpOutput { pub struct IpProvider { config: IpProviderConfig, + common: CommonProviderState, http_client: Client, } impl IpProvider { - pub fn new(config: IpProviderConfig) -> IpProvider { + pub fn new( + config: IpProviderConfig, + common: CommonProviderState, + ) -> IpProvider { IpProvider { config, + common, http_client: Client::new(), } } diff --git a/packages/desktop/src/providers/keyboard/keyboard_provider.rs b/packages/desktop/src/providers/keyboard/keyboard_provider.rs index 31d2d00b..5e27d2b6 100644 --- a/packages/desktop/src/providers/keyboard/keyboard_provider.rs +++ b/packages/desktop/src/providers/keyboard/keyboard_provider.rs @@ -9,7 +9,10 @@ use windows::Win32::{ }, }; -use crate::{impl_interval_provider, providers::ProviderOutput}; +use crate::{ + impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -25,11 +28,15 @@ pub struct KeyboardOutput { pub struct KeyboardProvider { config: KeyboardProviderConfig, + common: CommonProviderState, } impl KeyboardProvider { - pub fn new(config: KeyboardProviderConfig) -> KeyboardProvider { - KeyboardProvider { config } + pub fn new( + config: KeyboardProviderConfig, + common: CommonProviderState, + ) -> KeyboardProvider { + KeyboardProvider { config, common } } fn refresh_interval_ms(&self) -> u64 { diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index 048cf23f..6f9b75a9 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -16,7 +16,9 @@ use super::{ KomorebiContainer, KomorebiLayout, KomorebiLayoutFlip, KomorebiMonitor, KomorebiWindow, KomorebiWorkspace, }; -use crate::providers::{Provider, ProviderEmission, ProviderOutput}; +use crate::providers::{ + CommonProviderState, Provider, ProviderEmission, ProviderOutput, +}; const SOCKET_NAME: &str = "zebar.sock"; @@ -32,12 +34,15 @@ pub struct KomorebiOutput { } pub struct KomorebiProvider { - _config: KomorebiProviderConfig, + common: CommonProviderState, } impl KomorebiProvider { - pub fn new(config: KomorebiProviderConfig) -> KomorebiProvider { - KomorebiProvider { _config: config } + pub fn new( + _config: KomorebiProviderConfig, + common: CommonProviderState, + ) -> KomorebiProvider { + KomorebiProvider { common } } fn create_socket( diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index c6e29faf..e881f19f 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -20,7 +20,8 @@ use windows::{ }; use crate::providers::{ - Provider, ProviderEmission, ProviderOutput, RuntimeType, + CommonProviderState, Provider, ProviderEmission, ProviderOutput, + RuntimeType, }; #[derive(Deserialize, Debug)] @@ -55,12 +56,15 @@ struct EventTokens { } pub struct MediaProvider { - _config: MediaProviderConfig, + common: CommonProviderState, } impl MediaProvider { - pub fn new(config: MediaProviderConfig) -> MediaProvider { - MediaProvider { _config: config } + pub fn new( + _config: MediaProviderConfig, + common: CommonProviderState, + ) -> MediaProvider { + MediaProvider { common } } fn emit_media_info( diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index c76d8d3d..39771afb 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -4,7 +4,10 @@ use serde::{Deserialize, Serialize}; use sysinfo::System; use tokio::sync::Mutex; -use crate::{impl_interval_provider, providers::ProviderOutput}; +use crate::{ + impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -26,15 +29,21 @@ pub struct MemoryOutput { pub struct MemoryProvider { config: MemoryProviderConfig, + common: CommonProviderState, sysinfo: Arc>, } impl MemoryProvider { pub fn new( config: MemoryProviderConfig, + common: CommonProviderState, sysinfo: Arc>, ) -> MemoryProvider { - MemoryProvider { config, sysinfo } + MemoryProvider { + config, + common, + sysinfo, + } } fn refresh_interval_ms(&self) -> u64 { diff --git a/packages/desktop/src/providers/network/network_provider.rs b/packages/desktop/src/providers/network/network_provider.rs index 29734ef5..61d27feb 100644 --- a/packages/desktop/src/providers/network/network_provider.rs +++ b/packages/desktop/src/providers/network/network_provider.rs @@ -12,7 +12,7 @@ use super::{ use crate::{ common::{to_iec_bytes, to_si_bytes}, impl_interval_provider, - providers::ProviderOutput, + providers::{CommonProviderState, ProviderOutput}, }; #[derive(Deserialize, Debug)] @@ -32,13 +32,18 @@ pub struct NetworkOutput { pub struct NetworkProvider { config: NetworkProviderConfig, + common: CommonProviderState, netinfo: Arc>, } impl NetworkProvider { - pub fn new(config: NetworkProviderConfig) -> NetworkProvider { + pub fn new( + config: NetworkProviderConfig, + common: CommonProviderState, + ) -> NetworkProvider { NetworkProvider { config, + common, netinfo: Arc::new(Mutex::new(Networks::new_with_refreshed_list())), } } diff --git a/packages/desktop/src/providers/weather/weather_provider.rs b/packages/desktop/src/providers/weather/weather_provider.rs index 8b57d300..a27fdcd2 100644 --- a/packages/desktop/src/providers/weather/weather_provider.rs +++ b/packages/desktop/src/providers/weather/weather_provider.rs @@ -6,7 +6,7 @@ use crate::{ impl_interval_provider, providers::{ ip::{IpProvider, IpProviderConfig}, - ProviderOutput, + CommonProviderState, ProviderOutput, }, }; @@ -47,13 +47,18 @@ pub enum WeatherStatus { pub struct WeatherProvider { config: WeatherProviderConfig, + common: CommonProviderState, http_client: Client, } impl WeatherProvider { - pub fn new(config: WeatherProviderConfig) -> WeatherProvider { + pub fn new( + config: WeatherProviderConfig, + common: CommonProviderState, + ) -> WeatherProvider { WeatherProvider { config, + common, http_client: Client::new(), } } @@ -67,9 +72,12 @@ impl WeatherProvider { match (self.config.latitude, self.config.longitude) { (Some(lat), Some(lon)) => (lat, lon), _ => { - let ip_output = IpProvider::new(IpProviderConfig { - refresh_interval: 0, - }) + let ip_output = IpProvider::new( + IpProviderConfig { + refresh_interval: 0, + }, + self.common, + ) .run_interval() .await?; From ca02eb279b28ece659cbc2b6a708b8092100ad1c Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 17:46:31 +0800 Subject: [PATCH 12/38] fixes + change how providers are stopped --- .../providers/komorebi/komorebi_provider.rs | 21 +++---- .../src/providers/media/media_provider.rs | 58 +++++++------------ .../src/providers/memory/memory_provider.rs | 14 +---- packages/desktop/src/providers/mod.rs | 2 - .../desktop/src/providers/provider_manager.rs | 25 ++++---- 5 files changed, 44 insertions(+), 76 deletions(-) diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index 6f9b75a9..c838a372 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -9,7 +9,6 @@ use komorebi_client::{ Container, Monitor, SocketMessage, Window, Workspace, }; use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc::{self}; use tracing::debug; use super::{ @@ -17,7 +16,7 @@ use super::{ KomorebiWindow, KomorebiWorkspace, }; use crate::providers::{ - CommonProviderState, Provider, ProviderEmission, ProviderOutput, + CommonProviderState, Provider, ProviderOutput, RuntimeType, }; const SOCKET_NAME: &str = "zebar.sock"; @@ -45,10 +44,7 @@ impl KomorebiProvider { KomorebiProvider { common } } - fn create_socket( - &self, - emit_result_tx: mpsc::UnboundedSender, - ) -> anyhow::Result<()> { + fn create_socket(&mut self) -> anyhow::Result<()> { let socket = komorebi_client::subscribe(SOCKET_NAME) .context("Failed to initialize Komorebi socket.")?; @@ -83,7 +79,7 @@ impl KomorebiProvider { &String::from_utf8(buffer).unwrap(), ) { - emit_result_tx.send( + self.common.emit_result_tx.send( Ok(ProviderOutput::Komorebi(Self::transform_response( notification.state, ))) @@ -91,7 +87,7 @@ impl KomorebiProvider { ); } } - Err(_) => emit_result_tx.send( + Err(_) => self.common.emit_result_tx.send( Err(anyhow::anyhow!("Failed to read Komorebi stream.")).into(), ), } @@ -187,12 +183,9 @@ impl Provider for KomorebiProvider { RuntimeType::Sync } - fn start_sync( - &mut self, - emit_result_tx: mpsc::UnboundedSender, - ) { - if let Err(err) = self.create_socket(emit_result_tx.clone()) { - emit_result_tx.try_send(Err(err).into()); + fn start_sync(&mut self) { + if let Err(err) = self.create_socket() { + self.common.emit_result_tx.try_send(Err(err).into()); } } } diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index e881f19f..19583c1f 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -5,10 +5,7 @@ use std::{ use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use tokio::{ - sync::mpsc::{self}, - task, -}; +use tokio::sync::mpsc::{self}; use tracing::{debug, error}; use windows::{ Foundation::{EventRegistrationToken, TypedEventHandler}, @@ -72,11 +69,12 @@ impl MediaProvider { emit_result_tx: mpsc::UnboundedSender, ) { let _ = match Self::media_output(session) { - Ok(media_output) => emit_result_tx - .blocking_send(Ok(ProviderOutput::Media(media_output)).into()), + Ok(media_output) => { + emit_result_tx.send(Ok(ProviderOutput::Media(media_output)).into()) + } Err(err) => { error!("Error retrieving media output: {:?}", err); - emit_result_tx.blocking_send(Err(err).into()) + emit_result_tx.send(Err(err).into()) } }; } @@ -132,9 +130,7 @@ impl MediaProvider { })) } - fn create_session_manager( - emit_result_tx: mpsc::UnboundedSender, - ) -> anyhow::Result<()> { + fn create_session_manager(&mut self) -> anyhow::Result<()> { debug!("Creating media session manager."); // Find the current GSMTC session & add listeners. @@ -144,7 +140,7 @@ impl MediaProvider { let event_tokens = match ¤t_session { Some(session) => Some(Self::add_session_listeners( session, - emit_result_tx.clone(), + self.common.emit_result_tx.clone(), )?), None => None, }; @@ -152,7 +148,7 @@ impl MediaProvider { // Emit initial media info. Self::emit_media_info( current_session.as_ref(), - emit_result_tx.clone(), + self.common.emit_result_tx.clone(), ); let current_session = Arc::new(Mutex::new(current_session)); @@ -182,12 +178,12 @@ impl MediaProvider { let tokens = Self::add_session_listeners( &new_session, - emit_result_tx.clone(), + self.common.emit_result_tx.clone(), )?; Self::emit_media_info( Some(&new_session), - emit_result_tx.clone(), + self.common.emit_result_tx.clone(), ); *current_session = Some(new_session); @@ -271,19 +267,13 @@ impl MediaProvider { }) }; - let timeline_token = session - .TimelinePropertiesChanged(&timeline_properties_changed_handler)?; - let playback_token = - session.PlaybackInfoChanged(&playback_info_changed_handler)?; - let media_token = - session.MediaPropertiesChanged(&media_properties_changed_handler)?; - - Ok({ - EventTokens { - playback_info_changed_token: playback_token, - media_properties_changed_token: media_token, - timeline_properties_changed_token: timeline_token, - } + Ok(EventTokens { + playback_info_changed_token: session + .PlaybackInfoChanged(&playback_info_changed_handler)?, + media_properties_changed_token: session + .MediaPropertiesChanged(&media_properties_changed_handler)?, + timeline_properties_changed_token: session + .TimelinePropertiesChanged(&timeline_properties_changed_handler)?, }) } } @@ -291,16 +281,12 @@ impl MediaProvider { #[async_trait] impl Provider for MediaProvider { fn runtime_type(&self) -> RuntimeType { - RuntimeType::Async + RuntimeType::Sync } - async fn start_async(&mut self) { - task::spawn_blocking(move || { - if let Err(err) = - Self::create_session_manager(emit_result_tx.clone()) - { - let _ = emit_result_tx.blocking_send(Err(err).into()); - } - }); + fn start_sync(&mut self) { + if let Err(err) = self.create_session_manager() { + let _ = self.common.emit_result_tx.send(Err(err).into()); + } } } diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index 39771afb..3d609a90 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -1,8 +1,4 @@ -use std::sync::Arc; - use serde::{Deserialize, Serialize}; -use sysinfo::System; -use tokio::sync::Mutex; use crate::{ impl_interval_provider, @@ -30,20 +26,14 @@ pub struct MemoryOutput { pub struct MemoryProvider { config: MemoryProviderConfig, common: CommonProviderState, - sysinfo: Arc>, } impl MemoryProvider { pub fn new( config: MemoryProviderConfig, common: CommonProviderState, - sysinfo: Arc>, ) -> MemoryProvider { - MemoryProvider { - config, - common, - sysinfo, - } + MemoryProvider { config, common } } fn refresh_interval_ms(&self) -> u64 { @@ -51,7 +41,7 @@ impl MemoryProvider { } async fn run_interval(&self) -> anyhow::Result { - let mut sysinfo = self.sysinfo.lock().await; + let mut sysinfo = self.common.sysinfo.lock().await; sysinfo.refresh_memory(); let usage = (sysinfo.used_memory() as f32 diff --git a/packages/desktop/src/providers/mod.rs b/packages/desktop/src/providers/mod.rs index 65a5b5fc..0900643a 100644 --- a/packages/desktop/src/providers/mod.rs +++ b/packages/desktop/src/providers/mod.rs @@ -18,7 +18,6 @@ mod provider_config; mod provider_function; mod provider_manager; mod provider_output; -mod provider_ref; mod weather; pub use provider::*; @@ -26,4 +25,3 @@ pub use provider_config::*; pub use provider_function::*; pub use provider_manager::*; pub use provider_output::*; -pub use provider_ref::*; diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 393873f0..f2ca5ce0 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -24,23 +24,26 @@ use super::{ /// Common fields for a provider. pub struct CommonProviderState { /// Receiver channel for stopping the provider. - stop_rx: oneshot::Receiver<()>, + pub stop_rx: oneshot::Receiver<()>, /// Receiver channel for sending function calls to the provider. - function_rx: mpsc::Receiver<( + pub function_rx: mpsc::Receiver<( ProviderFunction, oneshot::Sender, )>, /// Hash of the provider's config. - config_hash: String, + pub config_hash: String, /// Shared `sysinfo` instance. - sysinfo: Arc>, + pub sysinfo: Arc>, } +/// A thread-safe `Result` type for provider outputs and errors. +pub type ProviderEmission = Result; + /// Reference to an active provider. -pub struct ProviderRef { +struct ProviderRef { /// Sender channel for stopping the provider. stop_tx: oneshot::Sender<()>, @@ -54,9 +57,6 @@ pub struct ProviderRef { task_handle: task::JoinHandle<()>, } -/// A thread-safe `Result` type for provider outputs and errors. -pub type ProviderEmission = Result; - /// Manages the creation and cleanup of providers. pub struct ProviderManager { /// Handle to the Tauri application. @@ -146,7 +146,7 @@ impl ProviderManager { config_hash: String, common: CommonProviderState, ) -> anyhow::Result> { - let provider: Box = match config { + let mut provider: Box = match config { ProviderConfig::Battery(config) => { Box::new(BatteryProvider::new(config, common)) } @@ -229,10 +229,11 @@ impl ProviderManager { .remove(&config_hash) .context("No provider found with config.")?; - let (tx, rx) = oneshot::channel(); - provider_ref.stop_tx.send(tx).await?; + // Send shutdown signal to the provider and wait for it to stop. + provider_ref.stop_tx.send(()); + provider_ref.task_handle.await?; - Ok(rx.await?) + Ok(()) } /// Updates the cache with the given provider emission. From 5bfb93ae5051d51a3292c6802c5e0497a14f3897 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 18:13:07 +0800 Subject: [PATCH 13/38] feat: add emit_tx --- .../providers/komorebi/komorebi_provider.rs | 6 ++-- .../src/providers/media/media_provider.rs | 30 +++++++++---------- packages/desktop/src/providers/provider.rs | 2 +- .../desktop/src/providers/provider_manager.rs | 6 +++- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index c838a372..a17c0497 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -79,7 +79,7 @@ impl KomorebiProvider { &String::from_utf8(buffer).unwrap(), ) { - self.common.emit_result_tx.send( + self.common.emit_tx.send( Ok(ProviderOutput::Komorebi(Self::transform_response( notification.state, ))) @@ -87,7 +87,7 @@ impl KomorebiProvider { ); } } - Err(_) => self.common.emit_result_tx.send( + Err(_) => self.common.emit_tx.send( Err(anyhow::anyhow!("Failed to read Komorebi stream.")).into(), ), } @@ -185,7 +185,7 @@ impl Provider for KomorebiProvider { fn start_sync(&mut self) { if let Err(err) = self.create_socket() { - self.common.emit_result_tx.try_send(Err(err).into()); + self.common.emit_tx.try_send(Err(err).into()); } } } diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index 19583c1f..30d93f55 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -66,15 +66,15 @@ impl MediaProvider { fn emit_media_info( session: Option<&GsmtcSession>, - emit_result_tx: mpsc::UnboundedSender, + emit_tx: mpsc::UnboundedSender, ) { let _ = match Self::media_output(session) { Ok(media_output) => { - emit_result_tx.send(Ok(ProviderOutput::Media(media_output)).into()) + emit_tx.send(Ok(ProviderOutput::Media(media_output)).into()) } Err(err) => { error!("Error retrieving media output: {:?}", err); - emit_result_tx.send(Err(err).into()) + emit_tx.send(Err(err).into()) } }; } @@ -140,7 +140,7 @@ impl MediaProvider { let event_tokens = match ¤t_session { Some(session) => Some(Self::add_session_listeners( session, - self.common.emit_result_tx.clone(), + self.common.emit_tx.clone(), )?), None => None, }; @@ -148,7 +148,7 @@ impl MediaProvider { // Emit initial media info. Self::emit_media_info( current_session.as_ref(), - self.common.emit_result_tx.clone(), + self.common.emit_tx.clone(), ); let current_session = Arc::new(Mutex::new(current_session)); @@ -178,12 +178,12 @@ impl MediaProvider { let tokens = Self::add_session_listeners( &new_session, - self.common.emit_result_tx.clone(), + self.common.emit_tx.clone(), )?; Self::emit_media_info( Some(&new_session), - self.common.emit_result_tx.clone(), + self.common.emit_tx.clone(), ); *current_session = Some(new_session); @@ -221,18 +221,18 @@ impl MediaProvider { fn add_session_listeners( session: &GsmtcSession, - emit_result_tx: mpsc::UnboundedSender, + emit_tx: mpsc::UnboundedSender, ) -> windows::core::Result { debug!("Adding session listeners."); let media_properties_changed_handler = { - let emit_result_tx = emit_result_tx.clone(); + let emit_tx = emit_tx.clone(); TypedEventHandler::new(move |session: &Option, _| { debug!("Media properties changed event triggered."); if let Some(session) = session { - Self::emit_media_info(Some(session), emit_result_tx.clone()); + Self::emit_media_info(Some(session), emit_tx.clone()); } Ok(()) @@ -240,13 +240,13 @@ impl MediaProvider { }; let playback_info_changed_handler = { - let emit_result_tx = emit_result_tx.clone(); + let emit_tx = emit_tx.clone(); TypedEventHandler::new(move |session: &Option, _| { debug!("Playback info changed event triggered."); if let Some(session) = session { - Self::emit_media_info(Some(session), emit_result_tx.clone()); + Self::emit_media_info(Some(session), emit_tx.clone()); } Ok(()) @@ -254,13 +254,13 @@ impl MediaProvider { }; let timeline_properties_changed_handler = { - let emit_result_tx = emit_result_tx.clone(); + let emit_tx = emit_tx.clone(); TypedEventHandler::new(move |session: &Option, _| { debug!("Timeline properties changed event triggered."); if let Some(session) = session { - Self::emit_media_info(Some(session), emit_result_tx.clone()); + Self::emit_media_info(Some(session), emit_tx.clone()); } Ok(()) @@ -286,7 +286,7 @@ impl Provider for MediaProvider { fn start_sync(&mut self) { if let Err(err) = self.create_session_manager() { - let _ = self.common.emit_result_tx.send(Err(err).into()); + let _ = self.common.emit_tx.send(Err(err).into()); } } } diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index 7bc4a705..d43d1562 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -120,7 +120,7 @@ macro_rules! impl_interval_provider { if $allow_identical_emits || last_interval_res.as_ref() != Some(&interval_res) { - let send_res = emit_result_tx.send(interval_res.clone()).await; + let send_res = emit_tx.send(interval_res.clone()).await; if let Err(err) = send_res { tracing::error!("Error sending provider result: {:?}", err); diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index f2ca5ce0..41b8feb4 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -26,12 +26,15 @@ pub struct CommonProviderState { /// Receiver channel for stopping the provider. pub stop_rx: oneshot::Receiver<()>, - /// Receiver channel for sending function calls to the provider. + /// Receiver channel for incoming function calls to the provider. pub function_rx: mpsc::Receiver<( ProviderFunction, oneshot::Sender, )>, + /// Sender channel for outgoing provider emissions. + pub emit_tx: mpsc::UnboundedSender, + /// Hash of the provider's config. pub config_hash: String, @@ -120,6 +123,7 @@ impl ProviderManager { let common = CommonProviderState { stop_rx, function_rx, + emit_tx: self.emit_tx.clone(), config_hash: config_hash.clone(), sysinfo: self.sysinfo.clone(), }; From fa8202ce4654a635499536bbd1c293db896eb4d0 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 18:50:47 +0800 Subject: [PATCH 14/38] feat: change `ProviderEmission` to a struct --- packages/desktop/src/main.rs | 8 ++++---- .../desktop/src/providers/provider_manager.rs | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/desktop/src/main.rs b/packages/desktop/src/main.rs index 740f6332..9cf26e93 100644 --- a/packages/desktop/src/main.rs +++ b/packages/desktop/src/main.rs @@ -242,10 +242,10 @@ fn listen_events( info!("Widget configs changed."); widget_factory.relaunch_by_paths(&changed_configs.keys().cloned().collect()).await }, - Some(provider_emit) = emit_rx.recv() => { - info!("Provider emission: {:?}", provider_emit); - app_handle.emit("provider-emit", provider_emit.clone()); - manager.update_cache(provider_emit).await; + Some(provider_emission) = emit_rx.recv() => { + info!("Provider emission: {:?}", provider_emission); + app_handle.emit("provider-emit", provider_emission.clone()); + manager.update_cache(provider_emission).await; Ok(()) }, }; diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 41b8feb4..c604555f 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::{bail, Context}; +use serde::Serialize; use tauri::{AppHandle, Emitter}; use tokio::{ sync::{mpsc, oneshot, Mutex}, @@ -42,8 +43,15 @@ pub struct CommonProviderState { pub sysinfo: Arc>, } -/// A thread-safe `Result` type for provider outputs and errors. -pub type ProviderEmission = Result; +/// Emission from a provider. +#[derive(Debug, Clone, Serialize)] +pub struct ProviderEmission { + /// Hash of the provider's config. + pub config_hash: String, + + /// A thread-safe `Result` type for provider outputs and errors. + pub result: Result, +} /// Reference to an active provider. struct ProviderRef { @@ -241,8 +249,8 @@ impl ProviderManager { } /// Updates the cache with the given provider emission. - pub async fn update_cache(&self, emit: ProviderEmission) { + pub async fn update_cache(&self, emission: ProviderEmission) { let mut cache = self.emit_cache.lock().await; - cache.insert(emit.config_hash, emit); + cache.insert(emission.config_hash.clone(), emission); } } From 695e5555ac98aacd7b2f9ce18236fa8c4ea90a35 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 19:05:11 +0800 Subject: [PATCH 15/38] feat: add `query_ip` method to `IpProvider` --- .../desktop/src/providers/ip/ip_provider.rs | 18 +++++++++--------- .../src/providers/weather/weather_provider.rs | 17 ++--------------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/packages/desktop/src/providers/ip/ip_provider.rs b/packages/desktop/src/providers/ip/ip_provider.rs index 894ef7a6..431f3415 100644 --- a/packages/desktop/src/providers/ip/ip_provider.rs +++ b/packages/desktop/src/providers/ip/ip_provider.rs @@ -3,10 +3,7 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use super::ipinfo_res::IpinfoRes; -use crate::{ - impl_interval_provider, - providers::{CommonProviderState, ProviderOutput}, -}; +use crate::{impl_interval_provider, providers::CommonProviderState}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -46,9 +43,12 @@ impl IpProvider { self.config.refresh_interval } - pub async fn run_interval(&self) -> anyhow::Result { - let res = self - .http_client + async fn run_interval(&self) -> anyhow::Result { + Self::query_ip(&self.http_client).await + } + + pub async fn query_ip(http_client: &Client) -> anyhow::Result { + let res = http_client .get("https://ipinfo.io/json") .send() .await? @@ -57,7 +57,7 @@ impl IpProvider { let mut loc_parts = res.loc.split(','); - Ok(ProviderOutput::Ip(IpOutput { + Ok(IpOutput { address: res.ip, approx_city: res.city, approx_country: res.country, @@ -69,7 +69,7 @@ impl IpProvider { .next() .and_then(|long| long.parse::().ok()) .context("Failed to parse longitude from IPinfo.")?, - })) + }) } } diff --git a/packages/desktop/src/providers/weather/weather_provider.rs b/packages/desktop/src/providers/weather/weather_provider.rs index a27fdcd2..f34960ac 100644 --- a/packages/desktop/src/providers/weather/weather_provider.rs +++ b/packages/desktop/src/providers/weather/weather_provider.rs @@ -72,21 +72,8 @@ impl WeatherProvider { match (self.config.latitude, self.config.longitude) { (Some(lat), Some(lon)) => (lat, lon), _ => { - let ip_output = IpProvider::new( - IpProviderConfig { - refresh_interval: 0, - }, - self.common, - ) - .run_interval() - .await?; - - match ip_output { - ProviderOutput::Ip(ip_output) => { - (ip_output.approx_latitude, ip_output.approx_longitude) - } - _ => anyhow::bail!("Unexpected output from IP provider."), - } + let ip_output = IpProvider::query_ip(&self.http_client).await?; + (ip_output.approx_latitude, ip_output.approx_longitude) } } }; From d7e1ca24d12b28cea362184da972c45446ea4abc Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 19:05:30 +0800 Subject: [PATCH 16/38] feat: add `SyncInterval` and `AsyncInterval` --- packages/desktop/src/common/interval.rs | 60 +++++++++++++++++++++++++ packages/desktop/src/common/mod.rs | 2 + 2 files changed, 62 insertions(+) create mode 100644 packages/desktop/src/common/interval.rs diff --git a/packages/desktop/src/common/interval.rs b/packages/desktop/src/common/interval.rs new file mode 100644 index 00000000..c8a9cba0 --- /dev/null +++ b/packages/desktop/src/common/interval.rs @@ -0,0 +1,60 @@ +use std::{ + thread, + time::{Duration, Instant}, +}; + +/// A synchronous timer for running intervals at a fixed rate. +/// +/// The timer sleeps the thread until the next tick. +pub struct SyncInterval { + interval: Duration, + next_tick: Instant, +} + +impl SyncInterval { + pub fn new(interval_ms: u64) -> Self { + Self { + interval: Duration::from_millis(interval_ms), + next_tick: Instant::now(), + } + } + + /// Sleeps until the next tick if needed, then updates the next tick + /// time. + pub fn tick(&mut self) { + // Sleep until next tick if needed. + if let Some(wait_duration) = + self.next_tick.checked_duration_since(Instant::now()) + { + thread::sleep(wait_duration); + } + + // Calculate next tick time. + self.next_tick += self.interval; + } +} + +/// An asynchronous timer for running intervals at a fixed rate. +/// +/// The timer sleeps the Tokio task until the next tick. +pub struct AsyncInterval { + interval: tokio::time::Interval, +} + +impl AsyncInterval { + pub fn new(interval_ms: u64) -> Self { + let mut interval = + tokio::time::interval(Duration::from_millis(interval_ms)); + + // Skip missed ticks when the interval runs. This prevents a burst + // of backlogged ticks after a delay. + interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + Self { interval } + } + + pub async fn tick(&mut self) { + self.interval.tick().await; + } +} diff --git a/packages/desktop/src/common/mod.rs b/packages/desktop/src/common/mod.rs index eb5cf80f..3bf9634a 100644 --- a/packages/desktop/src/common/mod.rs +++ b/packages/desktop/src/common/mod.rs @@ -1,5 +1,6 @@ mod format_bytes; mod fs_util; +mod interval; mod length_value; #[cfg(target_os = "macos")] pub mod macos; @@ -9,5 +10,6 @@ pub mod windows; pub use format_bytes::*; pub use fs_util::*; +pub use interval::*; pub use length_value::*; pub use path_ext::*; From 0dd075bdb843d8b4d2bdae10e944e2ec1b3343c7 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 20:16:12 +0800 Subject: [PATCH 17/38] feat: remove `refresh_interval_ms` getter; add `emit_provider_output` fn --- .../src/providers/battery/battery_provider.rs | 35 ++++-- .../desktop/src/providers/cpu/cpu_provider.rs | 6 +- .../src/providers/disk/disk_provider.rs | 6 +- .../src/providers/host/host_provider.rs | 6 +- .../desktop/src/providers/ip/ip_provider.rs | 6 +- .../providers/keyboard/keyboard_provider.rs | 6 +- .../src/providers/memory/memory_provider.rs | 6 +- .../src/providers/network/network_provider.rs | 6 +- packages/desktop/src/providers/provider.rs | 49 -------- .../desktop/src/providers/provider_manager.rs | 20 +++ .../desktop/src/providers/provider_output.rs | 114 ++++++++++++++++++ .../src/providers/weather/weather_provider.rs | 13 +- 12 files changed, 176 insertions(+), 97 deletions(-) diff --git a/packages/desktop/src/providers/battery/battery_provider.rs b/packages/desktop/src/providers/battery/battery_provider.rs index 972558e0..567425b3 100644 --- a/packages/desktop/src/providers/battery/battery_provider.rs +++ b/packages/desktop/src/providers/battery/battery_provider.rs @@ -9,8 +9,10 @@ use starship_battery::{ }; use crate::{ - impl_interval_provider, - providers::{CommonProviderState, ProviderOutput}, + common::SyncInterval, + providers::{ + CommonProviderState, Provider, ProviderOutput, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -46,18 +48,14 @@ impl BatteryProvider { BatteryProvider { config, common } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } - - async fn run_interval(&self) -> anyhow::Result { + fn run_interval(&self) -> anyhow::Result { let battery = Manager::new()? .batteries() .and_then(|mut batteries| batteries.nth(0).transpose()) .unwrap_or(None) .context("No battery found.")?; - Ok(ProviderOutput::Battery(BatteryOutput { + Ok(BatteryOutput { charge_percent: battery.state_of_charge().get::(), health_percent: battery.state_of_health().get::(), state: battery.state().to_string(), @@ -71,8 +69,25 @@ impl BatteryProvider { power_consumption: battery.energy_rate().get::(), voltage: battery.voltage().get::(), cycle_count: battery.cycle_count(), - })) + }) } } -impl_interval_provider!(BatteryProvider, true); +impl Provider for BatteryProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync + } + + fn start_sync(&mut self) { + let mut interval = SyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick(); + + self + .common + .emit_tx + .send(ProviderOutput::Battery(self.run_interval())); + } + } +} diff --git a/packages/desktop/src/providers/cpu/cpu_provider.rs b/packages/desktop/src/providers/cpu/cpu_provider.rs index ef630fd9..d1f5954e 100644 --- a/packages/desktop/src/providers/cpu/cpu_provider.rs +++ b/packages/desktop/src/providers/cpu/cpu_provider.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, }; @@ -34,9 +34,7 @@ impl CpuProvider { CpuProvider { config, common } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } + async fn run_interval(&self) -> anyhow::Result { let mut sysinfo = self.common.sysinfo.lock().await; diff --git a/packages/desktop/src/providers/disk/disk_provider.rs b/packages/desktop/src/providers/disk/disk_provider.rs index c997ac5b..5f214f5d 100644 --- a/packages/desktop/src/providers/disk/disk_provider.rs +++ b/packages/desktop/src/providers/disk/disk_provider.rs @@ -6,7 +6,7 @@ use tokio::sync::Mutex; use crate::{ common::{to_iec_bytes, to_si_bytes}, - impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, }; @@ -62,9 +62,7 @@ impl DiskProvider { } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } + async fn run_interval(&self) -> anyhow::Result { let mut disks = self.disks.lock().await; diff --git a/packages/desktop/src/providers/host/host_provider.rs b/packages/desktop/src/providers/host/host_provider.rs index 46b66c3d..4ddf4436 100644 --- a/packages/desktop/src/providers/host/host_provider.rs +++ b/packages/desktop/src/providers/host/host_provider.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use sysinfo::System; use crate::{ - impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, }; @@ -36,9 +36,7 @@ impl HostProvider { HostProvider { config, common } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } + async fn run_interval(&self) -> anyhow::Result { Ok(ProviderOutput::Host(HostOutput { diff --git a/packages/desktop/src/providers/ip/ip_provider.rs b/packages/desktop/src/providers/ip/ip_provider.rs index 431f3415..1ea48f27 100644 --- a/packages/desktop/src/providers/ip/ip_provider.rs +++ b/packages/desktop/src/providers/ip/ip_provider.rs @@ -3,7 +3,7 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use super::ipinfo_res::IpinfoRes; -use crate::{impl_interval_provider, providers::CommonProviderState}; +use crate::{ providers::CommonProviderState}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -39,9 +39,7 @@ impl IpProvider { } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } + async fn run_interval(&self) -> anyhow::Result { Self::query_ip(&self.http_client).await diff --git a/packages/desktop/src/providers/keyboard/keyboard_provider.rs b/packages/desktop/src/providers/keyboard/keyboard_provider.rs index 5e27d2b6..fe321a54 100644 --- a/packages/desktop/src/providers/keyboard/keyboard_provider.rs +++ b/packages/desktop/src/providers/keyboard/keyboard_provider.rs @@ -10,7 +10,7 @@ use windows::Win32::{ }; use crate::{ - impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, }; @@ -39,9 +39,7 @@ impl KeyboardProvider { KeyboardProvider { config, common } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } + async fn run_interval(&self) -> anyhow::Result { let keyboard_layout = unsafe { diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index 3d609a90..efd1a36e 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, }; @@ -36,9 +36,7 @@ impl MemoryProvider { MemoryProvider { config, common } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } + async fn run_interval(&self) -> anyhow::Result { let mut sysinfo = self.common.sysinfo.lock().await; diff --git a/packages/desktop/src/providers/network/network_provider.rs b/packages/desktop/src/providers/network/network_provider.rs index 61d27feb..b4d3d193 100644 --- a/packages/desktop/src/providers/network/network_provider.rs +++ b/packages/desktop/src/providers/network/network_provider.rs @@ -11,7 +11,7 @@ use super::{ }; use crate::{ common::{to_iec_bytes, to_si_bytes}, - impl_interval_provider, + providers::{CommonProviderState, ProviderOutput}, }; @@ -48,9 +48,7 @@ impl NetworkProvider { } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } + async fn run_interval(&self) -> anyhow::Result { let mut netinfo = self.netinfo.lock().await; diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index d43d1562..189eaa57 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -84,52 +84,3 @@ pub enum RuntimeType { Sync, Async, } - -/// Implements the `Provider` trait for the given struct. -/// -/// Expects that the struct has a `refresh_interval_ms` and `run_interval` -/// method. -#[macro_export] -macro_rules! impl_interval_provider { - ($type:ty, $allow_identical_emits:expr) => { - #[async_trait::async_trait] - impl crate::providers::Provider for $type { - fn runtime_type(&self) -> crate::providers::RuntimeType { - crate::providers::RuntimeType::Async - } - - async fn start_async(&mut self) { - let mut interval = tokio::time::interval( - std::time::Duration::from_millis(self.refresh_interval_ms()), - ); - - // Skip missed ticks when the interval runs. This prevents a burst - // of backlogged ticks after a delay. - interval - .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - let mut last_interval_res: Option< - crate::providers::ProviderEmission, - > = None; - - loop { - interval.tick().await; - - let interval_res = self.run_interval().await.into(); - - if $allow_identical_emits - || last_interval_res.as_ref() != Some(&interval_res) - { - let send_res = emit_tx.send(interval_res.clone()).await; - - if let Err(err) = send_res { - tracing::error!("Error sending provider result: {:?}", err); - } - - last_interval_res = Some(interval_res); - } - } - } - } - }; -} diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index c604555f..2e216e6d 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -43,6 +43,26 @@ pub struct CommonProviderState { pub sysinfo: Arc>, } +impl CommonProviderState { + pub fn emit(&self, emission: ProviderEmission) { + self.emit_tx.send(emission); + } + + pub async fn emit_provider_output(&self, output: anyhow::Result) + where + T: Into, + { + let send_res = self.emit_tx.send(ProviderEmission { + config_hash: self.config_hash.clone(), + result: output.map(Into::into).map_err(|err| err.to_string()), + }); + + if let Err(err) = send_res { + tracing::error!("Error sending provider result: {:?}", err); + } + } +} + /// Emission from a provider. #[derive(Debug, Clone, Serialize)] pub struct ProviderEmission { diff --git a/packages/desktop/src/providers/provider_output.rs b/packages/desktop/src/providers/provider_output.rs index c922fdd8..c44015a7 100644 --- a/packages/desktop/src/providers/provider_output.rs +++ b/packages/desktop/src/providers/provider_output.rs @@ -31,3 +31,117 @@ pub enum ProviderOutput { #[cfg(windows)] Keyboard(KeyboardOutput), } + +macro_rules! impl_provider_conversions { + // Single pattern that handles both regular and Windows variants + ($enum_name:ident { + // Regular variants + $($variant:ident($type:ty)),* $(,)? + + // Optional Windows variants + $(#[cfg(windows)] $win_variant:ident($win_type:ty)),* $(,)? + }) => { + // Regular implementations + $( + impl From<$type> for $enum_name { + fn from(value: $type) -> Self { + Self::$variant(value) + } + } + )* + + // Windows implementations + $( + #[cfg(windows)] + impl From<$win_type> for $enum_name { + fn from(value: $win_type) -> Self { + Self::$win_variant(value) + } + } + )* + }; +} + +// Usage is now simpler and mirrors the enum definition more closely +impl_provider_conversions!(ProviderOutput { + Battery(BatteryOutput), + Cpu(CpuOutput), + Host(HostOutput), + Ip(IpOutput), + Memory(MemoryOutput), + Disk(DiskOutput), + Network(NetworkOutput), + Weather(WeatherOutput), + #[cfg(windows)] Komorebi(KomorebiOutput), + #[cfg(windows)] Media(MediaOutput), + #[cfg(windows)] Keyboard(KeyboardOutput) +}); + +// impl From for ProviderOutput { +// fn from(output: BatteryOutput) -> Self { +// ProviderOutput::Battery(output) +// } +// } + +// impl From for ProviderOutput { +// fn from(output: CpuOutput) -> Self { +// ProviderOutput::Cpu(output) +// } +// } + +// impl From for ProviderOutput { +// fn from(output: HostOutput) -> Self { +// ProviderOutput::Host(output) +// } +// } + +// impl From for ProviderOutput { +// fn from(output: IpOutput) -> Self { +// ProviderOutput::Ip(output) +// } +// } + +// impl From for ProviderOutput { +// fn from(output: MemoryOutput) -> Self { +// ProviderOutput::Memory(output) +// } +// } + +// impl From for ProviderOutput { +// fn from(output: DiskOutput) -> Self { +// ProviderOutput::Disk(output) +// } +// } + +// impl From for ProviderOutput { +// fn from(output: NetworkOutput) -> Self { +// ProviderOutput::Network(output) +// } +// } + +// impl From for ProviderOutput { +// fn from(output: WeatherOutput) -> Self { +// ProviderOutput::Weather(output) +// } +// } + +// #[cfg(windows)] +// impl From for ProviderOutput { +// fn from(output: KomorebiOutput) -> Self { +// ProviderOutput::Komorebi(output) +// } +// } + +// #[cfg(windows)] +// impl From for ProviderOutput { +// fn from(output: MediaOutput) -> Self { +// ProviderOutput::Media(output) +// } +// } + +// #[cfg(windows)] +// impl From for ProviderOutput { +// fn from(output: KeyboardOutput) -> Self { +// ProviderOutput::Keyboard(output) +// } +// } diff --git a/packages/desktop/src/providers/weather/weather_provider.rs b/packages/desktop/src/providers/weather/weather_provider.rs index f34960ac..df71dc05 100644 --- a/packages/desktop/src/providers/weather/weather_provider.rs +++ b/packages/desktop/src/providers/weather/weather_provider.rs @@ -2,12 +2,9 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use super::open_meteo_res::OpenMeteoRes; -use crate::{ - impl_interval_provider, - providers::{ - ip::{IpProvider, IpProviderConfig}, - CommonProviderState, ProviderOutput, - }, +use crate::providers::{ + ip::{IpProvider, IpProviderConfig}, + CommonProviderState, ProviderOutput, }; #[derive(Deserialize, Debug)] @@ -63,10 +60,6 @@ impl WeatherProvider { } } - fn refresh_interval_ms(&self) -> u64 { - self.config.refresh_interval - } - async fn run_interval(&self) -> anyhow::Result { let (latitude, longitude) = { match (self.config.latitude, self.config.longitude) { From 8607697707279548a6f609e8c1a0f4dfe6adc505 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 21:10:33 +0800 Subject: [PATCH 18/38] feat: remove use of `impl_interval_provider`; manually implement `Provider` --- .../src/providers/battery/battery_provider.rs | 10 +---- .../desktop/src/providers/cpu/cpu_provider.rs | 29 ++++++++---- .../src/providers/disk/disk_provider.rs | 40 ++++++++++------- .../src/providers/host/host_provider.rs | 29 ++++++++---- .../desktop/src/providers/ip/ip_provider.rs | 26 ++++++++--- .../providers/keyboard/keyboard_provider.rs | 29 ++++++++---- .../providers/komorebi/komorebi_provider.rs | 25 +++++------ .../src/providers/media/media_provider.rs | 9 ++-- .../src/providers/memory/memory_provider.rs | 31 +++++++++---- .../src/providers/network/network_provider.rs | 44 +++++++++++-------- packages/desktop/src/providers/provider.rs | 6 ++- .../desktop/src/providers/provider_manager.rs | 6 +-- .../src/providers/weather/weather_provider.rs | 31 ++++++++++--- 13 files changed, 202 insertions(+), 113 deletions(-) diff --git a/packages/desktop/src/providers/battery/battery_provider.rs b/packages/desktop/src/providers/battery/battery_provider.rs index 567425b3..4f0b33d6 100644 --- a/packages/desktop/src/providers/battery/battery_provider.rs +++ b/packages/desktop/src/providers/battery/battery_provider.rs @@ -10,9 +10,7 @@ use starship_battery::{ use crate::{ common::SyncInterval, - providers::{ - CommonProviderState, Provider, ProviderOutput, RuntimeType, - }, + providers::{CommonProviderState, Provider, RuntimeType}, }; #[derive(Deserialize, Debug)] @@ -83,11 +81,7 @@ impl Provider for BatteryProvider { loop { interval.tick(); - - self - .common - .emit_tx - .send(ProviderOutput::Battery(self.run_interval())); + self.common.emit_output(self.run_interval()); } } } diff --git a/packages/desktop/src/providers/cpu/cpu_provider.rs b/packages/desktop/src/providers/cpu/cpu_provider.rs index d1f5954e..bb280d5f 100644 --- a/packages/desktop/src/providers/cpu/cpu_provider.rs +++ b/packages/desktop/src/providers/cpu/cpu_provider.rs @@ -1,8 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - - providers::{CommonProviderState, ProviderOutput}, + common::SyncInterval, + providers::{CommonProviderState, Provider, RuntimeType}, }; #[derive(Deserialize, Debug)] @@ -34,13 +34,11 @@ impl CpuProvider { CpuProvider { config, common } } - - - async fn run_interval(&self) -> anyhow::Result { - let mut sysinfo = self.common.sysinfo.lock().await; + fn run_interval(&self) -> anyhow::Result { + let mut sysinfo = self.common.sysinfo.blocking_lock(); sysinfo.refresh_cpu(); - Ok(ProviderOutput::Cpu(CpuOutput { + Ok(CpuOutput { usage: sysinfo.global_cpu_info().cpu_usage(), frequency: sysinfo.global_cpu_info().frequency(), logical_core_count: sysinfo.cpus().len(), @@ -48,8 +46,21 @@ impl CpuProvider { .physical_core_count() .unwrap_or(sysinfo.cpus().len()), vendor: sysinfo.global_cpu_info().vendor_id().into(), - })) + }) } } -impl_interval_provider!(CpuProvider, true); +impl Provider for CpuProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync + } + + fn start_sync(&mut self) { + let mut interval = SyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick(); + self.common.emit_output(self.run_interval()); + } + } +} diff --git a/packages/desktop/src/providers/disk/disk_provider.rs b/packages/desktop/src/providers/disk/disk_provider.rs index 5f214f5d..dfd1d6fb 100644 --- a/packages/desktop/src/providers/disk/disk_provider.rs +++ b/packages/desktop/src/providers/disk/disk_provider.rs @@ -1,13 +1,9 @@ -use std::sync::Arc; - use serde::{Deserialize, Serialize}; use sysinfo::Disks; -use tokio::sync::Mutex; use crate::{ - common::{to_iec_bytes, to_si_bytes}, - - providers::{CommonProviderState, ProviderOutput}, + common::{to_iec_bytes, to_si_bytes, SyncInterval}, + providers::{CommonProviderState, Provider, RuntimeType}, }; #[derive(Deserialize, Debug)] @@ -37,7 +33,7 @@ pub struct Disk { pub struct DiskProvider { config: DiskProviderConfig, common: CommonProviderState, - disks: Arc>, + disks: Disks, } #[derive(Debug, Clone, PartialEq, Serialize)] @@ -58,17 +54,15 @@ impl DiskProvider { DiskProvider { config, common, - disks: Arc::new(Mutex::new(Disks::new_with_refreshed_list())), + disks: Disks::new_with_refreshed_list(), } } + fn run_interval(&mut self) -> anyhow::Result { + self.disks.refresh(); - - async fn run_interval(&self) -> anyhow::Result { - let mut disks = self.disks.lock().await; - disks.refresh(); - - let disks = disks + let disks = self + .disks .iter() .map(|disk| -> anyhow::Result { let name = disk.name().to_string_lossy().to_string(); @@ -87,7 +81,7 @@ impl DiskProvider { }) .collect::>>()?; - Ok(ProviderOutput::Disk(DiskOutput { disks })) + Ok(DiskOutput { disks }) } fn to_disk_size_measure(bytes: u64) -> anyhow::Result { @@ -104,4 +98,18 @@ impl DiskProvider { } } -impl_interval_provider!(DiskProvider, true); +impl Provider for DiskProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync + } + + fn start_sync(&mut self) { + let mut interval = SyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick(); + let output = self.run_interval(); + self.common.emit_output(output); + } + } +} diff --git a/packages/desktop/src/providers/host/host_provider.rs b/packages/desktop/src/providers/host/host_provider.rs index 4ddf4436..adf9df95 100644 --- a/packages/desktop/src/providers/host/host_provider.rs +++ b/packages/desktop/src/providers/host/host_provider.rs @@ -2,8 +2,8 @@ use serde::{Deserialize, Serialize}; use sysinfo::System; use crate::{ - - providers::{CommonProviderState, ProviderOutput}, + common::SyncInterval, + providers::{CommonProviderState, Provider, RuntimeType}, }; #[derive(Deserialize, Debug)] @@ -36,18 +36,31 @@ impl HostProvider { HostProvider { config, common } } - - - async fn run_interval(&self) -> anyhow::Result { - Ok(ProviderOutput::Host(HostOutput { + fn run_interval(&mut self) -> anyhow::Result { + Ok(HostOutput { hostname: System::host_name(), os_name: System::name(), os_version: System::os_version(), friendly_os_version: System::long_os_version(), boot_time: System::boot_time() * 1000, uptime: System::uptime() * 1000, - })) + }) } } -impl_interval_provider!(HostProvider, false); +impl Provider for HostProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync + } + + fn start_sync(&mut self) { + let mut interval = SyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick(); + + let output = self.run_interval(); + self.common.emit_output(output); + } + } +} diff --git a/packages/desktop/src/providers/ip/ip_provider.rs b/packages/desktop/src/providers/ip/ip_provider.rs index 1ea48f27..a0b6164b 100644 --- a/packages/desktop/src/providers/ip/ip_provider.rs +++ b/packages/desktop/src/providers/ip/ip_provider.rs @@ -3,7 +3,10 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use super::ipinfo_res::IpinfoRes; -use crate::{ providers::CommonProviderState}; +use crate::{ + common::AsyncInterval, + providers::{CommonProviderState, Provider, RuntimeType}, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -39,9 +42,7 @@ impl IpProvider { } } - - - async fn run_interval(&self) -> anyhow::Result { + async fn run_interval(&mut self) -> anyhow::Result { Self::query_ip(&self.http_client).await } @@ -71,4 +72,19 @@ impl IpProvider { } } -impl_interval_provider!(IpProvider, false); +impl Provider for IpProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Async + } + + async fn start_async(&mut self) { + let mut interval = AsyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick().await; + + let output = self.run_interval().await; + self.common.emit_output(output); + } + } +} diff --git a/packages/desktop/src/providers/keyboard/keyboard_provider.rs b/packages/desktop/src/providers/keyboard/keyboard_provider.rs index fe321a54..68d2a654 100644 --- a/packages/desktop/src/providers/keyboard/keyboard_provider.rs +++ b/packages/desktop/src/providers/keyboard/keyboard_provider.rs @@ -10,8 +10,8 @@ use windows::Win32::{ }; use crate::{ - - providers::{CommonProviderState, ProviderOutput}, + common::SyncInterval, + providers::{CommonProviderState, Provider, RuntimeType}, }; #[derive(Deserialize, Debug)] @@ -39,9 +39,7 @@ impl KeyboardProvider { KeyboardProvider { config, common } } - - - async fn run_interval(&self) -> anyhow::Result { + fn run_interval(&mut self) -> anyhow::Result { let keyboard_layout = unsafe { GetKeyboardLayout(GetWindowThreadProcessId( GetForegroundWindow(), @@ -67,10 +65,25 @@ impl KeyboardProvider { let layout_name = String::from_utf16_lossy(&locale_name[..result as usize]); - Ok(ProviderOutput::Keyboard(KeyboardOutput { + Ok(KeyboardOutput { layout: layout_name, - })) + }) } } -impl_interval_provider!(KeyboardProvider, false); +impl Provider for KeyboardProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync + } + + fn start_sync(&mut self) { + let mut interval = SyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick(); + + let output = self.run_interval(); + self.common.emit_output(output); + } + } +} diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index a17c0497..3b808b73 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -15,9 +15,7 @@ use super::{ KomorebiContainer, KomorebiLayout, KomorebiLayoutFlip, KomorebiMonitor, KomorebiWindow, KomorebiWorkspace, }; -use crate::providers::{ - CommonProviderState, Provider, ProviderOutput, RuntimeType, -}; +use crate::providers::{CommonProviderState, Provider, RuntimeType}; const SOCKET_NAME: &str = "zebar.sock"; @@ -79,17 +77,18 @@ impl KomorebiProvider { &String::from_utf8(buffer).unwrap(), ) { - self.common.emit_tx.send( - Ok(ProviderOutput::Komorebi(Self::transform_response( - notification.state, - ))) - .into(), - ); + self.common.emit_output(Ok(Self::transform_response( + notification.state, + ))); } } - Err(_) => self.common.emit_tx.send( - Err(anyhow::anyhow!("Failed to read Komorebi stream.")).into(), - ), + Err(_) => { + self + .common + .emit_output::(Err(anyhow::anyhow!( + "Failed to read Komorebi stream." + ))) + } } } @@ -185,7 +184,7 @@ impl Provider for KomorebiProvider { fn start_sync(&mut self) { if let Err(err) = self.create_socket() { - self.common.emit_tx.try_send(Err(err).into()); + self.common.emit_output::(Err(err)); } } } diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index 30d93f55..89b8ef7f 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -155,8 +155,8 @@ impl MediaProvider { let event_tokens = Arc::new(Mutex::new(event_tokens)); // Clean up & rebind listeners when session changes. - let session_changed_handler = TypedEventHandler::new( - move |session_manager: &Option, _| { + let session_changed_handler = + TypedEventHandler::new(move |_: &Option, _| { { let mut current_session = current_session.lock().unwrap(); let mut event_tokens = event_tokens.lock().unwrap(); @@ -191,8 +191,7 @@ impl MediaProvider { } Ok(()) - }, - ); + }); session_manager.CurrentSessionChanged(&session_changed_handler)?; @@ -286,7 +285,7 @@ impl Provider for MediaProvider { fn start_sync(&mut self) { if let Err(err) = self.create_session_manager() { - let _ = self.common.emit_tx.send(Err(err).into()); + self.common.emit_output::(Err(err)); } } } diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index efd1a36e..cabfe112 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -1,8 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - - providers::{CommonProviderState, ProviderOutput}, + common::SyncInterval, + providers::{CommonProviderState, Provider, RuntimeType}, }; #[derive(Deserialize, Debug)] @@ -36,17 +36,15 @@ impl MemoryProvider { MemoryProvider { config, common } } - - - async fn run_interval(&self) -> anyhow::Result { - let mut sysinfo = self.common.sysinfo.lock().await; + fn run_interval(&mut self) -> anyhow::Result { + let mut sysinfo = self.common.sysinfo.blocking_lock(); sysinfo.refresh_memory(); let usage = (sysinfo.used_memory() as f32 / sysinfo.total_memory() as f32) * 100.0; - Ok(ProviderOutput::Memory(MemoryOutput { + Ok(MemoryOutput { usage, free_memory: sysinfo.free_memory(), used_memory: sysinfo.used_memory(), @@ -54,8 +52,23 @@ impl MemoryProvider { free_swap: sysinfo.free_swap(), used_swap: sysinfo.used_swap(), total_swap: sysinfo.total_swap(), - })) + }) } } -impl_interval_provider!(MemoryProvider, true); +impl Provider for MemoryProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync + } + + fn start_sync(&mut self) { + let mut interval = SyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick(); + + let output = self.run_interval(); + self.common.emit_output(output); + } + } +} diff --git a/packages/desktop/src/providers/network/network_provider.rs b/packages/desktop/src/providers/network/network_provider.rs index b4d3d193..b5bdecee 100644 --- a/packages/desktop/src/providers/network/network_provider.rs +++ b/packages/desktop/src/providers/network/network_provider.rs @@ -1,8 +1,5 @@ -use std::sync::Arc; - use serde::{Deserialize, Serialize}; use sysinfo::Networks; -use tokio::sync::Mutex; use super::{ wifi_hotspot::{default_gateway_wifi, WifiHotstop}, @@ -10,9 +7,8 @@ use super::{ NetworkTrafficMeasure, }; use crate::{ - common::{to_iec_bytes, to_si_bytes}, - - providers::{CommonProviderState, ProviderOutput}, + common::{to_iec_bytes, to_si_bytes, SyncInterval}, + providers::{CommonProviderState, Provider, RuntimeType}, }; #[derive(Deserialize, Debug)] @@ -33,7 +29,7 @@ pub struct NetworkOutput { pub struct NetworkProvider { config: NetworkProviderConfig, common: CommonProviderState, - netinfo: Arc>, + netinfo: Networks, } impl NetworkProvider { @@ -44,28 +40,25 @@ impl NetworkProvider { NetworkProvider { config, common, - netinfo: Arc::new(Mutex::new(Networks::new_with_refreshed_list())), + netinfo: Networks::new_with_refreshed_list(), } } - - - async fn run_interval(&self) -> anyhow::Result { - let mut netinfo = self.netinfo.lock().await; - netinfo.refresh(); + fn run_interval(&mut self) -> anyhow::Result { + self.netinfo.refresh(); let interfaces = netdev::get_interfaces(); let default_interface = netdev::get_default_interface().ok(); - let (received, total_received) = Self::bytes_received(&netinfo); + let (received, total_received) = Self::bytes_received(&self.netinfo); let received_per_sec = received / self.config.refresh_interval * 1000; let (transmitted, total_transmitted) = - Self::bytes_transmitted(&netinfo); + Self::bytes_transmitted(&self.netinfo); let transmitted_per_sec = transmitted / self.config.refresh_interval * 1000; - Ok(ProviderOutput::Network(NetworkOutput { + Ok(NetworkOutput { default_interface: default_interface .as_ref() .map(Self::transform_interface), @@ -90,7 +83,7 @@ impl NetworkProvider { total_transmitted, )?, }, - })) + }) } fn to_network_traffic_measure( @@ -200,4 +193,19 @@ impl NetworkProvider { } } -impl_interval_provider!(NetworkProvider, true); +impl Provider for NetworkProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync + } + + fn start_sync(&mut self) { + let mut interval = SyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick(); + + let output = self.run_interval(); + self.common.emit_output(output); + } + } +} diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index 189eaa57..324c4931 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -45,8 +45,9 @@ pub trait Provider: Send + Sync { /// Panics if wrong runtime type is used. fn call_function_sync( &self, - #[allow(unused_variables)] function: ProviderFunction, + function: ProviderFunction, ) -> anyhow::Result { + let _function = function; match self.runtime_type() { RuntimeType::Sync => { unreachable!("Sync providers must implement `call_function_sync`.") @@ -64,8 +65,9 @@ pub trait Provider: Send + Sync { /// Panics if wrong runtime type is used. async fn call_function_async( &self, - #[allow(unused_variables)] function: ProviderFunction, + function: ProviderFunction, ) -> anyhow::Result { + let _function = function; match self.runtime_type() { RuntimeType::Async => { unreachable!( diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 2e216e6d..f996198d 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -44,11 +44,7 @@ pub struct CommonProviderState { } impl CommonProviderState { - pub fn emit(&self, emission: ProviderEmission) { - self.emit_tx.send(emission); - } - - pub async fn emit_provider_output(&self, output: anyhow::Result) + pub fn emit_output(&self, output: anyhow::Result) where T: Into, { diff --git a/packages/desktop/src/providers/weather/weather_provider.rs b/packages/desktop/src/providers/weather/weather_provider.rs index df71dc05..58d4eec5 100644 --- a/packages/desktop/src/providers/weather/weather_provider.rs +++ b/packages/desktop/src/providers/weather/weather_provider.rs @@ -2,9 +2,11 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use super::open_meteo_res::OpenMeteoRes; -use crate::providers::{ - ip::{IpProvider, IpProviderConfig}, - CommonProviderState, ProviderOutput, +use crate::{ + common::AsyncInterval, + providers::{ + ip::IpProvider, CommonProviderState, Provider, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -60,7 +62,7 @@ impl WeatherProvider { } } - async fn run_interval(&self) -> anyhow::Result { + async fn run_interval(&self) -> anyhow::Result { let (latitude, longitude) = { match (self.config.latitude, self.config.longitude) { (Some(lat), Some(lon)) => (lat, lon), @@ -90,7 +92,7 @@ impl WeatherProvider { let current_weather = res.current_weather; let is_daytime = current_weather.is_day == 1; - Ok(ProviderOutput::Weather(WeatherOutput { + Ok(WeatherOutput { is_daytime, status: Self::get_weather_status( current_weather.weather_code, @@ -101,7 +103,7 @@ impl WeatherProvider { current_weather.temperature, ), wind_speed: current_weather.wind_speed, - })) + }) } fn celsius_to_fahrenheit(celsius_temp: f32) -> f32 { @@ -147,4 +149,19 @@ impl WeatherProvider { } } -impl_interval_provider!(WeatherProvider, true); +impl Provider for WeatherProvider { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Async + } + + async fn start_async(&mut self) { + let mut interval = AsyncInterval::new(self.config.refresh_interval); + + loop { + interval.tick().await; + + let output = self.run_interval().await; + self.common.emit_output(output); + } + } +} From c7a45800098801ae51749b73044ae36f6fe28448 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 22:32:49 +0800 Subject: [PATCH 19/38] fix: add `async_trait` to async providers --- packages/desktop/src/providers/ip/ip_provider.rs | 2 ++ .../desktop/src/providers/media/media_provider.rs | 12 ++++-------- .../src/providers/weather/weather_provider.rs | 2 ++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/desktop/src/providers/ip/ip_provider.rs b/packages/desktop/src/providers/ip/ip_provider.rs index a0b6164b..d5ccd77e 100644 --- a/packages/desktop/src/providers/ip/ip_provider.rs +++ b/packages/desktop/src/providers/ip/ip_provider.rs @@ -1,4 +1,5 @@ use anyhow::Context; +use async_trait::async_trait; use reqwest::Client; use serde::{Deserialize, Serialize}; @@ -72,6 +73,7 @@ impl IpProvider { } } +#[async_trait] impl Provider for IpProvider { fn runtime_type(&self) -> RuntimeType { RuntimeType::Async diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index 89b8ef7f..d5ae121e 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -153,6 +153,7 @@ impl MediaProvider { let current_session = Arc::new(Mutex::new(current_session)); let event_tokens = Arc::new(Mutex::new(event_tokens)); + let emit_tx = self.common.emit_tx.clone(); // Clean up & rebind listeners when session changes. let session_changed_handler = @@ -176,15 +177,10 @@ impl MediaProvider { let new_session = GsmtcManager::RequestAsync()?.get()?.GetCurrentSession()?; - let tokens = Self::add_session_listeners( - &new_session, - self.common.emit_tx.clone(), - )?; + let tokens = + Self::add_session_listeners(&new_session, emit_tx.clone())?; - Self::emit_media_info( - Some(&new_session), - self.common.emit_tx.clone(), - ); + Self::emit_media_info(Some(&new_session), emit_tx.clone()); *current_session = Some(new_session); *event_tokens = Some(tokens); diff --git a/packages/desktop/src/providers/weather/weather_provider.rs b/packages/desktop/src/providers/weather/weather_provider.rs index 58d4eec5..e4f98ec2 100644 --- a/packages/desktop/src/providers/weather/weather_provider.rs +++ b/packages/desktop/src/providers/weather/weather_provider.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use reqwest::Client; use serde::{Deserialize, Serialize}; @@ -149,6 +150,7 @@ impl WeatherProvider { } } +#[async_trait] impl Provider for WeatherProvider { fn runtime_type(&self) -> RuntimeType { RuntimeType::Async From efb9f0d8f82cca5d0054c367b36ff3bab069cabd Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sun, 17 Nov 2024 23:55:07 +0800 Subject: [PATCH 20/38] feat: add `ProviderEmitter` helper struct --- .../src/providers/battery/battery_provider.rs | 4 +- .../desktop/src/providers/cpu/cpu_provider.rs | 4 +- .../src/providers/disk/disk_provider.rs | 3 +- .../src/providers/host/host_provider.rs | 2 +- .../desktop/src/providers/ip/ip_provider.rs | 2 +- .../providers/keyboard/keyboard_provider.rs | 2 +- .../providers/komorebi/komorebi_provider.rs | 14 ++--- .../src/providers/media/media_provider.rs | 51 ++++++++----------- .../src/providers/memory/memory_provider.rs | 2 +- .../src/providers/network/network_provider.rs | 2 +- .../desktop/src/providers/provider_manager.rs | 25 ++++++--- .../src/providers/weather/weather_provider.rs | 2 +- 12 files changed, 56 insertions(+), 57 deletions(-) diff --git a/packages/desktop/src/providers/battery/battery_provider.rs b/packages/desktop/src/providers/battery/battery_provider.rs index 4f0b33d6..5a640378 100644 --- a/packages/desktop/src/providers/battery/battery_provider.rs +++ b/packages/desktop/src/providers/battery/battery_provider.rs @@ -81,7 +81,9 @@ impl Provider for BatteryProvider { loop { interval.tick(); - self.common.emit_output(self.run_interval()); + + let output = self.run_interval(); + self.common.emitter.emit_output(output); } } } diff --git a/packages/desktop/src/providers/cpu/cpu_provider.rs b/packages/desktop/src/providers/cpu/cpu_provider.rs index bb280d5f..c381b3cb 100644 --- a/packages/desktop/src/providers/cpu/cpu_provider.rs +++ b/packages/desktop/src/providers/cpu/cpu_provider.rs @@ -60,7 +60,9 @@ impl Provider for CpuProvider { loop { interval.tick(); - self.common.emit_output(self.run_interval()); + + let output = self.run_interval(); + self.common.emitter.emit_output(output); } } } diff --git a/packages/desktop/src/providers/disk/disk_provider.rs b/packages/desktop/src/providers/disk/disk_provider.rs index dfd1d6fb..75e3c80d 100644 --- a/packages/desktop/src/providers/disk/disk_provider.rs +++ b/packages/desktop/src/providers/disk/disk_provider.rs @@ -108,8 +108,9 @@ impl Provider for DiskProvider { loop { interval.tick(); + let output = self.run_interval(); - self.common.emit_output(output); + self.common.emitter.emit_output(output); } } } diff --git a/packages/desktop/src/providers/host/host_provider.rs b/packages/desktop/src/providers/host/host_provider.rs index adf9df95..fe0fa21f 100644 --- a/packages/desktop/src/providers/host/host_provider.rs +++ b/packages/desktop/src/providers/host/host_provider.rs @@ -60,7 +60,7 @@ impl Provider for HostProvider { interval.tick(); let output = self.run_interval(); - self.common.emit_output(output); + self.common.emitter.emit_output(output); } } } diff --git a/packages/desktop/src/providers/ip/ip_provider.rs b/packages/desktop/src/providers/ip/ip_provider.rs index d5ccd77e..35398e65 100644 --- a/packages/desktop/src/providers/ip/ip_provider.rs +++ b/packages/desktop/src/providers/ip/ip_provider.rs @@ -86,7 +86,7 @@ impl Provider for IpProvider { interval.tick().await; let output = self.run_interval().await; - self.common.emit_output(output); + self.common.emitter.emit_output(output); } } } diff --git a/packages/desktop/src/providers/keyboard/keyboard_provider.rs b/packages/desktop/src/providers/keyboard/keyboard_provider.rs index 68d2a654..dd39f392 100644 --- a/packages/desktop/src/providers/keyboard/keyboard_provider.rs +++ b/packages/desktop/src/providers/keyboard/keyboard_provider.rs @@ -83,7 +83,7 @@ impl Provider for KeyboardProvider { interval.tick(); let output = self.run_interval(); - self.common.emit_output(output); + self.common.emitter.emit_output(output); } } } diff --git a/packages/desktop/src/providers/komorebi/komorebi_provider.rs b/packages/desktop/src/providers/komorebi/komorebi_provider.rs index 3b808b73..e4bada56 100644 --- a/packages/desktop/src/providers/komorebi/komorebi_provider.rs +++ b/packages/desktop/src/providers/komorebi/komorebi_provider.rs @@ -77,18 +77,14 @@ impl KomorebiProvider { &String::from_utf8(buffer).unwrap(), ) { - self.common.emit_output(Ok(Self::transform_response( + self.common.emitter.emit_output(Ok(Self::transform_response( notification.state, ))); } } - Err(_) => { - self - .common - .emit_output::(Err(anyhow::anyhow!( - "Failed to read Komorebi stream." - ))) - } + Err(_) => self.common.emitter.emit_output::(Err( + anyhow::anyhow!("Failed to read Komorebi stream."), + )), } } @@ -184,7 +180,7 @@ impl Provider for KomorebiProvider { fn start_sync(&mut self) { if let Err(err) = self.create_socket() { - self.common.emit_output::(Err(err)); + self.common.emitter.emit_output::(Err(err)); } } } diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index d5ae121e..bddc9655 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -17,8 +17,8 @@ use windows::{ }; use crate::providers::{ - CommonProviderState, Provider, ProviderEmission, ProviderOutput, - RuntimeType, + CommonProviderState, Provider, ProviderEmission, ProviderEmitter, + ProviderOutput, RuntimeType, }; #[derive(Deserialize, Debug)] @@ -66,17 +66,10 @@ impl MediaProvider { fn emit_media_info( session: Option<&GsmtcSession>, - emit_tx: mpsc::UnboundedSender, + emitter: &ProviderEmitter, ) { - let _ = match Self::media_output(session) { - Ok(media_output) => { - emit_tx.send(Ok(ProviderOutput::Media(media_output)).into()) - } - Err(err) => { - error!("Error retrieving media output: {:?}", err); - emit_tx.send(Err(err).into()) - } - }; + let media_output = Self::media_output(session); + emitter.emit_output(media_output); } fn media_output( @@ -138,22 +131,18 @@ impl MediaProvider { let current_session = session_manager.GetCurrentSession().ok(); let event_tokens = match ¤t_session { - Some(session) => Some(Self::add_session_listeners( - session, - self.common.emit_tx.clone(), - )?), + Some(session) => { + Some(Self::add_session_listeners(session, &self.common.emitter)?) + } None => None, }; // Emit initial media info. - Self::emit_media_info( - current_session.as_ref(), - self.common.emit_tx.clone(), - ); + Self::emit_media_info(current_session.as_ref(), &self.common.emitter); let current_session = Arc::new(Mutex::new(current_session)); let event_tokens = Arc::new(Mutex::new(event_tokens)); - let emit_tx = self.common.emit_tx.clone(); + let emitter = self.common.emitter.clone(); // Clean up & rebind listeners when session changes. let session_changed_handler = @@ -178,9 +167,9 @@ impl MediaProvider { GsmtcManager::RequestAsync()?.get()?.GetCurrentSession()?; let tokens = - Self::add_session_listeners(&new_session, emit_tx.clone())?; + Self::add_session_listeners(&new_session, &emitter)?; - Self::emit_media_info(Some(&new_session), emit_tx.clone()); + Self::emit_media_info(Some(&new_session), &emitter); *current_session = Some(new_session); *event_tokens = Some(tokens); @@ -216,18 +205,18 @@ impl MediaProvider { fn add_session_listeners( session: &GsmtcSession, - emit_tx: mpsc::UnboundedSender, + emitter: &ProviderEmitter, ) -> windows::core::Result { debug!("Adding session listeners."); let media_properties_changed_handler = { - let emit_tx = emit_tx.clone(); + let emitter = emitter.clone(); TypedEventHandler::new(move |session: &Option, _| { debug!("Media properties changed event triggered."); if let Some(session) = session { - Self::emit_media_info(Some(session), emit_tx.clone()); + Self::emit_media_info(Some(session), &emitter); } Ok(()) @@ -235,13 +224,13 @@ impl MediaProvider { }; let playback_info_changed_handler = { - let emit_tx = emit_tx.clone(); + let emitter = emitter.clone(); TypedEventHandler::new(move |session: &Option, _| { debug!("Playback info changed event triggered."); if let Some(session) = session { - Self::emit_media_info(Some(session), emit_tx.clone()); + Self::emit_media_info(Some(session), &emitter); } Ok(()) @@ -249,13 +238,13 @@ impl MediaProvider { }; let timeline_properties_changed_handler = { - let emit_tx = emit_tx.clone(); + let emitter = emitter.clone(); TypedEventHandler::new(move |session: &Option, _| { debug!("Timeline properties changed event triggered."); if let Some(session) = session { - Self::emit_media_info(Some(session), emit_tx.clone()); + Self::emit_media_info(Some(session), &emitter); } Ok(()) @@ -281,7 +270,7 @@ impl Provider for MediaProvider { fn start_sync(&mut self) { if let Err(err) = self.create_session_manager() { - self.common.emit_output::(Err(err)); + self.common.emitter.emit_output::(Err(err)); } } } diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index cabfe112..9b8ee650 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -68,7 +68,7 @@ impl Provider for MemoryProvider { interval.tick(); let output = self.run_interval(); - self.common.emit_output(output); + self.common.emitter.emit_output(output); } } } diff --git a/packages/desktop/src/providers/network/network_provider.rs b/packages/desktop/src/providers/network/network_provider.rs index b5bdecee..a63fbd9f 100644 --- a/packages/desktop/src/providers/network/network_provider.rs +++ b/packages/desktop/src/providers/network/network_provider.rs @@ -205,7 +205,7 @@ impl Provider for NetworkProvider { interval.tick(); let output = self.run_interval(); - self.common.emit_output(output); + self.common.emitter.emit_output(output); } } } diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index f996198d..780913e4 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -33,17 +33,24 @@ pub struct CommonProviderState { oneshot::Sender, )>, - /// Sender channel for outgoing provider emissions. - pub emit_tx: mpsc::UnboundedSender, - - /// Hash of the provider's config. - pub config_hash: String, + /// Wrapper for the sender channel for outgoing provider emissions. + pub emitter: ProviderEmitter, /// Shared `sysinfo` instance. pub sysinfo: Arc>, } -impl CommonProviderState { +/// A lightweight handle for emitting provider outputs from any thread. +#[derive(Clone, Debug)] +pub struct ProviderEmitter { + /// Sender channel for outgoing provider emissions. + emit_tx: mpsc::UnboundedSender, + + /// Hash of the provider's config. + config_hash: String, +} + +impl ProviderEmitter { pub fn emit_output(&self, output: anyhow::Result) where T: Into, @@ -147,8 +154,10 @@ impl ProviderManager { let common = CommonProviderState { stop_rx, function_rx, - emit_tx: self.emit_tx.clone(), - config_hash: config_hash.clone(), + emitter: ProviderEmitter { + emit_tx: self.emit_tx.clone(), + config_hash: config_hash.clone(), + }, sysinfo: self.sysinfo.clone(), }; diff --git a/packages/desktop/src/providers/weather/weather_provider.rs b/packages/desktop/src/providers/weather/weather_provider.rs index e4f98ec2..91704b37 100644 --- a/packages/desktop/src/providers/weather/weather_provider.rs +++ b/packages/desktop/src/providers/weather/weather_provider.rs @@ -163,7 +163,7 @@ impl Provider for WeatherProvider { interval.tick().await; let output = self.run_interval().await; - self.common.emit_output(output); + self.common.emitter.emit_output(output); } } } From a225157faafc085708fcb1cc49f57ef35d53603a Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Mon, 18 Nov 2024 01:43:41 +0800 Subject: [PATCH 21/38] feat: add custom serializer for provider emission (compiling and working again) --- .../src/providers/media/media_provider.rs | 4 +- .../desktop/src/providers/provider_manager.rs | 41 +++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/desktop/src/providers/media/media_provider.rs b/packages/desktop/src/providers/media/media_provider.rs index bddc9655..008858ed 100644 --- a/packages/desktop/src/providers/media/media_provider.rs +++ b/packages/desktop/src/providers/media/media_provider.rs @@ -5,7 +5,6 @@ use std::{ use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc::{self}; use tracing::{debug, error}; use windows::{ Foundation::{EventRegistrationToken, TypedEventHandler}, @@ -17,8 +16,7 @@ use windows::{ }; use crate::providers::{ - CommonProviderState, Provider, ProviderEmission, ProviderEmitter, - ProviderOutput, RuntimeType, + CommonProviderState, Provider, ProviderEmitter, RuntimeType, }; #[derive(Deserialize, Debug)] diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 780913e4..b3a56f75 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::{bail, Context}; -use serde::Serialize; +use serde::{ser::SerializeStruct, Serialize}; use tauri::{AppHandle, Emitter}; use tokio::{ sync::{mpsc, oneshot, Mutex}, @@ -33,14 +33,14 @@ pub struct CommonProviderState { oneshot::Sender, )>, - /// Wrapper for the sender channel for outgoing provider emissions. + /// Wrapper around the sender channel of provider emissions. pub emitter: ProviderEmitter, /// Shared `sysinfo` instance. pub sysinfo: Arc>, } -/// A lightweight handle for emitting provider outputs from any thread. +/// Handle for sending provider emissions. #[derive(Clone, Debug)] pub struct ProviderEmitter { /// Sender channel for outgoing provider emissions. @@ -51,6 +51,7 @@ pub struct ProviderEmitter { } impl ProviderEmitter { + /// Emits an output from a provider. pub fn emit_output(&self, output: anyhow::Result) where T: Into, @@ -68,11 +69,13 @@ impl ProviderEmitter { /// Emission from a provider. #[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] pub struct ProviderEmission { /// Hash of the provider's config. pub config_hash: String, /// A thread-safe `Result` type for provider outputs and errors. + #[serde(serialize_with = "serialize_result")] pub result: Result, } @@ -143,7 +146,7 @@ impl ProviderManager { if let Some(found_emit) = self.emit_cache.lock().await.get(&config_hash) { - self.app_handle.emit("provider-emit", found_emit); + self.app_handle.emit("provider-emit", found_emit)?; return Ok(()); }; } @@ -243,7 +246,7 @@ impl ProviderManager { /// provider. /// /// Returns the result of the function execution. - async fn call_function( + pub async fn call_function( &self, config_hash: String, function: ProviderFunction, @@ -266,8 +269,12 @@ impl ProviderManager { .remove(&config_hash) .context("No provider found with config.")?; - // Send shutdown signal to the provider and wait for it to stop. - provider_ref.stop_tx.send(()); + // Send shutdown signal to the provider. + if let Err(_) = provider_ref.stop_tx.send(()) { + bail!("Failed to send shutdown signal to provider."); + } + + // Wait for the provider to stop. provider_ref.task_handle.await?; Ok(()) @@ -279,3 +286,23 @@ impl ProviderManager { cache.insert(emission.config_hash.clone(), emission); } } + +/// Custom serializer for Result that converts: +/// - Ok(output) -> {"output": output} +/// - Err(error) -> {"error": error} +fn serialize_result( + result: &Result, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + let mut state = serializer.serialize_struct("Result", 1)?; + + match result { + Ok(output) => state.serialize_field("output", output)?, + Err(error) => state.serialize_field("error", error)?, + } + + state.end() +} From 48ba00310f7d24105c5780760ef90d4a49152140 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Mon, 18 Nov 2024 02:13:28 +0800 Subject: [PATCH 22/38] feat: add tauri command for calling provider functions --- packages/desktop/src/commands.rs | 17 ++++++++++++++++- packages/desktop/src/main.rs | 1 + packages/desktop/src/providers/provider.rs | 6 +++--- .../desktop/src/providers/provider_function.rs | 14 +++++--------- .../desktop/src/providers/provider_manager.rs | 8 ++++---- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/desktop/src/commands.rs b/packages/desktop/src/commands.rs index 5500ce75..0b295cbc 100644 --- a/packages/desktop/src/commands.rs +++ b/packages/desktop/src/commands.rs @@ -8,7 +8,10 @@ use crate::common::macos::WindowExtMacOs; use crate::common::windows::WindowExtWindows; use crate::{ config::{Config, WidgetConfig, WidgetPlacement}, - providers::{ProviderConfig, ProviderManager}, + providers::{ + ProviderConfig, ProviderFunction, ProviderFunctionResponse, + ProviderManager, + }, widget_factory::{WidgetFactory, WidgetOpenOptions, WidgetState}, }; @@ -103,6 +106,18 @@ pub async fn unlisten_provider( .map_err(|err| err.to_string()) } +#[tauri::command] +pub async fn call_provider_function( + config_hash: String, + function: ProviderFunction, + provider_manager: State<'_, Arc>, +) -> anyhow::Result { + provider_manager + .call_function(config_hash, function) + .await + .map_err(|err| err.to_string()) +} + /// Tauri's implementation of `always_on_top` places the window above /// all normal windows (but not the MacOS menu bar). The following instead /// sets the z-order of the window to be above the menu bar. diff --git a/packages/desktop/src/main.rs b/packages/desktop/src/main.rs index 9cf26e93..9dae60cf 100644 --- a/packages/desktop/src/main.rs +++ b/packages/desktop/src/main.rs @@ -89,6 +89,7 @@ async fn main() -> anyhow::Result<()> { commands::update_widget_config, commands::listen_provider, commands::unlisten_provider, + commands::call_provider_function, commands::set_always_on_top, commands::set_skip_taskbar ]) diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index 324c4931..b0f26cd2 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; -use super::{ProviderFunction, ProviderFunctionResult}; +use super::{ProviderFunction, ProviderFunctionResponse}; #[async_trait] pub trait Provider: Send + Sync { @@ -46,7 +46,7 @@ pub trait Provider: Send + Sync { fn call_function_sync( &self, function: ProviderFunction, - ) -> anyhow::Result { + ) -> anyhow::Result { let _function = function; match self.runtime_type() { RuntimeType::Sync => { @@ -66,7 +66,7 @@ pub trait Provider: Send + Sync { async fn call_function_async( &self, function: ProviderFunction, - ) -> anyhow::Result { + ) -> anyhow::Result { let _function = function; match self.runtime_type() { RuntimeType::Async => { diff --git a/packages/desktop/src/providers/provider_function.rs b/packages/desktop/src/providers/provider_function.rs index 2c53d0ce..8a6378c8 100644 --- a/packages/desktop/src/providers/provider_function.rs +++ b/packages/desktop/src/providers/provider_function.rs @@ -12,14 +12,10 @@ pub enum MediaFunction { Previous, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ProviderFunctionResult { - Media(MediaFunctionResult), -} +pub type ProviderFunctionResult = Result; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum MediaFunctionResult { - PlayPause(bool), - Next(bool), - Previous(bool), +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum ProviderFunctionResponse { + Null, } diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index b3a56f75..1cd8e99a 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -13,8 +13,8 @@ use super::{ battery::BatteryProvider, cpu::CpuProvider, disk::DiskProvider, host::HostProvider, ip::IpProvider, memory::MemoryProvider, network::NetworkProvider, weather::WeatherProvider, Provider, - ProviderConfig, ProviderFunction, ProviderFunctionResult, - ProviderOutput, RuntimeType, + ProviderConfig, ProviderFunction, ProviderFunctionResponse, + ProviderFunctionResult, ProviderOutput, RuntimeType, }; #[cfg(windows)] use super::{ @@ -250,7 +250,7 @@ impl ProviderManager { &self, config_hash: String, function: ProviderFunction, - ) -> anyhow::Result { + ) -> anyhow::Result { let provider_refs = self.provider_refs.lock().await; let provider_ref = provider_refs .get(&config_hash) @@ -259,7 +259,7 @@ impl ProviderManager { let (tx, rx) = oneshot::channel(); provider_ref.function_tx.send((function, tx)).await?; - Ok(rx.await?) + rx.await?.map_err(anyhow::Error::msg) } /// Destroys and cleans up the provider with the given config. From 1184282b21ad463b4719ec65f88241c95e602311 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Mon, 18 Nov 2024 02:53:32 +0800 Subject: [PATCH 23/38] feat: remove `stop` and `function` channels in favor of a single `message` channel --- .../desktop/src/providers/provider_manager.rs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 1cd8e99a..c913ae98 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -22,16 +22,15 @@ use super::{ media::MediaProvider, }; +pub enum IncomingProviderMessage { + Function(ProviderFunction, oneshot::Sender), + Stop, +} + /// Common fields for a provider. pub struct CommonProviderState { - /// Receiver channel for stopping the provider. - pub stop_rx: oneshot::Receiver<()>, - - /// Receiver channel for incoming function calls to the provider. - pub function_rx: mpsc::Receiver<( - ProviderFunction, - oneshot::Sender, - )>, + /// Receiver channel for incoming messages to the provider. + pub message_rx: mpsc::Receiver, /// Wrapper around the sender channel of provider emissions. pub emitter: ProviderEmitter, @@ -81,14 +80,8 @@ pub struct ProviderEmission { /// Reference to an active provider. struct ProviderRef { - /// Sender channel for stopping the provider. - stop_tx: oneshot::Sender<()>, - - /// Sender channel for sending function calls to the provider. - function_tx: mpsc::Sender<( - ProviderFunction, - oneshot::Sender, - )>, + /// Sender channel for sending messages to the provider. + message_tx: mpsc::Sender, /// Handle to the provider's task. task_handle: task::JoinHandle<()>, @@ -151,12 +144,10 @@ impl ProviderManager { }; } - let (stop_tx, stop_rx) = oneshot::channel(); - let (function_tx, function_rx) = mpsc::channel(1); + let (message_tx, message_rx) = mpsc::channel(1); let common = CommonProviderState { - stop_rx, - function_rx, + message_rx, emitter: ProviderEmitter { emit_tx: self.emit_tx.clone(), config_hash: config_hash.clone(), @@ -168,8 +159,7 @@ impl ProviderManager { self.create_instance(config, config_hash.clone(), common)?; let provider_ref = ProviderRef { - stop_tx, - function_tx, + message_tx, task_handle, }; @@ -257,7 +247,11 @@ impl ProviderManager { .context("No provider found with config.")?; let (tx, rx) = oneshot::channel(); - provider_ref.function_tx.send((function, tx)).await?; + provider_ref + .message_tx + .send(IncomingProviderMessage::Function(function, tx)) + .await + .context("Failed to send function call to provider.")?; rx.await?.map_err(anyhow::Error::msg) } @@ -270,9 +264,11 @@ impl ProviderManager { .context("No provider found with config.")?; // Send shutdown signal to the provider. - if let Err(_) = provider_ref.stop_tx.send(()) { - bail!("Failed to send shutdown signal to provider."); - } + provider_ref + .message_tx + .send(IncomingProviderMessage::Stop) + .await + .context("Failed to send shutdown signal to provider.")?; // Wait for the provider to stop. provider_ref.task_handle.await?; From 712c06ac73a1937cc4cefec21060ac37a33f4878 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Mon, 18 Nov 2024 22:29:36 +0800 Subject: [PATCH 24/38] wip sync + async receiver with crossbeam --- Cargo.lock | 23 ++++++++ packages/desktop/Cargo.toml | 1 + .../src/providers/memory/memory_provider.rs | 44 ++++++++++++++- packages/desktop/src/providers/provider.rs | 8 +++ .../desktop/src/providers/provider_manager.rs | 56 ++++++++++++++----- 5 files changed, 116 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6b5b0af..3ac7d230 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -895,6 +895,19 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -923,6 +936,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -6791,6 +6813,7 @@ dependencies = [ "async-trait", "clap", "cocoa 0.25.0", + "crossbeam", "komorebi-client", "netdev", "regex", diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index b545ec88..086fe7ad 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -16,6 +16,7 @@ tauri-build = { version = "2.0", features = [] } anyhow = "1" async-trait = "0.1" clap = { version = "4", features = ["derive"] } +crossbeam = "0.8" reqwest = { version = "0.11", features = ["json"] } tauri = { version = "2.0", features = [ "devtools", diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index 9b8ee650..14aa7d50 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -62,13 +62,53 @@ impl Provider for MemoryProvider { } fn start_sync(&mut self) { - let mut interval = SyncInterval::new(self.config.refresh_interval); + let mut interval = tokio::time::interval(Duration::from_secs(1)); + interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { - interval.tick(); + crossbeam::select! { + recv(interval) -> _ => { + let output = self.run_interval(); + self.common.emitter.emit_output(output); + } + recv(self.common.message_rx) -> cmd => { + match cmd { + IncomingProviderMessage::Stop => break, + IncomingProviderMessage::Function(function, response_tx) => { + let result = self.call_function_sync(function); + let _ = response_tx.send(result); + } + } + } + } + } + + // let mut interval = SyncInterval::new(self.config.refresh_interval); + // let (tick_tx, tick_rx) = mpsc::channel(); + + // Spawn timer thread + // std::thread::spawn(move || { + // let interval = + // Duration::from_millis(self.config.refresh_interval); loop { + // std::thread::sleep(interval); + // if tick_tx.send(()).is_err() { + // break; + // } + // } + // }); + while let Ok(message) = self.common.receiver.try_recv() { let output = self.run_interval(); self.common.emitter.emit_output(output); } + + let (sender1, receiver1) = std::sync::mpsc::channel::(); + match receiver1.recv_timeout(std::time::Duration::from_millis(100)) { + Ok(message) => { + println!("Received from receiver1: {}", message); + } + Err(_) => {} // No message, continue + } } } diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index b0f26cd2..9c994d53 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -38,6 +38,14 @@ pub trait Provider: Send + Sync { } } + // fn handle_message(&mut self, message: IncomingProviderMessage) { + // match message { + // IncomingProviderMessage::Function(function, tx) => { + // let res = self.call_function_sync(function); + // } + // } + // } + /// Runs the given function. /// /// # Panics diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index c913ae98..b66a4422 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -29,16 +29,39 @@ pub enum IncomingProviderMessage { /// Common fields for a provider. pub struct CommonProviderState { - /// Receiver channel for incoming messages to the provider. - pub message_rx: mpsc::Receiver, - /// Wrapper around the sender channel of provider emissions. pub emitter: ProviderEmitter, + /// Wrapper around the receiver channel for incoming messages to the + /// provider. + pub receiver: ProviderReceiver, + /// Shared `sysinfo` instance. pub sysinfo: Arc>, } +/// Handle for receiving provider messages. +pub enum ProviderReceiver { + /// Async receiver channel for incoming messages to the provider. + Async(mpsc::Receiver), + + /// Sync receiver channel for incoming messages to the provider. + Sync(crossbeam::channel::Receiver), +} + +impl ProviderReceiver { + /// Returns the sync receiver channel for incoming messages to the + /// provider. + pub fn sync( + &self, + ) -> &crossbeam::channel::Receiver { + match self { + ProviderReceiver::Sync(rx) => rx, + _ => unreachable!(), + } + } +} + /// Handle for sending provider emissions. #[derive(Clone, Debug)] pub struct ProviderEmitter { @@ -144,17 +167,6 @@ impl ProviderManager { }; } - let (message_tx, message_rx) = mpsc::channel(1); - - let common = CommonProviderState { - message_rx, - emitter: ProviderEmitter { - emit_tx: self.emit_tx.clone(), - config_hash: config_hash.clone(), - }, - sysinfo: self.sysinfo.clone(), - }; - let task_handle = self.create_instance(config, config_hash.clone(), common)?; @@ -217,6 +229,22 @@ impl ProviderManager { _ => bail!("Provider not supported on this operating system."), }; + let (async_message_tx, async_message_rx) = mpsc::channel(1); + let (sync_message_tx, sync_message_rx) = + crossbeam::channel::bounded(1); + + let common = CommonProviderState { + receiver: match provider.runtime_type() { + RuntimeType::Async => ProviderReceiver::Async(async_message_rx), + RuntimeType::Sync => ProviderReceiver::Sync(sync_message_rx), + }, + emitter: ProviderEmitter { + emit_tx: self.emit_tx.clone(), + config_hash: config_hash.clone(), + }, + sysinfo: self.sysinfo.clone(), + }; + // Spawn the provider's task based on its runtime type. let task_handle = match provider.runtime_type() { RuntimeType::Async => task::spawn(async move { From d4ff885f1af4b58ed66fe3066fd6b652b695314b Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Mon, 18 Nov 2024 22:53:10 +0800 Subject: [PATCH 25/38] wip add `ProviderConsumer` --- .../src/providers/memory/memory_provider.rs | 2 +- .../desktop/src/providers/provider_manager.rs | 75 ++++++++----------- 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index 14aa7d50..dccd35e3 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -98,7 +98,7 @@ impl Provider for MemoryProvider { // } // }); - while let Ok(message) = self.common.receiver.try_recv() { + while let Ok(message) = self.common.consumer.try_recv() { let output = self.run_interval(); self.common.emitter.emit_output(output); } diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index b66a4422..834f58ec 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -22,11 +22,6 @@ use super::{ media::MediaProvider, }; -pub enum IncomingProviderMessage { - Function(ProviderFunction, oneshot::Sender), - Stop, -} - /// Common fields for a provider. pub struct CommonProviderState { /// Wrapper around the sender channel of provider emissions. @@ -34,32 +29,24 @@ pub struct CommonProviderState { /// Wrapper around the receiver channel for incoming messages to the /// provider. - pub receiver: ProviderReceiver, + pub consumer: ProviderConsumer, /// Shared `sysinfo` instance. pub sysinfo: Arc>, } /// Handle for receiving provider messages. -pub enum ProviderReceiver { +pub struct ProviderConsumer { /// Async receiver channel for incoming messages to the provider. - Async(mpsc::Receiver), + async_rx: mpsc::Receiver, /// Sync receiver channel for incoming messages to the provider. - Sync(crossbeam::channel::Receiver), + sync_rx: crossbeam::channel::Receiver, } -impl ProviderReceiver { - /// Returns the sync receiver channel for incoming messages to the - /// provider. - pub fn sync( - &self, - ) -> &crossbeam::channel::Receiver { - match self { - ProviderReceiver::Sync(rx) => rx, - _ => unreachable!(), - } - } +pub enum ProviderConsumerMessage { + Function(ProviderFunction, oneshot::Sender), + Stop, } /// Handle for sending provider emissions. @@ -104,7 +91,10 @@ pub struct ProviderEmission { /// Reference to an active provider. struct ProviderRef { /// Sender channel for sending messages to the provider. - message_tx: mpsc::Sender, + async_consumer_tx: mpsc::Sender, + + /// Sender channel for sending messages to the provider. + sync_consumer_tx: crossbeam::channel::Sender, /// Handle to the provider's task. task_handle: task::JoinHandle<()>, @@ -167,11 +157,28 @@ impl ProviderManager { }; } + let (async_consumer_tx, async_consumer_rx) = mpsc::channel(1); + let (sync_consumer_tx, sync_consumer_rx) = + crossbeam::channel::bounded(1); + + let common = CommonProviderState { + consumer: ProviderConsumer { + async_rx: async_consumer_rx, + sync_rx: sync_consumer_rx, + }, + emitter: ProviderEmitter { + emit_tx: self.emit_tx.clone(), + config_hash: config_hash.clone(), + }, + sysinfo: self.sysinfo.clone(), + }; + let task_handle = self.create_instance(config, config_hash.clone(), common)?; let provider_ref = ProviderRef { - message_tx, + async_consumer_tx, + sync_consumer_tx, task_handle, }; @@ -229,22 +236,6 @@ impl ProviderManager { _ => bail!("Provider not supported on this operating system."), }; - let (async_message_tx, async_message_rx) = mpsc::channel(1); - let (sync_message_tx, sync_message_rx) = - crossbeam::channel::bounded(1); - - let common = CommonProviderState { - receiver: match provider.runtime_type() { - RuntimeType::Async => ProviderReceiver::Async(async_message_rx), - RuntimeType::Sync => ProviderReceiver::Sync(sync_message_rx), - }, - emitter: ProviderEmitter { - emit_tx: self.emit_tx.clone(), - config_hash: config_hash.clone(), - }, - sysinfo: self.sysinfo.clone(), - }; - // Spawn the provider's task based on its runtime type. let task_handle = match provider.runtime_type() { RuntimeType::Async => task::spawn(async move { @@ -276,8 +267,8 @@ impl ProviderManager { let (tx, rx) = oneshot::channel(); provider_ref - .message_tx - .send(IncomingProviderMessage::Function(function, tx)) + .async_consumer_tx + .send(ProviderConsumerMessage::Function(function, tx)) .await .context("Failed to send function call to provider.")?; @@ -293,8 +284,8 @@ impl ProviderManager { // Send shutdown signal to the provider. provider_ref - .message_tx - .send(IncomingProviderMessage::Stop) + .async_consumer_tx + .send(ProviderConsumerMessage::Stop) .await .context("Failed to send shutdown signal to provider.")?; From eb35221b525da3d292c5be34c2c2b158af545504 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Mon, 18 Nov 2024 22:55:44 +0800 Subject: [PATCH 26/38] rename `ProviderConsumer` -> `ProviderInput` --- .../src/providers/memory/memory_provider.rs | 2 +- .../desktop/src/providers/provider_manager.rs | 49 +++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index dccd35e3..63bfc9a5 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -98,7 +98,7 @@ impl Provider for MemoryProvider { // } // }); - while let Ok(message) = self.common.consumer.try_recv() { + while let Ok(message) = self.common.input.try_recv() { let output = self.run_interval(); self.common.emitter.emit_output(output); } diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 834f58ec..c76e7c41 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -27,24 +27,24 @@ pub struct CommonProviderState { /// Wrapper around the sender channel of provider emissions. pub emitter: ProviderEmitter, - /// Wrapper around the receiver channel for incoming messages to the + /// Wrapper around the receiver channel for incoming inputs to the /// provider. - pub consumer: ProviderConsumer, + pub input: ProviderInput, /// Shared `sysinfo` instance. pub sysinfo: Arc>, } -/// Handle for receiving provider messages. -pub struct ProviderConsumer { - /// Async receiver channel for incoming messages to the provider. - async_rx: mpsc::Receiver, +/// Handle for receiving provider inputs. +pub struct ProviderInput { + /// Async receiver channel for incoming inputs to the provider. + async_rx: mpsc::Receiver, - /// Sync receiver channel for incoming messages to the provider. - sync_rx: crossbeam::channel::Receiver, + /// Sync receiver channel for incoming inputs to the provider. + sync_rx: crossbeam::channel::Receiver, } -pub enum ProviderConsumerMessage { +pub enum ProviderInputMsg { Function(ProviderFunction, oneshot::Sender), Stop, } @@ -90,11 +90,11 @@ pub struct ProviderEmission { /// Reference to an active provider. struct ProviderRef { - /// Sender channel for sending messages to the provider. - async_consumer_tx: mpsc::Sender, + /// Sender channel for sending inputs to the provider. + async_input_tx: mpsc::Sender, - /// Sender channel for sending messages to the provider. - sync_consumer_tx: crossbeam::channel::Sender, + /// Sender channel for sending inputs to the provider. + sync_input_tx: crossbeam::channel::Sender, /// Handle to the provider's task. task_handle: task::JoinHandle<()>, @@ -157,14 +157,13 @@ impl ProviderManager { }; } - let (async_consumer_tx, async_consumer_rx) = mpsc::channel(1); - let (sync_consumer_tx, sync_consumer_rx) = - crossbeam::channel::bounded(1); + let (async_input_tx, async_input_rx) = mpsc::channel(1); + let (sync_input_tx, sync_input_rx) = crossbeam::channel::bounded(1); let common = CommonProviderState { - consumer: ProviderConsumer { - async_rx: async_consumer_rx, - sync_rx: sync_consumer_rx, + input: ProviderInput { + async_rx: async_input_rx, + sync_rx: sync_input_rx, }, emitter: ProviderEmitter { emit_tx: self.emit_tx.clone(), @@ -177,8 +176,8 @@ impl ProviderManager { self.create_instance(config, config_hash.clone(), common)?; let provider_ref = ProviderRef { - async_consumer_tx, - sync_consumer_tx, + async_input_tx, + sync_input_tx, task_handle, }; @@ -267,8 +266,8 @@ impl ProviderManager { let (tx, rx) = oneshot::channel(); provider_ref - .async_consumer_tx - .send(ProviderConsumerMessage::Function(function, tx)) + .async_input_tx + .send(ProviderInputMsg::Function(function, tx)) .await .context("Failed to send function call to provider.")?; @@ -284,8 +283,8 @@ impl ProviderManager { // Send shutdown signal to the provider. provider_ref - .async_consumer_tx - .send(ProviderConsumerMessage::Stop) + .async_input_tx + .send(ProviderInputMsg::Stop) .await .context("Failed to send shutdown signal to provider.")?; From 73d454617975250f2a2b83597115339fc3daf190 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Mon, 18 Nov 2024 23:10:34 +0800 Subject: [PATCH 27/38] feat: handle sending inputs to async + sync runtimes --- .../desktop/src/providers/provider_manager.rs | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index c76e7c41..199495fc 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -38,10 +38,10 @@ pub struct CommonProviderState { /// Handle for receiving provider inputs. pub struct ProviderInput { /// Async receiver channel for incoming inputs to the provider. - async_rx: mpsc::Receiver, + pub async_rx: mpsc::Receiver, /// Sync receiver channel for incoming inputs to the provider. - sync_rx: crossbeam::channel::Receiver, + pub sync_rx: crossbeam::channel::Receiver, } pub enum ProviderInputMsg { @@ -98,6 +98,9 @@ struct ProviderRef { /// Handle to the provider's task. task_handle: task::JoinHandle<()>, + + /// Runtime type of the provider. + runtime_type: RuntimeType, } /// Manages the creation and cleanup of providers. @@ -172,13 +175,14 @@ impl ProviderManager { sysinfo: self.sysinfo.clone(), }; - let task_handle = + let (task_handle, runtime_type) = self.create_instance(config, config_hash.clone(), common)?; let provider_ref = ProviderRef { async_input_tx, sync_input_tx, task_handle, + runtime_type, }; let mut providers = self.provider_refs.lock().await; @@ -193,7 +197,7 @@ impl ProviderManager { config: ProviderConfig, config_hash: String, common: CommonProviderState, - ) -> anyhow::Result> { + ) -> anyhow::Result<(task::JoinHandle<()>, RuntimeType)> { let mut provider: Box = match config { ProviderConfig::Battery(config) => { Box::new(BatteryProvider::new(config, common)) @@ -236,7 +240,8 @@ impl ProviderManager { }; // Spawn the provider's task based on its runtime type. - let task_handle = match provider.runtime_type() { + let runtime_type = provider.runtime_type(); + let task_handle = match &runtime_type { RuntimeType::Async => task::spawn(async move { provider.start_async().await; info!("Provider stopped: {}", config_hash); @@ -247,7 +252,7 @@ impl ProviderManager { }), }; - Ok(task_handle) + Ok((task_handle, runtime_type)) } /// Sends a function call through a channel to be executed by the @@ -265,11 +270,21 @@ impl ProviderManager { .context("No provider found with config.")?; let (tx, rx) = oneshot::channel(); - provider_ref - .async_input_tx - .send(ProviderInputMsg::Function(function, tx)) - .await - .context("Failed to send function call to provider.")?; + match provider_ref.runtime_type { + RuntimeType::Async => { + provider_ref + .async_input_tx + .send(ProviderInputMsg::Function(function, tx)) + .await + .context("Failed to send function call to provider.")?; + } + RuntimeType::Sync => { + provider_ref + .sync_input_tx + .send(ProviderInputMsg::Function(function, tx)) + .context("Failed to send function call to provider.")?; + } + } rx.await?.map_err(anyhow::Error::msg) } @@ -282,11 +297,21 @@ impl ProviderManager { .context("No provider found with config.")?; // Send shutdown signal to the provider. - provider_ref - .async_input_tx - .send(ProviderInputMsg::Stop) - .await - .context("Failed to send shutdown signal to provider.")?; + match provider_ref.runtime_type { + RuntimeType::Async => { + provider_ref + .async_input_tx + .send(ProviderInputMsg::Stop) + .await + .context("Failed to send shutdown signal to provider.")?; + } + RuntimeType::Sync => { + provider_ref + .sync_input_tx + .send(ProviderInputMsg::Stop) + .context("Failed to send shutdown signal to provider.")?; + } + } // Wait for the provider to stop. provider_ref.task_handle.await?; From fe97d94cdfe4f92beb137a002a95f75fda7cd621 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Mon, 18 Nov 2024 23:11:02 +0800 Subject: [PATCH 28/38] feat: working crossbeam impl for memory provider (but ticks don't fire immediately) --- .../src/providers/memory/memory_provider.rs | 52 ++++--------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index 63bfc9a5..d13ff488 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -1,8 +1,10 @@ +use std::time::Duration; + +use crossbeam::channel::tick; use serde::{Deserialize, Serialize}; -use crate::{ - common::SyncInterval, - providers::{CommonProviderState, Provider, RuntimeType}, +use crate::providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, }; #[derive(Deserialize, Debug)] @@ -62,53 +64,21 @@ impl Provider for MemoryProvider { } fn start_sync(&mut self) { - let mut interval = tokio::time::interval(Duration::from_secs(1)); - interval - .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let ticker = tick(Duration::from_millis(self.config.refresh_interval)); loop { crossbeam::select! { - recv(interval) -> _ => { + recv(ticker) -> _ => { let output = self.run_interval(); self.common.emitter.emit_output(output); } - recv(self.common.message_rx) -> cmd => { - match cmd { - IncomingProviderMessage::Stop => break, - IncomingProviderMessage::Function(function, response_tx) => { - let result = self.call_function_sync(function); - let _ = response_tx.send(result); - } + recv(self.common.input.sync_rx) -> input => { + match input { + Ok(ProviderInputMsg::Stop) => break, + _ => {} } } } } - - // let mut interval = SyncInterval::new(self.config.refresh_interval); - // let (tick_tx, tick_rx) = mpsc::channel(); - - // Spawn timer thread - // std::thread::spawn(move || { - // let interval = - // Duration::from_millis(self.config.refresh_interval); loop { - // std::thread::sleep(interval); - // if tick_tx.send(()).is_err() { - // break; - // } - // } - // }); - - while let Ok(message) = self.common.input.try_recv() { - let output = self.run_interval(); - self.common.emitter.emit_output(output); - } - - let (sender1, receiver1) = std::sync::mpsc::channel::(); - match receiver1.recv_timeout(std::time::Duration::from_millis(100)) { - Ok(message) => { - println!("Received from receiver1: {}", message); - } - Err(_) => {} // No message, continue - } } } From 8d477d1b1b5098ed44610674815b591418611823 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 00:52:00 +0800 Subject: [PATCH 29/38] feat: use crossbeam select! in all sync interval providers --- packages/desktop/src/common/interval.rs | 20 +++++++++---------- .../src/providers/battery/battery_provider.rs | 19 +++++++++++++----- .../desktop/src/providers/cpu/cpu_provider.rs | 19 +++++++++++++----- .../src/providers/disk/disk_provider.rs | 19 +++++++++++++----- .../src/providers/host/host_provider.rs | 19 +++++++++++++----- .../providers/keyboard/keyboard_provider.rs | 19 +++++++++++++----- .../src/providers/memory/memory_provider.rs | 19 +++++++++--------- .../src/providers/network/network_provider.rs | 19 +++++++++++++----- 8 files changed, 103 insertions(+), 50 deletions(-) diff --git a/packages/desktop/src/common/interval.rs b/packages/desktop/src/common/interval.rs index c8a9cba0..eec55cdc 100644 --- a/packages/desktop/src/common/interval.rs +++ b/packages/desktop/src/common/interval.rs @@ -1,7 +1,4 @@ -use std::{ - thread, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; /// A synchronous timer for running intervals at a fixed rate. /// @@ -21,16 +18,19 @@ impl SyncInterval { /// Sleeps until the next tick if needed, then updates the next tick /// time. - pub fn tick(&mut self) { - // Sleep until next tick if needed. + pub fn tick(&mut self) -> crossbeam::channel::Receiver { if let Some(wait_duration) = self.next_tick.checked_duration_since(Instant::now()) { - thread::sleep(wait_duration); + let timer = crossbeam::channel::after(wait_duration); + self.next_tick += self.interval; + timer + } else { + while self.next_tick <= Instant::now() { + self.next_tick += self.interval; + } + crossbeam::channel::after(self.next_tick - Instant::now()) } - - // Calculate next tick time. - self.next_tick += self.interval; } } diff --git a/packages/desktop/src/providers/battery/battery_provider.rs b/packages/desktop/src/providers/battery/battery_provider.rs index 5a640378..83a07e0a 100644 --- a/packages/desktop/src/providers/battery/battery_provider.rs +++ b/packages/desktop/src/providers/battery/battery_provider.rs @@ -10,7 +10,9 @@ use starship_battery::{ use crate::{ common::SyncInterval, - providers::{CommonProviderState, Provider, RuntimeType}, + providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -80,10 +82,17 @@ impl Provider for BatteryProvider { let mut interval = SyncInterval::new(self.config.refresh_interval); loop { - interval.tick(); - - let output = self.run_interval(); - self.common.emitter.emit_output(output); + crossbeam::select! { + recv(interval.tick()) -> _ => { + let output = self.run_interval(); + self.common.emitter.emit_output(output); + } + recv(self.common.input.sync_rx) -> input => { + if let Ok(ProviderInputMsg::Stop) = input { + break; + } + } + } } } } diff --git a/packages/desktop/src/providers/cpu/cpu_provider.rs b/packages/desktop/src/providers/cpu/cpu_provider.rs index c381b3cb..fbdd6c6f 100644 --- a/packages/desktop/src/providers/cpu/cpu_provider.rs +++ b/packages/desktop/src/providers/cpu/cpu_provider.rs @@ -2,7 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::{ common::SyncInterval, - providers::{CommonProviderState, Provider, RuntimeType}, + providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -59,10 +61,17 @@ impl Provider for CpuProvider { let mut interval = SyncInterval::new(self.config.refresh_interval); loop { - interval.tick(); - - let output = self.run_interval(); - self.common.emitter.emit_output(output); + crossbeam::select! { + recv(interval.tick()) -> _ => { + let output = self.run_interval(); + self.common.emitter.emit_output(output); + } + recv(self.common.input.sync_rx) -> input => { + if let Ok(ProviderInputMsg::Stop) = input { + break; + } + } + } } } } diff --git a/packages/desktop/src/providers/disk/disk_provider.rs b/packages/desktop/src/providers/disk/disk_provider.rs index 75e3c80d..acd18112 100644 --- a/packages/desktop/src/providers/disk/disk_provider.rs +++ b/packages/desktop/src/providers/disk/disk_provider.rs @@ -3,7 +3,9 @@ use sysinfo::Disks; use crate::{ common::{to_iec_bytes, to_si_bytes, SyncInterval}, - providers::{CommonProviderState, Provider, RuntimeType}, + providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -107,10 +109,17 @@ impl Provider for DiskProvider { let mut interval = SyncInterval::new(self.config.refresh_interval); loop { - interval.tick(); - - let output = self.run_interval(); - self.common.emitter.emit_output(output); + crossbeam::select! { + recv(interval.tick()) -> _ => { + let output = self.run_interval(); + self.common.emitter.emit_output(output); + } + recv(self.common.input.sync_rx) -> input => { + if let Ok(ProviderInputMsg::Stop) = input { + break; + } + } + } } } } diff --git a/packages/desktop/src/providers/host/host_provider.rs b/packages/desktop/src/providers/host/host_provider.rs index fe0fa21f..a95070bd 100644 --- a/packages/desktop/src/providers/host/host_provider.rs +++ b/packages/desktop/src/providers/host/host_provider.rs @@ -3,7 +3,9 @@ use sysinfo::System; use crate::{ common::SyncInterval, - providers::{CommonProviderState, Provider, RuntimeType}, + providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -57,10 +59,17 @@ impl Provider for HostProvider { let mut interval = SyncInterval::new(self.config.refresh_interval); loop { - interval.tick(); - - let output = self.run_interval(); - self.common.emitter.emit_output(output); + crossbeam::select! { + recv(interval.tick()) -> _ => { + let output = self.run_interval(); + self.common.emitter.emit_output(output); + } + recv(self.common.input.sync_rx) -> input => { + if let Ok(ProviderInputMsg::Stop) = input { + break; + } + } + } } } } diff --git a/packages/desktop/src/providers/keyboard/keyboard_provider.rs b/packages/desktop/src/providers/keyboard/keyboard_provider.rs index dd39f392..513f1d31 100644 --- a/packages/desktop/src/providers/keyboard/keyboard_provider.rs +++ b/packages/desktop/src/providers/keyboard/keyboard_provider.rs @@ -11,7 +11,9 @@ use windows::Win32::{ use crate::{ common::SyncInterval, - providers::{CommonProviderState, Provider, RuntimeType}, + providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -80,10 +82,17 @@ impl Provider for KeyboardProvider { let mut interval = SyncInterval::new(self.config.refresh_interval); loop { - interval.tick(); - - let output = self.run_interval(); - self.common.emitter.emit_output(output); + crossbeam::select! { + recv(interval.tick()) -> _ => { + let output = self.run_interval(); + self.common.emitter.emit_output(output); + } + recv(self.common.input.sync_rx) -> input => { + if let Ok(ProviderInputMsg::Stop) = input { + break; + } + } + } } } } diff --git a/packages/desktop/src/providers/memory/memory_provider.rs b/packages/desktop/src/providers/memory/memory_provider.rs index d13ff488..ceaa76d6 100644 --- a/packages/desktop/src/providers/memory/memory_provider.rs +++ b/packages/desktop/src/providers/memory/memory_provider.rs @@ -1,10 +1,10 @@ -use std::time::Duration; - -use crossbeam::channel::tick; use serde::{Deserialize, Serialize}; -use crate::providers::{ - CommonProviderState, Provider, ProviderInputMsg, RuntimeType, +use crate::{ + common::SyncInterval, + providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -64,18 +64,17 @@ impl Provider for MemoryProvider { } fn start_sync(&mut self) { - let ticker = tick(Duration::from_millis(self.config.refresh_interval)); + let mut interval = SyncInterval::new(self.config.refresh_interval); loop { crossbeam::select! { - recv(ticker) -> _ => { + recv(interval.tick()) -> _ => { let output = self.run_interval(); self.common.emitter.emit_output(output); } recv(self.common.input.sync_rx) -> input => { - match input { - Ok(ProviderInputMsg::Stop) => break, - _ => {} + if let Ok(ProviderInputMsg::Stop) = input { + break; } } } diff --git a/packages/desktop/src/providers/network/network_provider.rs b/packages/desktop/src/providers/network/network_provider.rs index a63fbd9f..d08f3927 100644 --- a/packages/desktop/src/providers/network/network_provider.rs +++ b/packages/desktop/src/providers/network/network_provider.rs @@ -8,7 +8,9 @@ use super::{ }; use crate::{ common::{to_iec_bytes, to_si_bytes, SyncInterval}, - providers::{CommonProviderState, Provider, RuntimeType}, + providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -202,10 +204,17 @@ impl Provider for NetworkProvider { let mut interval = SyncInterval::new(self.config.refresh_interval); loop { - interval.tick(); - - let output = self.run_interval(); - self.common.emitter.emit_output(output); + crossbeam::select! { + recv(interval.tick()) -> _ => { + let output = self.run_interval(); + self.common.emitter.emit_output(output); + } + recv(self.common.input.sync_rx) -> input => { + if let Ok(ProviderInputMsg::Stop) = input { + break; + } + } + } } } } From 4d9b3de586b05d671e8de4896f84f8ecce3df1f6 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 01:29:26 +0800 Subject: [PATCH 30/38] feat: use `select!` in ip and weather provider --- .../desktop/src/providers/ip/ip_provider.rs | 19 ++++++++++++++----- .../src/providers/weather/weather_provider.rs | 18 +++++++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/desktop/src/providers/ip/ip_provider.rs b/packages/desktop/src/providers/ip/ip_provider.rs index 35398e65..eececc5a 100644 --- a/packages/desktop/src/providers/ip/ip_provider.rs +++ b/packages/desktop/src/providers/ip/ip_provider.rs @@ -6,7 +6,9 @@ use serde::{Deserialize, Serialize}; use super::ipinfo_res::IpinfoRes; use crate::{ common::AsyncInterval, - providers::{CommonProviderState, Provider, RuntimeType}, + providers::{ + CommonProviderState, Provider, ProviderInputMsg, RuntimeType, + }, }; #[derive(Deserialize, Debug)] @@ -83,10 +85,17 @@ impl Provider for IpProvider { let mut interval = AsyncInterval::new(self.config.refresh_interval); loop { - interval.tick().await; - - let output = self.run_interval().await; - self.common.emitter.emit_output(output); + tokio::select! { + _ = interval.tick() => { + let output = self.run_interval().await; + self.common.emitter.emit_output(output); + } + Some(message) = self.common.input.async_rx.recv() => { + if let ProviderInputMsg::Stop = message { + break; + } + } + } } } } diff --git a/packages/desktop/src/providers/weather/weather_provider.rs b/packages/desktop/src/providers/weather/weather_provider.rs index 91704b37..22ed5692 100644 --- a/packages/desktop/src/providers/weather/weather_provider.rs +++ b/packages/desktop/src/providers/weather/weather_provider.rs @@ -6,7 +6,8 @@ use super::open_meteo_res::OpenMeteoRes; use crate::{ common::AsyncInterval, providers::{ - ip::IpProvider, CommonProviderState, Provider, RuntimeType, + ip::IpProvider, CommonProviderState, Provider, ProviderInputMsg, + RuntimeType, }, }; @@ -160,10 +161,17 @@ impl Provider for WeatherProvider { let mut interval = AsyncInterval::new(self.config.refresh_interval); loop { - interval.tick().await; - - let output = self.run_interval().await; - self.common.emitter.emit_output(output); + tokio::select! { + _ = interval.tick() => { + let output = self.run_interval().await; + self.common.emitter.emit_output(output); + } + Some(message) = self.common.input.async_rx.recv() => { + if let ProviderInputMsg::Stop = message { + break; + } + } + } } } } From c986c834dc6e7c599403b3f369eec91d92833b1a Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 01:30:39 +0800 Subject: [PATCH 31/38] feat: evict cache on provider stop --- .../desktop/src/providers/provider_manager.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 199495fc..082a1964 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -291,10 +291,19 @@ impl ProviderManager { /// Destroys and cleans up the provider with the given config. pub async fn stop(&self, config_hash: String) -> anyhow::Result<()> { - let mut provider_refs = self.provider_refs.lock().await; - let provider_ref = provider_refs - .remove(&config_hash) - .context("No provider found with config.")?; + let provider_ref = { + let mut provider_refs = self.provider_refs.lock().await; + + // Evict the provider's emission from cache. Hold the lock for + // `provider_refs` to avoid a race condition with provider + // creation. + let mut provider_cache = self.emit_cache.lock().await; + let _ = provider_cache.remove(&config_hash); + + provider_refs + .remove(&config_hash) + .context("No provider found with config.")? + }; // Send shutdown signal to the provider. match provider_ref.runtime_type { From d925568a2d8ca21e49f01c2266e7cc393417dab4 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 02:22:21 +0800 Subject: [PATCH 32/38] feat: prevent duplicate providers from being created --- .../desktop/src/providers/provider_manager.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index 082a1964..df0689a1 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -160,6 +160,18 @@ impl ProviderManager { }; } + // Hold the lock for `provider_refs` to prevent duplicate providers + // from potentially being created. + let mut provider_refs = self.provider_refs.lock().await; + + // No-op if the provider has already been created (but has not emitted + // yet). Multiple frontend clients can call `create` for the same + // provider, and all will receive the same output once the provider + // emits. + if provider_refs.contains_key(&config_hash) { + return Ok(()); + } + let (async_input_tx, async_input_rx) = mpsc::channel(1); let (sync_input_tx, sync_input_rx) = crossbeam::channel::bounded(1); @@ -185,8 +197,7 @@ impl ProviderManager { runtime_type, }; - let mut providers = self.provider_refs.lock().await; - providers.insert(config_hash, provider_ref); + provider_refs.insert(config_hash, provider_ref); Ok(()) } From 6a082375b132c2af2d0553a7a17323be2e15c92c Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 02:35:04 +0800 Subject: [PATCH 33/38] refactor: improve comments in sync + async interval structs --- packages/desktop/src/common/interval.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/desktop/src/common/interval.rs b/packages/desktop/src/common/interval.rs index eec55cdc..c1529a49 100644 --- a/packages/desktop/src/common/interval.rs +++ b/packages/desktop/src/common/interval.rs @@ -1,8 +1,8 @@ use std::time::{Duration, Instant}; -/// A synchronous timer for running intervals at a fixed rate. +/// An interval timer for synchronous contexts using crossbeam. /// -/// The timer sleeps the thread until the next tick. +/// For use with crossbeam's `select!` macro. pub struct SyncInterval { interval: Duration, next_tick: Instant, @@ -16,27 +16,27 @@ impl SyncInterval { } } - /// Sleeps until the next tick if needed, then updates the next tick - /// time. + /// Returns a receiver that will get a message at the next tick time. pub fn tick(&mut self) -> crossbeam::channel::Receiver { if let Some(wait_duration) = self.next_tick.checked_duration_since(Instant::now()) { + // Wait normally until the next tick. let timer = crossbeam::channel::after(wait_duration); self.next_tick += self.interval; timer } else { + // We're behind - skip missed ticks to catch up. while self.next_tick <= Instant::now() { self.next_tick += self.interval; } + crossbeam::channel::after(self.next_tick - Instant::now()) } } } -/// An asynchronous timer for running intervals at a fixed rate. -/// -/// The timer sleeps the Tokio task until the next tick. +/// An interval timer for asynchronous contexts using tokio. pub struct AsyncInterval { interval: tokio::time::Interval, } @@ -54,6 +54,7 @@ impl AsyncInterval { Self { interval } } + /// Returns a future that will complete at the next tick time. pub async fn tick(&mut self) { self.interval.tick().await; } From 6a4b28d13d35a2b8fe73cffb03851cac597e1d84 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 15:29:41 +0800 Subject: [PATCH 34/38] refactor: simplify macro for implementing `From` for `ProviderOutput` --- .../desktop/src/providers/provider_output.rs | 139 ++++-------------- 1 file changed, 28 insertions(+), 111 deletions(-) diff --git a/packages/desktop/src/providers/provider_output.rs b/packages/desktop/src/providers/provider_output.rs index c44015a7..8fed9747 100644 --- a/packages/desktop/src/providers/provider_output.rs +++ b/packages/desktop/src/providers/provider_output.rs @@ -11,6 +11,19 @@ use super::{ network::NetworkOutput, weather::WeatherOutput, }; +/// Implements `From` for `ProviderOutput` for each given variant. +macro_rules! impl_provider_output { + ($($variant:ident($type:ty)),* $(,)?) => { + $( + impl From<$type> for ProviderOutput { + fn from(value: $type) -> Self { + Self::$variant(value) + } + } + )* + }; +} + #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum ProviderOutput { @@ -32,116 +45,20 @@ pub enum ProviderOutput { Keyboard(KeyboardOutput), } -macro_rules! impl_provider_conversions { - // Single pattern that handles both regular and Windows variants - ($enum_name:ident { - // Regular variants - $($variant:ident($type:ty)),* $(,)? - - // Optional Windows variants - $(#[cfg(windows)] $win_variant:ident($win_type:ty)),* $(,)? - }) => { - // Regular implementations - $( - impl From<$type> for $enum_name { - fn from(value: $type) -> Self { - Self::$variant(value) - } - } - )* - - // Windows implementations - $( - #[cfg(windows)] - impl From<$win_type> for $enum_name { - fn from(value: $win_type) -> Self { - Self::$win_variant(value) - } - } - )* - }; +impl_provider_output! { + Battery(BatteryOutput), + Cpu(CpuOutput), + Host(HostOutput), + Ip(IpOutput), + Memory(MemoryOutput), + Disk(DiskOutput), + Network(NetworkOutput), + Weather(WeatherOutput) } -// Usage is now simpler and mirrors the enum definition more closely -impl_provider_conversions!(ProviderOutput { - Battery(BatteryOutput), - Cpu(CpuOutput), - Host(HostOutput), - Ip(IpOutput), - Memory(MemoryOutput), - Disk(DiskOutput), - Network(NetworkOutput), - Weather(WeatherOutput), - #[cfg(windows)] Komorebi(KomorebiOutput), - #[cfg(windows)] Media(MediaOutput), - #[cfg(windows)] Keyboard(KeyboardOutput) -}); - -// impl From for ProviderOutput { -// fn from(output: BatteryOutput) -> Self { -// ProviderOutput::Battery(output) -// } -// } - -// impl From for ProviderOutput { -// fn from(output: CpuOutput) -> Self { -// ProviderOutput::Cpu(output) -// } -// } - -// impl From for ProviderOutput { -// fn from(output: HostOutput) -> Self { -// ProviderOutput::Host(output) -// } -// } - -// impl From for ProviderOutput { -// fn from(output: IpOutput) -> Self { -// ProviderOutput::Ip(output) -// } -// } - -// impl From for ProviderOutput { -// fn from(output: MemoryOutput) -> Self { -// ProviderOutput::Memory(output) -// } -// } - -// impl From for ProviderOutput { -// fn from(output: DiskOutput) -> Self { -// ProviderOutput::Disk(output) -// } -// } - -// impl From for ProviderOutput { -// fn from(output: NetworkOutput) -> Self { -// ProviderOutput::Network(output) -// } -// } - -// impl From for ProviderOutput { -// fn from(output: WeatherOutput) -> Self { -// ProviderOutput::Weather(output) -// } -// } - -// #[cfg(windows)] -// impl From for ProviderOutput { -// fn from(output: KomorebiOutput) -> Self { -// ProviderOutput::Komorebi(output) -// } -// } - -// #[cfg(windows)] -// impl From for ProviderOutput { -// fn from(output: MediaOutput) -> Self { -// ProviderOutput::Media(output) -// } -// } - -// #[cfg(windows)] -// impl From for ProviderOutput { -// fn from(output: KeyboardOutput) -> Self { -// ProviderOutput::Keyboard(output) -// } -// } +#[cfg(windows)] +impl_provider_output! { + Komorebi(KomorebiOutput), + Media(MediaOutput), + Keyboard(KeyboardOutput) +} From 06e7a78c2a5ec41816556cc9f0fb5bd91aa77b8d Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 15:31:40 +0800 Subject: [PATCH 35/38] fix: emit immediately with `SyncInterval` --- packages/desktop/src/common/interval.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/desktop/src/common/interval.rs b/packages/desktop/src/common/interval.rs index c1529a49..243d9451 100644 --- a/packages/desktop/src/common/interval.rs +++ b/packages/desktop/src/common/interval.rs @@ -6,6 +6,7 @@ use std::time::{Duration, Instant}; pub struct SyncInterval { interval: Duration, next_tick: Instant, + is_first: bool, } impl SyncInterval { @@ -13,12 +14,17 @@ impl SyncInterval { Self { interval: Duration::from_millis(interval_ms), next_tick: Instant::now(), + is_first: true, } } /// Returns a receiver that will get a message at the next tick time. pub fn tick(&mut self) -> crossbeam::channel::Receiver { - if let Some(wait_duration) = + if self.is_first { + // Emit immediately on the first tick. + self.is_first = false; + crossbeam::channel::after(Duration::from_secs(0)) + } else if let Some(wait_duration) = self.next_tick.checked_duration_since(Instant::now()) { // Wait normally until the next tick. From 922b6bac1d04f9a3da4e4fb4f7db22f45d09657b Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 15:33:21 +0800 Subject: [PATCH 36/38] feat: add logging --- packages/desktop/src/providers/provider_manager.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index df0689a1..f16f3aa6 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -155,6 +155,11 @@ impl ProviderManager { if let Some(found_emit) = self.emit_cache.lock().await.get(&config_hash) { + tracing::info!( + "Emitting cached provider emission for: {}", + config_hash + ); + self.app_handle.emit("provider-emit", found_emit)?; return Ok(()); }; @@ -172,6 +177,8 @@ impl ProviderManager { return Ok(()); } + tracing::info!("Creating provider: {}", config_hash); + let (async_input_tx, async_input_rx) = mpsc::channel(1); let (sync_input_tx, sync_input_rx) = crossbeam::channel::bounded(1); From 72d7f4e3a26da5b686d24d7a8e67436d1a010cee Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Tue, 19 Nov 2024 15:37:13 +0800 Subject: [PATCH 37/38] refactor: remove commented fn --- packages/desktop/src/providers/provider.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/desktop/src/providers/provider.rs b/packages/desktop/src/providers/provider.rs index 9c994d53..b0f26cd2 100644 --- a/packages/desktop/src/providers/provider.rs +++ b/packages/desktop/src/providers/provider.rs @@ -38,14 +38,6 @@ pub trait Provider: Send + Sync { } } - // fn handle_message(&mut self, message: IncomingProviderMessage) { - // match message { - // IncomingProviderMessage::Function(function, tx) => { - // let res = self.call_function_sync(function); - // } - // } - // } - /// Runs the given function. /// /// # Panics From 25eefdcb5f4bb741bcecc0a0de95c8235d75ea97 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Wed, 20 Nov 2024 22:27:26 +0800 Subject: [PATCH 38/38] feat: rebase changes for audio provider --- .../src/providers/audio/audio_provider.rs | 56 +++++++++---------- .../desktop/src/providers/provider_manager.rs | 14 +++-- .../desktop/src/providers/provider_output.rs | 1 + 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/packages/desktop/src/providers/audio/audio_provider.rs b/packages/desktop/src/providers/audio/audio_provider.rs index 3c71e1c4..d150b529 100644 --- a/packages/desktop/src/providers/audio/audio_provider.rs +++ b/packages/desktop/src/providers/audio/audio_provider.rs @@ -5,12 +5,10 @@ use std::{ time::Duration, }; -use async_trait::async_trait; use serde::{Deserialize, Serialize}; use tokio::{ - sync::mpsc::{self, Sender}, + sync::mpsc::{self}, task, - time::sleep, }; use tracing::debug; use windows::Win32::{ @@ -33,10 +31,11 @@ use windows::Win32::{ }; use windows_core::PCWSTR; -use crate::providers::{Provider, ProviderOutput, ProviderResult}; +use crate::providers::{ + CommonProviderState, Provider, ProviderEmitter, RuntimeType, +}; -static PROVIDER_TX: OnceLock> = - OnceLock::new(); +static PROVIDER_TX: OnceLock = OnceLock::new(); static AUDIO_STATE: OnceLock>> = OnceLock::new(); @@ -279,26 +278,29 @@ impl IMMNotificationClient_Impl for MediaDeviceEventHandler_Impl { } pub struct AudioProvider { - _config: AudioProviderConfig, + common: CommonProviderState, } impl AudioProvider { - pub fn new(config: AudioProviderConfig) -> Self { - Self { _config: config } + pub fn new( + _config: AudioProviderConfig, + common: CommonProviderState, + ) -> Self { + Self { common } } fn emit_volume() { if let Some(tx) = PROVIDER_TX.get() { let output = AUDIO_STATE.get().unwrap().lock().unwrap().clone(); - let _ = tx.try_send(Ok(ProviderOutput::Audio(output)).into()); + tx.emit_output(Ok(output)); } } - async fn handle_volume_updates(mut rx: mpsc::Receiver<(String, u32)>) { + fn handle_volume_updates(mut rx: mpsc::Receiver<(String, u32)>) { const PROCESS_DELAY: Duration = Duration::from_millis(50); let mut latest_updates = HashMap::new(); - while let Some((device_id, volume)) = rx.recv().await { + while let Some((device_id, volume)) = rx.blocking_recv() { latest_updates.insert(device_id, volume); // Collect any additional pending updates without waiting. @@ -307,7 +309,7 @@ impl AudioProvider { } // Brief delay to collect more potential updates. - sleep(PROCESS_DELAY).await; + std::thread::sleep(PROCESS_DELAY); // Process all collected updates. if let Some(state) = AUDIO_STATE.get() { @@ -369,11 +371,14 @@ impl AudioProvider { } } -#[async_trait] impl Provider for AudioProvider { - async fn run(&self, emit_result_tx: Sender) { + fn runtime_type(&self) -> RuntimeType { + RuntimeType::Sync + } + + fn start_sync(&mut self) { PROVIDER_TX - .set(emit_result_tx.clone()) + .set(self.common.emitter.clone()) .expect("Error setting provider tx in audio provider"); AUDIO_STATE @@ -383,22 +388,11 @@ impl Provider for AudioProvider { // Create a channel for volume updates. let (update_tx, update_rx) = mpsc::channel(100); - // Spawn both tasks. - let update_handler = - task::spawn(Self::handle_volume_updates(update_rx)); - - let manager = task::spawn_blocking(move || { - if let Err(err) = Self::create_audio_manager(update_tx) { - emit_result_tx - .blocking_send(Err(err).into()) - .expect("Error with media provider"); - } - }); + // Spawn task for handling volume updates. + task::spawn_blocking(move || Self::handle_volume_updates(update_rx)); - // Wait for either task to complete (though they should run forever). - tokio::select! { - _ = manager => debug!("Audio manager stopped unexpectedly"), - _ = update_handler => debug!("Update handler stopped unexpectedly"), + if let Err(err) = Self::create_audio_manager(update_tx) { + self.common.emitter.emit_output::(Err(err)); } } } diff --git a/packages/desktop/src/providers/provider_manager.rs b/packages/desktop/src/providers/provider_manager.rs index f16f3aa6..67aa541d 100644 --- a/packages/desktop/src/providers/provider_manager.rs +++ b/packages/desktop/src/providers/provider_manager.rs @@ -9,6 +9,11 @@ use tokio::{ }; use tracing::info; +#[cfg(windows)] +use super::{ + audio::AudioProvider, keyboard::KeyboardProvider, + komorebi::KomorebiProvider, media::MediaProvider, +}; use super::{ battery::BatteryProvider, cpu::CpuProvider, disk::DiskProvider, host::HostProvider, ip::IpProvider, memory::MemoryProvider, @@ -16,11 +21,6 @@ use super::{ ProviderConfig, ProviderFunction, ProviderFunctionResponse, ProviderFunctionResult, ProviderOutput, RuntimeType, }; -#[cfg(windows)] -use super::{ - keyboard::KeyboardProvider, komorebi::KomorebiProvider, - media::MediaProvider, -}; /// Common fields for a provider. pub struct CommonProviderState { @@ -217,6 +217,10 @@ impl ProviderManager { common: CommonProviderState, ) -> anyhow::Result<(task::JoinHandle<()>, RuntimeType)> { let mut provider: Box = match config { + #[cfg(windows)] + ProviderConfig::Audio(config) => { + Box::new(AudioProvider::new(config, common)) + } ProviderConfig::Battery(config) => { Box::new(BatteryProvider::new(config, common)) } diff --git a/packages/desktop/src/providers/provider_output.rs b/packages/desktop/src/providers/provider_output.rs index 8fed9747..b10d96e1 100644 --- a/packages/desktop/src/providers/provider_output.rs +++ b/packages/desktop/src/providers/provider_output.rs @@ -58,6 +58,7 @@ impl_provider_output! { #[cfg(windows)] impl_provider_output! { + Audio(AudioOutput), Komorebi(KomorebiOutput), Media(MediaOutput), Keyboard(KeyboardOutput)