One-click backups for AT Protocol

feat: refresh on focus

Turtlepaw 6082d4b6 5deabd93

Changed files
+14 -223
src
-115
src/components/TestAutoBackup.tsx
··· 1 - import { useState } from "react"; 2 - import { Button } from "@/components/ui/button"; 3 - import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 4 - import { TestAutoBackupScheduler } from "@/lib/testAutoBackup"; 5 - import { useAuth } from "@/Auth"; 6 - import { settingsManager } from "@/lib/settings"; 7 - import { toast } from "sonner"; 8 - 9 - export function TestAutoBackup() { 10 - const [isRunning, setIsRunning] = useState(false); 11 - const [scheduler, setScheduler] = useState<TestAutoBackupScheduler | null>( 12 - null 13 - ); 14 - const [lastBackupDate, setLastBackupDate] = useState<string | undefined>(); 15 - const [frequency, setFrequency] = useState<"daily" | "weekly">("daily"); 16 - const { agent } = useAuth(); 17 - 18 - const startTest = () => { 19 - if (!agent) { 20 - toast("No agent available"); 21 - return; 22 - } 23 - 24 - const testScheduler = new TestAutoBackupScheduler(agent); 25 - testScheduler.start(); 26 - setScheduler(testScheduler); 27 - setIsRunning(true); 28 - toast("🧪 Test auto-backup started! Check console for logs."); 29 - }; 30 - 31 - const stopTest = () => { 32 - if (scheduler) { 33 - scheduler.stop(); 34 - setScheduler(null); 35 - setIsRunning(false); 36 - toast("🧪 Test auto-backup stopped"); 37 - } 38 - }; 39 - 40 - const checkStatus = async () => { 41 - const lastBackup = await settingsManager.getLastBackupDate(); 42 - const freq = await settingsManager.getBackupFrequency(); 43 - setLastBackupDate(lastBackup); 44 - setFrequency(freq); 45 - }; 46 - 47 - const clearLastBackup = async () => { 48 - await settingsManager.updateSettings({ lastBackupDate: undefined }); 49 - await checkStatus(); 50 - toast("🧪 Cleared last backup date"); 51 - }; 52 - 53 - return ( 54 - <Card className="bg-card border-white/10 mb-4"> 55 - <CardHeader> 56 - <CardTitle className="text-white">🧪 Test Auto-Backup</CardTitle> 57 - </CardHeader> 58 - <CardContent className="space-y-4"> 59 - <div className="space-y-2"> 60 - <p className="text-white/80 text-sm"> 61 - Current frequency: <span className="font-bold">{frequency}</span> 62 - </p> 63 - <p className="text-white/80 text-sm"> 64 - Last backup:{" "} 65 - {lastBackupDate 66 - ? new Date(lastBackupDate).toLocaleString() 67 - : "None"} 68 - </p> 69 - </div> 70 - 71 - <div className="flex gap-2"> 72 - <Button 73 - onClick={startTest} 74 - disabled={isRunning} 75 - variant="outline" 76 - className="cursor-pointer" 77 - > 78 - Start Test (30s intervals) 79 - </Button> 80 - 81 - <Button 82 - onClick={stopTest} 83 - disabled={!isRunning} 84 - variant="outline" 85 - className="cursor-pointer" 86 - > 87 - Stop Test 88 - </Button> 89 - 90 - <Button 91 - onClick={checkStatus} 92 - variant="outline" 93 - className="cursor-pointer" 94 - > 95 - Check Status 96 - </Button> 97 - 98 - <Button 99 - onClick={clearLastBackup} 100 - variant="outline" 101 - className="cursor-pointer" 102 - > 103 - Clear Last Backup 104 - </Button> 105 - </div> 106 - 107 - <div className="text-xs text-white/60"> 108 - <p>• Test uses 1 minute for "daily" and 2 minutes for "weekly"</p> 109 - <p>• Check browser console for detailed logs</p> 110 - <p>• Change frequency in Settings to test different intervals</p> 111 - </div> 112 - </CardContent> 113 - </Card> 114 - ); 115 - }
-104
src/lib/testAutoBackup.ts
··· 1 - import { Agent } from "@atproto/api"; 2 - import { BackupAgent } from "./backup"; 3 - import { settingsManager } from "./settings"; 4 - 5 - export class TestAutoBackupScheduler { 6 - private agent: Agent; 7 - private intervalId: NodeJS.Timeout | null = null; 8 - private isRunning = false; 9 - 10 - constructor(agent: Agent) { 11 - this.agent = agent; 12 - } 13 - 14 - start(): void { 15 - if (this.isRunning) return; 16 - 17 - this.isRunning = true; 18 - // Check every 30 seconds for testing (instead of 1 hour) 19 - this.intervalId = setInterval(() => { 20 - this.checkAndPerformBackup(); 21 - }, 30 * 1000); // 30 seconds 22 - 23 - // Also check immediately when starting 24 - this.checkAndPerformBackup(); 25 - } 26 - 27 - stop(): void { 28 - if (this.intervalId) { 29 - clearInterval(this.intervalId); 30 - this.intervalId = null; 31 - } 32 - this.isRunning = false; 33 - } 34 - 35 - private async checkAndPerformBackup(): Promise<void> { 36 - try { 37 - const shouldBackup = await this.shouldPerformBackup(); 38 - 39 - if (shouldBackup) { 40 - console.log("🧪 TEST: Automatic backup due, starting backup..."); 41 - await this.performBackup(); 42 - } else { 43 - console.log("🧪 TEST: No backup needed yet"); 44 - } 45 - } catch (error) { 46 - console.error("Error in automatic backup check:", error); 47 - } 48 - } 49 - 50 - private async shouldPerformBackup(): Promise<boolean> { 51 - try { 52 - const lastBackupDate = await settingsManager.getLastBackupDate(); 53 - const frequency = await settingsManager.getBackupFrequency(); 54 - 55 - if (!lastBackupDate) { 56 - console.log("🧪 TEST: No previous backup, should do one"); 57 - return true; 58 - } 59 - 60 - const lastBackup = new Date(lastBackupDate); 61 - const now = new Date(); 62 - const timeDiff = now.getTime() - lastBackup.getTime(); 63 - 64 - // For testing: use shorter intervals 65 - if (frequency === "daily") { 66 - // Test with 1 minute instead of 24 hours 67 - const oneMinute = 60 * 1000; 68 - const shouldBackup = timeDiff >= oneMinute; 69 - console.log( 70 - `🧪 TEST: Daily backup check - ${timeDiff}ms since last backup, need ${oneMinute}ms` 71 - ); 72 - return shouldBackup; 73 - } else if (frequency === "weekly") { 74 - // Test with 2 minutes instead of 7 days 75 - const twoMinutes = 2 * 60 * 1000; 76 - const shouldBackup = timeDiff >= twoMinutes; 77 - console.log( 78 - `🧪 TEST: Weekly backup check - ${timeDiff}ms since last backup, need ${twoMinutes}ms` 79 - ); 80 - return shouldBackup; 81 - } 82 - 83 - return false; 84 - } catch (error) { 85 - console.error("Error checking if backup is due:", error); 86 - return false; 87 - } 88 - } 89 - 90 - private async performBackup(): Promise<void> { 91 - try { 92 - console.log("🧪 TEST: Starting automatic backup..."); 93 - const manager = new BackupAgent(this.agent); 94 - await manager.startBackup(); 95 - 96 - // Update the last backup date 97 - await settingsManager.setLastBackupDate(new Date().toISOString()); 98 - 99 - console.log("🧪 TEST: Automatic backup completed successfully"); 100 - } catch (error) { 101 - console.error("🧪 TEST: Automatic backup failed:", error); 102 - } 103 - } 104 - }
+14 -4
src/routes/Home.tsx
··· 42 42 DialogTitle, 43 43 DialogTrigger, 44 44 } from "@/components/ui/dialog"; 45 + import { getCurrentWindow } from "@tauri-apps/api/window"; 45 46 46 47 export function Home({ 47 48 profile, ··· 259 260 >({}); 260 261 const { agent } = useAuth(); 261 262 const contentRefs = useRef<Record<string, HTMLDivElement | null>>({}); 263 + const appWindow = getCurrentWindow(); 262 264 263 - const loadBackups = async () => { 265 + const loadBackups = async (silent: boolean = false) => { 264 266 if (agent == null) { 265 267 return; 266 268 } 267 - setIsLoading(true); 269 + if (!silent) setIsLoading(true); 268 270 try { 269 271 const manager = new BackupAgent(agent); 270 272 const backupsList = await manager.getBackups(); ··· 278 280 toast(err.toString()); 279 281 console.error(err); 280 282 } finally { 281 - setIsLoading(false); 283 + if (!silent) setIsLoading(false); 282 284 } 283 285 }; 284 286 285 287 useEffect(() => { 286 288 loadBackups(); 287 - }, [agent, refreshTrigger]); // Add refreshTrigger to dependencies 289 + const unlistenVisible = appWindow.listen("tauri://focus", () => { 290 + loadBackups(true); 291 + }); 292 + 293 + return () => { 294 + // Cleanup listeners 295 + unlistenVisible.then((unlisten) => unlisten()); 296 + }; 297 + }, []); 288 298 289 299 //@ts-expect-error 290 300 const units: Record<Intl.RelativeTimeFormatUnit, number> = {