From 0d94362ca217f3117ce4e1b25c01c36d30728aad Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Fri, 21 Mar 2025 18:49:22 +0100 Subject: [PATCH] ReModel the Alteration system into subfiles. --- .../lwa/model/alteration/AlterationStore.kt | 101 +++------------- .../lwa/model/campaign/CampaignStore.kt | 54 ++++++--- .../model/character/CharacterSheetService.kt | 2 +- .../model/character/CharacterSheetStore.kt | 113 ++++++++++-------- 4 files changed, 115 insertions(+), 155 deletions(-) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt index db8ed65..cee2d82 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt @@ -1,8 +1,6 @@ package com.pixelized.server.lwa.model.alteration import com.pixelized.shared.lwa.model.alteration.AlterationJson -import com.pixelized.shared.lwa.model.campaign.Campaign -import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.utils.PathProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -15,12 +13,10 @@ import java.io.File class AlterationStore( private val pathProvider: PathProvider, - private val campaignJsonFactory: CampaignJsonFactory, private val json: Json, ) { private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() } private val alterationsFlow = MutableStateFlow>(emptyList()) - private val activeFlow = MutableStateFlow>>(emptyMap()) init { // build a coroutine scope for async calls @@ -28,113 +24,44 @@ class AlterationStore( // load the initial data scope.launch { updateAlterations() - updateActiveAlterations() } } fun alterationsFlow(): StateFlow> = alterationsFlow - fun activeFlow(): StateFlow>> = activeFlow - private fun updateAlterations() { - alterationsFlow.value = loadAlterations() - } - - private fun updateActiveAlterations() { - activeFlow.value = loadActiveAlterations() - } - - private fun loadAlterations(): List { - return try { - val alterationFile = file() - val json = alterationFile.readText(charset = Charsets.UTF_8) - if (json.isBlank()) error("alterations file is empty") - this.json.decodeFromString>(json) + alterationsFlow.value = try { + loadAlterations() } catch (exception: Exception) { - // TODO log exception + println(exception) // TODO proper exception handling emptyList() } } - private fun loadActiveAlterations(): Map> { - val mainFile = file() - val jsonExt = ".json" + @Throws( + FileReadException::class, + JsonConversionException::class, + ) + private fun loadAlterations(): List { return directory .listFiles() - ?.filter { file -> - // guard ignore the main alteration file and non json files. - file.name != mainFile.name && file.name.contains(jsonExt) - } ?.mapNotNull { file -> - // read the alteration file. val json = try { file.readText(charset = Charsets.UTF_8) } catch (exception: Exception) { throw FileReadException(root = exception) } + // Guard, if the json is blank no alteration have been save, ignore this file. + if (json.isBlank()) { + return@mapNotNull null + } try { - val alterationIds = this.json.decodeFromString>(json) - val characterInstanceId = file.name.dropLast(n = jsonExt.length) - characterInstanceId to alterationIds + this.json.decodeFromString(json) } catch (exception: Exception) { - // TODO log exception throw JsonConversionException(root = exception) } } - ?.toMap() - ?: emptyMap() - } - - fun toggleActiveAlteration( - characterInstanceId: Campaign.CharacterInstance.Id, - alterationId: String, - ): Boolean { - val id = campaignJsonFactory.convertToJson(id = characterInstanceId) - // toggle the activation state - val characterActiveAlterationIds = activeFlow.value[id] - ?.toMutableList() - ?.toggle(alterationId = alterationId) - ?: listOf(alterationId) - // build the json string to save - val json = try { - this.json.encodeToString(characterActiveAlterationIds) - } catch (exception: Exception) { - throw JsonConversionException(root = exception) - } - // write the file - try { - val file = file(id = id) - file.writeText( - text = json, - charset = Charsets.UTF_8, - ) - } catch (exception: Exception) { - throw FileWriteException(root = exception) - } - // Update the dataflow. - activeFlow.value = activeFlow.value.toMutableMap().also { - it[id] = characterActiveAlterationIds - } - return true - } - - private fun file(): File { - return File("${pathProvider.alterationsPath()}alterations.json") - } - - private fun file( - id: String, - ): File { - return File("${pathProvider.alterationsPath()}$id.json") - } - - private fun MutableList.toggle(alterationId: String): MutableList { - if (contains(alterationId)) { - remove(alterationId) - } else { - add(alterationId) - } - return this + ?: emptyList() } sealed class AlterationStoreException(root: Exception) : Exception(root) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt index b5eabbf..6f3ef36 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt @@ -18,7 +18,7 @@ class CampaignStore( private val factory: CampaignJsonFactory, private val json: Json, ) { - private val flow = MutableStateFlow(value = Campaign.EMPTY) + private val campaignFlow = MutableStateFlow(value = Campaign.EMPTY) init { // create the directory if needed. @@ -27,28 +27,51 @@ class CampaignStore( val scope = CoroutineScope(Dispatchers.IO + Job()) // load the initial data scope.launch { - update() + updateCampaignFromDisk() } } - fun campaignFlow(): StateFlow = flow + fun campaignFlow(): StateFlow = campaignFlow - suspend fun update() { - flow.value = load() - } - - suspend fun load(): Campaign { - return try { - val json = file().readText(charset = Charsets.UTF_8) - if (json.isBlank()) error("Campaign file is empty") - val campaign = this.json.decodeFromString(json) - factory.convertFromJson(campaign) + private fun updateCampaignFromDisk() { + campaignFlow.value = try { + loadCampaign() } catch (exception: Exception) { + println(exception) // TODO proper exception handling Campaign.EMPTY } } - suspend fun save(campaign: Campaign) { + @Throws( + FileReadException::class, + JsonConversionException::class, + ) + fun loadCampaign(): Campaign { + val file = file() + val json = try { + file.readText(charset = Charsets.UTF_8) + } catch (exception: Exception) { + throw FileReadException(root = exception) + } + + // Guard, if the file is empty we load a default campaign. + if (json.isBlank()) return Campaign.EMPTY + + val campaign = try { + val data = this.json.decodeFromString(json) + factory.convertFromJson(data) + } catch (exception: Exception) { + throw JsonConversionException(root = exception) + } + + return campaign + } + + @Throws( + JsonConversionException::class, + FileWriteException::class, + ) + fun save(campaign: Campaign) { // convert the data to json format val json = try { factory.convertToJson(data = campaign).let(json::encodeToString) @@ -66,12 +89,13 @@ class CampaignStore( throw FileWriteException(root = exception) } // Update the dataflow. - flow.value = campaign + campaignFlow.value = campaign } sealed class CampaignStoreException(root: Exception) : Exception(root) class JsonConversionException(root: Exception) : CampaignStoreException(root) class FileWriteException(root: Exception) : CampaignStoreException(root) + class FileReadException(root: Exception) : CampaignStoreException(root) private fun file(): File { return File("${pathProvider.campaignPath()}campaign.json") diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt index be3f642..3756d0c 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt @@ -18,7 +18,7 @@ class CharacterSheetService( ) { private val scope = CoroutineScope(Dispatchers.IO + Job()) private val sheets get() = sheetsFlow.value - private val sheetsFlow = store.characterSheetFlow() + private val sheetsFlow = store.characterSheetsFlow() .map { entry -> entry.associateBy { character -> character.id } } .stateIn( scope = scope, diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt index 2e678d8..20df234 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt @@ -20,74 +20,33 @@ class CharacterSheetStore( private val json: Json, ) { private val directory = File(pathProvider.characterStorePath()).also { it.mkdirs() } - private val flow = MutableStateFlow>(value = emptyList()) + private val characterSheetsFlow = MutableStateFlow>(value = emptyList()) init { + // build a coroutine scope for async calls val scope = CoroutineScope(Dispatchers.IO + Job()) + // load the initial data scope.launch { - flow.value = load() + updateCharacterSheets() } } - fun characterSheetFlow(): StateFlow> = flow + fun characterSheetsFlow(): StateFlow> = characterSheetsFlow - @Throws( - CharacterSheetStoreException::class, - FileWriteException::class, - JsonConversionException::class, - ) - fun save(sheet: CharacterSheet) { - // convert the character sheet into json format. - val json = try { - factory.convertToJson(sheet = sheet).let(json::encodeToString) + private suspend fun updateCharacterSheets() { + characterSheetsFlow.value = try { + loadCharacterSheets() } catch (exception: Exception) { - throw JsonConversionException(root = exception) + println(exception) // TODO proper exception handling + emptyList() } - // write the character file. - try { - val file = characterSheetFile(id = sheet.id) - file.writeText( - text = json, - charset = Charsets.UTF_8, - ) - } catch (exception: Exception) { - throw FileWriteException(root = exception) - } - // Update the dataflow. - flow.value = flow.value - .toMutableList() - .also { data -> - val index = data.indexOfFirst { it.id == sheet.id } - if (index >= 0) { - data[index] = sheet - } else { - data.add(sheet) - } - } - .sortedWith(compareBy(Collator.getInstance()) { it.name }) - } - - fun delete(id: String): Boolean { - val file = characterSheetFile(id = id) - val deleted = file.delete() - if (deleted) { - flow.value = flow.value.toMutableList() - .also { data -> - data.removeIf { it.id == id } - } - .sortedBy { - it.name - } - } - return deleted } @Throws( - CharacterSheetStoreException::class, FileReadException::class, JsonConversionException::class, ) - suspend fun load(): List { + suspend fun loadCharacterSheets(): List { return directory .listFiles() ?.mapNotNull { file -> @@ -111,6 +70,56 @@ class CharacterSheetStore( ?: emptyList() } + @Throws( + FileWriteException::class, + JsonConversionException::class, + ) + fun save(sheet: CharacterSheet) { + // convert the character sheet into json format. + val json = try { + factory.convertToJson(sheet = sheet).let(json::encodeToString) + } catch (exception: Exception) { + throw JsonConversionException(root = exception) + } + // write the character file. + try { + val file = characterSheetFile(id = sheet.id) + file.writeText( + text = json, + charset = Charsets.UTF_8, + ) + } catch (exception: Exception) { + throw FileWriteException(root = exception) + } + // Update the dataflow. + characterSheetsFlow.value = characterSheetsFlow.value + .toMutableList() + .also { data -> + val index = data.indexOfFirst { it.id == sheet.id } + if (index >= 0) { + data[index] = sheet + } else { + data.add(sheet) + } + } + .sortedWith(compareBy(Collator.getInstance()) { it.name }) + } + + fun delete(id: String): Boolean { + val file = characterSheetFile(id = id) + val deleted = file.delete() + if (deleted) { + characterSheetsFlow.value = characterSheetsFlow.value.toMutableList() + .also { data -> + data.removeIf { it.id == id } + } + .sortedBy { + it.name + } + } + return deleted + } + private fun characterSheetFile(id: String): File { return File("${pathProvider.characterStorePath()}${id}.json") }