Config screen
This commit is contained in:
parent
ac88e62abe
commit
4b3c512a9d
28 changed files with 2015 additions and 59 deletions
144
docs/plans/feature-3-backup.md
Normal file
144
docs/plans/feature-3-backup.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# 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 `<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
|
||||
|
||||
```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<SchedulerState>`:
|
||||
|
||||
```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: <reason>"
|
||||
- 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue