Skip to main content

stakpak_shared/
file_backup_manager.rs

1use crate::local_store::LocalStore;
2use crate::remote_connection::RemoteConnection;
3use crate::remote_store::RemoteStore;
4use std::path::Path;
5use std::sync::Arc;
6use uuid::Uuid;
7
8/// Manages file backups to the local session store and remote backup locations
9pub struct FileBackupManager;
10
11impl FileBackupManager {
12    /// Move a local path (file or directory) to backup location in the session store
13    pub fn move_local_path_to_backup(path: &str) -> Result<String, String> {
14        let path_obj = Path::new(path);
15
16        if !path_obj.exists() {
17            return Err(format!("Path does not exist: {}", path));
18        }
19
20        let backup_session_id = Uuid::new_v4().to_string();
21
22        let backup_session_path = LocalStore::get_backup_session_path(&backup_session_id);
23        let full_backup_dir = LocalStore::get_local_session_store_path().join(&backup_session_path);
24
25        if let Err(e) = std::fs::create_dir_all(&full_backup_dir) {
26            return Err(format!("Failed to create backup directory: {}", e));
27        }
28
29        let item_name = path_obj
30            .file_name()
31            .and_then(|n| n.to_str())
32            .unwrap_or("unknown_item");
33        let backup_path = full_backup_dir.join(item_name);
34
35        match std::fs::rename(path_obj, &backup_path) {
36            Ok(()) => Ok(backup_path.to_string_lossy().to_string()),
37            Err(e) => Err(format!(
38                "Failed to move local path '{}' to backup: {}",
39                path, e
40            )),
41        }
42    }
43
44    /// Move a remote path (file or directory) to backup location on the remote machine
45    pub async fn move_remote_path_to_backup(
46        conn: &Arc<RemoteConnection>,
47        path: &str,
48    ) -> Result<String, String> {
49        let backup_session_id = Uuid::new_v4().to_string();
50
51        let absolute_backup_dir =
52            match RemoteStore::get_absolute_backup_session_path(conn, &backup_session_id).await {
53                Ok(abs_path) => abs_path,
54                Err(e) => return Err(e),
55            };
56
57        let item_name = Path::new(&path)
58            .file_name()
59            .and_then(|n| n.to_str())
60            .unwrap_or("unknown_item");
61        let backup_path = format!("{}/{}", absolute_backup_dir, item_name);
62
63        match conn.rename(path, &backup_path).await {
64            Ok(()) => Ok(backup_path),
65            Err(e) => Err(format!(
66                "Failed to move remote path '{}' to backup: {}",
67                path, e
68            )),
69        }
70    }
71
72    /// Format backup mapping into XML structure with location type
73    pub fn format_backup_xml(
74        backup_mapping: &std::collections::HashMap<String, String>,
75        location: &str,
76    ) -> String {
77        let mut inner_content = String::new();
78
79        for (original_path, backup_path) in backup_mapping {
80            inner_content.push_str(&format!(
81                "\n    <file\n        original_path=\"{}\"\n        backup_path=\"{}\"\n        location=\"{}\"\n    />",
82                Self::escape_xml(original_path),
83                Self::escape_xml(backup_path),
84                location
85            ));
86        }
87
88        format!("<file_backups>{}\n</file_backups>", inner_content)
89    }
90
91    /// Escape XML special characters
92    fn escape_xml(text: &str) -> String {
93        text.replace('&', "&amp;")
94            .replace('<', "&lt;")
95            .replace('>', "&gt;")
96            .replace('"', "&quot;")
97            .replace('\'', "&apos;")
98    }
99}