Skip to main content

json_gettext/common/rocket_feature/reloadable/
json_get_text_manager.rs

1use std::{
2    collections::HashMap,
3    mem,
4    ops::Deref,
5    path::{Path, PathBuf},
6    sync::atomic::{AtomicBool, Ordering},
7    time::SystemTime,
8};
9
10use rocket::fairing::Fairing;
11
12use crate::{
13    DebuggableMutate, JSONGetText, JSONGetTextBuildError, JSONGetTextBuilder, JSONGetTextFairing,
14};
15
16#[derive(Debug)]
17pub struct JSONGetTextManager {
18    empty:        JSONGetText<'static>,
19    json_gettext: DebuggableMutate<JSONGetText<'static>>,
20    files:        DebuggableMutate<HashMap<crate::Key, (PathBuf, Option<SystemTime>)>>,
21    reloading:    AtomicBool,
22}
23
24impl JSONGetTextManager {
25    pub fn from_files(
26        default_key: crate::Key,
27        source: Vec<(crate::Key, &'static str)>,
28    ) -> Result<JSONGetTextManager, JSONGetTextBuildError> {
29        let mut files = HashMap::with_capacity(source.len());
30
31        for (key, json_file) in source {
32            let metadata = Path::new(json_file).metadata()?;
33
34            let mtime = metadata.modified().ok();
35
36            files.insert(key, (PathBuf::from(json_file), mtime));
37        }
38
39        let mut builder = JSONGetTextBuilder::new(default_key);
40
41        for (key, (path, _)) in &files {
42            builder.add_json_file(key, path)?;
43        }
44
45        let json_gettext = builder.build()?;
46
47        // Reuse the built default key so the empty context does not need another copy of it.
48        let mut empty_builder = JSONGetTextBuilder::new(json_gettext.get_default_key());
49
50        empty_builder.add_map(json_gettext.get_default_key(), HashMap::new())?;
51
52        Ok(JSONGetTextManager {
53            empty:        empty_builder.build()?,
54            json_gettext: DebuggableMutate::new(json_gettext),
55            files:        DebuggableMutate::new(files),
56            reloading:    AtomicBool::new(false),
57        })
58    }
59
60    pub fn reload_if_needed(&self) -> Result<(), JSONGetTextBuildError> {
61        if self
62            .reloading
63            .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
64            .is_ok()
65        {
66            let mut do_reload = false;
67
68            let files = self.files.get_mut();
69
70            for (path, mtime) in files.values_mut() {
71                let metadata = path.metadata().inspect_err(|_| {
72                    self.reloading.store(false, Ordering::Relaxed);
73                })?;
74
75                let (reload, new_mtime) = match mtime {
76                    Some(mtime) => match metadata.modified() {
77                        Ok(new_mtime) => (new_mtime > *mtime, Some(new_mtime)),
78                        Err(_) => (true, None),
79                    },
80                    None => match metadata.modified() {
81                        Ok(new_mtime) => (true, Some(new_mtime)),
82                        Err(_) => (true, None),
83                    },
84                };
85
86                if reload {
87                    *mtime = new_mtime;
88
89                    do_reload = true;
90                }
91            }
92
93            if do_reload {
94                let mut builder = JSONGetTextBuilder::new(self.get_default_key());
95
96                for (key, (path, _)) in files.iter() {
97                    builder.add_json_file(key, path).inspect_err(|_| {
98                        self.reloading.store(false, Ordering::Relaxed);
99                    })?;
100                }
101
102                let json_gettext = builder.build().inspect_err(|_| {
103                    self.reloading.store(false, Ordering::Relaxed);
104                })?;
105
106                drop(mem::replace(self.json_gettext.get_mut(), json_gettext));
107            }
108
109            self.reloading.store(false, Ordering::Relaxed);
110        }
111
112        Ok(())
113    }
114}
115
116impl JSONGetTextManager {
117    /// Create the fairing of `JSONGetTextManager`.
118    pub fn fairing<F>(f: F) -> impl Fairing
119    where
120        F: Fn() -> (crate::Key, Vec<(crate::Key, &'static str)>) + Send + Sync + 'static, {
121        JSONGetTextFairing {
122            custom_callback: Box::new(f)
123        }
124    }
125}
126
127impl Deref for JSONGetTextManager {
128    type Target = JSONGetText<'static>;
129
130    #[inline]
131    fn deref(&self) -> &JSONGetText<'static> {
132        // NOTICE: This is only sound enough for debug-time hot-reloading, not a production concurrency guarantee. A reader here can still race with the reloader; the empty context is returned while reloading to reduce the window.
133        if self.reloading.load(Ordering::Relaxed) { &self.empty } else { self.json_gettext.get() }
134    }
135}