use alloc::vec::Vec;
pub use azul_core::keyring::{KeyringRequest, KeyringResult};
use azul_core::dom::DomNodeId;
use azul_core::events::{
EventData, EventProvider, EventSource as CoreEventSource, EventType, SyntheticEvent,
};
use azul_core::task::Instant;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct KeyringManager {
pub last_result: Option<KeyringResult>,
pub in_flight: u32,
pub pending_event: bool,
}
impl KeyringManager {
#[must_use] pub fn new() -> Self {
Self::default()
}
#[must_use] pub const fn last_result(&self) -> Option<&KeyringResult> {
self.last_result.as_ref()
}
pub fn set_last_result(&mut self, result: KeyringResult) -> bool {
let changed = self.last_result.as_ref() != Some(&result);
self.last_result = Some(result);
self.pending_event = true;
self.in_flight = self.in_flight.saturating_sub(1);
changed
}
pub const fn mark_requests_dispatched(&mut self, n: u32) {
self.in_flight = self.in_flight.saturating_add(n);
}
pub const fn clear_pending_event(&mut self) {
self.pending_event = false;
}
#[must_use] pub const fn has_pending_async(&self) -> bool {
self.in_flight > 0
}
}
impl EventProvider for KeyringManager {
fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent> {
if self.pending_event {
alloc::vec![SyntheticEvent::new(
EventType::KeyringResult,
CoreEventSource::User,
DomNodeId::ROOT,
timestamp,
EventData::None,
)]
} else {
Vec::new()
}
}
}
static PENDING_REQUESTS: std::sync::Mutex<Vec<KeyringRequest>> =
std::sync::Mutex::new(Vec::new());
pub fn push_keyring_request(request: KeyringRequest) {
let mut q = PENDING_REQUESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
q.push(request);
}
pub fn drain_keyring_requests() -> Vec<KeyringRequest> {
let mut q = PENDING_REQUESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
core::mem::take(&mut *q)
}
pub fn has_queued_requests() -> bool {
PENDING_REQUESTS
.lock()
.map_or_else(|e| !e.into_inner().is_empty(), |q| !q.is_empty())
}
static PENDING_RESULTS: std::sync::Mutex<Vec<KeyringResult>> =
std::sync::Mutex::new(Vec::new());
pub fn push_keyring_result(result: KeyringResult) {
let mut q = PENDING_RESULTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
q.push(result);
}
pub fn drain_keyring_results() -> Vec<KeyringResult> {
let mut q = PENDING_RESULTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
core::mem::take(&mut *q)
}
#[cfg(test)]
mod tests {
use super::*;
use azul_css::AzString;
#[test]
fn manager_defaults_to_no_result() {
let mgr = KeyringManager::new();
assert_eq!(mgr.last_result(), None);
}
#[test]
fn set_last_result_returns_change_flag() {
let mut mgr = KeyringManager::new();
assert!(mgr.set_last_result(KeyringResult::Stored));
assert_eq!(mgr.last_result(), Some(&KeyringResult::Stored));
assert!(!mgr.set_last_result(KeyringResult::Stored));
assert!(mgr.set_last_result(KeyringResult::Deleted));
}
#[test]
fn result_helpers() {
let secret = KeyringResult::Retrieved(AzString::from_const_str("hunter2"));
assert_eq!(secret.secret().map(AzString::as_str), Some("hunter2"));
assert!(secret.is_ok());
assert!(KeyringResult::Stored.is_ok());
assert!(KeyringResult::Deleted.is_ok());
for r in [
KeyringResult::NotFound,
KeyringResult::Denied,
KeyringResult::Unavailable,
KeyringResult::Error,
] {
assert!(!r.is_ok(), "{r:?} must not be ok");
assert_eq!(r.secret(), None);
}
}
#[test]
fn requests_round_trip_through_channel() {
drop(drain_keyring_requests());
push_keyring_request(KeyringRequest::Store {
key: AzString::from_const_str("token"),
secret: AzString::from_const_str("abc"),
require_biometry: true,
});
push_keyring_request(KeyringRequest::Get {
key: AzString::from_const_str("token"),
});
let drained = drain_keyring_requests();
assert_eq!(drained.len(), 2, "both queued requests drain in order");
assert!(matches!(drained[0], KeyringRequest::Store { .. }));
assert!(matches!(drained[1], KeyringRequest::Get { .. }));
assert!(drain_keyring_requests().is_empty());
}
#[test]
fn results_round_trip_through_manager() {
drop(drain_keyring_results());
push_keyring_result(KeyringResult::NotFound);
push_keyring_result(KeyringResult::Retrieved(AzString::from_const_str("s"))); let drained = drain_keyring_results();
assert_eq!(drained.len(), 2);
let mut mgr = KeyringManager::new();
for r in drained {
mgr.set_last_result(r);
}
assert_eq!(
mgr.last_result().and_then(|r| r.secret()).map(AzString::as_str),
Some("s"),
"the last applied result wins"
);
assert!(drain_keyring_results().is_empty());
}
}
#[cfg(test)]
mod pump_provider_tests {
use super::*;
use azul_core::task::{Instant, SystemTick};
fn ts() -> Instant {
Instant::Tick(SystemTick::new(0))
}
#[test]
fn in_flight_and_events_track_op_lifecycle() {
let mut mgr = KeyringManager::new();
assert!(!mgr.has_pending_async());
mgr.mark_requests_dispatched(1);
assert!(mgr.has_pending_async());
assert!(mgr.get_pending_events(ts()).is_empty(), "no outcome yet");
mgr.set_last_result(KeyringResult::Stored);
assert!(!mgr.has_pending_async());
let events = mgr.get_pending_events(ts());
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_type, EventType::KeyringResult);
mgr.clear_pending_event();
mgr.mark_requests_dispatched(1);
mgr.set_last_result(KeyringResult::Stored);
assert_eq!(mgr.get_pending_events(ts()).len(), 1);
}
}