Add PDF archive store, raw-body IPC and export file helpers

The searchable PDF can be archived content-addressed under the local data
dir with its sha256 and layout fingerprint; a different PDF for an archived
invoice is refused and reads verify the hash. Export writes go through a
raw-body command that remembers the last folder; reveal-in-folder and open
fall back to xdg-open / explorer. Adds a Windows-safe export file name
builder. Migration M4 adds the archive columns and last export dir.
This commit is contained in:
2026-10-04 05:54:26 +05:30
parent cedf4839bd
commit 7162dac6e7
17 changed files with 891 additions and 36 deletions
-21
View File
@@ -133,22 +133,6 @@ pub fn remove_asset(state: State<AppState>, path: String) -> Result<(), String>
remove_asset_file(&state.data_dir, &path)
}
/// Write a base64 payload (e.g. a generated PDF) to a user-chosen path.
#[tauri::command]
pub fn save_binary_file(path: String, data_base64: String) -> Result<(), String> {
if path.trim().is_empty() {
return Err("No file path given".into());
}
let bytes = STANDARD
.decode(data_base64.as_bytes())
.map_err(|e| e.to_string())?;
if let Some(parent) = Path::new(&path).parent() {
// A missing parent is created; if that fails the write below reports the real error.
std::fs::create_dir_all(parent).ok();
}
std::fs::write(&path, bytes).map_err(|e| e.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
@@ -248,9 +232,4 @@ mod tests {
assert!(remove_asset_file(dir.path(), "voiced.db").is_err());
assert!(dir.path().join("voiced.db").exists());
}
#[test]
fn save_binary_file_rejects_empty_path() {
assert!(save_binary_file(" ".into(), "AAAA".into()).is_err());
}
}