refactor: extract write_json_atomic helper, DRY 8 call sites
Move the crash-safe write-then-rename pattern into zesdex-utils::write_json_atomic and apply it across: - zesdex-cms: app_config_repo, conversation_repo, settings_repo - zesdex-iam: oauth_repo, session_repo - zesdex-entities: Conversation::save_conversation, Session::save Excluded (non-JSON format): - rewind_blob_repo (binary blob) - memory_repo (markdown + frontmatter, not JSON) - session_lock (PID string, not JSON) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9a6ab62562
commit
4cd38c9291
@@ -0,0 +1,46 @@
|
||||
//! Crash-safe atomic file write helper.
|
||||
//!
|
||||
//! Writes serializable data to a temp file, fsyncs, then renames into
|
||||
//! place to guarantee atomicity. On Unix, an optional `mode` sets the
|
||||
//! permissions of the final file (e.g. `0o600` for OAuth tokens).
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// Atomically write serializable `data` to `path`.
|
||||
///
|
||||
/// Flow: serialize to pretty JSON -> write to `path.tmp` -> fsync -> rename -> fsync parent.
|
||||
/// If `mode` is `Some`, set permissions before rename (Unix only).
|
||||
///
|
||||
/// Edge case: tmp file name uses `with_extension("tmp")` which replaces
|
||||
/// the existing extension -- correct for `foo.json` -> `foo.tmp`. For paths
|
||||
/// without an extension (unlikely in this codebase), appends `.tmp`.
|
||||
pub fn write_json_atomic<T: Serialize>(path: &Path, data: &T, mode: Option<u32>) -> anyhow::Result<()> {
|
||||
let tmp = path.with_extension("tmp");
|
||||
let bytes = serde_json::to_vec_pretty(data)?;
|
||||
{
|
||||
let mut f = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(&tmp)?;
|
||||
f.write_all(&bytes)?;
|
||||
f.sync_all()?;
|
||||
}
|
||||
if let Some(m) = mode {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&tmp, std::fs::Permissions::from_mode(m))?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{ let _ = m; }
|
||||
}
|
||||
std::fs::rename(&tmp, path)?;
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::File::open(parent).and_then(|d| d.sync_all());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod atomic_write;
|
||||
pub use atomic_write::write_json_atomic;
|
||||
pub mod clipboard;
|
||||
pub mod error;
|
||||
pub mod logger;
|
||||
|
||||
Reference in New Issue
Block a user