From c37164f63f92d2739a9268f98f4c0c6a86a51eed Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Sun, 19 Nov 2023 23:44:27 +0100 Subject: [PATCH] profile reading --- src/main/java/de/bixilon/minosoft/Minosoft.kt | 4 +- .../config/profile/manager/ProfileManagers.kt | 14 +- .../config/profile/profiles/Profile.kt | 2 - .../FileStorage.kt} | 22 +-- .../ProfileIOUtil.kt} | 14 +- .../config/profile/storage/ProfileStorage.kt | 1 - .../profile/storage/StorageProfileManager.kt | 128 +++++++++++++++++- .../dialog/profiles/ProfileCreateDialog.kt | 3 +- 8 files changed, 157 insertions(+), 31 deletions(-) rename src/main/java/de/bixilon/minosoft/config/profile/{manager/GlobalProfileManager.kt => storage/FileStorage.kt} (67%) rename src/main/java/de/bixilon/minosoft/config/profile/{ProfileState.kt => storage/ProfileIOUtil.kt} (78%) diff --git a/src/main/java/de/bixilon/minosoft/Minosoft.kt b/src/main/java/de/bixilon/minosoft/Minosoft.kt index 1721a8102..a2f5c74d3 100644 --- a/src/main/java/de/bixilon/minosoft/Minosoft.kt +++ b/src/main/java/de/bixilon/minosoft/Minosoft.kt @@ -33,7 +33,7 @@ import de.bixilon.kutil.unit.UnitFormatter.formatNanos import de.bixilon.minosoft.assets.file.ResourcesAssetsUtil import de.bixilon.minosoft.assets.meta.MinosoftMeta import de.bixilon.minosoft.assets.properties.version.AssetsVersionProperties -import de.bixilon.minosoft.config.profile.manager.GlobalProfileManager +import de.bixilon.minosoft.config.profile.manager.ProfileManagers import de.bixilon.minosoft.config.profile.profiles.eros.ErosProfileManager import de.bixilon.minosoft.data.entities.event.EntityEvents import de.bixilon.minosoft.data.language.LanguageUtil @@ -102,7 +102,7 @@ object Minosoft { taskWorker += WorkerTask(identifier = BootTasks.VERSIONS, priority = ThreadPool.HIGHER, executor = VersionLoader::load) taskWorker += WorkerTask(identifier = BootTasks.FILE_WATCHER, priority = ThreadPool.HIGHER, optional = true, executor = this::startFileWatcherService) - taskWorker += WorkerTask(identifier = BootTasks.PROFILES, priority = ThreadPool.HIGHER, dependencies = arrayOf(BootTasks.FILE_WATCHER), executor = GlobalProfileManager::initialize) + taskWorker += WorkerTask(identifier = BootTasks.PROFILES, priority = ThreadPool.HIGHER, dependencies = arrayOf(BootTasks.FILE_WATCHER), executor = ProfileManagers::load) taskWorker += WorkerTask(identifier = BootTasks.LANGUAGE_FILES, dependencies = arrayOf(BootTasks.PROFILES), executor = this::loadLanguageFiles) taskWorker += WorkerTask(identifier = BootTasks.ASSETS_PROPERTIES, dependencies = arrayOf(BootTasks.VERSIONS), executor = AssetsVersionProperties::load) diff --git a/src/main/java/de/bixilon/minosoft/config/profile/manager/ProfileManagers.kt b/src/main/java/de/bixilon/minosoft/config/profile/manager/ProfileManagers.kt index 57e353d86..a02681e03 100644 --- a/src/main/java/de/bixilon/minosoft/config/profile/manager/ProfileManagers.kt +++ b/src/main/java/de/bixilon/minosoft/config/profile/manager/ProfileManagers.kt @@ -13,6 +13,8 @@ package de.bixilon.minosoft.config.profile.manager +import de.bixilon.kutil.concurrent.worker.unconditional.UnconditionalWorker +import de.bixilon.kutil.latch.AbstractLatch import de.bixilon.minosoft.config.profile.profiles.account.AccountProfileManager import de.bixilon.minosoft.config.profile.profiles.audio.AudioProfileManager import de.bixilon.minosoft.config.profile.profiles.block.BlockProfileManager @@ -41,4 +43,14 @@ object ProfileManagers : DefaultFactory>( GUIProfileManager, ControlsProfileManager, OtherProfileManager, -) +) { + + + fun load(latch: AbstractLatch?) { + val worker = UnconditionalWorker() + for (manager in ProfileManagers) { + worker += { manager.load() } + } + worker.work(latch) + } +} diff --git a/src/main/java/de/bixilon/minosoft/config/profile/profiles/Profile.kt b/src/main/java/de/bixilon/minosoft/config/profile/profiles/Profile.kt index 1b3f8e9c1..1ed2b93ce 100644 --- a/src/main/java/de/bixilon/minosoft/config/profile/profiles/Profile.kt +++ b/src/main/java/de/bixilon/minosoft/config/profile/profiles/Profile.kt @@ -14,12 +14,10 @@ package de.bixilon.minosoft.config.profile.profiles import com.fasterxml.jackson.annotation.JsonIgnore -import com.fasterxml.jackson.annotation.JsonInclude import de.bixilon.kutil.concurrent.lock.Lock import de.bixilon.minosoft.config.profile.storage.ProfileStorage interface Profile { @get:JsonIgnore val storage: ProfileStorage? - @get:JsonInclude(JsonInclude.Include.NON_NULL) val version: Int? get() = storage?.version @get:JsonIgnore val lock: Lock } diff --git a/src/main/java/de/bixilon/minosoft/config/profile/manager/GlobalProfileManager.kt b/src/main/java/de/bixilon/minosoft/config/profile/storage/FileStorage.kt similarity index 67% rename from src/main/java/de/bixilon/minosoft/config/profile/manager/GlobalProfileManager.kt rename to src/main/java/de/bixilon/minosoft/config/profile/storage/FileStorage.kt index 48f304bfa..9761917f8 100644 --- a/src/main/java/de/bixilon/minosoft/config/profile/manager/GlobalProfileManager.kt +++ b/src/main/java/de/bixilon/minosoft/config/profile/storage/FileStorage.kt @@ -11,19 +11,19 @@ * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ -package de.bixilon.minosoft.config.profile.manager +package de.bixilon.minosoft.config.profile.storage -import de.bixilon.kutil.latch.AbstractLatch -import de.bixilon.minosoft.terminal.RunConfiguration +class FileStorage( + override val name: String, + val manager: StorageProfileManager<*>, + override val path: String, +) : ProfileStorage { + var updating = false + var invalid = false -object GlobalProfileManager { - private val SELECTED_PROFILES_PATH = RunConfiguration.CONFIG_DIRECTORY.resolve("selected_profiles.json").toFile() - init { + override fun invalidate() { + if (updating) return + invalid = true } - - @Synchronized - fun initialize(latch: AbstractLatch?) { - } - } diff --git a/src/main/java/de/bixilon/minosoft/config/profile/ProfileState.kt b/src/main/java/de/bixilon/minosoft/config/profile/storage/ProfileIOUtil.kt similarity index 78% rename from src/main/java/de/bixilon/minosoft/config/profile/ProfileState.kt rename to src/main/java/de/bixilon/minosoft/config/profile/storage/ProfileIOUtil.kt index 1e756a0e4..a7fa62e50 100644 --- a/src/main/java/de/bixilon/minosoft/config/profile/ProfileState.kt +++ b/src/main/java/de/bixilon/minosoft/config/profile/storage/ProfileIOUtil.kt @@ -11,10 +11,12 @@ * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ -package de.bixilon.minosoft.config.profile +package de.bixilon.minosoft.config.profile.storage -data class ProfileState( - var initializing: Boolean = true, - var reloading: Boolean = false, - var saving: Boolean = false, -) +object ProfileIOUtil { + val NAME_REGEX = "[\\w_ ]{1,32}".toRegex() + + fun String.isValidName(): Boolean { + return NAME_REGEX.matches(this) + } +} diff --git a/src/main/java/de/bixilon/minosoft/config/profile/storage/ProfileStorage.kt b/src/main/java/de/bixilon/minosoft/config/profile/storage/ProfileStorage.kt index f8972d1df..ff570a101 100644 --- a/src/main/java/de/bixilon/minosoft/config/profile/storage/ProfileStorage.kt +++ b/src/main/java/de/bixilon/minosoft/config/profile/storage/ProfileStorage.kt @@ -15,7 +15,6 @@ package de.bixilon.minosoft.config.profile.storage interface ProfileStorage { val name: String - val version: Int val path: String? fun invalidate() = Unit diff --git a/src/main/java/de/bixilon/minosoft/config/profile/storage/StorageProfileManager.kt b/src/main/java/de/bixilon/minosoft/config/profile/storage/StorageProfileManager.kt index b49952f58..92ed08149 100644 --- a/src/main/java/de/bixilon/minosoft/config/profile/storage/StorageProfileManager.kt +++ b/src/main/java/de/bixilon/minosoft/config/profile/storage/StorageProfileManager.kt @@ -13,17 +13,34 @@ package de.bixilon.minosoft.config.profile.storage +import com.fasterxml.jackson.databind.InjectableValues +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.node.ObjectNode +import de.bixilon.kutil.cast.CastUtil.nullCast import de.bixilon.kutil.cast.CastUtil.unsafeNull import de.bixilon.kutil.collections.CollectionUtil.mutableBiMapOf import de.bixilon.kutil.collections.map.bi.AbstractMutableBiMap +import de.bixilon.kutil.exception.Broken import de.bixilon.kutil.json.MutableJsonObject import de.bixilon.kutil.observer.DataObserver.Companion.observed import de.bixilon.kutil.observer.map.bi.BiMapObserver.Companion.observedBiMap import de.bixilon.kutil.primitive.IntUtil.toInt +import de.bixilon.minosoft.assets.util.FileUtil.mkdirParent +import de.bixilon.minosoft.assets.util.InputStreamUtil.readJsonObject import de.bixilon.minosoft.config.profile.ProfileType import de.bixilon.minosoft.config.profile.profiles.Profile +import de.bixilon.minosoft.config.profile.storage.ProfileIOUtil.isValidName import de.bixilon.minosoft.data.registries.identified.Identified +import de.bixilon.minosoft.protocol.ProtocolUtil.encodeNetwork +import de.bixilon.minosoft.terminal.RunConfiguration import de.bixilon.minosoft.util.json.Jackson +import de.bixilon.minosoft.util.logging.Log +import de.bixilon.minosoft.util.logging.LogLevels +import de.bixilon.minosoft.util.logging.LogMessageType +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import kotlin.io.path.absolutePathString abstract class StorageProfileManager

: Iterable

, Identified { @@ -40,26 +57,124 @@ abstract class StorageProfileManager

: Iterable

, Identified { open fun migrate(version: Int, data: MutableJsonObject) = Unit - open fun migrate(data: MutableJsonObject) { + open fun migrate(data: MutableJsonObject): Int { val version = data["version"]?.toInt() ?: throw IllegalArgumentException("Data has no version set!") when { - version == latestVersion -> return + version == latestVersion -> return -1 version > latestVersion -> throw IllegalArgumentException("Profile was created with a newer version!") version < latestVersion -> { for (version in version until latestVersion) { migrate(version, data) } - // TODO: log, save + return version } } + Broken() } - fun load(name: String, data: MutableJsonObject) = Unit - fun create(name: String): P = TODO() + private fun createDefault() { + val default = create(DEFAULT_NAME) + selected = default + } + + private fun loadAll() { + val root = RunConfiguration.CONFIG_DIRECTORY.resolve(identifier.namespace).resolve(identifier.path).toFile() + if (!root.exists()) { + root.mkdirs() + return createDefault() + } + var selected = DEFAULT_NAME // root.resolve("selected") + if (!selected.isValidName()) selected = DEFAULT_NAME + val files = root.listFiles() ?: return createDefault() + + for (file in files) { + if (!file.name.endsWith(".json")) continue + val name = file.name.removeSuffix(".json") + if (!name.isValidName()) { + Log.log(LogMessageType.PROFILES, LogLevels.WARN) { "Not loading $file: Invalid name!" } + continue + } + val profile = load(name, file) + profiles[name] = profile + } + + this.selected = this[selected] ?: create(selected) + } + + private fun load(name: String, path: File): P { + val content = FileInputStream(path).readJsonObject(true).toMutableMap() // TODO: is copy needed? + val storage = FileStorage(name, this, path.absolutePath) + return load(storage, content) + } + + fun load() { + loadAll() + } + + fun load(storage: FileStorage, data: MutableJsonObject): P { + val profile = type.create(storage) + update(profile, data) + return profile + } + + fun update(profile: P, data: MutableJsonObject) { + val storage = profile.storage.nullCast() ?: throw IllegalArgumentException("Storage not set!") + val migrated = migrate(data) + if (migrated >= 0) { + Log.log(LogMessageType.PROFILES, LogLevels.INFO) { "Profile ${storage.name} (type=$identifier) was migrated from version $migrated to $latestVersion" } + storage.invalidate() + } + profile.lock.lock() + storage.updating = true + + val injectable = InjectableValues.Std() + injectable.addValue(type.clazz, profile) + Jackson.MAPPER + .readerForUpdating(profile) + .with(injectable) + .readValue

(Jackson.MAPPER.valueToTree(data) as JsonNode) + + storage.updating = false + storage.invalid = false + profile.lock.unlock() + } + + fun create(name: String): P { + if (!name.isValidName()) throw IllegalArgumentException("Invalid profile name!") + val path = RunConfiguration.CONFIG_DIRECTORY.resolve(identifier.namespace).resolve(identifier.path).resolve("$name.json") + val storage = FileStorage(name, this, path.absolutePathString()) + val profile = type.create(storage) + this.profiles[name] = profile + + storage.invalidate() + + return profile + } + + fun save(profile: P) { + val storage = profile.storage?.nullCast() ?: throw IllegalArgumentException("Storage unset!") + if (!storage.invalid) return + val path = File(storage.path) + path.mkdirParent() + + profile.lock.acquire() + val node = Jackson.MAPPER.valueToTree(profile) + node.put("version", latestVersion) + val string = Jackson.MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(node) + val stream = FileOutputStream(path) + stream.write(string.encodeNetwork()) + stream.close() + + storage.invalid = false + + profile.lock.release() + } fun delete(name: String) = Unit fun delete(profile: P) = Unit - operator fun get(name: String): P = TODO() + operator fun get(name: String): P? { + return profiles[name] + } override fun iterator(): Iterator

{ @@ -69,6 +184,5 @@ abstract class StorageProfileManager

: Iterable

, Identified { companion object { const val DEFAULT_NAME = "Default" - val NAME_REGEX = "[\\w_ ]{1,32}".toRegex() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/dialog/profiles/ProfileCreateDialog.kt b/src/main/java/de/bixilon/minosoft/gui/eros/dialog/profiles/ProfileCreateDialog.kt index 6b147b6b3..433719b62 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/dialog/profiles/ProfileCreateDialog.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/dialog/profiles/ProfileCreateDialog.kt @@ -16,6 +16,7 @@ package de.bixilon.minosoft.gui.eros.dialog.profiles import de.bixilon.kutil.cast.CastUtil.unsafeCast import de.bixilon.minosoft.config.profile.manager.ProfileManagers import de.bixilon.minosoft.config.profile.profiles.Profile +import de.bixilon.minosoft.config.profile.storage.ProfileIOUtil.isValidName import de.bixilon.minosoft.config.profile.storage.StorageProfileManager import de.bixilon.minosoft.data.registries.identified.ResourceLocation import de.bixilon.minosoft.gui.eros.controller.JavaFXWindowController @@ -81,7 +82,7 @@ class ProfileCreateDialog( cancelButtonFX.ctext = CANCEL_BUTTON nameFX.textProperty().addListener { _, _, new -> - createButtonFX.isDisable = !StorageProfileManager.NAME_REGEX.matches(new) || manager.profiles[new] != null + createButtonFX.isDisable = !new.isValidName() || manager[new] != null } }