summaryrefslogtreecommitdiffstats
path: root/src/files.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-25 19:59:14 +0100
committertv <tv@krebsco.de>2026-10-25 19:59:14 +0100
commit72472131a4bb3798759884da597cfb4ef7793782 (patch)
tree5f094dfda00da31e1b564cc033ef5f1d24ba89df /src/files.rs
parent866924934d635df8a8f222a022f806c44581a5f5 (diff)
allow to persist document
Diffstat (limited to 'src/files.rs')
-rw-r--r--src/files.rs41
1 files changed, 36 insertions, 5 deletions
diff --git a/src/files.rs b/src/files.rs
index 81c8b1e..7bf9117 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -1,9 +1,12 @@
//! The files this node reads and writes for itself, and how much it did (`/admin/stats`,
-//! `disk`): the state file (`swarm::state_file`) and a backup to recover from
-//! (`swarm::bootstrap`). Not the Mycelium daemon's files, configuration or logs.
+//! `disk`): the state file (`swarm::state_file`), the document file
+//! (`document::file`) and a backup to recover from (`swarm::bootstrap`). Not the
+//! Mycelium daemon's files, configuration or logs.
//!
//! Writes replace a file atomically: a temporary file next to it, flushed to disk, then
-//! renamed over it, so a crash leaves the old content or the new one.
+//! renamed over it, so a crash leaves the old content or the new one. Appends add to the
+//! end and are flushed to disk before they count as done; a crash may leave the last one
+//! cut short, which the file's reader has to expect.
use std::io::Write;
use std::path::{Path, PathBuf};
@@ -13,15 +16,17 @@ use std::sync::{Mutex, MutexGuard};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum FileKind {
State,
+ Document,
Backup,
}
impl FileKind {
- pub(crate) const ALL: [FileKind; 2] = [FileKind::State, FileKind::Backup];
+ pub(crate) const ALL: [FileKind; 3] = [FileKind::State, FileKind::Document, FileKind::Backup];
pub(crate) fn as_str(self) -> &'static str {
match self {
FileKind::State => "state",
+ FileKind::Document => "document",
FileKind::Backup => "backup",
}
}
@@ -36,9 +41,12 @@ impl FileKind {
pub(crate) struct Counts {
pub(crate) reads: u64,
pub(crate) read_bytes: u64,
+ /// Whole files written.
pub(crate) writes: u64,
pub(crate) written_bytes: u64,
- /// Reads or writes that failed (a missing file read is no failure).
+ pub(crate) appends: u64,
+ pub(crate) appended_bytes: u64,
+ /// Reads, writes or appends that failed (a missing file read is no failure).
pub(crate) errors: u64,
}
@@ -96,6 +104,23 @@ impl Files {
result
}
+ /// Adds `bytes` to the end of the file (which must exist), flushed to disk.
+ pub(crate) fn append(&self, kind: FileKind, path: &Path, bytes: &[u8]) -> std::io::Result<()> {
+ let result = (|| {
+ let mut file = std::fs::OpenOptions::new().append(true).open(path)?;
+ file.write_all(bytes)?;
+ file.sync_data()
+ })();
+ self.count(kind, |counts| match &result {
+ Ok(()) => {
+ counts.appends += 1;
+ counts.appended_bytes += bytes.len() as u64;
+ }
+ Err(_) => counts.errors += 1,
+ });
+ result
+ }
+
/// Moves an unusable file aside (`<path><suffix>`), returning where to.
pub(crate) fn move_aside(&self, path: &Path, suffix: &str) -> std::io::Result<PathBuf> {
let aside = with_suffix(path, suffix);
@@ -167,6 +192,12 @@ mod tests {
assert_eq!(kind, FileKind::State);
assert_eq!((state.writes, state.written_bytes), (2, 8));
assert_eq!((state.reads, state.read_bytes, state.errors), (1, 5, 0));
+ files.append(FileKind::State, &path, b"!").unwrap();
+ assert_eq!(
+ files.read(FileKind::State, &path).unwrap().as_deref(),
+ Some(&b"three!"[..])
+ );
+ assert_eq!(files.counts()[0].1.appended_bytes, 1);
assert_eq!(files.counts()[1].1.reads, 0);
std::fs::remove_dir_all(&dir).unwrap();