winbrew_database\journal/
writer.rs

1use anyhow::{Context, Result};
2use std::fs::{File, OpenOptions};
3use std::io::{BufWriter, Write};
4use std::path::{Path, PathBuf};
5
6use super::{JournalEntry, JournalReadError, JournalReader};
7use crate::core::ResolvedPaths;
8
9#[derive(Debug)]
10pub struct JournalWriter {
11    path: PathBuf,
12    writer: BufWriter<File>,
13}
14
15impl JournalWriter {
16    pub fn open_for_package(root: &Path, package_id: &str, version: &str) -> Result<Self> {
17        let package_key = crate::journal::package_journal_key(package_id, version);
18        let journal_path = crate::core::package_journal_file_at(root, &package_key);
19
20        Self::open_at(journal_path)
21    }
22
23    pub fn open_for_package_in(
24        paths: &ResolvedPaths,
25        package_id: &str,
26        version: &str,
27    ) -> Result<Self> {
28        let package_key = crate::journal::package_journal_key(package_id, version);
29        let journal_path = paths.package_journal_file(&package_key);
30
31        Self::open_at(journal_path)
32    }
33
34    fn open_at(journal_path: PathBuf) -> Result<Self> {
35        if journal_path.exists() {
36            match JournalReader::read_committed(&journal_path) {
37                Ok(_) => {
38                    anyhow::bail!(
39                        "journal at {} is already committed — use a new version or remove it first",
40                        journal_path.display()
41                    );
42                }
43                Err(JournalReadError::Incomplete { .. }) => {}
44                Err(JournalReadError::Read { .. }) => {}
45                Err(_) => {
46                    anyhow::bail!(
47                        "journal at {} is in an unexpected state",
48                        journal_path.display()
49                    );
50                }
51            }
52        }
53
54        let file = OpenOptions::new()
55            .create(true)
56            .append(true)
57            .open(&journal_path)
58            .with_context(|| format!("failed to open {}", journal_path.display()))?;
59
60        Ok(Self {
61            path: journal_path,
62            writer: BufWriter::new(file),
63        })
64    }
65
66    pub fn append(&mut self, entry: &JournalEntry) -> Result<()> {
67        serde_json::to_writer(&mut self.writer, entry)
68            .context("failed to serialize journal entry")?;
69        self.writer
70            .write_all(b"\n")
71            .context("failed to write journal entry delimiter")?;
72
73        Ok(())
74    }
75
76    pub fn flush(&mut self) -> Result<()> {
77        self.writer
78            .flush()
79            .context("failed to flush journal writer")
80    }
81
82    pub fn path(&self) -> &Path {
83        &self.path
84    }
85}
86
87impl Drop for JournalWriter {
88    fn drop(&mut self) {
89        let _ = self.writer.flush();
90    }
91}