walker_common/
since.rs

1//! Handling of detecting changes "since"
2use std::fs::File;
3use std::io::{BufReader, BufWriter, ErrorKind, Read, Write};
4use std::ops::Deref;
5use std::path::{Path, PathBuf};
6use std::time::{Duration, SystemTime};
7
8#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
9pub struct SinceState {
10    pub last_run: SystemTime,
11}
12
13impl SinceState {
14    /// Load the since state
15    pub fn load<R>(reader: R) -> anyhow::Result<Self>
16    where
17        R: Read,
18    {
19        Ok(serde_json::from_reader(reader)?)
20    }
21
22    /// Load the since state from a file, returning [`None`] if the file doesn't exist.
23    pub fn load_from<F>(file: F) -> anyhow::Result<Option<Self>>
24    where
25        F: AsRef<Path>,
26    {
27        match File::open(file) {
28            Ok(file) => Self::load(BufReader::new(file)).map(Option::Some),
29            Err(err) if err.kind() == ErrorKind::NotFound => Ok(None),
30            Err(err) => Err(err.into()),
31        }
32    }
33
34    /// Store the since state.
35    pub fn store<W>(&self, writer: W) -> anyhow::Result<()>
36    where
37        W: Write,
38    {
39        Ok(serde_json::to_writer(writer, &self)?)
40    }
41}
42
43/// Load and record since state
44pub struct Since {
45    pub since: Option<SystemTime>,
46    pub last_run: SystemTime,
47    pub since_file: Option<PathBuf>,
48}
49
50impl Deref for Since {
51    type Target = Option<SystemTime>;
52
53    fn deref(&self) -> &Self::Target {
54        &self.since
55    }
56}
57
58impl Since {
59    pub fn new(
60        since: Option<impl Into<SystemTime>>,
61        since_file: Option<PathBuf>,
62        since_file_offset: Duration,
63    ) -> anyhow::Result<Self> {
64        let since = match (since, &since_file) {
65            // try file, then fall back to dedicated "since"
66            (skip, Some(file)) => match SinceState::load_from(file)? {
67                Some(since) => {
68                    let result = since.last_run + since_file_offset;
69                    log::info!(
70                        "Since state from file - last run: {}, offset: {} = {}",
71                        humantime::Timestamp::from(since.last_run),
72                        humantime::Duration::from(since_file_offset),
73                        humantime::Timestamp::from(result),
74                    );
75                    Some(result)
76                }
77                None => skip.map(|s| s.into()),
78            },
79            // dedicated "since" value
80            (Some(skip), None) => {
81                let since = skip.into();
82                log::info!("Using provided since {}", humantime::Timestamp::from(since));
83                Some(since)
84            }
85            // no "since" at all
86            (None, None) => None,
87        };
88
89        let last_run = SystemTime::now();
90
91        Ok(Since {
92            since,
93            last_run,
94            since_file,
95        })
96    }
97
98    pub fn store(self) -> anyhow::Result<()> {
99        if let Some(path) = &self.since_file {
100            log::info!(
101                "Storing last_run = {}",
102                humantime::Timestamp::from(self.last_run)
103            );
104            SinceState {
105                last_run: self.last_run,
106            }
107            .store(BufWriter::new(File::create(path)?))?;
108        }
109        Ok(())
110    }
111}