profile reading

This commit is contained in:
Moritz Zwerger 2023-11-19 23:44:27 +01:00
parent 7f1c74c36f
commit c37164f63f
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
8 changed files with 157 additions and 31 deletions

View File

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

View File

@ -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<StorageProfileManager<*>>(
GUIProfileManager,
ControlsProfileManager,
OtherProfileManager,
)
) {
fun load(latch: AbstractLatch?) {
val worker = UnconditionalWorker()
for (manager in ProfileManagers) {
worker += { manager.load() }
}
worker.work(latch)
}
}

View File

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

View File

@ -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?) {
}
}

View File

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

View File

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

View File

@ -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<P : Profile> : Iterable<P>, Identified {
@ -40,26 +57,124 @@ abstract class StorageProfileManager<P : Profile> : Iterable<P>, 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<FileStorage>() ?: 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<P>(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<FileStorage>() ?: throw IllegalArgumentException("Storage unset!")
if (!storage.invalid) return
val path = File(storage.path)
path.mkdirParent()
profile.lock.acquire()
val node = Jackson.MAPPER.valueToTree<ObjectNode>(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<P> {
@ -69,6 +184,5 @@ abstract class StorageProfileManager<P : Profile> : Iterable<P>, Identified {
companion object {
const val DEFAULT_NAME = "Default"
val NAME_REGEX = "[\\w_ ]{1,32}".toRegex()
}
}

View File

@ -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<T : Profile>(
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
}
}