winbrew_database\journal/
writer.rs1use 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}