# 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 `/config.json` (returns defaults if file missing) - Save config to `/config.json` (pretty-printed JSON) - Expose config as `StateFlow` 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 `/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