diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-08 18:27:16 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-08 18:27:16 +0300 |
| commit | c62599de008fa898a973138c558fd32732e206de (patch) | |
| tree | b65f15385943c5dbfe4e3da54787572f9d76f30f /src/persist.rs | |
| parent | 3689515f6a11d0f7635067de2f964b63288c645b (diff) | |
feat: add debounced session autosave
Diffstat (limited to 'src/persist.rs')
| -rw-r--r-- | src/persist.rs | 96 |
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, |
