mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 11:07:04 +00:00
fix(persist): atomic+durable .margin writes
Route save_book, write_file, write_bytes through a shared atomic_write helper: write to a temp file, fsync, then rename into place so a crash mid-write can never leave a truncated or empty manuscript. Rotate the prior copy to .bak for manuscripts (not exports) so one good copy always survives. Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
This commit is contained in:
1 parent
61bd7cf6e5
commit
92941a2758
2 files changed
+31
-4
No files matched your search
@@ -72,7 +72,7 @@ pub fn load_book(app: tauri::AppHandle, id: String) -> Result<String, String> {
|
||||
#[tauri::command]
|
||||
pub fn save_book(app: tauri::AppHandle, id: String, contents: String) -> Result<(), String> {
|
||||
let path = library_dir(&app)?.join(format!("{id}.margin"));
|
||||
fs::write(&path, contents).map_err(|e| e.to_string())
|
||||
crate::project::atomic_write(&path, contents.as_bytes(), true)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -1,6 +1,33 @@
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use std::fs;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn with_suffix(path: &Path, suffix: &str) -> PathBuf {
|
||||
let mut name = path.as_os_str().to_owned();
|
||||
name.push(suffix);
|
||||
PathBuf::from(name)
|
||||
}
|
||||
|
||||
pub fn atomic_write(path: &Path, bytes: &[u8], backup: bool) -> Result<(), String> {
|
||||
let tmp = with_suffix(path, ".tmp");
|
||||
{
|
||||
let mut f = File::create(&tmp).map_err(|e| e.to_string())?;
|
||||
f.write_all(bytes).map_err(|e| e.to_string())?;
|
||||
f.sync_all().map_err(|e| e.to_string())?;
|
||||
}
|
||||
if path.exists() {
|
||||
if backup {
|
||||
let bak = with_suffix(path, ".bak");
|
||||
let _ = fs::remove_file(&bak);
|
||||
fs::rename(path, &bak).map_err(|e| e.to_string())?;
|
||||
} else {
|
||||
fs::remove_file(path).map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
fs::rename(&tmp, path).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_file(path: String) -> Result<String, String> {
|
||||
@@ -9,11 +36,11 @@ pub fn read_file(path: String) -> Result<String, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn write_file(path: String, contents: String) -> Result<(), String> {
|
||||
fs::write(&path, contents).map_err(|e| e.to_string())
|
||||
atomic_write(Path::new(&path), contents.as_bytes(), true)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn write_bytes(path: String, data: String) -> Result<(), String> {
|
||||
let bytes = STANDARD.decode(data.as_bytes()).map_err(|e| e.to_string())?;
|
||||
fs::write(&path, bytes).map_err(|e| e.to_string())
|
||||
atomic_write(Path::new(&path), &bytes, false)
|
||||
}
|
||||
Reference in new issue
Block a user