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

154 lines
5.9 KiB
Markdown

# Feature 0: Foundation
## Context
Before implementing any user-facing features, we need shared infrastructure: package structure, platform abstraction, config persistence, and logging. Every subsequent feature depends on this.
## Dependencies
- None (this is the base layer)
- **Depended on by**: All other features (1-5)
## New Dependencies to Add
### gradle/libs.versions.toml
| Library | Coordinate | Version | Purpose |
|---------|-----------|---------|---------|
| kotlinx-serialization-json | org.jetbrains.kotlinx:kotlinx-serialization-json | 1.10.0 | JSON config persistence |
| kotlin-logging-jvm | io.github.oshai:kotlin-logging-jvm | 7.0.3 | Kotlin-idiomatic logging |
| logback-classic | ch.qos.logback:logback-classic | 1.5.18 | Logging backend with file rotation |
### Plugins
| Plugin | ID | Version |
|--------|----|---------|
| Kotlin Serialization | org.jetbrains.kotlin.plugin.serialization | (uses kotlin version) |
## Package Structure
```
com.rukira.wowbackup/
├── main.kt # Entry point (existing, to be updated)
├── App.kt # Root composable (existing, to be gutted)
├── platform/
│ ├── Platform.kt # OS enum + detection
│ ├── AppDirectories.kt # Platform-specific app data paths
│ └── WoWLocations.kt # Default WoW install detection
├── config/
│ ├── AppConfig.kt # @Serializable data class
│ └── ConfigManager.kt # Read/write JSON config file
└── logging/
└── LoggingSetup.kt # Logback initialization
```
## Implementation Steps
### Step 1: Add dependencies
**Files to modify:**
- `gradle/libs.versions.toml` — add versions, libraries, and serialization plugin
- `build.gradle.kts` (root) — register serialization plugin
- `composeApp/build.gradle.kts` — apply serialization plugin, add new deps to jvmMain
- `settings.gradle.kts` — rename root project from "MyApplication" to "WoWBackup"
### Step 2: Platform abstraction
**Files to create:**
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/platform/Platform.kt`
```kotlin
enum class OS { Windows, Mac, Linux, Other }
object Platform {
val current: OS = System.getProperty("os.name").lowercase().let { name ->
when {
"mac" in name -> OS.Mac
"win" in name -> OS.Windows
"nux" in name || "nix" in name -> OS.Linux
else -> OS.Other
}
}
}
```
**Files to delete:**
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/Platform.kt` (old template file)
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/Greeting.kt` (template boilerplate)
### Step 3: App directories
**Files to create:**
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/platform/AppDirectories.kt`
Resolves platform-specific app data directory:
- macOS: `~/Library/Application Support/WoWBackup/`
- Windows: `%APPDATA%/WoWBackup/`
- Linux: `$XDG_CONFIG_HOME/WoWBackup/` (fallback `~/.config/WoWBackup/`)
Creates directory on first access if it doesn't exist.
### Step 4: WoW install detection
**Files to create:**
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/platform/WoWLocations.kt`
Scans known default install paths:
- macOS: `/Applications/World of Warcraft/`, `~/Applications/World of Warcraft/`
- Windows: `C:\Program Files (x86)\World of Warcraft\`, `C:\Program Files\World of Warcraft\`
Validates by checking for WTF folder, WoW executable, or .app bundle.
Returns `File?` — null if not found.
### Step 5: Config data model + persistence
**Files to create:**
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/config/AppConfig.kt`
```kotlin
@Serializable
data class AppConfig(
val wowInstallPath: String? = null,
val backupPath: String? = null,
val backupTimeOfDay: LocalTime = LocalTime(hour = 3, minute = 0), // daily at 3:00 AM
val backupHistoryCount: Int = 5,
val forceBackupWhileRunning: Boolean = false,
val backupWtf: Boolean = true,
val backupInterface: Boolean = true,
val compressionEnabled: Boolean = false,
val notificationsEnabled: Boolean = true,
val runAtStartup: Boolean = false,
)
```
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/config/ConfigManager.kt`
Responsibilities:
- Load config from `<appDataDir>/config.json` (returns defaults if file missing)
- Save config to `<appDataDir>/config.json` (pretty-printed JSON)
- Expose config as `StateFlow<AppConfig>` for reactive UI updates
- `isConfigured: Boolean` — true when wowInstallPath and backupPath are set
- Thread-safe reads/writes
### Step 6: Logging setup
**Files to create:**
- `composeApp/src/jvmMain/resources/logback.xml`
Configuration:
- Console appender (for development)
- Rolling file appender to `<appDataDir>/logs/wowbackup.log`
- Daily rotation + 10MB max size per file
- 30 days retention / 100MB total cap
- Pattern: `%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n`
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/logging/LoggingSetup.kt`
Programmatically sets the log directory to the platform-specific app data path at startup (since logback.xml can't reference runtime-resolved paths directly).
### Step 7: Update entry point
**Files to modify:**
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/main.kt`
- Initialize logging on startup
- Load config via ConfigManager
- Update window title to "WoW Backup"
- `composeApp/src/jvmMain/kotlin/com/rukira/wowbackup/App.kt`
- Strip template boilerplate
- Simple placeholder screen showing config status ("Configured" / "Not configured")
## Verification
1. `./gradlew composeApp:run` — app launches, shows placeholder UI
2. Check `~/Library/Application Support/WoWBackup/` (macOS) exists after launch
3. Check `config.json` is created with defaults after first run
4. Check `logs/wowbackup.log` exists and contains startup log entries
5. If WoW is installed in default location, verify auto-detection logs the path