gix_fs/
snapshot.rs

1// TODO: tests
2use gix_features::threading::{get_mut, get_ref, MutableOnDemand, OwnShared};
3use std::ops::Deref;
4
5/// A structure holding enough information to reload a value if its on-disk representation changes as determined by its modified time.
6#[derive(Debug)]
7pub struct FileSnapshot<T: std::fmt::Debug> {
8    value: T,
9    modified: std::time::SystemTime,
10}
11
12/// Lifecycle
13impl<T: std::fmt::Debug> FileSnapshot<T> {
14    /// A way for users to create 'fake' snapshot from `value` that isn't actually linked to a file on disk.
15    ///
16    /// This is useful if there are alternative ways of obtaining the contained instance as fallback to trying
17    /// to read it from disk.
18    pub fn new(value: T) -> Self {
19        FileSnapshot {
20            value,
21            modified: std::time::UNIX_EPOCH,
22        }
23    }
24}
25
26impl<T: std::fmt::Debug> From<T> for FileSnapshot<T> {
27    fn from(value: T) -> Self {
28        FileSnapshot::new(value)
29    }
30}
31
32impl<T: Clone + std::fmt::Debug> Clone for FileSnapshot<T> {
33    fn clone(&self) -> Self {
34        Self {
35            value: self.value.clone(),
36            modified: self.modified,
37        }
38    }
39}
40
41impl<T: Clone + std::fmt::Debug> FileSnapshot<T> {
42    /// Return the contained instance if nobody else is holding it, or clone it otherwise.
43    pub fn into_owned_or_cloned(self: OwnShared<Self>) -> T {
44        match OwnShared::try_unwrap(self) {
45            Ok(this) => this.value,
46            Err(this) => this.value.clone(),
47        }
48    }
49}
50
51/// A snapshot of a resource which is up-to-date in the moment it is retrieved.
52pub type SharedFileSnapshot<T> = OwnShared<FileSnapshot<T>>;
53
54/// Use this type for fields in structs that are to store the [`FileSnapshot`], typically behind an [`OwnShared`].
55///
56/// Note that the resource itself is behind another [`OwnShared`] to allow it to be used without holding any kind of lock, hence
57/// without blocking updates while it is used.
58#[derive(Debug, Default)]
59pub struct SharedFileSnapshotMut<T: std::fmt::Debug>(pub MutableOnDemand<Option<SharedFileSnapshot<T>>>);
60
61impl<T: std::fmt::Debug> Deref for FileSnapshot<T> {
62    type Target = T;
63
64    fn deref(&self) -> &Self::Target {
65        &self.value
66    }
67}
68
69impl<T: std::fmt::Debug> std::ops::DerefMut for FileSnapshot<T> {
70    fn deref_mut(&mut self) -> &mut Self::Target {
71        &mut self.value
72    }
73}
74
75impl<T: std::fmt::Debug> Deref for SharedFileSnapshotMut<T> {
76    type Target = MutableOnDemand<Option<SharedFileSnapshot<T>>>;
77
78    fn deref(&self) -> &Self::Target {
79        &self.0
80    }
81}
82
83impl<T: std::fmt::Debug> SharedFileSnapshotMut<T> {
84    /// Create a new instance of this type.
85    ///
86    /// Useful in case `Default::default()` isn't working for some reason.
87    pub fn new() -> Self {
88        SharedFileSnapshotMut(MutableOnDemand::new(None))
89    }
90
91    /// Refresh `state` forcefully by re-`open`ing the resource. Note that `open()` returns `None` if the resource isn't
92    /// present on disk, and that it's critical that the modified time is obtained _before_ opening the resource.
93    pub fn force_refresh<E>(
94        &self,
95        open: impl FnOnce() -> Result<Option<(std::time::SystemTime, T)>, E>,
96    ) -> Result<(), E> {
97        let mut state = get_mut(&self.0);
98        *state = open()?.map(|(modified, value)| OwnShared::new(FileSnapshot { value, modified }));
99        Ok(())
100    }
101
102    /// Assure that the resource in `state` is up-to-date by comparing the `current_modification_time` with the one we know in `state`
103    /// and by acting accordingly.
104    /// Returns the potentially updated/reloaded resource if it is still present on disk, which then represents a snapshot that is up-to-date
105    /// in that very moment, or `None` if the underlying file doesn't exist.
106    ///
107    /// Note that even though this is racy, each time a request is made there is a chance to see the actual state.
108    pub fn recent_snapshot<E>(
109        &self,
110        mut current_modification_time: impl FnMut() -> Option<std::time::SystemTime>,
111        open: impl FnOnce() -> Result<Option<T>, E>,
112    ) -> Result<Option<SharedFileSnapshot<T>>, E> {
113        let state = get_ref(self);
114        let recent_modification = current_modification_time();
115        let buffer = match (&*state, recent_modification) {
116            (None, None) => (*state).clone(),
117            (Some(_), None) => {
118                drop(state);
119                let mut state = get_mut(self);
120                *state = None;
121                (*state).clone()
122            }
123            (Some(snapshot), Some(modified_time)) => {
124                if snapshot.modified < modified_time {
125                    drop(state);
126                    let mut state = get_mut(self);
127
128                    if let (Some(_snapshot), Some(modified_time)) = (&*state, current_modification_time()) {
129                        *state = open()?.map(|value| {
130                            OwnShared::new(FileSnapshot {
131                                value,
132                                modified: modified_time,
133                            })
134                        });
135                    }
136
137                    (*state).clone()
138                } else {
139                    // Note that this relies on sub-section precision or else is a race when the packed file was just changed.
140                    // It's nothing we can know though, so… up to the caller unfortunately.
141                    Some(snapshot.clone())
142                }
143            }
144            (None, Some(_modified_time)) => {
145                drop(state);
146                let mut state = get_mut(self);
147                // Still in the same situation? If so, load the buffer. This compensates for the trampling herd
148                // during lazy-loading at the expense of another mtime check.
149                if let (None, Some(modified_time)) = (&*state, current_modification_time()) {
150                    *state = open()?.map(|value| {
151                        OwnShared::new(FileSnapshot {
152                            value,
153                            modified: modified_time,
154                        })
155                    });
156                }
157                (*state).clone()
158            }
159        };
160        Ok(buffer)
161    }
162}