| // TODO: tests |
| use std::ops::Deref; |
| |
| use gix_features::threading::{get_mut, get_ref, MutableOnDemand, OwnShared}; |
| |
| /// A structure holding enough information to reload a value if its on-disk representation changes as determined by its modified time. |
| #[derive(Debug)] |
| pub struct FileSnapshot<T: std::fmt::Debug> { |
| value: T, |
| modified: std::time::SystemTime, |
| } |
| |
| /// Lifecycle |
| impl<T: std::fmt::Debug> FileSnapshot<T> { |
| /// A way for users to create 'fake' snapshot from `value` that isn't actually linked to a file on disk. |
| /// |
| /// This is useful if there are alternative ways of obtaining the contained instance as fallback to trying |
| /// to read it from disk. |
| pub fn new(value: T) -> Self { |
| FileSnapshot { |
| value, |
| modified: std::time::UNIX_EPOCH, |
| } |
| } |
| } |
| |
| impl<T: std::fmt::Debug> From<T> for FileSnapshot<T> { |
| fn from(value: T) -> Self { |
| FileSnapshot::new(value) |
| } |
| } |
| |
| impl<T: Clone + std::fmt::Debug> Clone for FileSnapshot<T> { |
| fn clone(&self) -> Self { |
| Self { |
| value: self.value.clone(), |
| modified: self.modified, |
| } |
| } |
| } |
| |
| /// A snapshot of a resource which is up-to-date in the moment it is retrieved. |
| pub type SharedFileSnapshot<T> = OwnShared<FileSnapshot<T>>; |
| |
| /// Use this type for fields in structs that are to store the [`FileSnapshot`], typically behind an [`OwnShared`]. |
| /// |
| /// Note that the resource itself is behind another [`OwnShared`] to allow it to be used without holding any kind of lock, hence |
| /// without blocking updates while it is used. |
| #[derive(Debug, Default)] |
| pub struct SharedFileSnapshotMut<T: std::fmt::Debug>(pub MutableOnDemand<Option<SharedFileSnapshot<T>>>); |
| |
| impl<T: std::fmt::Debug> Deref for FileSnapshot<T> { |
| type Target = T; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.value |
| } |
| } |
| |
| impl<T: std::fmt::Debug> std::ops::DerefMut for FileSnapshot<T> { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut self.value |
| } |
| } |
| |
| impl<T: std::fmt::Debug> Deref for SharedFileSnapshotMut<T> { |
| type Target = MutableOnDemand<Option<SharedFileSnapshot<T>>>; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.0 |
| } |
| } |
| |
| impl<T: std::fmt::Debug> SharedFileSnapshotMut<T> { |
| /// Create a new instance of this type. |
| /// |
| /// Useful in case `Default::default()` isn't working for some reason. |
| pub fn new() -> Self { |
| SharedFileSnapshotMut(MutableOnDemand::new(None)) |
| } |
| |
| /// Refresh `state` forcefully by re-`open`ing the resource. Note that `open()` returns `None` if the resource isn't |
| /// present on disk, and that it's critical that the modified time is obtained _before_ opening the resource. |
| pub fn force_refresh<E>( |
| &self, |
| open: impl FnOnce() -> Result<Option<(std::time::SystemTime, T)>, E>, |
| ) -> Result<(), E> { |
| let mut state = get_mut(&self.0); |
| *state = open()?.map(|(modified, value)| OwnShared::new(FileSnapshot { value, modified })); |
| Ok(()) |
| } |
| |
| /// Assure that the resource in `state` is up-to-date by comparing the `current_modification_time` with the one we know in `state` |
| /// and by acting accordingly. |
| /// Returns the potentially updated/reloaded resource if it is still present on disk, which then represents a snapshot that is up-to-date |
| /// in that very moment, or `None` if the underlying file doesn't exist. |
| /// |
| /// Note that even though this is racy, each time a request is made there is a chance to see the actual state. |
| pub fn recent_snapshot<E>( |
| &self, |
| mut current_modification_time: impl FnMut() -> Option<std::time::SystemTime>, |
| open: impl FnOnce() -> Result<Option<T>, E>, |
| ) -> Result<Option<SharedFileSnapshot<T>>, E> { |
| let state = get_ref(self); |
| let recent_modification = current_modification_time(); |
| let buffer = match (&*state, recent_modification) { |
| (None, None) => (*state).clone(), |
| (Some(_), None) => { |
| drop(state); |
| let mut state = get_mut(self); |
| *state = None; |
| (*state).clone() |
| } |
| (Some(snapshot), Some(modified_time)) => { |
| if snapshot.modified < modified_time { |
| drop(state); |
| let mut state = get_mut(self); |
| |
| if let (Some(_snapshot), Some(modified_time)) = (&*state, current_modification_time()) { |
| *state = open()?.map(|value| { |
| OwnShared::new(FileSnapshot { |
| value, |
| modified: modified_time, |
| }) |
| }); |
| } |
| |
| (*state).clone() |
| } else { |
| // Note that this relies on sub-section precision or else is a race when the packed file was just changed. |
| // It's nothing we can know though, so… up to the caller unfortunately. |
| Some(snapshot.clone()) |
| } |
| } |
| (None, Some(_modified_time)) => { |
| drop(state); |
| let mut state = get_mut(self); |
| // Still in the same situation? If so, load the buffer. This compensates for the trampling herd |
| // during lazy-loading at the expense of another mtime check. |
| if let (None, Some(modified_time)) = (&*state, current_modification_time()) { |
| *state = open()?.map(|value| { |
| OwnShared::new(FileSnapshot { |
| value, |
| modified: modified_time, |
| }) |
| }); |
| } |
| (*state).clone() |
| } |
| }; |
| Ok(buffer) |
| } |
| } |