One-click backups for AT Protocol
1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::json;
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6use tauri::{App, AppHandle, Emitter, Manager};
7use tauri_plugin_store::StoreExt;
8use tokio::sync::Mutex;
9use tokio::time::sleep;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct BackupSettings {
13 pub backupFrequency: String, // "daily" or "weekly"
14 pub lastBackupDate: Option<String>,
15}
16
17pub struct BackgroundScheduler {
18 app: AppHandle,
19 is_running: Arc<Mutex<bool>>,
20}
21
22impl BackgroundScheduler {
23 pub fn new(app: AppHandle) -> Self {
24 Self {
25 app,
26 is_running: Arc::new(Mutex::new(false)),
27 }
28 }
29
30 pub async fn start(&self) {
31 let mut is_running = self.is_running.lock().await;
32 if *is_running {
33 return;
34 }
35 *is_running = true;
36 drop(is_running);
37
38 let is_running = self.is_running.clone();
39 let app = self.app.clone();
40
41 tokio::spawn(async move {
42 loop {
43 // Your shared flag
44 if !*is_running.lock().await {
45 break;
46 }
47
48 // Use cloned app
49 if let Err(e) = Self::check_and_perform_backup(&app).await {
50 eprintln!("Background backup check failed: {}", e);
51 }
52
53 sleep(Duration::from_secs(30 * 60)).await;
54 }
55 });
56 }
57
58 pub async fn stop(&self) {
59 let mut is_running = self.is_running.lock().await;
60 *is_running = false;
61 }
62
63 async fn check_and_perform_backup(app: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
64 println!("Background: Checking if backup is needed...");
65 // Get settings from store
66 let store = app.store("settings.json")?;
67 let raw_settings: Option<serde_json::Value> = store.get("settings");
68
69 let value = raw_settings.unwrap_or(json!({
70 "backupFrequency": "daily",
71 "last_backup_date": null
72 }));
73
74 let settings: BackupSettings = serde_json::from_value(value)?;
75
76 // Check if backup is needed
77 if Self::should_perform_backup(&settings).await? {
78 println!("Background: Backup due, starting backup...");
79
80 // Emit event to frontend to perform backup
81 match app.emit("perform-backup", serde_json::json!({})) {
82 Ok(_) => println!("Event emitted successfully"),
83 Err(e) => eprintln!("Failed to emit event: {}", e),
84 }
85
86 println!("Background: Backup completed");
87 }
88
89 Ok(())
90 }
91
92 async fn should_perform_backup(
93 settings: &BackupSettings,
94 ) -> Result<bool, Box<dyn std::error::Error>> {
95 println!("[DEBUG] Checking if backup should be performed...");
96
97 if settings.lastBackupDate.is_none() {
98 println!("[DEBUG] No last_backup_date found; should perform backup.");
99 return Ok(true);
100 }
101
102 let last_backup_str = settings.lastBackupDate.as_ref().unwrap();
103 println!("[DEBUG] Last backup date string: {}", last_backup_str);
104
105 let last_backup = DateTime::parse_from_rfc3339(last_backup_str)?;
106 let now = Utc::now();
107 let time_diff = now.signed_duration_since(last_backup);
108
109 println!("[DEBUG] Current time: {}", now);
110 println!("[DEBUG] Last backup time: {}", last_backup);
111 println!(
112 "[DEBUG] Time since last backup: {} seconds",
113 time_diff.num_seconds()
114 );
115
116 let required_interval = match settings.backupFrequency.as_str() {
117 "daily" => chrono::Duration::days(1),
118 "weekly" => chrono::Duration::weeks(1),
119 other => {
120 println!("[DEBUG] Unknown frequency '{}', defaulting to daily", other);
121 chrono::Duration::days(1)
122 }
123 };
124
125 println!(
126 "[DEBUG] Required interval (seconds): {}",
127 required_interval.num_seconds()
128 );
129 println!(
130 "[DEBUG] Should perform backup? {}",
131 time_diff >= required_interval
132 );
133
134 Ok(time_diff >= required_interval)
135 }
136}
137
138#[tauri::command]
139pub async fn start_background_scheduler(app: AppHandle) -> Result<(), String> {
140 println!("Starting background scheduler...");
141 let scheduler = BackgroundScheduler::new(app);
142 scheduler.start().await;
143 Ok(())
144}
145
146#[tauri::command]
147pub async fn stop_background_scheduler() -> Result<(), String> {
148 // This would need to be implemented with a global scheduler reference
149 // For now, we'll handle this differently
150 Ok(())
151}