Skip to main content

ed_journals/modules/fs/models/common_async/
async_changed_json_file.rs

1use crate::fs::common::JsonFile;
2use crate::fs::{LogFSError, Unblocker};
3use serde::de::DeserializeOwned;
4use std::path::Path;
5use std::sync::Arc;
6use twox_hash::XxHash64;
7
8/// Async variant of [ChangedJsonFile](crate::fs::common::ChangedJsonFile).
9pub struct AsyncChangedJsonFile<R>
10where
11    R: DeserializeOwned,
12{
13    inner: JsonFile<R>,
14    last_hash: Option<u64>,
15}
16
17impl<R> AsyncChangedJsonFile<R>
18where
19    R: DeserializeOwned + PartialEq,
20{
21    pub fn new<P: AsRef<Path>>(
22        path: P,
23        unblocker: impl Into<Arc<dyn Unblocker>>,
24    ) -> Result<AsyncChangedJsonFile<R>, LogFSError> {
25        Ok(AsyncChangedJsonFile {
26            inner: JsonFile::new(path, unblocker)?,
27            last_hash: None,
28        })
29    }
30
31    /// Returns the current contents of the file as a deserialized object, or [None] if the contents
32    /// haven't changed since the last read or if the file is empty (which happens when the game
33    /// clears the file before it starts to write.)
34    pub async fn content(&mut self) -> Result<Option<R>, LogFSError> {
35        let bytes = self.inner.byte_content_async().await?;
36        if bytes.is_empty() {
37            return Ok(None);
38        }
39
40        let hash = XxHash64::oneshot(0, &bytes);
41
42        if self.last_hash.is_some_and(|v| v == hash) {
43            return Ok(None);
44        }
45
46        self.last_hash = Some(hash);
47        Ok(Some(serde_json::from_slice(&bytes)?))
48    }
49}