Skip to main content

reovim_kernel/panic/
recovery.rs

1//! Crash recovery utilities.
2//!
3//! Linux equivalent: `kernel/panic.c` `emergency_restart`
4//!
5//! Provides utilities to save state during panic and recover after restart.
6
7use std::{fmt::Write, path::PathBuf};
8
9/// Recovery state snapshot.
10#[derive(Debug)]
11pub struct RecoverySnapshot {
12    /// Unsaved buffers at time of crash.
13    pub unsaved_buffers: Vec<UnsavedBuffer>,
14    /// Timestamp of the crash.
15    pub timestamp: std::time::SystemTime,
16}
17
18/// Information about an unsaved buffer.
19#[derive(Debug)]
20pub struct UnsavedBuffer {
21    /// Buffer ID.
22    pub id: u32,
23    /// Original file path (if any).
24    pub path: Option<PathBuf>,
25    /// Hash of content for deduplication.
26    pub content_hash: u64,
27    /// Number of lines.
28    pub line_count: usize,
29}
30
31/// Get the recovery data directory.
32///
33/// Returns `~/.local/share/reovim/recovery/` or equivalent.
34#[must_use]
35pub fn recovery_dir() -> PathBuf {
36    reovim_arch::dirs::data_local_dir()
37        .unwrap_or_else(|| PathBuf::from("."))
38        .join("reovim")
39        .join("recovery")
40}
41
42/// Save buffer content to recovery directory.
43///
44/// Creates a recovery file with metadata header and buffer content.
45///
46/// # Arguments
47///
48/// * `buffer_id` - Numeric buffer ID
49/// * `original_path` - Original file path (if any)
50/// * `content` - Buffer content
51///
52/// # Returns
53///
54/// Path to the recovery file, or an error.
55///
56/// # Errors
57///
58/// Returns an error if:
59/// - Unable to create the recovery directory
60/// - Unable to write the recovery file
61///
62/// # Example
63///
64/// ```ignore
65/// let path = save_buffer_for_recovery(1, Some(Path::new("main.rs")), "fn main() {}");
66/// println!("Saved to: {}", path?.display());
67/// ```
68pub fn save_buffer_for_recovery(
69    buffer_id: u32,
70    original_path: Option<&std::path::Path>,
71    content: &str,
72) -> std::io::Result<PathBuf> {
73    let dir = recovery_dir();
74    std::fs::create_dir_all(&dir)?;
75
76    let filename = format!(
77        "buffer-{}-{}.txt",
78        buffer_id,
79        std::time::SystemTime::now()
80            .duration_since(std::time::UNIX_EPOCH)
81            .map_or(0, |d| d.as_secs())
82    );
83
84    let path = dir.join(&filename);
85
86    // Write metadata header
87    let mut output = String::new();
88    writeln!(output, "# Recovery file for buffer {buffer_id}").ok();
89    if let Some(orig) = original_path {
90        writeln!(output, "# Original path: {}", orig.display()).ok();
91    }
92    writeln!(output, "# Lines: {}", content.lines().count()).ok();
93    output.push_str("# ---\n");
94    output.push_str(content);
95
96    std::fs::write(&path, output)?;
97    Ok(path)
98}
99
100/// List all recovery files.
101///
102/// # Returns
103///
104/// List of paths to recovery files, sorted by modification time (newest first).
105///
106/// # Errors
107///
108/// Returns an error if unable to read the recovery directory.
109#[cfg_attr(coverage_nightly, coverage(off))]
110pub fn list_recovery_files() -> std::io::Result<Vec<PathBuf>> {
111    let dir = recovery_dir();
112    if !dir.exists() {
113        return Ok(Vec::new());
114    }
115
116    let mut files: Vec<(PathBuf, std::time::SystemTime)> = Vec::new();
117
118    for entry in std::fs::read_dir(dir)? {
119        let entry = entry?;
120        let path = entry.path();
121        if path.extension().is_some_and(|e| e == "txt")
122            && let Ok(meta) = std::fs::metadata(&path)
123            && let Ok(modified) = meta.modified()
124        {
125            files.push((path, modified));
126        }
127    }
128
129    // Sort by modification time (newest first)
130    files.sort_by_key(|b| std::cmp::Reverse(b.1));
131
132    Ok(files.into_iter().map(|(p, _)| p).collect())
133}
134
135/// Clean up old recovery files.
136///
137/// Removes recovery files older than the specified age.
138///
139/// # Arguments
140///
141/// * `max_age_secs` - Maximum age in seconds
142///
143/// # Returns
144///
145/// Number of files removed.
146///
147/// # Errors
148///
149/// Returns an error if:
150/// - Unable to list recovery files
151/// - Unable to remove a file
152#[cfg_attr(coverage_nightly, coverage(off))]
153pub fn cleanup_old_recovery_files(max_age_secs: u64) -> std::io::Result<usize> {
154    let now = std::time::SystemTime::now();
155    let mut removed = 0;
156
157    for path in list_recovery_files()? {
158        if let Ok(meta) = std::fs::metadata(&path)
159            && let Ok(modified) = meta.modified()
160            && let Ok(age) = now.duration_since(modified)
161            && age.as_secs() > max_age_secs
162        {
163            std::fs::remove_file(&path)?;
164            removed += 1;
165        }
166    }
167
168    Ok(removed)
169}