mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 19:17:03 +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]
|
#[tauri::command]
|
||||||
pub fn save_book(app: tauri::AppHandle, id: String, contents: String) -> Result<(), String> {
|
pub fn save_book(app: tauri::AppHandle, id: String, contents: String) -> Result<(), String> {
|
||||||
let path = library_dir(&app)?.join(format!("{id}.margin"));
|
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]
|
#[tauri::command]
|
||||||
|
|||||||
@@ -1,6 +1,33 @@
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose::STANDARD;
|
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]
|
#[tauri::command]
|
||||||
pub fn read_file(path: String) -> Result<String, String> {
|
pub fn read_file(path: String) -> Result<String, String> {
|
||||||
@@ -9,11 +36,11 @@ pub fn read_file(path: String) -> Result<String, String> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn write_file(path: String, contents: String) -> Result<(), String> {
|
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]
|
#[tauri::command]
|
||||||
pub fn write_bytes(path: String, data: String) -> Result<(), String> {
|
pub fn write_bytes(path: String, data: String) -> Result<(), String> {
|
||||||
let bytes = STANDARD.decode(data.as_bytes()).map_err(|e| e.to_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