zng_ext_l10n/sources/
swap.rs

1use std::{collections::HashMap, path::PathBuf, sync::Arc};
2
3use zng_var::{ArcEq, ArcVar, BoxedVar, BoxedWeakVar, Var as _, VarHandle, WeakVar as _, types::WeakArcVar, var};
4
5use crate::{L10nSource, Lang, LangFilePath, LangMap, LangResourceStatus};
6
7use super::NilL10nSource;
8
9/// Represents localization source that can swap the actual source without disconnecting variables
10/// taken on resources.
11///
12/// Note that [`L10N.load`] already uses this source internally.
13///
14/// [`L10N.load`]: crate::L10N::load
15pub struct SwapL10nSource {
16    actual: Box<dyn L10nSource>,
17
18    available_langs: ArcVar<Arc<LangMap<HashMap<LangFilePath, PathBuf>>>>,
19    available_langs_status: ArcVar<LangResourceStatus>,
20
21    res: HashMap<(Lang, LangFilePath), SwapFile>,
22}
23impl SwapL10nSource {
24    /// New with [`NilL10nSource`].
25    pub fn new() -> Self {
26        Self {
27            actual: Box::new(NilL10nSource),
28            available_langs: var(Arc::default()),
29            available_langs_status: var(LangResourceStatus::NotAvailable),
30            res: HashMap::new(),
31        }
32    }
33
34    /// Swaps the backend source with `source`.
35    pub fn load(&mut self, source: impl L10nSource) {
36        self.swap_source(Box::new(source))
37    }
38    fn swap_source(&mut self, new: Box<dyn L10nSource>) {
39        self.actual = new;
40
41        let actual_langs = self.actual.available_langs();
42        self.available_langs.set_from(&actual_langs);
43        actual_langs.bind(&self.available_langs).perm();
44
45        let actual_status = self.actual.available_langs_status();
46        self.available_langs_status.set_from(&actual_status);
47        actual_status.bind(&self.available_langs_status).perm();
48
49        for ((lang, file), f) in &mut self.res {
50            if let Some(res) = f.res.upgrade() {
51                let actual_f = self.actual.lang_resource(lang.clone(), file.clone());
52                f.actual_weak_res = actual_f.bind(&res); // weak ref to `res` is held by `actual_f`
53                f.res_strong_actual = res.hold(actual_f); // strong ref to `actual_f` is held by `res`.
54
55                let actual_s = self.actual.lang_resource_status(lang.clone(), file.clone());
56                f.status.set_from(&actual_s);
57                f.actual_weak_status = actual_s.bind(&f.status);
58            } else {
59                f.status.set(LangResourceStatus::NotAvailable);
60            }
61        }
62    }
63}
64impl Default for SwapL10nSource {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69impl L10nSource for SwapL10nSource {
70    fn available_langs(&mut self) -> BoxedVar<Arc<LangMap<HashMap<LangFilePath, PathBuf>>>> {
71        self.available_langs.read_only().boxed()
72    }
73
74    fn available_langs_status(&mut self) -> BoxedVar<LangResourceStatus> {
75        self.available_langs_status.read_only().boxed()
76    }
77
78    fn lang_resource(&mut self, lang: Lang, file: LangFilePath) -> BoxedVar<Option<ArcEq<fluent::FluentResource>>> {
79        match self.res.entry((lang, file)) {
80            std::collections::hash_map::Entry::Occupied(mut e) => {
81                if let Some(res) = e.get().res.upgrade() {
82                    res
83                } else {
84                    let (lang, file) = e.key();
85                    let actual_f = self.actual.lang_resource(lang.clone(), file.clone());
86                    let actual_s = self.actual.lang_resource_status(lang.clone(), file.clone());
87
88                    let f = e.get_mut();
89
90                    let res = var(actual_f.get());
91                    f.actual_weak_res = actual_f.bind(&res); // weak ref to `res` is held by `actual_f`
92                    f.res_strong_actual = res.hold(actual_f); // strong ref to `actual_f` is held by `res`.
93                    let res = res.boxed();
94                    f.res = res.downgrade();
95
96                    f.status.set_from(&actual_s);
97                    f.actual_weak_status = actual_s.bind(&f.status);
98
99                    res
100                }
101            }
102            std::collections::hash_map::Entry::Vacant(e) => {
103                let mut f = SwapFile::new();
104                let (lang, file) = e.key();
105                let actual_f = self.actual.lang_resource(lang.clone(), file.clone());
106                let actual_s = self.actual.lang_resource_status(lang.clone(), file.clone());
107
108                let res = var(actual_f.get());
109                f.actual_weak_res = actual_f.bind(&res); // weak ref to `res` is held by `actual_f`
110                f.res_strong_actual = res.hold(actual_f); // strong ref to `actual_f` is held by `res`.
111                let res = res.boxed();
112                f.res = res.downgrade();
113
114                f.status.set_from(&actual_s);
115                f.actual_weak_status = actual_s.bind(&f.status);
116
117                e.insert(f);
118
119                res
120            }
121        }
122    }
123
124    fn lang_resource_status(&mut self, lang: Lang, file: LangFilePath) -> BoxedVar<LangResourceStatus> {
125        self.res
126            .entry((lang, file))
127            .or_insert_with(SwapFile::new)
128            .status
129            .read_only()
130            .boxed()
131    }
132}
133struct SwapFile {
134    res: BoxedWeakVar<Option<ArcEq<fluent::FluentResource>>>,
135    status: ArcVar<LangResourceStatus>,
136    actual_weak_res: VarHandle,
137    res_strong_actual: VarHandle,
138    actual_weak_status: VarHandle,
139}
140impl SwapFile {
141    fn new() -> Self {
142        Self {
143            res: WeakArcVar::default().boxed(),
144            status: var(LangResourceStatus::Loading),
145            actual_weak_res: VarHandle::dummy(),
146            res_strong_actual: VarHandle::dummy(),
147            actual_weak_status: VarHandle::dummy(),
148        }
149    }
150}