kumo_jsonl/
checkpoint.rs

1use camino::Utf8PathBuf;
2use serde::{Deserialize, Serialize};
3use std::io::Write;
4
5/// Persisted checkpoint data recording the current file and line position.
6#[derive(Deserialize, Serialize, Debug, Clone)]
7pub struct CheckpointData {
8    pub file: String,
9    pub line: usize,
10}
11
12impl CheckpointData {
13    /// Load a checkpoint from the given path.
14    /// Returns `Ok(None)` if the file does not exist.
15    pub async fn load(path: &Utf8PathBuf) -> anyhow::Result<Option<Self>> {
16        match tokio::fs::read(path.as_std_path()).await {
17            Ok(bytes) => {
18                let data: Self = serde_json::from_slice(&bytes)?;
19                Ok(Some(data))
20            }
21            Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
22            Err(err) => Err(err.into()),
23        }
24    }
25
26    /// Atomically write a checkpoint file by writing to a temporary
27    /// file in the same directory and then renaming it into place.
28    pub fn save_atomic(
29        checkpoint_path: &Utf8PathBuf,
30        file: &Utf8PathBuf,
31        line: usize,
32    ) -> anyhow::Result<()> {
33        let data = Self {
34            file: file.to_string(),
35            line,
36        };
37        let json = serde_json::to_string(&data)?;
38        let dir = checkpoint_path
39            .parent()
40            .map(|p| p.as_std_path())
41            .unwrap_or_else(|| std::path::Path::new("."));
42        let prefix = checkpoint_path.file_name().unwrap_or(".checkpoint");
43        let mut tmp = tempfile::Builder::new().prefix(prefix).tempfile_in(dir)?;
44        tmp.write_all(json.as_bytes())?;
45        tmp.persist(checkpoint_path.as_std_path())?;
46        Ok(())
47    }
48}