refactor(core): derive refresh announcements from transaction commit

This commit is contained in:
Tunglies
2026-10-01 14:34:16 +08:00
committed by GitHub
parent 438f6f7461
commit 7aed7be2f1
11 changed files with 211 additions and 125 deletions
+77
View File
@@ -0,0 +1,77 @@
use super::{handle::Handle, notification::FrontendEvent};
use anyhow::Result;
use std::{cell::RefCell, future::Future};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Refresh {
Clash,
Verge,
Profiles,
Proxies,
}
tokio::task_local! {
static PENDING: RefCell<Vec<Refresh>>;
}
pub fn announce(refresh: Refresh) {
if PENDING.try_with(|pending| pending.borrow_mut().push(refresh)).is_err() {
emit(refresh);
}
}
fn emit(refresh: Refresh) {
Handle::send_event(match refresh {
Refresh::Clash => FrontendEvent::RefreshClash,
Refresh::Verge => FrontendEvent::RefreshVerge,
Refresh::Profiles => FrontendEvent::RefreshProfiles,
Refresh::Proxies => FrontendEvent::RefreshProxyConfig,
});
}
async fn collect_committed<T: Send>(operation: impl Future<Output = Result<T>> + Send) -> Result<(T, Vec<Refresh>)> {
PENDING
.scope(RefCell::new(Vec::new()), async {
let value = operation.await?;
let refreshes = PENDING.with(|pending| pending.take());
Ok((value, refreshes))
})
.await
}
pub async fn after_commit<T: Send>(operation: impl Future<Output = Result<T>> + Send) -> Result<T> {
let (value, refreshes) = collect_committed(operation).await?;
// Nested transactions hand their notifications to the outer transaction.
for refresh in refreshes {
announce(refresh);
}
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn only_successful_transactions_publish_refreshes() -> Result<()> {
let (_, refreshes) = collect_committed(async {
announce(Refresh::Clash);
after_commit(async {
announce(Refresh::Verge);
Ok(())
})
.await?;
let failed = after_commit(async {
announce(Refresh::Profiles);
Err::<(), _>(anyhow::anyhow!("persistence failed"))
})
.await;
assert!(failed.is_err());
announce(Refresh::Clash);
Ok(())
})
.await?;
assert_eq!(refreshes, [Refresh::Clash, Refresh::Verge, Refresh::Clash]);
Ok(())
}
}
+3 -11
View File
@@ -40,19 +40,11 @@ impl Handle {
}
pub fn refresh_clash() {
Self::send_event(FrontendEvent::RefreshClash);
super::announce(super::Refresh::Clash);
}
pub fn refresh_verge() {
Self::send_event(FrontendEvent::RefreshVerge);
}
pub fn refresh_profiles() {
Self::send_event(FrontendEvent::RefreshProfiles);
}
pub fn refresh_proxy_config() {
Self::send_event(FrontendEvent::RefreshProxyConfig);
super::announce(super::Refresh::Verge);
}
/// Push a Run State snapshot to the frontend.
@@ -103,7 +95,7 @@ impl Handle {
self.is_exiting.load(Ordering::Acquire)
}
fn send_event(event: FrontendEvent) {
pub(super) fn send_event(event: FrontendEvent) {
let handle = Self::global();
if handle.is_exiting() {
return;
+2
View File
@@ -1,3 +1,5 @@
mod announce;
pub use announce::{Refresh, after_commit, announce};
pub mod desktop;
pub mod handle;
pub mod notification;