|
1 | | -use notify::Watcher; |
| 1 | +#[cfg(feature = "subscription")] |
| 2 | +use iced_futures::futures::channel::mpsc; |
| 3 | +#[cfg(feature = "subscription")] |
| 4 | +use iced_futures::subscription; |
| 5 | +use notify::{RecommendedWatcher, Watcher}; |
2 | 6 | use serde::{de::DeserializeOwned, Serialize}; |
3 | 7 | use std::{ |
| 8 | + borrow::Cow, |
4 | 9 | fs, |
| 10 | + hash::Hash, |
5 | 11 | io::Write, |
6 | 12 | path::{Path, PathBuf}, |
7 | 13 | sync::Mutex, |
8 | 14 | }; |
9 | 15 |
|
| 16 | +#[cfg(feature = "macro")] |
| 17 | +pub use cosmic_config_derive; |
| 18 | + |
10 | 19 | #[cfg(feature = "calloop")] |
11 | 20 | pub mod calloop; |
12 | 21 |
|
@@ -251,3 +260,123 @@ impl<'a> ConfigSet for ConfigTransaction<'a> { |
251 | 260 | Ok(()) |
252 | 261 | } |
253 | 262 | } |
| 263 | + |
| 264 | +#[cfg(feature = "subscription")] |
| 265 | +pub enum ConfigState<T> { |
| 266 | + Init(Cow<'static, str>, u64), |
| 267 | + Waiting(T, RecommendedWatcher, mpsc::Receiver<()>, Config), |
| 268 | + Failed, |
| 269 | +} |
| 270 | + |
| 271 | +#[cfg(feature = "subscription")] |
| 272 | +pub enum ConfigUpdate<T> { |
| 273 | + Update(T), |
| 274 | + UpdateError(T, Vec<crate::Error>), |
| 275 | + Failed, |
| 276 | +} |
| 277 | + |
| 278 | +pub trait CosmicConfigEntry |
| 279 | +where |
| 280 | + Self: Sized, |
| 281 | +{ |
| 282 | + fn write_entry(&self, config: &Config) -> Result<(), crate::Error>; |
| 283 | + fn get_entry(config: &Config) -> Result<Self, (Vec<crate::Error>, Self)>; |
| 284 | +} |
| 285 | + |
| 286 | +#[cfg(feature = "subscription")] |
| 287 | +pub fn config_subscription< |
| 288 | + I: 'static + Copy + Send + Sync + Hash, |
| 289 | + T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, |
| 290 | +>( |
| 291 | + id: I, |
| 292 | + config_id: Cow<'static, str>, |
| 293 | + config_version: u64, |
| 294 | +) -> iced_futures::Subscription<(I, Result<T, (Vec<crate::Error>, T)>)> { |
| 295 | + subscription::unfold( |
| 296 | + id, |
| 297 | + ConfigState::Init(config_id, config_version), |
| 298 | + move |state| start_listening_loop(id, state), |
| 299 | + ) |
| 300 | +} |
| 301 | + |
| 302 | +#[cfg(feature = "subscription")] |
| 303 | +async fn start_listening< |
| 304 | + I: Copy, |
| 305 | + T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, |
| 306 | +>( |
| 307 | + id: I, |
| 308 | + state: ConfigState<T>, |
| 309 | +) -> ( |
| 310 | + Option<(I, Result<T, (Vec<crate::Error>, T)>)>, |
| 311 | + ConfigState<T>, |
| 312 | +) { |
| 313 | + use iced_futures::futures::{future::pending, StreamExt}; |
| 314 | + |
| 315 | + match state { |
| 316 | + ConfigState::Init(config_id, version) => { |
| 317 | + let (tx, rx) = mpsc::channel(100); |
| 318 | + let config = match Config::new(&config_id, version) { |
| 319 | + Ok(c) => c, |
| 320 | + Err(_) => return (None, ConfigState::Failed), |
| 321 | + }; |
| 322 | + let watcher = match config.watch(move |_helper, _keys| { |
| 323 | + let mut tx = tx.clone(); |
| 324 | + let _ = tx.try_send(()); |
| 325 | + }) { |
| 326 | + Ok(w) => w, |
| 327 | + Err(_) => return (None, ConfigState::Failed), |
| 328 | + }; |
| 329 | + |
| 330 | + match T::get_entry(&config) { |
| 331 | + Ok(t) => ( |
| 332 | + Some((id, Ok(t.clone()))), |
| 333 | + ConfigState::Waiting(t, watcher, rx, config), |
| 334 | + ), |
| 335 | + Err((errors, t)) => ( |
| 336 | + Some((id, Err((errors, t.clone())))), |
| 337 | + ConfigState::Waiting(t, watcher, rx, config), |
| 338 | + ), |
| 339 | + } |
| 340 | + } |
| 341 | + ConfigState::Waiting(old, watcher, mut rx, config) => match rx.next().await { |
| 342 | + Some(_) => match T::get_entry(&config) { |
| 343 | + Ok(t) => ( |
| 344 | + if t != old { |
| 345 | + Some((id, Ok(t.clone()))) |
| 346 | + } else { |
| 347 | + None |
| 348 | + }, |
| 349 | + ConfigState::Waiting(t, watcher, rx, config), |
| 350 | + ), |
| 351 | + Err((errors, t)) => ( |
| 352 | + if t != old { |
| 353 | + Some((id, Err((errors, t.clone())))) |
| 354 | + } else { |
| 355 | + None |
| 356 | + }, |
| 357 | + ConfigState::Waiting(t, watcher, rx, config), |
| 358 | + ), |
| 359 | + }, |
| 360 | + |
| 361 | + None => (None, ConfigState::Failed), |
| 362 | + }, |
| 363 | + ConfigState::Failed => pending().await, |
| 364 | + } |
| 365 | +} |
| 366 | + |
| 367 | +#[cfg(feature = "subscription")] |
| 368 | +async fn start_listening_loop< |
| 369 | + I: Copy, |
| 370 | + T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry, |
| 371 | +>( |
| 372 | + id: I, |
| 373 | + mut state: ConfigState<T>, |
| 374 | +) -> ((I, Result<T, (Vec<crate::Error>, T)>), ConfigState<T>) { |
| 375 | + loop { |
| 376 | + let (update, new_state) = start_listening(id, state).await; |
| 377 | + state = new_state; |
| 378 | + if let Some(update) = update { |
| 379 | + return (update, state); |
| 380 | + } |
| 381 | + } |
| 382 | +} |
0 commit comments