Skip to main content

ed_journals/modules/fs/models/common/
changed_json_file.rs

1use crate::fs::common::json_file::JsonFile;
2use crate::fs::{LogFSError, Unblocker};
3use serde::de::DeserializeOwned;
4use std::path::Path;
5use std::sync::Arc;
6use twox_hash::XxHash64;
7
8/// A wrapper around [JsonFile] which can be used to read the contents of a file and only return
9/// the contents if they have changed since the last read.
10pub struct ChangedJsonFile<R>
11where
12    R: DeserializeOwned,
13{
14    inner: JsonFile<R>,
15    last_hash: Option<u64>,
16}
17
18impl<R> ChangedJsonFile<R>
19where
20    R: DeserializeOwned + PartialEq,
21{
22    /// Opens the file at the provided path and returns a [ChangedJsonFile] which can be used to
23    /// read the contents of the file.
24    pub fn new<P: AsRef<Path>>(
25        path: P,
26        unblocker: impl Into<Arc<dyn Unblocker>>,
27    ) -> Result<ChangedJsonFile<R>, LogFSError> {
28        Ok(ChangedJsonFile {
29            inner: JsonFile::new(path, unblocker)?,
30            last_hash: None,
31        })
32    }
33
34    /// Returns the current contents of the file as a deserialized object, or [None] if the contents
35    /// haven't changed since the last read or if the file is empty (which happens when the game
36    /// clears the file before it starts to write.)
37    pub fn content(&mut self) -> Result<Option<R>, LogFSError> {
38        let bytes = self.inner.byte_content()?;
39        if bytes.is_empty() {
40            return Ok(None);
41        }
42
43        let hash = XxHash64::oneshot(0, &bytes);
44
45        if self.last_hash.is_some_and(|v| v == hash) {
46            return Ok(None);
47        }
48
49        self.last_hash = Some(hash);
50        Ok(Some(serde_json::from_slice(&bytes)?))
51    }
52
53    /// The same as [ChangedJsonFile::content], but asynchronous.
54    #[cfg(feature = "asynchronous")]
55    pub async fn content_async(&mut self) -> Result<Option<R>, LogFSError> {
56        let bytes = self.inner.byte_content_async().await?;
57        if bytes.is_empty() {
58            return Ok(None);
59        }
60
61        let hash = XxHash64::oneshot(0, &bytes);
62
63        if self.last_hash.is_some_and(|v| v == hash) {
64            return Ok(None);
65        }
66
67        self.last_hash = Some(hash);
68        Ok(Some(serde_json::from_slice(&bytes)?))
69    }
70}