wow-backup/docs/plans/feature-3-backup.md
2026-03-04 14:19:19 +00:00

5.4 KiB

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
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:

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 <backupDir>/<timestamp>/

For ZIP:

  • Walk source directories, write to <backupDir>/<timestamp>.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
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<SchedulerState>:
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