zng_ext_config/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Config service and sources.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9// suppress nag about very simple boxed closure signatures.
10#![expect(clippy::type_complexity)]
11#![warn(unused_extern_crates)]
12#![warn(missing_docs)]
13
14mod fallback;
15pub use fallback::*;
16
17mod json;
18pub use json::*;
19
20mod swap;
21pub use swap::*;
22
23mod switch;
24pub use switch::*;
25
26mod sync;
27pub use sync::*;
28
29#[cfg(feature = "toml")]
30mod toml;
31#[cfg(feature = "toml")]
32pub use self::toml::*;
33
34#[cfg(feature = "ron")]
35mod ron;
36#[cfg(feature = "ron")]
37pub use self::ron::*;
38
39#[cfg(feature = "yaml")]
40mod yaml;
41#[cfg(feature = "yaml")]
42pub use self::yaml::*;
43
44pub mod settings;
45
46use std::{
47    any::Any,
48    collections::{HashMap, hash_map},
49    fmt, io,
50    sync::Arc,
51};
52
53use zng_app::{AppExtension, update::EventUpdate, view_process::raw_events::LOW_MEMORY_EVENT};
54use zng_app_context::app_local;
55use zng_clone_move::clmv;
56use zng_ext_fs_watcher::{WatchFile, WatcherReadStatus, WatcherSyncStatus, WriteFile};
57use zng_task as task;
58use zng_txt::Txt;
59use zng_var::{AnyVar, AnyWeakVar, ArcVar, BoxedVar, LocalVar, Var, VarHandles, VarModify, VarValue, WeakVar, types::WeakArcVar, var};
60
61/// Application extension that provides mouse events and service.
62///
63/// # Services
64///
65/// Services this extension provides.
66///
67/// * [`CONFIG`]
68#[derive(Default)]
69pub struct ConfigManager {}
70
71impl AppExtension for ConfigManager {
72    fn event_preview(&mut self, update: &mut EventUpdate) {
73        if LOW_MEMORY_EVENT.on(update).is_some() {
74            CONFIG_SV.write().low_memory();
75        }
76    }
77}
78
79/// Represents the app main config.
80///
81/// Config sources must be loaded using [`CONFIG.load`], otherwise the config only lives for the
82/// duration of the app instance.
83///
84/// [`CONFIG.load`]: CONFIG::load
85pub struct CONFIG;
86impl CONFIG {
87    /// Replace the config source.
88    ///
89    /// Variables and bindings survive source replacement, updating to the new value or setting the new source
90    /// if the key is not present in the new source.
91    pub fn load(&self, source: impl AnyConfig) {
92        CONFIG_SV.write().load(source)
93    }
94
95    /// Gets a read-only variable that represents the IO status of the config.
96    pub fn status(&self) -> BoxedVar<ConfigStatus> {
97        CONFIG_SV.read().status()
98    }
99
100    /// Wait until [`status`] is idle (not loading nor saving).
101    ///
102    /// [`status`]: Self::status
103    pub async fn wait_idle(&self) {
104        task::yield_now().await; // in case a `load` request was just made
105        self.status().wait_value(|s| s.is_idle()).await;
106    }
107
108    /// Gets a variable that is bound to the config `key`.
109    ///
110    /// The same variable is returned for multiple requests of the same key. If the loaded config is not read-only the
111    /// returned variable can be set to update the config source.
112    ///
113    /// The `default` value is used if the key is not found in the config, the default value
114    /// is not inserted in the config, the key is inserted or replaced only when the returned variable updates.
115    pub fn get<T: ConfigValue>(&self, key: impl Into<ConfigKey>, default: T) -> BoxedVar<T> {
116        CONFIG_SV.write().get(key.into(), default, false)
117    }
118
119    /// Gets a variable that is bound to the config `key`, the `value` is set and if the `key` was not present it is also inserted on the config.
120    pub fn insert<T: ConfigValue>(&self, key: impl Into<ConfigKey>, value: T) -> BoxedVar<T> {
121        CONFIG_SV.write().get(key.into(), value, true)
122    }
123}
124impl AnyConfig for CONFIG {
125    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool, shared: bool) -> BoxedVar<RawConfigValue> {
126        CONFIG_SV.write().get_raw(key, default, insert, shared)
127    }
128
129    fn contains_key(&mut self, key: ConfigKey) -> BoxedVar<bool> {
130        CONFIG_SV.write().contains_key(key)
131    }
132
133    fn status(&self) -> BoxedVar<ConfigStatus> {
134        CONFIG.status()
135    }
136
137    fn remove(&mut self, key: &ConfigKey) -> bool {
138        CONFIG_SV.write().remove(key)
139    }
140
141    fn low_memory(&mut self) {
142        CONFIG_SV.write().low_memory()
143    }
144}
145impl Config for CONFIG {
146    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool) -> BoxedVar<T> {
147        CONFIG_SV.write().get(key, default, insert)
148    }
149}
150
151app_local! {
152    static CONFIG_SV: SwapConfig = SwapConfig::new();
153}
154
155/// Unique key to a config entry.
156pub type ConfigKey = Txt;
157
158/// Marker trait for types that can stored in a [`Config`].
159///
160/// This trait is already implemented for types it applies.
161#[diagnostic::on_unimplemented(note = "`ConfigValue` is implemented for all `T: VarValue + Serialize + DeserializeOwned`")]
162pub trait ConfigValue: VarValue + serde::Serialize + serde::de::DeserializeOwned {}
163impl<T: VarValue + serde::Serialize + serde::de::DeserializeOwned> ConfigValue for T {}
164
165/// Represents any entry type in a config.
166#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
167pub struct RawConfigValue(pub serde_json::Value);
168impl RawConfigValue {
169    /// Serialize to the raw config format.
170    pub fn serialize<T: serde::Serialize>(value: T) -> Result<Self, serde_json::Error> {
171        serde_json::to_value(value).map(Self)
172    }
173
174    /// Deserialize from the raw config format.
175    pub fn deserialize<T: serde::de::DeserializeOwned>(self) -> Result<T, serde_json::Error> {
176        serde_json::from_value(self.0)
177    }
178}
179
180/// Represents a full config map in memory.
181///
182/// This can be used with [`SyncConfig`] to implement a full config.
183pub trait ConfigMap: VarValue + fmt::Debug {
184    /// New empty map.
185    fn empty() -> Self;
186
187    /// Read a map from the file.
188    ///
189    /// This method runs in unblocked context.
190    fn read(file: WatchFile) -> io::Result<Self>;
191    /// Write the map to a file.
192    ///
193    /// This method runs in unblocked context.
194    fn write(self, file: &mut WriteFile) -> io::Result<()>;
195
196    /// Gets the weak typed value.
197    ///
198    /// This method is used when `T` cannot be passed because the map is behind a dynamic reference,
199    /// the backend must convert the value from the in memory representation to [`RawConfigValue`].
200    ///
201    /// This method can run in blocking contexts, work with in memory storage only.
202    fn get_raw(&self, key: &ConfigKey) -> Result<Option<RawConfigValue>, Arc<dyn std::error::Error + Send + Sync>>;
203
204    /// Sets the weak typed value.
205    ///
206    /// This method is used when `T` cannot be passed because the map is behind a dynamic reference,
207    /// the backend must convert to the in memory representation.
208    ///
209    /// If `map` is dereferenced mutable a write task will, if possible check if the entry already has the same value
210    /// before mutating the map to avoid a potentially expensive IO write.
211    ///
212    /// This method can run in blocking contexts, work with in memory storage only.
213    fn set_raw(map: &mut VarModify<Self>, key: ConfigKey, value: RawConfigValue) -> Result<(), Arc<dyn std::error::Error + Send + Sync>>;
214
215    /// Returns if the key in config.
216    ///
217    /// This method can run in blocking contexts, work with in memory storage only.
218    fn contains_key(&self, key: &ConfigKey) -> bool;
219
220    /// Remove the config entry associated with the key.
221    fn remove(map: &mut VarModify<Self>, key: &ConfigKey);
222
223    /// Get the value if present.
224    ///
225    /// This method can run in blocking contexts, work with in memory storage only.
226    fn get<O: ConfigValue>(&self, key: &ConfigKey) -> Result<Option<O>, Arc<dyn std::error::Error + Send + Sync>> {
227        if let Some(value) = self.get_raw(key)? {
228            match RawConfigValue::deserialize(value) {
229                Ok(s) => Ok(Some(s)),
230                Err(e) => Err(Arc::new(e)),
231            }
232        } else {
233            Ok(None)
234        }
235    }
236
237    /// Set the value.
238    ///
239    /// If possible check if the entry already has the same value before mutating the map to avoid a
240    /// potentially expensive clone operation. Note that the map will only be written if the map actually
241    /// changes, or an update is explicitly requested.
242    ///
243    /// This method can run in blocking contexts, work with in memory storage only.
244    fn set<O: ConfigValue>(map: &mut VarModify<Self>, key: ConfigKey, value: O) -> Result<(), Arc<dyn std::error::Error + Send + Sync>> {
245        match RawConfigValue::serialize(value) {
246            Ok(s) => Self::set_raw(map, key, s),
247            Err(e) => Err(Arc::new(e)),
248        }
249    }
250}
251
252/// Represents one or more config sources behind a dynamic reference.
253///
254/// See [`Config`] for the full trait.
255pub trait AnyConfig: Send + Any {
256    /// Gets a read-only variable that represents the IO status of the config.
257    fn status(&self) -> BoxedVar<ConfigStatus>;
258
259    /// Gets a weak typed variable to the config `key`.
260    ///
261    /// This method is used when `T` cannot be passed because the config is behind a dynamic reference,
262    /// the backend must convert the value from the in memory representation to [`RawConfigValue`].
263    ///
264    /// If `shared` is `true` and the key was already requested the same var is returned, if `false`
265    /// a new variable is always generated. Note that if you have two different variables for the same
266    /// key they will go out-of-sync as updates from setting one variable do not propagate to the other.
267    ///
268    /// The `default` value is used if the key is not found in the config, the default value
269    /// is only inserted in the config if `insert`, otherwise the key is inserted or replaced only when the returned variable changes.
270    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool, shared: bool) -> BoxedVar<RawConfigValue>;
271
272    /// Gets a read-only variable that tracks if an entry for the `key` is in the backing storage.
273    fn contains_key(&mut self, key: ConfigKey) -> BoxedVar<bool>;
274
275    /// Removes the `key` from the backing storage.
276    ///
277    /// Any active config variable for the key will continue to work normally, retaining the last config value and
278    /// re-inserting the key if assigned a new value.
279    ///
280    /// Returns `true` if the key was found and will be removed in the next app update.
281    /// Returns `false` if the key was not found or the config is read-only.
282    fn remove(&mut self, key: &ConfigKey) -> bool;
283
284    /// Cleanup and flush RAM caches.
285    fn low_memory(&mut self);
286}
287impl dyn AnyConfig {
288    /// Get raw config and setup a bidi binding that converts to and from `T`.
289    ///
290    /// See [`get_raw`](AnyConfig::get_raw) for more details about the inputs.
291    pub fn get_raw_serde_bidi<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool, shared: bool) -> BoxedVar<T> {
292        let key = key.into();
293        let source_var = self.get_raw(
294            key.clone(),
295            RawConfigValue::serialize(&default).unwrap_or_else(|e| panic!("invalid default value, {e}")),
296            insert,
297            shared,
298        );
299        let var = var(RawConfigValue::deserialize(source_var.get()).unwrap_or(default));
300
301        source_var
302            .bind_filter_map_bidi(
303                &var,
304                // Raw -> T
305                clmv!(key, |raw| {
306                    match RawConfigValue::deserialize(raw.clone()) {
307                        Ok(value) => Some(value),
308                        Err(e) => {
309                            tracing::error!("get_raw_serde_bidi({key:?}) error, {e:?}");
310                            None
311                        }
312                    }
313                }),
314                // T -> Raw
315                clmv!(key, source_var, |value| {
316                    let _strong_ref = &source_var;
317
318                    match RawConfigValue::serialize(value) {
319                        Ok(raw) => Some(raw),
320                        Err(e) => {
321                            tracing::error!("get_raw_serde_bidi({key:?}) error, {e:?}");
322                            None
323                        }
324                    }
325                }),
326            )
327            .perm();
328
329        var.boxed()
330    }
331}
332
333/// Represents one or more config sources.
334pub trait Config: AnyConfig {
335    /// Gets a variable that is bound to the config `key`.
336    ///
337    /// The same variable is returned for multiple requests of the same key. If the loaded config is not read-only the
338    /// returned variable can be set to update the config source.
339    ///
340    /// The `default` value is used if the key is not found in the config, the default value
341    /// is only inserted in the config if `insert`, otherwise the key is inserted or replaced only when the returned variable changes.
342    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool) -> BoxedVar<T>;
343}
344
345/// Config wrapper that only provides read-only variables from the inner config.
346pub struct ReadOnlyConfig<C: Config> {
347    cfg: C,
348}
349impl<C: Config> ReadOnlyConfig<C> {
350    /// New reading from `cfg`.
351    pub fn new(cfg: C) -> Self {
352        Self { cfg }
353    }
354}
355impl<C: Config> AnyConfig for ReadOnlyConfig<C> {
356    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, _: bool, shared: bool) -> BoxedVar<RawConfigValue> {
357        self.cfg.get_raw(key, default, false, shared).read_only()
358    }
359
360    fn contains_key(&mut self, key: ConfigKey) -> BoxedVar<bool> {
361        self.cfg.contains_key(key)
362    }
363
364    fn status(&self) -> BoxedVar<ConfigStatus> {
365        self.cfg.status()
366    }
367
368    fn remove(&mut self, _key: &ConfigKey) -> bool {
369        false
370    }
371
372    fn low_memory(&mut self) {
373        self.cfg.low_memory()
374    }
375}
376impl<C: Config> Config for ReadOnlyConfig<C> {
377    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, _: bool) -> BoxedVar<T> {
378        self.cfg.get(key.into(), default, false).read_only()
379    }
380}
381
382/// Memory only config.
383///
384/// Values are retained in memory even if all variables to the key are dropped, but they are lost when the process ends.
385#[derive(Default)]
386pub struct MemoryConfig {
387    values: HashMap<ConfigKey, ArcVar<RawConfigValue>>,
388    contains: HashMap<ConfigKey, WeakArcVar<bool>>,
389}
390
391impl AnyConfig for MemoryConfig {
392    fn status(&self) -> BoxedVar<ConfigStatus> {
393        LocalVar(ConfigStatus::Loaded).boxed()
394    }
395
396    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, _insert: bool, _shared: bool) -> BoxedVar<RawConfigValue> {
397        match self.values.entry(key) {
398            hash_map::Entry::Occupied(e) => e.get().clone().boxed(),
399            hash_map::Entry::Vacant(e) => {
400                let r = var(default);
401
402                if let Some(v) = self.contains.get(e.key()) {
403                    if let Some(v) = v.upgrade() {
404                        v.set(true);
405                    }
406                }
407
408                e.insert(r).clone().boxed()
409            }
410        }
411    }
412
413    fn contains_key(&mut self, key: ConfigKey) -> BoxedVar<bool> {
414        match self.contains.entry(key) {
415            hash_map::Entry::Occupied(mut e) => {
416                if let Some(r) = e.get().upgrade() {
417                    r.boxed()
418                } else {
419                    let r = var(self.values.contains_key(e.key()));
420                    e.insert(r.downgrade());
421                    r.boxed()
422                }
423            }
424            hash_map::Entry::Vacant(e) => {
425                let r = var(self.values.contains_key(e.key()));
426                e.insert(r.downgrade());
427                r.boxed()
428            }
429        }
430    }
431
432    fn remove(&mut self, key: &ConfigKey) -> bool {
433        if self.values.remove(key).is_some() {
434            self.contains.retain(|_, v| v.strong_count() > 0);
435
436            if let Some(v) = self.contains.get(key) {
437                if let Some(v) = v.upgrade() {
438                    v.set(false);
439                }
440            }
441            true
442        } else {
443            false
444        }
445    }
446
447    fn low_memory(&mut self) {
448        self.contains.retain(|_, v| v.strong_count() > 0);
449    }
450}
451impl Config for MemoryConfig {
452    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool) -> BoxedVar<T> {
453        self.get_raw(key.into(), RawConfigValue::serialize(default.clone()).unwrap(), insert, true)
454            .filter_map_bidi(
455                |m| m.clone().deserialize::<T>().ok(),
456                |v| RawConfigValue::serialize(v).ok(),
457                move || default.clone(),
458            )
459            .boxed()
460    }
461}
462
463struct ConfigVar<T: ConfigValue> {
464    var: WeakArcVar<T>,
465    binding: VarHandles,
466}
467impl<T: ConfigValue> ConfigVar<T> {
468    fn new_any(var: WeakArcVar<T>, binding: VarHandles) -> Box<dyn AnyConfigVar> {
469        Box::new(Self { var, binding })
470    }
471}
472struct ConfigContainsVar {
473    var: WeakArcVar<bool>,
474    binding: VarHandles,
475}
476
477/// Map of configs already bound to a variable.
478///
479/// The map only holds a weak reference to the variables.
480#[derive(Default)]
481pub struct ConfigVars {
482    values: HashMap<ConfigKey, Box<dyn AnyConfigVar>>,
483    contains: HashMap<ConfigKey, ConfigContainsVar>,
484}
485impl ConfigVars {
486    /// Gets the already bound variable or calls `bind` to generate a new binding.
487    pub fn get_or_bind<T: ConfigValue>(&mut self, key: ConfigKey, bind: impl FnOnce(&ConfigKey) -> BoxedVar<T>) -> BoxedVar<T> {
488        match self.values.entry(key) {
489            hash_map::Entry::Occupied(mut e) => {
490                if e.get().can_upgrade() {
491                    if let Some(x) = e.get().as_any().downcast_ref::<ConfigVar<T>>() {
492                        if let Some(var) = x.var.upgrade() {
493                            return var.boxed();
494                        }
495                    } else {
496                        tracing::error!(
497                            "cannot get key `{}` as `{}` because it is already requested with a different type",
498                            e.key(),
499                            std::any::type_name::<T>()
500                        );
501                        return bind(e.key());
502                    }
503                }
504                // cannot upgrade
505                let cfg = bind(e.key());
506
507                let res = var(cfg.get());
508                let binding = res.bind_map_bidi(
509                    &cfg,
510                    clmv!(cfg, |v| {
511                        let _strong_ref = &cfg;
512                        v.clone()
513                    }),
514                    Clone::clone,
515                );
516
517                e.insert(ConfigVar::new_any(res.downgrade(), binding));
518                res.boxed()
519            }
520            hash_map::Entry::Vacant(e) => {
521                let cfg = bind(e.key());
522                let res = var(cfg.get());
523                let binding = res.bind_map_bidi(
524                    &cfg,
525                    clmv!(cfg, |v| {
526                        let _strong_ref = &cfg;
527                        v.clone()
528                    }),
529                    Clone::clone,
530                );
531
532                e.insert(ConfigVar::new_any(res.downgrade(), binding));
533                res.boxed()
534            }
535        }
536    }
537
538    /// Bind the contains variable.
539    pub fn get_or_bind_contains(&mut self, key: ConfigKey, bind: impl FnOnce(&ConfigKey) -> BoxedVar<bool>) -> BoxedVar<bool> {
540        match self.contains.entry(key) {
541            hash_map::Entry::Occupied(mut e) => {
542                if let Some(res) = e.get().var.upgrade() {
543                    return res.boxed();
544                }
545
546                let cfg = bind(e.key());
547                let res = var(cfg.get());
548
549                let binding = VarHandles(vec![
550                    cfg.bind(&res),
551                    res.hook_any(Box::new(move |_| {
552                        let _strong_ref = &cfg;
553                        true
554                    })),
555                ]);
556
557                e.insert(ConfigContainsVar {
558                    var: res.downgrade(),
559                    binding,
560                });
561
562                res.boxed()
563            }
564            hash_map::Entry::Vacant(e) => {
565                let cfg = bind(e.key());
566                let res = var(cfg.get());
567
568                let binding = VarHandles(vec![
569                    cfg.bind(&res),
570                    res.hook_any(Box::new(move |_| {
571                        let _strong_ref = &cfg;
572                        true
573                    })),
574                ]);
575
576                e.insert(ConfigContainsVar {
577                    var: res.downgrade(),
578                    binding,
579                });
580
581                res.boxed()
582            }
583        }
584    }
585
586    /// Bind all variables to the new `source`.
587    ///
588    /// If the map entry is present in the `source` the variable is updated to the new value, if not the entry
589    /// is inserted in the source. The variable is then bound to the source.
590    pub fn rebind(&mut self, source: &mut dyn AnyConfig) {
591        self.values.retain(|key, wk_var| wk_var.rebind(key, source));
592        self.contains.retain(|key, wk_var| wk_var.rebind(key, source));
593    }
594
595    /// System warning low memory, flush caches.
596    pub fn low_memory(&mut self) {
597        self.values.retain(|_, v| v.can_upgrade());
598        self.contains.retain(|_, v| v.var.strong_count() > 0)
599    }
600}
601trait AnyConfigVar: Any + Send + Sync {
602    fn as_any(&self) -> &dyn Any;
603    fn can_upgrade(&self) -> bool;
604    fn rebind(&mut self, key: &ConfigKey, source: &mut dyn AnyConfig) -> bool;
605}
606impl<T: ConfigValue> AnyConfigVar for ConfigVar<T> {
607    fn as_any(&self) -> &dyn Any {
608        self
609    }
610
611    fn can_upgrade(&self) -> bool {
612        self.var.strong_count() > 0
613    }
614
615    fn rebind(&mut self, key: &ConfigKey, source: &mut dyn AnyConfig) -> bool {
616        let var = if let Some(var) = self.var.upgrade() {
617            var
618        } else {
619            // no need to retain, will bind directly to new source if requested later.
620            return false;
621        };
622
623        // get or insert the source var
624        let source_var = source.get_raw(key.clone(), RawConfigValue::serialize(var.get()).unwrap(), false, false);
625
626        // var.set_from_map(source_var)
627        var.modify(clmv!(source_var, key, |vm| {
628            match RawConfigValue::deserialize::<T>(source_var.get()) {
629                Ok(value) => {
630                    vm.set(value);
631                }
632                Err(e) => {
633                    // invalid data error
634                    tracing::error!("rebind config get({key:?}) error, {e:?}");
635
636                    // try to override
637                    let _ = source_var.set(RawConfigValue::serialize(vm.as_ref()).unwrap());
638                }
639            }
640        }));
641
642        let mut first = true;
643        self.binding = source_var.bind_filter_map_bidi(
644            &var,
645            // Raw -> T
646            clmv!(key, |raw| {
647                match RawConfigValue::deserialize(raw.clone()) {
648                    Ok(value) => Some(value),
649                    Err(e) => {
650                        tracing::error!("rebind config get({key:?}) error, {e:?}");
651                        None
652                    }
653                }
654            }),
655            // T -> Raw
656            clmv!(key, source_var, |value| {
657                if std::mem::take(&mut first) {
658                    return None; // skip value we just set.
659                }
660
661                let _strong_ref = &source_var;
662                match RawConfigValue::serialize(value) {
663                    Ok(raw) => Some(raw),
664                    Err(e) => {
665                        tracing::error!("rebind config set({key:?}) error, {e:?}");
666                        None
667                    }
668                }
669            }),
670        );
671
672        true
673    }
674}
675impl ConfigContainsVar {
676    fn rebind(&mut self, key: &ConfigKey, source: &mut dyn AnyConfig) -> bool {
677        if let Some(res) = self.var.upgrade() {
678            let cfg = source.contains_key(key.clone());
679            res.set_from(&cfg);
680
681            self.binding = VarHandles(vec![
682                cfg.bind(&res),
683                res.hook_any(Box::new(move |_| {
684                    let _strong_ref = &cfg;
685                    true
686                })),
687            ]);
688
689            true
690        } else {
691            false
692        }
693    }
694}
695
696/// Represents the current IO status of the config.
697#[derive(Debug, Clone)]
698pub enum ConfigStatus {
699    /// Config is loaded.
700    Loaded,
701    /// Config is loading.
702    Loading,
703    /// Config is saving.
704    Saving,
705    /// Config last load failed.
706    LoadErrors(ConfigStatusError),
707    /// Config last save failed.
708    SaveErrors(ConfigStatusError),
709}
710impl ConfigStatus {
711    /// If status is not loading nor saving.
712    pub fn is_idle(&self) -> bool {
713        !matches!(self, Self::Loading | Self::Saving)
714    }
715
716    /// If status is load or save errors.
717    pub fn is_err(&self) -> bool {
718        matches!(self, ConfigStatus::LoadErrors(_) | ConfigStatus::SaveErrors(_))
719    }
720
721    /// Errors list.
722    ///
723    /// Note that [`is_err`] may be true even when this is empty.
724    ///
725    /// [`is_err`]: Self::is_err
726    pub fn errors(&self) -> &[Arc<dyn std::error::Error + Send + Sync>] {
727        match self {
728            ConfigStatus::LoadErrors(e) => e,
729            ConfigStatus::SaveErrors(e) => e,
730            _ => &[],
731        }
732    }
733
734    /// merge all `status`.
735    pub fn merge_status(status: impl Iterator<Item = ConfigStatus>) -> ConfigStatus {
736        let mut load_errors = vec![];
737        let mut save_errors = vec![];
738        let mut loading = false;
739        let mut saving = false;
740        for s in status {
741            match s {
742                ConfigStatus::Loaded => {}
743                ConfigStatus::Loading => loading = true,
744                ConfigStatus::Saving => saving = true,
745                ConfigStatus::LoadErrors(e) => {
746                    if load_errors.is_empty() {
747                        load_errors = e;
748                    } else {
749                        load_errors.extend(e);
750                    }
751                }
752                ConfigStatus::SaveErrors(e) => {
753                    if save_errors.is_empty() {
754                        save_errors = e;
755                    } else {
756                        save_errors.extend(e);
757                    }
758                }
759            }
760        }
761
762        if loading {
763            ConfigStatus::Loading
764        } else if saving {
765            ConfigStatus::Saving
766        } else if !load_errors.is_empty() {
767            ConfigStatus::LoadErrors(load_errors)
768        } else if !save_errors.is_empty() {
769            ConfigStatus::SaveErrors(save_errors)
770        } else {
771            ConfigStatus::Loaded
772        }
773    }
774}
775impl fmt::Display for ConfigStatus {
776    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
777        match self {
778            Self::Loaded => Ok(()),
779            Self::Loading => write!(f, "loading…"),
780            Self::Saving => write!(f, "saving…"),
781            Self::LoadErrors(e) => {
782                writeln!(f, "read errors:")?;
783                for e in e {
784                    writeln!(f, "   {e}")?;
785                }
786                Ok(())
787            }
788            Self::SaveErrors(e) => {
789                writeln!(f, "write errors:")?;
790                for e in e {
791                    writeln!(f, "   {e}")?;
792                }
793                Ok(())
794            }
795        }
796    }
797}
798impl PartialEq for ConfigStatus {
799    fn eq(&self, other: &Self) -> bool {
800        match (self, other) {
801            (Self::LoadErrors(a), Self::LoadErrors(b)) => a.is_empty() && b.is_empty(),
802            (Self::SaveErrors(a), Self::SaveErrors(b)) => a.is_empty() && b.is_empty(),
803            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
804        }
805    }
806}
807impl Eq for ConfigStatus {}
808impl WatcherSyncStatus<ConfigStatusError, ConfigStatusError> for ConfigStatus {
809    fn writing() -> Self {
810        ConfigStatus::Saving
811    }
812
813    fn write_error(e: ConfigStatusError) -> Self {
814        ConfigStatus::SaveErrors(e)
815    }
816}
817impl WatcherReadStatus<ConfigStatusError> for ConfigStatus {
818    fn idle() -> Self {
819        ConfigStatus::Loaded
820    }
821
822    fn reading() -> Self {
823        ConfigStatus::Loading
824    }
825
826    fn read_error(e: ConfigStatusError) -> Self {
827        ConfigStatus::LoadErrors(e)
828    }
829}
830type ConfigStatusError = Vec<Arc<dyn std::error::Error + Send + Sync>>;