# Feature 3: Backup ## Context This is the core functionality — automatically backing up WoW's WTF and Interface folders on a daily schedule. The backup runs in the background, respects configuration, and notifies the user of results. ## Dependencies - **Depends on**: Feature 0 (ConfigManager, logging, platform), Feature 2 (config must be complete) - **Depended on by**: Feature 4 (Status — displays backup state), Feature 5 (Restore — reads backup history) ## Key Decisions - **Compression**: ZIP format using `java.util.zip` (no extra deps) - **Process detection**: `ProcessHandle.allProcesses()` to check if WoW is running - **Scheduling**: Coroutine-based scheduler, checks every minute if it's time to run - **Backup naming**: Timestamped folders like `2024-01-15_03-00-00/` (or `.zip`) ## Implementation Steps ### Step 1: WoW process detection **Files to create:** - `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/WoWProcessDetector.kt` ```kotlin object WoWProcessDetector { fun isWoWRunning(): Boolean { return ProcessHandle.allProcesses() .filter { it.isAlive } .anyMatch { handle -> val name = handle.info().command().orElse("").lowercase() name.contains("world of warcraft") || name.contains("wow.exe") || name.contains("wow-64") } } } ``` ### Step 2: File copy engine **Files to create:** - `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupEngine.kt` Responsibilities: - Copy selected folders (WTF, Interface) from WoW install to backup destination - Support two modes: plain copy and ZIP compression - Report progress: total files count, files copied so far, current file name - Cancellable via coroutine cancellation - Return a `BackupResult` (success/failure with details) Progress model: ```kotlin data class BackupProgress( val totalFiles: Int, val completedFiles: Int, val currentFile: String, ) sealed class BackupResult { data class Success(val backupPath: File, val sizeBytes: Long, val durationMs: Long) : BackupResult() data class Failure(val reason: String, val exception: Throwable? = null) : BackupResult() } ``` For plain copy: - Walk source directories, replicate structure in `//` For ZIP: - Walk source directories, write to `/.zip` using `ZipOutputStream` ### Step 3: Backup history management **Files to create:** - `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupHistory.kt` Responsibilities: - List existing backups in the backup directory (sorted by date, newest first) - Parse timestamps from folder/zip names - Prune old backups exceeding `backupHistoryCount` - Return list of `BackupEntry` for the Status and Restore screens ```kotlin data class BackupEntry( val path: File, val timestamp: LocalDateTime, val isCompressed: Boolean, val sizeBytes: Long, ) ``` ### Step 4: Backup scheduler **Files to create:** - `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupScheduler.kt` Coroutine-based scheduler that: - Runs in a `CoroutineScope` tied to the application lifecycle - Checks every 60 seconds if the current time matches `backupTimeOfDay` (within a 1-minute window) - Tracks whether today's backup has already run (to avoid duplicate runs) - Before starting backup, checks: 1. Config is complete (`ConfigManager.isConfigured`) 2. WoW is not running (unless `forceBackupWhileRunning` is true) - Delegates to `BackupEngine` for the actual work - After backup, calls `BackupHistory.pruneOldBackups()` - Exposes state as `StateFlow`: ```kotlin data class SchedulerState( val lastBackupTime: LocalDateTime? = null, val lastBackupResult: BackupResult? = null, val nextBackupTime: LocalDateTime? = null, val isRunning: Boolean = false, val currentProgress: BackupProgress? = null, val isWoWRunning: Boolean = false, ) ``` ### Step 5: Notifications integration **Files to create:** - `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/backup/BackupNotifier.kt` Uses `TrayState.sendNotification()` (from Compose Desktop's Tray) to send native notifications: - Backup started: "Backup in progress..." - Backup completed: "Backup complete (X files, Y MB)" - Backup failed: "Backup failed: " - Backup skipped (WoW running): "Backup skipped — WoW is running" Only sends if `notificationsEnabled` is true in config. ### Step 6: Wire scheduler into app lifecycle **Files to modify:** - `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt` Start `BackupScheduler` when app launches. Stop when app exits. Pass `TrayState` to `BackupNotifier`. ### Step 7: Manual backup trigger Add a "Backup Now" item to the tray context menu that triggers an immediate backup (bypassing the schedule, still respecting the WoW-running check). **Files to modify:** - `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt` — add menu item ## Verification 1. Set backup time to 1 minute from now, verify backup runs automatically 2. Verify WTF and Interface folders are copied correctly to backup destination 3. With compression enabled, verify a valid `.zip` is created 4. With WoW running and force=false, verify backup is skipped with notification 5. With WoW running and force=true, verify backup proceeds 6. Verify old backups are pruned when exceeding history count 7. "Backup Now" from tray menu triggers immediate backup 8. Notifications appear for start/complete/fail (when enabled) 9. Check logs for detailed backup operation entries