azul_layout/managers/
keyring.rs1use alloc::vec::Vec;
21
22pub use azul_core::keyring::{KeyringRequest, KeyringResult};
26
27use azul_core::dom::DomNodeId;
28use azul_core::events::{
29 EventData, EventProvider, EventSource as CoreEventSource, EventType, SyntheticEvent,
30};
31use azul_core::task::Instant;
32
33#[derive(Debug, Clone, PartialEq, Eq, Default)]
36pub struct KeyringManager {
37 pub last_result: Option<KeyringResult>,
40 pub in_flight: u32,
43 pub pending_event: bool,
49}
50
51impl KeyringManager {
52 #[must_use] pub fn new() -> Self {
53 Self::default()
54 }
55
56 #[must_use] pub const fn last_result(&self) -> Option<&KeyringResult> {
58 self.last_result.as_ref()
59 }
60
61 pub fn set_last_result(&mut self, result: KeyringResult) -> bool {
65 let changed = self.last_result.as_ref() != Some(&result);
66 self.last_result = Some(result);
67 self.pending_event = true;
70 self.in_flight = self.in_flight.saturating_sub(1);
71 changed
72 }
73
74 pub const fn mark_requests_dispatched(&mut self, n: u32) {
77 self.in_flight = self.in_flight.saturating_add(n);
78 }
79
80 pub const fn clear_pending_event(&mut self) {
83 self.pending_event = false;
84 }
85
86 #[must_use] pub const fn has_pending_async(&self) -> bool {
89 self.in_flight > 0
90 }
91}
92
93impl EventProvider for KeyringManager {
94 fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent> {
98 if self.pending_event {
99 alloc::vec![SyntheticEvent::new(
100 EventType::KeyringResult,
101 CoreEventSource::User,
102 DomNodeId::ROOT,
103 timestamp,
104 EventData::None,
105 )]
106 } else {
107 Vec::new()
108 }
109 }
110}
111
112static PENDING_REQUESTS: std::sync::Mutex<Vec<KeyringRequest>> =
115 std::sync::Mutex::new(Vec::new());
116
117pub fn push_keyring_request(request: KeyringRequest) {
120 let mut q = PENDING_REQUESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
121 q.push(request);
122}
123
124pub fn drain_keyring_requests() -> Vec<KeyringRequest> {
127 let mut q = PENDING_REQUESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
128 core::mem::take(&mut *q)
129}
130
131pub fn has_queued_requests() -> bool {
134 PENDING_REQUESTS
135 .lock()
136 .map_or_else(|e| !e.into_inner().is_empty(), |q| !q.is_empty())
137}
138
139static PENDING_RESULTS: std::sync::Mutex<Vec<KeyringResult>> =
142 std::sync::Mutex::new(Vec::new());
143
144pub fn push_keyring_result(result: KeyringResult) {
148 let mut q = PENDING_RESULTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
149 q.push(result);
150}
151
152pub fn drain_keyring_results() -> Vec<KeyringResult> {
155 let mut q = PENDING_RESULTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
156 core::mem::take(&mut *q)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use azul_css::AzString;
163
164 #[test]
165 fn manager_defaults_to_no_result() {
166 let mgr = KeyringManager::new();
167 assert_eq!(mgr.last_result(), None);
168 }
169
170 #[test]
171 fn set_last_result_returns_change_flag() {
172 let mut mgr = KeyringManager::new();
173 assert!(mgr.set_last_result(KeyringResult::Stored));
174 assert_eq!(mgr.last_result(), Some(&KeyringResult::Stored));
175 assert!(!mgr.set_last_result(KeyringResult::Stored));
177 assert!(mgr.set_last_result(KeyringResult::Deleted));
179 }
180
181 #[test]
182 fn result_helpers() {
183 let secret = KeyringResult::Retrieved(AzString::from_const_str("hunter2"));
184 assert_eq!(secret.secret().map(AzString::as_str), Some("hunter2"));
185 assert!(secret.is_ok());
186 assert!(KeyringResult::Stored.is_ok());
187 assert!(KeyringResult::Deleted.is_ok());
188 for r in [
189 KeyringResult::NotFound,
190 KeyringResult::Denied,
191 KeyringResult::Unavailable,
192 KeyringResult::Error,
193 ] {
194 assert!(!r.is_ok(), "{r:?} must not be ok");
195 assert_eq!(r.secret(), None);
196 }
197 }
198
199 #[test]
200 fn requests_round_trip_through_channel() {
201 drop(drain_keyring_requests());
202
203 push_keyring_request(KeyringRequest::Store {
204 key: AzString::from_const_str("token"),
205 secret: AzString::from_const_str("abc"),
206 require_biometry: true,
207 });
208 push_keyring_request(KeyringRequest::Get {
209 key: AzString::from_const_str("token"),
210 });
211 let drained = drain_keyring_requests();
212 assert_eq!(drained.len(), 2, "both queued requests drain in order");
213 assert!(matches!(drained[0], KeyringRequest::Store { .. }));
214 assert!(matches!(drained[1], KeyringRequest::Get { .. }));
215 assert!(drain_keyring_requests().is_empty());
216 }
217
218 #[test]
219 fn results_round_trip_through_manager() {
220 drop(drain_keyring_results());
221
222 push_keyring_result(KeyringResult::NotFound);
223 push_keyring_result(KeyringResult::Retrieved(AzString::from_const_str("s"))); let drained = drain_keyring_results();
225 assert_eq!(drained.len(), 2);
226
227 let mut mgr = KeyringManager::new();
228 for r in drained {
229 mgr.set_last_result(r);
230 }
231 assert_eq!(
232 mgr.last_result().and_then(|r| r.secret()).map(AzString::as_str),
233 Some("s"),
234 "the last applied result wins"
235 );
236 assert!(drain_keyring_results().is_empty());
237 }
238}
239
240#[cfg(test)]
241mod pump_provider_tests {
242 use super::*;
243 use azul_core::task::{Instant, SystemTick};
244
245 fn ts() -> Instant {
246 Instant::Tick(SystemTick::new(0))
247 }
248
249 #[test]
250 fn in_flight_and_events_track_op_lifecycle() {
251 let mut mgr = KeyringManager::new();
252 assert!(!mgr.has_pending_async());
253 mgr.mark_requests_dispatched(1);
254 assert!(mgr.has_pending_async());
255 assert!(mgr.get_pending_events(ts()).is_empty(), "no outcome yet");
256
257 mgr.set_last_result(KeyringResult::Stored);
258 assert!(!mgr.has_pending_async());
259 let events = mgr.get_pending_events(ts());
260 assert_eq!(events.len(), 1);
261 assert_eq!(events[0].event_type, EventType::KeyringResult);
262 mgr.clear_pending_event();
263
264 mgr.mark_requests_dispatched(1);
266 mgr.set_last_result(KeyringResult::Stored);
267 assert_eq!(mgr.get_pending_events(ts()).len(), 1);
268 }
269}