aboutsummaryrefslogtreecommitdiff
path: root/src/persist.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-08 18:27:16 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-08 18:27:16 +0300
commitc62599de008fa898a973138c558fd32732e206de (patch)
treeb65f15385943c5dbfe4e3da54787572f9d76f30f /src/persist.rs
parent3689515f6a11d0f7635067de2f964b63288c645b (diff)
feat: add debounced session autosave
Diffstat (limited to 'src/persist.rs')
-rw-r--r--src/persist.rs96
1 files changed, 75 insertions, 21 deletions
diff --git a/src/persist.rs b/src/persist.rs
index ccb44ace..6704dd17 100644
--- a/src/persist.rs
+++ b/src/persist.rs
@@ -487,36 +487,46 @@ fn session_path() -> PathBuf {
crate::config::config_dir().join("session.json")
}
-pub fn save(snapshot: &SessionSnapshot) {
- let path = session_path();
+fn save_to_path(path: &std::path::Path, snapshot: &SessionSnapshot) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
- if let Err(e) = std::fs::create_dir_all(parent) {
- error!(err = %e, "failed to create session directory");
- return;
- }
+ std::fs::create_dir_all(parent)?;
}
- let json = match serde_json::to_string_pretty(snapshot) {
- Ok(j) => j,
- Err(e) => {
- error!(err = %e, "failed to serialize session");
- return;
- }
- };
- // Atomic write: write to temp file, then rename
+ let json = serde_json::to_string_pretty(snapshot)?;
let tmp_path = path.with_extension("json.tmp");
- if let Err(e) = std::fs::write(&tmp_path, &json) {
- error!(err = %e, "failed to write session temp file");
- return;
- }
- if let Err(e) = std::fs::rename(&tmp_path, &path) {
- error!(err = %e, "failed to rename session file");
- // Clean up temp file
+ std::fs::write(&tmp_path, &json)?;
+ if let Err(err) = std::fs::rename(&tmp_path, path) {
let _ = std::fs::remove_file(&tmp_path);
+ return Err(err);
+ }
+ Ok(())
+}
+
+fn clear_path(path: &std::path::Path) -> std::io::Result<()> {
+ match std::fs::remove_file(path) {
+ Ok(()) => Ok(()),
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
+ Err(err) => Err(err),
+ }
+}
+
+pub fn save(snapshot: &SessionSnapshot) {
+ let path = session_path();
+ if let Err(err) = save_to_path(&path, snapshot) {
+ error!(err = %err, path = %path.display(), "failed to save session");
return;
}
info!(workspaces = snapshot.workspaces.len(), "session saved");
}
+pub fn clear() {
+ let path = session_path();
+ if let Err(err) = clear_path(&path) {
+ error!(err = %err, path = %path.display(), "failed to clear session");
+ return;
+ }
+ info!(path = %path.display(), "session cleared");
+}
+
fn parse_snapshot(content: &str) -> Result<SessionSnapshot, String> {
let raw = serde_json::from_str::<RawSessionSnapshot>(content).map_err(|e| e.to_string())?;
if raw.version > SNAPSHOT_VERSION {
@@ -583,6 +593,19 @@ mod tests {
}
}
+ fn temp_session_path(name: &str) -> PathBuf {
+ let unique = format!(
+ "herdr-session-tests-{}-{}-{}",
+ name,
+ std::process::id(),
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_nanos()
+ );
+ std::env::temp_dir().join(unique).join("session.json")
+ }
+
#[test]
fn round_trip_empty_session() {
let snap = SessionSnapshot {
@@ -603,6 +626,37 @@ mod tests {
}
#[test]
+ fn clear_path_removes_existing_session_file() {
+ let path = temp_session_path("clear-existing");
+ save_to_path(
+ &path,
+ &SessionSnapshot {
+ version: SNAPSHOT_VERSION,
+ workspaces: vec![],
+ active: None,
+ selected: 0,
+ agent_panel_scope: AgentPanelScope::CurrentWorkspace,
+ sidebar_width: Some(26),
+ sidebar_section_split: Some(0.5),
+ },
+ )
+ .unwrap();
+
+ clear_path(&path).unwrap();
+
+ assert!(!path.exists());
+ }
+
+ #[test]
+ fn clear_path_ignores_missing_session_file() {
+ let path = temp_session_path("clear-missing");
+
+ clear_path(&path).unwrap();
+
+ assert!(!path.exists());
+ }
+
+ #[test]
fn round_trip_layout_snapshot() {
let layout = LayoutSnapshot::Split {
direction: DirectionSnapshot::Horizontal,