Skip to main content

aster/session/
archive.rs

1//! Session Archive Support
2//!
3//! Provides functionality for archiving sessions.
4
5use crate::config::paths::Paths;
6use crate::session::SessionManager;
7use anyhow::Result;
8use std::fs;
9use std::path::PathBuf;
10use tracing::info;
11
12/// Get the archive directory path
13fn get_archive_dir() -> PathBuf {
14    Paths::data_dir().join("sessions").join("archive")
15}
16
17/// Ensure the archive directory exists
18fn ensure_archive_dir() -> Result<PathBuf> {
19    let dir = get_archive_dir();
20    if !dir.exists() {
21        fs::create_dir_all(&dir)?;
22    }
23    Ok(dir)
24}
25
26/// Archive a session by exporting it to the archive directory
27///
28/// Note: Since sessions are stored in SQLite, archiving exports
29/// the session to a JSON file in the archive directory.
30///
31/// # Arguments
32/// * `session_id` - The session ID to archive
33///
34/// # Returns
35/// The path to the archived session file
36pub async fn archive_session(session_id: &str) -> Result<PathBuf> {
37    let archive_dir = ensure_archive_dir()?;
38
39    // Export session to JSON
40    let json = SessionManager::export_session(session_id).await?;
41
42    // Write to archive file
43    let archive_path = archive_dir.join(format!("{}.json", session_id));
44    fs::write(&archive_path, &json)?;
45
46    info!(
47        "Session {} archived to {}",
48        session_id,
49        archive_path.display()
50    );
51
52    Ok(archive_path)
53}
54
55/// Archive and delete a session
56///
57/// Archives the session first, then deletes it from the database.
58///
59/// # Arguments
60/// * `session_id` - The session ID to archive and delete
61pub async fn archive_and_delete_session(session_id: &str) -> Result<PathBuf> {
62    let archive_path = archive_session(session_id).await?;
63    SessionManager::delete_session(session_id).await?;
64    info!("Session {} deleted after archiving", session_id);
65    Ok(archive_path)
66}
67
68/// Bulk archive sessions
69///
70/// # Arguments
71/// * `session_ids` - List of session IDs to archive
72///
73/// # Returns
74/// Results for each session (archived path or error)
75pub async fn bulk_archive_sessions(session_ids: &[String]) -> BulkArchiveResult {
76    let mut result = BulkArchiveResult::default();
77
78    for id in session_ids {
79        match archive_session(id).await {
80            Ok(path) => {
81                result.archived.push((id.clone(), path));
82            }
83            Err(e) => {
84                result.failed.push((id.clone(), e.to_string()));
85            }
86        }
87    }
88
89    result
90}
91
92/// Result of bulk archive operation
93#[derive(Debug, Default)]
94pub struct BulkArchiveResult {
95    /// Successfully archived sessions with their paths
96    pub archived: Vec<(String, PathBuf)>,
97    /// Failed sessions with error messages
98    pub failed: Vec<(String, String)>,
99}
100
101impl BulkArchiveResult {
102    /// Check if all archives succeeded
103    pub fn all_succeeded(&self) -> bool {
104        self.failed.is_empty()
105    }
106
107    /// Get count of successful archives
108    pub fn success_count(&self) -> usize {
109        self.archived.len()
110    }
111
112    /// Get count of failed archives
113    pub fn failure_count(&self) -> usize {
114        self.failed.len()
115    }
116}
117
118/// List archived sessions
119///
120/// # Returns
121/// List of archived session IDs
122pub fn list_archived_sessions() -> Result<Vec<String>> {
123    let archive_dir = get_archive_dir();
124
125    if !archive_dir.exists() {
126        return Ok(Vec::new());
127    }
128
129    let mut sessions = Vec::new();
130
131    for entry in fs::read_dir(&archive_dir)? {
132        let entry = entry?;
133        let path = entry.path();
134
135        if path.extension().is_some_and(|ext| ext == "json") {
136            if let Some(stem) = path.file_stem() {
137                sessions.push(stem.to_string_lossy().to_string());
138            }
139        }
140    }
141
142    Ok(sessions)
143}
144
145/// Restore an archived session
146///
147/// # Arguments
148/// * `session_id` - The archived session ID to restore
149pub async fn restore_archived_session(session_id: &str) -> Result<crate::session::Session> {
150    let archive_dir = get_archive_dir();
151    let archive_path = archive_dir.join(format!("{}.json", session_id));
152
153    if !archive_path.exists() {
154        anyhow::bail!("Archived session not found: {}", session_id);
155    }
156
157    let json = fs::read_to_string(&archive_path)?;
158    let session = SessionManager::import_session(&json).await?;
159
160    // Remove from archive after successful restore
161    fs::remove_file(&archive_path)?;
162
163    info!("Session {} restored from archive", session_id);
164
165    Ok(session)
166}
167
168/// Delete an archived session permanently
169///
170/// # Arguments
171/// * `session_id` - The archived session ID to delete
172pub fn delete_archived_session(session_id: &str) -> Result<()> {
173    let archive_dir = get_archive_dir();
174    let archive_path = archive_dir.join(format!("{}.json", session_id));
175
176    if archive_path.exists() {
177        fs::remove_file(&archive_path)?;
178        info!("Archived session {} deleted", session_id);
179    }
180
181    Ok(())
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_bulk_archive_result() {
190        let mut result = BulkArchiveResult::default();
191        assert!(result.all_succeeded());
192        assert_eq!(result.success_count(), 0);
193
194        result
195            .archived
196            .push(("test1".to_string(), PathBuf::from("/tmp/test1.json")));
197        assert!(result.all_succeeded());
198        assert_eq!(result.success_count(), 1);
199
200        result
201            .failed
202            .push(("test2".to_string(), "error".to_string()));
203        assert!(!result.all_succeeded());
204        assert_eq!(result.failure_count(), 1);
205    }
206}