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

5.9 KiB

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