ReModel the Alteration system into subfiles.
This commit is contained in:
parent
b314a28f82
commit
0d94362ca2
4 changed files with 115 additions and 155 deletions
|
|
@ -1,8 +1,6 @@
|
||||||
package com.pixelized.server.lwa.model.alteration
|
package com.pixelized.server.lwa.model.alteration
|
||||||
|
|
||||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
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 com.pixelized.shared.lwa.utils.PathProvider
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -15,12 +13,10 @@ import java.io.File
|
||||||
|
|
||||||
class AlterationStore(
|
class AlterationStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
private val campaignJsonFactory: CampaignJsonFactory,
|
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
) {
|
) {
|
||||||
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
|
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
|
||||||
private val alterationsFlow = MutableStateFlow<List<AlterationJson>>(emptyList())
|
private val alterationsFlow = MutableStateFlow<List<AlterationJson>>(emptyList())
|
||||||
private val activeFlow = MutableStateFlow<Map<String, List<String>>>(emptyMap())
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// build a coroutine scope for async calls
|
// build a coroutine scope for async calls
|
||||||
|
|
@ -28,113 +24,44 @@ class AlterationStore(
|
||||||
// load the initial data
|
// load the initial data
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateAlterations()
|
updateAlterations()
|
||||||
updateActiveAlterations()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun alterationsFlow(): StateFlow<List<AlterationJson>> = alterationsFlow
|
fun alterationsFlow(): StateFlow<List<AlterationJson>> = alterationsFlow
|
||||||
|
|
||||||
fun activeFlow(): StateFlow<Map<String, List<String>>> = activeFlow
|
|
||||||
|
|
||||||
private fun updateAlterations() {
|
private fun updateAlterations() {
|
||||||
alterationsFlow.value = loadAlterations()
|
alterationsFlow.value = try {
|
||||||
}
|
loadAlterations()
|
||||||
|
|
||||||
private fun updateActiveAlterations() {
|
|
||||||
activeFlow.value = loadActiveAlterations()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadAlterations(): List<AlterationJson> {
|
|
||||||
return try {
|
|
||||||
val alterationFile = file()
|
|
||||||
val json = alterationFile.readText(charset = Charsets.UTF_8)
|
|
||||||
if (json.isBlank()) error("alterations file is empty")
|
|
||||||
this.json.decodeFromString<List<AlterationJson>>(json)
|
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
// TODO log exception
|
println(exception) // TODO proper exception handling
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadActiveAlterations(): Map<String, List<String>> {
|
@Throws(
|
||||||
val mainFile = file()
|
FileReadException::class,
|
||||||
val jsonExt = ".json"
|
JsonConversionException::class,
|
||||||
|
)
|
||||||
|
private fun loadAlterations(): List<AlterationJson> {
|
||||||
return directory
|
return directory
|
||||||
.listFiles()
|
.listFiles()
|
||||||
?.filter { file ->
|
|
||||||
// guard ignore the main alteration file and non json files.
|
|
||||||
file.name != mainFile.name && file.name.contains(jsonExt)
|
|
||||||
}
|
|
||||||
?.mapNotNull { file ->
|
?.mapNotNull { file ->
|
||||||
// read the alteration file.
|
|
||||||
val json = try {
|
val json = try {
|
||||||
file.readText(charset = Charsets.UTF_8)
|
file.readText(charset = Charsets.UTF_8)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
throw FileReadException(root = 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 {
|
try {
|
||||||
val alterationIds = this.json.decodeFromString<List<String>>(json)
|
this.json.decodeFromString<AlterationJson>(json)
|
||||||
val characterInstanceId = file.name.dropLast(n = jsonExt.length)
|
|
||||||
characterInstanceId to alterationIds
|
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
// TODO log exception
|
|
||||||
throw JsonConversionException(root = exception)
|
throw JsonConversionException(root = exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?.toMap()
|
?: emptyList()
|
||||||
?: 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<String>.toggle(alterationId: String): MutableList<String> {
|
|
||||||
if (contains(alterationId)) {
|
|
||||||
remove(alterationId)
|
|
||||||
} else {
|
|
||||||
add(alterationId)
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class AlterationStoreException(root: Exception) : Exception(root)
|
sealed class AlterationStoreException(root: Exception) : Exception(root)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class CampaignStore(
|
||||||
private val factory: CampaignJsonFactory,
|
private val factory: CampaignJsonFactory,
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
) {
|
) {
|
||||||
private val flow = MutableStateFlow(value = Campaign.EMPTY)
|
private val campaignFlow = MutableStateFlow(value = Campaign.EMPTY)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// create the directory if needed.
|
// create the directory if needed.
|
||||||
|
|
@ -27,28 +27,51 @@ class CampaignStore(
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
// load the initial data
|
// load the initial data
|
||||||
scope.launch {
|
scope.launch {
|
||||||
update()
|
updateCampaignFromDisk()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun campaignFlow(): StateFlow<Campaign> = flow
|
fun campaignFlow(): StateFlow<Campaign> = campaignFlow
|
||||||
|
|
||||||
suspend fun update() {
|
private fun updateCampaignFromDisk() {
|
||||||
flow.value = load()
|
campaignFlow.value = try {
|
||||||
}
|
loadCampaign()
|
||||||
|
|
||||||
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<CampaignJson>(json)
|
|
||||||
factory.convertFromJson(campaign)
|
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
println(exception) // TODO proper exception handling
|
||||||
Campaign.EMPTY
|
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<CampaignJson>(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
|
// convert the data to json format
|
||||||
val json = try {
|
val json = try {
|
||||||
factory.convertToJson(data = campaign).let(json::encodeToString)
|
factory.convertToJson(data = campaign).let(json::encodeToString)
|
||||||
|
|
@ -66,12 +89,13 @@ class CampaignStore(
|
||||||
throw FileWriteException(root = exception)
|
throw FileWriteException(root = exception)
|
||||||
}
|
}
|
||||||
// Update the dataflow.
|
// Update the dataflow.
|
||||||
flow.value = campaign
|
campaignFlow.value = campaign
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class CampaignStoreException(root: Exception) : Exception(root)
|
sealed class CampaignStoreException(root: Exception) : Exception(root)
|
||||||
class JsonConversionException(root: Exception) : CampaignStoreException(root)
|
class JsonConversionException(root: Exception) : CampaignStoreException(root)
|
||||||
class FileWriteException(root: Exception) : CampaignStoreException(root)
|
class FileWriteException(root: Exception) : CampaignStoreException(root)
|
||||||
|
class FileReadException(root: Exception) : CampaignStoreException(root)
|
||||||
|
|
||||||
private fun file(): File {
|
private fun file(): File {
|
||||||
return File("${pathProvider.campaignPath()}campaign.json")
|
return File("${pathProvider.campaignPath()}campaign.json")
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class CharacterSheetService(
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
private val sheets get() = sheetsFlow.value
|
private val sheets get() = sheetsFlow.value
|
||||||
private val sheetsFlow = store.characterSheetFlow()
|
private val sheetsFlow = store.characterSheetsFlow()
|
||||||
.map { entry -> entry.associateBy { character -> character.id } }
|
.map { entry -> entry.associateBy { character -> character.id } }
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
|
|
|
||||||
|
|
@ -20,74 +20,33 @@ class CharacterSheetStore(
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
) {
|
) {
|
||||||
private val directory = File(pathProvider.characterStorePath()).also { it.mkdirs() }
|
private val directory = File(pathProvider.characterStorePath()).also { it.mkdirs() }
|
||||||
private val flow = MutableStateFlow<List<CharacterSheet>>(value = emptyList())
|
private val characterSheetsFlow = MutableStateFlow<List<CharacterSheet>>(value = emptyList())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
// build a coroutine scope for async calls
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
|
// load the initial data
|
||||||
scope.launch {
|
scope.launch {
|
||||||
flow.value = load()
|
updateCharacterSheets()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun characterSheetFlow(): StateFlow<List<CharacterSheet>> = flow
|
fun characterSheetsFlow(): StateFlow<List<CharacterSheet>> = characterSheetsFlow
|
||||||
|
|
||||||
@Throws(
|
private suspend fun updateCharacterSheets() {
|
||||||
CharacterSheetStoreException::class,
|
characterSheetsFlow.value = try {
|
||||||
FileWriteException::class,
|
loadCharacterSheets()
|
||||||
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) {
|
} 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(
|
@Throws(
|
||||||
CharacterSheetStoreException::class,
|
|
||||||
FileReadException::class,
|
FileReadException::class,
|
||||||
JsonConversionException::class,
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
suspend fun load(): List<CharacterSheet> {
|
suspend fun loadCharacterSheets(): List<CharacterSheet> {
|
||||||
return directory
|
return directory
|
||||||
.listFiles()
|
.listFiles()
|
||||||
?.mapNotNull { file ->
|
?.mapNotNull { file ->
|
||||||
|
|
@ -111,6 +70,56 @@ class CharacterSheetStore(
|
||||||
?: emptyList()
|
?: 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 {
|
private fun characterSheetFile(id: String): File {
|
||||||
return File("${pathProvider.characterStorePath()}${id}.json")
|
return File("${pathProvider.characterStorePath()}${id}.json")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue