Optimize server dispatcher
This commit is contained in:
parent
5632ec7c85
commit
a84c170396
12 changed files with 483 additions and 459 deletions
|
|
@ -12,6 +12,8 @@ import com.pixelized.server.lwa.model.tag.TagService
|
||||||
import com.pixelized.server.lwa.model.tag.TagStore
|
import com.pixelized.server.lwa.model.tag.TagStore
|
||||||
import com.pixelized.server.lwa.server.Engine
|
import com.pixelized.server.lwa.server.Engine
|
||||||
import com.pixelized.shared.lwa.utils.PathProvider
|
import com.pixelized.shared.lwa.utils.PathProvider
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import org.koin.core.module.dsl.createdAtStart
|
import org.koin.core.module.dsl.createdAtStart
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
@ -29,6 +31,9 @@ val toolsDependencies
|
||||||
single {
|
single {
|
||||||
PathProvider(appName = "LwaServer")
|
PathProvider(appName = "LwaServer")
|
||||||
}
|
}
|
||||||
|
factory<CoroutineScope> {
|
||||||
|
CoroutineScope(Job())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val engineDependencies
|
val engineDependencies
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class AlterationService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun save(
|
suspend fun save(
|
||||||
json: AlterationJson,
|
json: AlterationJson,
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
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
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
|
@ -24,15 +24,14 @@ import java.text.Collator
|
||||||
class AlterationStore(
|
class AlterationStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
private val factory: AlterationJsonFactory,
|
private val factory: AlterationJsonFactory,
|
||||||
private val json: Json,
|
private val jsonSerializer: Json,
|
||||||
|
scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
|
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
|
||||||
|
|
||||||
private val alterationFlow = MutableStateFlow<List<Alteration>>(emptyList())
|
private val alterationFlow = MutableStateFlow<List<Alteration>>(emptyList())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// build a coroutine scope for async calls
|
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
|
||||||
// load the initial data
|
// load the initial data
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateAlterationFlow()
|
updateAlterationFlow()
|
||||||
|
|
@ -41,7 +40,7 @@ class AlterationStore(
|
||||||
|
|
||||||
fun alterationsFlow(): StateFlow<List<Alteration>> = alterationFlow
|
fun alterationsFlow(): StateFlow<List<Alteration>> = alterationFlow
|
||||||
|
|
||||||
fun updateAlterationFlow() {
|
suspend fun updateAlterationFlow() {
|
||||||
alterationFlow.value = try {
|
alterationFlow.value = try {
|
||||||
load()
|
load()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|
@ -52,104 +51,110 @@ class AlterationStore(
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
FileReadException::class,
|
FileReadException::class,
|
||||||
|
JsonCodingException::class,
|
||||||
JsonConversionException::class,
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
private fun load(
|
private suspend fun load(
|
||||||
directory: File = this.directory,
|
directory: File = this.directory,
|
||||||
): List<Alteration> {
|
): List<Alteration> {
|
||||||
return directory
|
return withContext(Dispatchers.IO) {
|
||||||
.listFiles()
|
directory
|
||||||
?.mapNotNull { file ->
|
.listFiles()
|
||||||
val json = try {
|
?.mapNotNull { file ->
|
||||||
file.readText(charset = Charsets.UTF_8)
|
val json = try {
|
||||||
} catch (exception: Exception) {
|
file.readText(charset = Charsets.UTF_8)
|
||||||
throw FileReadException(root = exception)
|
} 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
|
||||||
|
}
|
||||||
|
// decode the file
|
||||||
|
val data = try {
|
||||||
|
jsonSerializer.decodeFromString<AlterationJson>(json)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonCodingException(root = exception)
|
||||||
|
}
|
||||||
|
// parse the json string.
|
||||||
|
val alterations = try {
|
||||||
|
factory.convertFromJson(data)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
|
return@mapNotNull alterations
|
||||||
}
|
}
|
||||||
// Guard, if the json is blank no alteration have been save, ignore this file.
|
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.name })
|
||||||
if (json.isBlank()) {
|
?: emptyList()
|
||||||
return@mapNotNull null
|
}
|
||||||
}
|
|
||||||
// decode the file
|
|
||||||
val data = try {
|
|
||||||
this.json.decodeFromString<AlterationJson>(json)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonCodingException(root = exception)
|
|
||||||
}
|
|
||||||
// parse the json string.
|
|
||||||
try {
|
|
||||||
factory.convertFromJson(data)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonConversionException(root = exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.name })
|
|
||||||
?: emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
BusinessException::class,
|
BusinessException::class,
|
||||||
JsonConversionException::class,
|
|
||||||
JsonCodingException::class,
|
|
||||||
FileWriteException::class,
|
FileWriteException::class,
|
||||||
|
JsonCodingException::class,
|
||||||
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
fun save(
|
suspend fun save(
|
||||||
json: AlterationJson,
|
json: AlterationJson,
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
) {
|
) {
|
||||||
val file = alterationFile(id = json.id)
|
withContext(Dispatchers.IO) {
|
||||||
// Guard case on update alteration
|
val file = alterationFile(id = json.id)
|
||||||
if (create && file.exists()) {
|
// Guard case on update alteration
|
||||||
throw BusinessException(
|
if (create && file.exists()) {
|
||||||
message = "Alteration already exist, creation is impossible.",
|
throw BusinessException(
|
||||||
)
|
message = "Alteration already exist, creation is impossible.",
|
||||||
}
|
)
|
||||||
// Transform the json into the model.
|
}
|
||||||
val alteration = try {
|
// Transform the json into the model.
|
||||||
factory.convertFromJson(json)
|
val alteration = try {
|
||||||
} catch (exception: Exception) {
|
factory.convertFromJson(json)
|
||||||
throw JsonConversionException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw JsonConversionException(root = exception)
|
||||||
if (alteration.id.isEmpty()) {
|
}
|
||||||
throw BusinessException(
|
if (alteration.id.isEmpty()) {
|
||||||
message = "Alteration 'id' is a mandatory field.",
|
throw BusinessException(
|
||||||
code = APIResponse.ErrorCode.AlterationId,
|
message = "Alteration 'id' is a mandatory field.",
|
||||||
)
|
code = APIResponse.ErrorCode.AlterationId,
|
||||||
}
|
)
|
||||||
if (alteration.metadata.name.isEmpty()) {
|
}
|
||||||
throw BusinessException(
|
if (alteration.metadata.name.isEmpty()) {
|
||||||
message = "Alteration 'name' is a mandatory field.",
|
throw BusinessException(
|
||||||
code = APIResponse.ErrorCode.AlterationName,
|
message = "Alteration 'name' is a mandatory field.",
|
||||||
)
|
code = APIResponse.ErrorCode.AlterationName,
|
||||||
}
|
)
|
||||||
// Encode the json into a string.
|
}
|
||||||
val data = try {
|
// Encode the json into a string.
|
||||||
this.json.encodeToString(json)
|
val data = try {
|
||||||
} catch (exception: Exception) {
|
jsonSerializer.encodeToString(json)
|
||||||
throw JsonCodingException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw JsonCodingException(root = exception)
|
||||||
// Write the alteration into a file.
|
}
|
||||||
try {
|
// Write the alteration into a file.
|
||||||
file.writeText(
|
try {
|
||||||
text = data,
|
file.writeText(
|
||||||
charset = Charsets.UTF_8,
|
text = data,
|
||||||
)
|
charset = Charsets.UTF_8,
|
||||||
} catch (exception: Exception) {
|
)
|
||||||
throw FileWriteException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw FileWriteException(root = exception)
|
||||||
// Update the dataflow.
|
}
|
||||||
alterationFlow.update { alterations ->
|
// Update the dataflow.
|
||||||
val index = alterations.indexOfFirst { it.id == json.id }
|
alterationFlow.update { alterations ->
|
||||||
alterations.toMutableList()
|
val index = alterations.indexOfFirst { it.id == json.id }
|
||||||
.also {
|
alterations.toMutableList()
|
||||||
if (index >= 0) {
|
.also {
|
||||||
it[index] = alteration
|
if (index >= 0) {
|
||||||
} else {
|
it[index] = alteration
|
||||||
it.add(alteration)
|
} else {
|
||||||
|
it.add(alteration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.sortedWith(compareBy(Collator.getInstance()) {
|
||||||
.sortedWith(compareBy(Collator.getInstance()) {
|
it.metadata.name
|
||||||
it.metadata.name
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class CampaignService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun addCharacter(
|
suspend fun addCharacter(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
) {
|
) {
|
||||||
// Check if the character is already in the campaign.
|
// Check if the character is already in the campaign.
|
||||||
|
|
@ -57,7 +57,7 @@ class CampaignService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun addNpc(
|
suspend fun addNpc(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
) {
|
) {
|
||||||
// Check if the character is already in the campaign.
|
// Check if the character is already in the campaign.
|
||||||
|
|
@ -75,7 +75,7 @@ class CampaignService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun removeCharacter(
|
suspend fun removeCharacter(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
) {
|
) {
|
||||||
// Check if the character is in the campaign.
|
// Check if the character is in the campaign.
|
||||||
|
|
@ -93,7 +93,7 @@ class CampaignService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun removeNpc(
|
suspend fun removeNpc(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
) {
|
) {
|
||||||
// Check if the character is in the campaign.
|
// Check if the character is in the campaign.
|
||||||
|
|
@ -111,7 +111,7 @@ class CampaignService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun setScene(
|
suspend fun setScene(
|
||||||
scene: Campaign.Scene,
|
scene: Campaign.Scene,
|
||||||
) {
|
) {
|
||||||
// save the campaign to the disk + update the flow.
|
// save the campaign to the disk + update the flow.
|
||||||
|
|
@ -122,7 +122,7 @@ class CampaignService(
|
||||||
|
|
||||||
// Data manipulation through WebSocket.
|
// Data manipulation through WebSocket.
|
||||||
|
|
||||||
fun updateToggleParty() {
|
suspend fun updateToggleParty() {
|
||||||
store.save(
|
store.save(
|
||||||
campaign = campaign.copy(
|
campaign = campaign.copy(
|
||||||
options = campaign.options.copy(
|
options = campaign.options.copy(
|
||||||
|
|
@ -132,7 +132,7 @@ class CampaignService(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateToggleNpc() {
|
suspend fun updateToggleNpc() {
|
||||||
store.save(
|
store.save(
|
||||||
campaign = campaign.copy(
|
campaign = campaign.copy(
|
||||||
options = campaign.options.copy(
|
options = campaign.options.copy(
|
||||||
|
|
|
||||||
|
|
@ -15,21 +15,21 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class CampaignStore(
|
class CampaignStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
private val factory: CampaignJsonFactory,
|
private val factory: CampaignJsonFactory,
|
||||||
private val json: Json,
|
private val jsonSerializer: Json,
|
||||||
|
scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val campaignFlow = MutableStateFlow(value = Campaign.empty())
|
private val campaignFlow = MutableStateFlow(value = Campaign.empty())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// create the directory if needed.
|
// create the directory if needed.
|
||||||
File(pathProvider.campaignPath()).also { it.mkdirs() }
|
File(pathProvider.campaignPath()).also { it.mkdirs() }
|
||||||
// build a coroutine scope for async calls
|
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
|
||||||
// load the initial data
|
// load the initial data
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateCampaignFlow()
|
updateCampaignFlow()
|
||||||
|
|
@ -38,7 +38,7 @@ class CampaignStore(
|
||||||
|
|
||||||
fun campaignFlow(): StateFlow<Campaign> = campaignFlow
|
fun campaignFlow(): StateFlow<Campaign> = campaignFlow
|
||||||
|
|
||||||
fun updateCampaignFlow() {
|
suspend fun updateCampaignFlow() {
|
||||||
campaignFlow.value = try {
|
campaignFlow.value = try {
|
||||||
load()
|
load()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|
@ -52,29 +52,31 @@ class CampaignStore(
|
||||||
JsonCodingException::class,
|
JsonCodingException::class,
|
||||||
JsonConversionException::class,
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
fun load(): Campaign {
|
suspend fun load(): Campaign {
|
||||||
val file = campaignFile()
|
return withContext(Dispatchers.IO) {
|
||||||
// Read the campaign file.
|
val file = campaignFile()
|
||||||
val data = try {
|
// Read the campaign file.
|
||||||
file.readText(charset = Charsets.UTF_8)
|
val data = try {
|
||||||
} catch (exception: Exception) {
|
file.readText(charset = Charsets.UTF_8)
|
||||||
throw FileReadException(root = exception)
|
} catch (exception: Exception) {
|
||||||
|
throw FileReadException(root = exception)
|
||||||
|
}
|
||||||
|
// Guard, if the file is empty we load a default campaign.
|
||||||
|
if (data.isBlank()) return@withContext Campaign.empty()
|
||||||
|
// Decode the json into a string.
|
||||||
|
val json = try {
|
||||||
|
jsonSerializer.decodeFromString<CampaignJson>(data)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonCodingException(root = exception)
|
||||||
|
}
|
||||||
|
// Convert from the Json format
|
||||||
|
val campaign = try {
|
||||||
|
factory.convertFromJson(json)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
|
return@withContext campaign
|
||||||
}
|
}
|
||||||
// Guard, if the file is empty we load a default campaign.
|
|
||||||
if (data.isBlank()) return Campaign.empty()
|
|
||||||
// Decode the json into a string.
|
|
||||||
val json = try {
|
|
||||||
this.json.decodeFromString<CampaignJson>(data)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonCodingException(root = exception)
|
|
||||||
}
|
|
||||||
// Convert from the Json format
|
|
||||||
val campaign = try {
|
|
||||||
factory.convertFromJson(json)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonConversionException(root = exception)
|
|
||||||
}
|
|
||||||
return campaign
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
|
|
@ -82,31 +84,33 @@ class CampaignStore(
|
||||||
JsonCodingException::class,
|
JsonCodingException::class,
|
||||||
FileWriteException::class,
|
FileWriteException::class,
|
||||||
)
|
)
|
||||||
fun save(campaign: Campaign) {
|
suspend fun save(campaign: Campaign) {
|
||||||
// Transform the json into the model.
|
withContext(Dispatchers.IO) {
|
||||||
val json = try {
|
// Transform the json into the model.
|
||||||
factory.convertToJson(campaign = campaign)
|
val json = try {
|
||||||
} catch (exception: Exception) {
|
factory.convertToJson(campaign = campaign)
|
||||||
throw JsonConversionException(root = exception)
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
|
// Encode the json into a string.
|
||||||
|
val data = try {
|
||||||
|
jsonSerializer.encodeToString(json)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonCodingException(root = exception)
|
||||||
|
}
|
||||||
|
// Write the file
|
||||||
|
try {
|
||||||
|
val file = campaignFile()
|
||||||
|
file.writeText(
|
||||||
|
text = data,
|
||||||
|
charset = Charsets.UTF_8,
|
||||||
|
)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw FileWriteException(root = exception)
|
||||||
|
}
|
||||||
|
// Update the dataflow.
|
||||||
|
campaignFlow.update { campaign }
|
||||||
}
|
}
|
||||||
// Encode the json into a string.
|
|
||||||
val data = try {
|
|
||||||
this.json.encodeToString(json)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonCodingException(root = exception)
|
|
||||||
}
|
|
||||||
// Write the file
|
|
||||||
try {
|
|
||||||
val file = campaignFile()
|
|
||||||
file.writeText(
|
|
||||||
text = data,
|
|
||||||
charset = Charsets.UTF_8,
|
|
||||||
)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw FileWriteException(root = exception)
|
|
||||||
}
|
|
||||||
// Update the dataflow.
|
|
||||||
campaignFlow.update { campaign }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun campaignFile(): File {
|
private fun campaignFile(): File {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class CharacterSheetService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun deleteCharacterSheet(
|
suspend fun deleteCharacterSheet(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
) {
|
) {
|
||||||
characterStore.delete(
|
characterStore.delete(
|
||||||
|
|
@ -65,7 +65,7 @@ class CharacterSheetService(
|
||||||
|
|
||||||
// Data manipulation through WebSocket.
|
// Data manipulation through WebSocket.
|
||||||
|
|
||||||
fun updateAlteration(
|
suspend fun updateAlteration(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
alterationId: String,
|
alterationId: String,
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
|
|
@ -97,7 +97,7 @@ class CharacterSheetService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDamage(
|
suspend fun updateDamage(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
damage: Int,
|
damage: Int,
|
||||||
) {
|
) {
|
||||||
|
|
@ -110,7 +110,7 @@ class CharacterSheetService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDiminished(
|
suspend fun updateDiminished(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
diminished: Int,
|
diminished: Int,
|
||||||
) {
|
) {
|
||||||
|
|
@ -123,7 +123,7 @@ class CharacterSheetService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFatigue(
|
suspend fun updateFatigue(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
fatigue: Int,
|
fatigue: Int,
|
||||||
) {
|
) {
|
||||||
|
|
@ -136,7 +136,7 @@ class CharacterSheetService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCharacterSkillUsage(
|
suspend fun updateCharacterSkillUsage(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
skillId: String,
|
skillId: String,
|
||||||
used: Boolean,
|
used: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
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
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
|
@ -24,14 +24,13 @@ import java.text.Collator
|
||||||
class CharacterSheetStore(
|
class CharacterSheetStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
private val factory: CharacterSheetJsonFactory,
|
private val factory: CharacterSheetJsonFactory,
|
||||||
private val json: Json,
|
private val jsonSerializer: Json,
|
||||||
|
scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val directory = File(pathProvider.characterStorePath()).also { it.mkdirs() }
|
private val directory = File(pathProvider.characterStorePath()).also { it.mkdirs() }
|
||||||
private val characterSheetsFlow = 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())
|
|
||||||
// load the initial data
|
// load the initial data
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateCharacterFlow()
|
updateCharacterFlow()
|
||||||
|
|
@ -40,7 +39,7 @@ class CharacterSheetStore(
|
||||||
|
|
||||||
fun characterSheetsFlow(): StateFlow<List<CharacterSheet>> = characterSheetsFlow
|
fun characterSheetsFlow(): StateFlow<List<CharacterSheet>> = characterSheetsFlow
|
||||||
|
|
||||||
fun updateCharacterFlow() {
|
suspend fun updateCharacterFlow() {
|
||||||
characterSheetsFlow.value = try {
|
characterSheetsFlow.value = try {
|
||||||
load()
|
load()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|
@ -54,112 +53,118 @@ class CharacterSheetStore(
|
||||||
JsonCodingException::class,
|
JsonCodingException::class,
|
||||||
JsonConversionException::class,
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
fun load(
|
suspend fun load(
|
||||||
directory: File = this.directory,
|
directory: File = this.directory,
|
||||||
): List<CharacterSheet> {
|
): List<CharacterSheet> {
|
||||||
return directory
|
return withContext(Dispatchers.IO) {
|
||||||
.listFiles()
|
directory
|
||||||
?.mapNotNull { file ->
|
.listFiles()
|
||||||
val json = try {
|
?.mapNotNull { file ->
|
||||||
file.readText(charset = Charsets.UTF_8)
|
val json = try {
|
||||||
} catch (exception: Exception) {
|
file.readText(charset = Charsets.UTF_8)
|
||||||
throw FileReadException(root = exception)
|
} catch (exception: Exception) {
|
||||||
|
throw FileReadException(root = exception)
|
||||||
|
}
|
||||||
|
// Guard, if the json is blank no character have been save, ignore this file.
|
||||||
|
if (json.isBlank()) {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
// decode the file
|
||||||
|
val data = try {
|
||||||
|
jsonSerializer.decodeFromString<CharacterSheetJson>(json)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonCodingException(root = exception)
|
||||||
|
}
|
||||||
|
// parse the json string.
|
||||||
|
try {
|
||||||
|
factory.convertFromJson(data)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Guard, if the json is blank no character have been save, ignore this file.
|
?.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
||||||
if (json.isBlank()) {
|
?: emptyList()
|
||||||
return@mapNotNull null
|
}
|
||||||
}
|
|
||||||
// decode the file
|
|
||||||
val data = try {
|
|
||||||
this.json.decodeFromString<CharacterSheetJson>(json)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonCodingException(root = exception)
|
|
||||||
}
|
|
||||||
// parse the json string.
|
|
||||||
try {
|
|
||||||
factory.convertFromJson(data)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonConversionException(root = exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
|
||||||
?: emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
BusinessException::class,
|
BusinessException::class,
|
||||||
JsonConversionException::class,
|
|
||||||
JsonCodingException::class,
|
|
||||||
FileWriteException::class,
|
FileWriteException::class,
|
||||||
|
JsonCodingException::class,
|
||||||
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
fun save(
|
suspend fun save(
|
||||||
sheet: CharacterSheet,
|
sheet: CharacterSheet,
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
) {
|
) {
|
||||||
val file = characterSheetFile(id = sheet.id)
|
withContext(Dispatchers.IO) {
|
||||||
// Guard case on update alteration
|
val file = characterSheetFile(id = sheet.id)
|
||||||
if (create && file.exists()) {
|
// Guard case on update alteration
|
||||||
throw BusinessException(message = "Character already exist, creation is impossible.")
|
if (create && file.exists()) {
|
||||||
}
|
throw BusinessException(message = "Character already exist, creation is impossible.")
|
||||||
// Transform the json into the model.
|
}
|
||||||
val json = try {
|
// Transform the json into the model.
|
||||||
factory.convertToJson(sheet = sheet)
|
val json = try {
|
||||||
} catch (exception: Exception) {
|
factory.convertToJson(sheet = sheet)
|
||||||
throw JsonConversionException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw JsonConversionException(root = exception)
|
||||||
// Encode the json into a string.
|
}
|
||||||
val data = try {
|
// Encode the json into a string.
|
||||||
this.json.encodeToString(json)
|
val data = try {
|
||||||
} catch (exception: Exception) {
|
jsonSerializer.encodeToString(json)
|
||||||
throw JsonCodingException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw JsonCodingException(root = exception)
|
||||||
// write the character file.
|
}
|
||||||
try {
|
// write the character file.
|
||||||
file.writeText(
|
try {
|
||||||
text = data,
|
file.writeText(
|
||||||
charset = Charsets.UTF_8,
|
text = data,
|
||||||
)
|
charset = Charsets.UTF_8,
|
||||||
} catch (exception: Exception) {
|
)
|
||||||
throw FileWriteException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw FileWriteException(root = exception)
|
||||||
// Update the dataflow.
|
}
|
||||||
characterSheetsFlow.update { characters ->
|
// Update the dataflow.
|
||||||
characters.toMutableList()
|
characterSheetsFlow.update { characters ->
|
||||||
.also { data ->
|
characters.toMutableList()
|
||||||
val index = data.indexOfFirst { it.id == sheet.id }
|
.also { data ->
|
||||||
if (index >= 0) {
|
val index = data.indexOfFirst { it.id == sheet.id }
|
||||||
data[index] = sheet
|
if (index >= 0) {
|
||||||
} else {
|
data[index] = sheet
|
||||||
data.add(sheet)
|
} else {
|
||||||
|
data.add(sheet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.sortedWith(compareBy(Collator.getInstance()) {
|
||||||
.sortedWith(compareBy(Collator.getInstance()) {
|
it.name
|
||||||
it.name
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(BusinessException::class)
|
@Throws(BusinessException::class)
|
||||||
fun delete(characterSheetId: String) {
|
suspend fun delete(characterSheetId: String) {
|
||||||
val file = characterSheetFile(id = characterSheetId)
|
withContext(Dispatchers.IO) {
|
||||||
// Guard case on the file existence.
|
val file = characterSheetFile(id = characterSheetId)
|
||||||
if (file.exists().not()) {
|
// Guard case on the file existence.
|
||||||
throw BusinessException(
|
if (file.exists().not()) {
|
||||||
message = "Character file with id:$characterSheetId doesn't not exist.",
|
throw BusinessException(
|
||||||
code = APIResponse.ErrorCode.CharacterSheetId
|
message = "Character file with id:$characterSheetId doesn't not exist.",
|
||||||
)
|
code = APIResponse.ErrorCode.CharacterSheetId
|
||||||
}
|
)
|
||||||
// Guard case on the file deletion
|
}
|
||||||
if (file.delete().not()) {
|
// Guard case on the file deletion
|
||||||
throw BusinessException(
|
if (file.delete().not()) {
|
||||||
message = "Character file have not been deleted for unknown reason.",
|
throw BusinessException(
|
||||||
)
|
message = "Character file have not been deleted for unknown reason.",
|
||||||
}
|
)
|
||||||
// Update the data model with the deleted character.
|
}
|
||||||
characterSheetsFlow.update { characters ->
|
// Update the data model with the deleted character.
|
||||||
characters.toMutableList()
|
characterSheetsFlow.update { characters ->
|
||||||
.also { data -> data.removeIf { it.id == characterSheetId } }
|
characters.toMutableList()
|
||||||
.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
.also { data -> data.removeIf { it.id == characterSheetId } }
|
||||||
|
.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class InventoryService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun updatePurse(
|
suspend fun updatePurse(
|
||||||
purse: ApiPurseJson,
|
purse: ApiPurseJson,
|
||||||
) {
|
) {
|
||||||
val inventory = inventory(
|
val inventory = inventory(
|
||||||
|
|
@ -59,7 +59,7 @@ class InventoryService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun save(
|
suspend fun save(
|
||||||
inventoryJson: InventoryJson,
|
inventoryJson: InventoryJson,
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
@ -70,12 +70,12 @@ class InventoryService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun delete(characterSheetId: String) {
|
suspend fun delete(characterSheetId: String) {
|
||||||
inventoryStore.delete(characterSheetId = characterSheetId)
|
inventoryStore.delete(characterSheetId = characterSheetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun createInventoryItem(
|
suspend fun createInventoryItem(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
itemId: String,
|
itemId: String,
|
||||||
count: Float,
|
count: Float,
|
||||||
|
|
@ -107,7 +107,7 @@ class InventoryService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun changeInventoryItemCount(
|
suspend fun changeInventoryItemCount(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
inventoryId: String,
|
inventoryId: String,
|
||||||
count: Float,
|
count: Float,
|
||||||
|
|
@ -145,7 +145,7 @@ class InventoryService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun consumeInventoryItem(
|
suspend fun consumeInventoryItem(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
inventoryId: String,
|
inventoryId: String,
|
||||||
) {
|
) {
|
||||||
|
|
@ -185,7 +185,7 @@ class InventoryService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun equipInventoryItem(
|
suspend fun equipInventoryItem(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
inventoryId: String,
|
inventoryId: String,
|
||||||
equip: Boolean,
|
equip: Boolean,
|
||||||
|
|
@ -223,7 +223,7 @@ class InventoryService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun deleteInventoryItem(
|
suspend fun deleteInventoryItem(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
inventoryId: String,
|
inventoryId: String,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -12,26 +12,24 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
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
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
class InventoryStore(
|
class InventoryStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
private val factory: InventoryJsonFactory,
|
private val factory: InventoryJsonFactory,
|
||||||
private val json: Json,
|
private val jsonSerializer: Json,
|
||||||
|
scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val directory = File(pathProvider.inventoryPath()).also { it.mkdirs() }
|
private val directory = File(pathProvider.inventoryPath()).also { it.mkdirs() }
|
||||||
private val inventoryFlow = MutableStateFlow<Map<String, Inventory>>(value = emptyMap())
|
private val inventoryFlow = MutableStateFlow<Map<String, Inventory>>(value = emptyMap())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// build a coroutine scope for async calls
|
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
|
||||||
// load the initial data
|
// load the initial data
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateInventoryFlow()
|
updateInventoryFlow()
|
||||||
|
|
@ -40,7 +38,7 @@ class InventoryStore(
|
||||||
|
|
||||||
fun inventoryFlow(): StateFlow<Map<String, Inventory>> = inventoryFlow
|
fun inventoryFlow(): StateFlow<Map<String, Inventory>> = inventoryFlow
|
||||||
|
|
||||||
fun updateInventoryFlow() {
|
suspend fun updateInventoryFlow() {
|
||||||
inventoryFlow.value = try {
|
inventoryFlow.value = try {
|
||||||
load()
|
load()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|
@ -54,102 +52,104 @@ class InventoryStore(
|
||||||
JsonCodingException::class,
|
JsonCodingException::class,
|
||||||
JsonConversionException::class,
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
private fun load(
|
private suspend fun load(
|
||||||
directory: File = this.directory,
|
directory: File = this.directory,
|
||||||
): Map<String, Inventory> {
|
): Map<String, Inventory> {
|
||||||
return directory
|
return withContext(Dispatchers.IO) {
|
||||||
.listFiles()
|
directory
|
||||||
?.mapNotNull { file ->
|
.listFiles()
|
||||||
val json = try {
|
?.mapNotNull { file ->
|
||||||
file.readText(charset = Charsets.UTF_8)
|
val json = try {
|
||||||
} catch (exception: Exception) {
|
file.readText(charset = Charsets.UTF_8)
|
||||||
throw FileReadException(root = exception)
|
} catch (exception: Exception) {
|
||||||
|
throw FileReadException(root = exception)
|
||||||
|
}
|
||||||
|
// Guard, if the json is blank no character have been save, ignore this file.
|
||||||
|
if (json.isBlank()) {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
// decode the file
|
||||||
|
val data = try {
|
||||||
|
jsonSerializer.decodeFromString<InventoryJson>(json)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonCodingException(root = exception)
|
||||||
|
}
|
||||||
|
// parse the json string.
|
||||||
|
val inventory = try {
|
||||||
|
factory.convertFromJson(data)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
|
inventory.characterSheetId to inventory
|
||||||
}
|
}
|
||||||
// Guard, if the json is blank no character have been save, ignore this file.
|
?.toMap()
|
||||||
if (json.isBlank()) {
|
?: emptyMap()
|
||||||
return@mapNotNull null
|
}
|
||||||
}
|
|
||||||
// decode the file
|
|
||||||
val data = try {
|
|
||||||
this.json.decodeFromString<InventoryJson>(json)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonCodingException(root = exception)
|
|
||||||
}
|
|
||||||
// parse the json string.
|
|
||||||
val inventory = try {
|
|
||||||
factory.convertFromJson(data)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonConversionException(root = exception)
|
|
||||||
}
|
|
||||||
inventory.characterSheetId to inventory
|
|
||||||
}
|
|
||||||
?.toMap()
|
|
||||||
?: emptyMap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
BusinessException::class,
|
BusinessException::class,
|
||||||
JsonConversionException::class,
|
|
||||||
JsonCodingException::class,
|
|
||||||
FileWriteException::class,
|
FileWriteException::class,
|
||||||
|
JsonCodingException::class,
|
||||||
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
fun save(
|
suspend fun save(
|
||||||
inventory: Inventory,
|
inventory: Inventory,
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
) {
|
) {
|
||||||
val file = inventoryFile(id = inventory.characterSheetId)
|
withContext(Dispatchers.IO) {
|
||||||
|
val file = inventoryFile(
|
||||||
if (create && file.exists()) {
|
id = inventory.characterSheetId
|
||||||
throw BusinessException(message = "Inventory already exist, creation is impossible.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val json = try {
|
|
||||||
factory.convertToJson(inventory = inventory)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonConversionException(root = exception)
|
|
||||||
}
|
|
||||||
|
|
||||||
val data = try {
|
|
||||||
this.json.encodeToString(json)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonCodingException(root = exception)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
file.writeText(
|
|
||||||
text = data,
|
|
||||||
charset = Charsets.UTF_8,
|
|
||||||
)
|
)
|
||||||
} catch (exception: Exception) {
|
if (create && file.exists()) {
|
||||||
throw FileWriteException(root = exception)
|
throw BusinessException(message = "Inventory already exist, creation is impossible.")
|
||||||
}
|
}
|
||||||
|
val json = try {
|
||||||
inventoryFlow.update { flow ->
|
factory.convertToJson(inventory = inventory)
|
||||||
flow.toMutableMap().also { data ->
|
} catch (exception: Exception) {
|
||||||
data[inventory.characterSheetId] = inventory
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
|
val data = try {
|
||||||
|
jsonSerializer.encodeToString(json)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonCodingException(root = exception)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
file.writeText(
|
||||||
|
text = data,
|
||||||
|
charset = Charsets.UTF_8,
|
||||||
|
)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw FileWriteException(root = exception)
|
||||||
|
}
|
||||||
|
inventoryFlow.update { flow ->
|
||||||
|
flow.toMutableMap().also { data ->
|
||||||
|
data[inventory.characterSheetId] = inventory
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(BusinessException::class)
|
@Throws(BusinessException::class)
|
||||||
fun delete(characterSheetId: String) {
|
suspend fun delete(characterSheetId: String) {
|
||||||
val file = inventoryFile(id = characterSheetId)
|
withContext(Dispatchers.IO) {
|
||||||
|
val file = inventoryFile(
|
||||||
if (file.exists().not()) {
|
id = characterSheetId,
|
||||||
throw BusinessException(
|
|
||||||
message = "Inventory file with id:$characterSheetId doesn't not exist.",
|
|
||||||
code = APIResponse.ErrorCode.CharacterSheetId
|
|
||||||
)
|
)
|
||||||
}
|
if (file.exists().not()) {
|
||||||
|
throw BusinessException(
|
||||||
if (file.delete().not()) {
|
message = "Inventory file with id:$characterSheetId doesn't not exist.",
|
||||||
throw BusinessException(
|
code = APIResponse.ErrorCode.CharacterSheetId
|
||||||
message = "Inventory file have not been deleted for unknown reason.",
|
)
|
||||||
)
|
}
|
||||||
}
|
if (file.delete().not()) {
|
||||||
|
throw BusinessException(
|
||||||
inventoryFlow.update { characters ->
|
message = "Inventory file have not been deleted for unknown reason.",
|
||||||
characters.toMutableMap().also { data -> data.remove(characterSheetId) }
|
)
|
||||||
|
}
|
||||||
|
inventoryFlow.update { characters ->
|
||||||
|
characters.toMutableMap().also { data -> data.remove(characterSheetId) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class ItemService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun save(
|
suspend fun save(
|
||||||
json: ItemJson,
|
json: ItemJson,
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
@ -43,7 +43,7 @@ class ItemService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
fun delete(itemId: String) {
|
suspend fun delete(itemId: String) {
|
||||||
itemStore.delete(id = itemId)
|
itemStore.delete(id = itemId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
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
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
|
@ -24,14 +24,13 @@ import java.text.Collator
|
||||||
class ItemStore(
|
class ItemStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
private val factory: ItemJsonFactory,
|
private val factory: ItemJsonFactory,
|
||||||
private val json: Json,
|
private val jsonSerializer: Json,
|
||||||
|
scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val directory = File(pathProvider.itemsPath()).also { it.mkdirs() }
|
private val directory = File(pathProvider.itemsPath()).also { it.mkdirs() }
|
||||||
private val itemFlow = MutableStateFlow<List<Item>>(emptyList())
|
private val itemFlow = MutableStateFlow<List<Item>>(emptyList())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateItemsFlow()
|
updateItemsFlow()
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +42,7 @@ class ItemStore(
|
||||||
|
|
||||||
fun itemsFlow(): StateFlow<List<Item>> = itemFlow
|
fun itemsFlow(): StateFlow<List<Item>> = itemFlow
|
||||||
|
|
||||||
fun updateItemsFlow() {
|
suspend fun updateItemsFlow() {
|
||||||
itemFlow.value = try {
|
itemFlow.value = try {
|
||||||
load()
|
load()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|
@ -54,131 +53,138 @@ class ItemStore(
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
FileReadException::class,
|
FileReadException::class,
|
||||||
|
JsonCodingException::class,
|
||||||
JsonConversionException::class,
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
private fun load(
|
private suspend fun load(
|
||||||
directory: File = this.directory,
|
directory: File = this.directory,
|
||||||
): List<Item> {
|
): List<Item> {
|
||||||
return directory
|
return withContext(Dispatchers.IO) {
|
||||||
.listFiles()
|
directory
|
||||||
?.mapNotNull { file ->
|
.listFiles()
|
||||||
val json = try {
|
?.mapNotNull { file ->
|
||||||
file.readText(charset = Charsets.UTF_8)
|
val json = try {
|
||||||
} catch (exception: Exception) {
|
file.readText(charset = Charsets.UTF_8)
|
||||||
throw FileReadException(root = exception)
|
} catch (exception: Exception) {
|
||||||
|
throw FileReadException(root = exception)
|
||||||
|
}
|
||||||
|
// Guard, if the json is blank no item have been save, ignore this file.
|
||||||
|
if (json.isBlank()) {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
// decode the file
|
||||||
|
val data = try {
|
||||||
|
jsonSerializer.decodeFromString<ItemJson>(json)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonCodingException(root = exception)
|
||||||
|
}
|
||||||
|
// parse the json string.
|
||||||
|
try {
|
||||||
|
factory.convertFromJson(data)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Guard, if the json is blank no item have been save, ignore this file.
|
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.label })
|
||||||
if (json.isBlank()) {
|
?: emptyList()
|
||||||
return@mapNotNull null
|
}
|
||||||
}
|
|
||||||
// decode the file
|
|
||||||
val data = try {
|
|
||||||
this.json.decodeFromString<ItemJson>(json)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonCodingException(root = exception)
|
|
||||||
}
|
|
||||||
// parse the json string.
|
|
||||||
try {
|
|
||||||
factory.convertFromJson(data)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
throw JsonConversionException(root = exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.label })
|
|
||||||
?: emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
BusinessException::class,
|
BusinessException::class,
|
||||||
JsonConversionException::class,
|
|
||||||
JsonCodingException::class,
|
|
||||||
FileWriteException::class,
|
FileWriteException::class,
|
||||||
|
JsonCodingException::class,
|
||||||
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
fun save(
|
suspend fun save(
|
||||||
json: ItemJson,
|
json: ItemJson,
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
) {
|
) {
|
||||||
val file = itemFile(id = json.id)
|
withContext(Dispatchers.IO) {
|
||||||
// Guard case on update alteration
|
val file = itemFile(id = json.id)
|
||||||
if (create && file.exists()) {
|
// Guard case on update alteration
|
||||||
throw BusinessException(
|
if (create && file.exists()) {
|
||||||
message = "Item already exist, creation is impossible.",
|
throw BusinessException(
|
||||||
)
|
message = "Item already exist, creation is impossible.",
|
||||||
}
|
)
|
||||||
// Transform the json into the model.
|
}
|
||||||
val item = try {
|
// Transform the json into the model.
|
||||||
factory.convertFromJson(json)
|
val item = try {
|
||||||
} catch (exception: Exception) {
|
factory.convertFromJson(json)
|
||||||
throw JsonConversionException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw JsonConversionException(root = exception)
|
||||||
if (item.id.isEmpty()) {
|
}
|
||||||
throw BusinessException(
|
if (item.id.isEmpty()) {
|
||||||
message = "Item 'id' is a mandatory field.",
|
throw BusinessException(
|
||||||
code = APIResponse.ErrorCode.ItemId,
|
message = "Item 'id' is a mandatory field.",
|
||||||
)
|
code = APIResponse.ErrorCode.ItemId,
|
||||||
}
|
)
|
||||||
if (item.metadata.label.isEmpty()) {
|
}
|
||||||
throw BusinessException(
|
if (item.metadata.label.isEmpty()) {
|
||||||
message = "Item 'name' is a mandatory field.",
|
throw BusinessException(
|
||||||
code = APIResponse.ErrorCode.ItemName,
|
message = "Item 'name' is a mandatory field.",
|
||||||
)
|
code = APIResponse.ErrorCode.ItemName,
|
||||||
}
|
)
|
||||||
// Encode the json into a string.
|
}
|
||||||
val data = try {
|
// Encode the json into a string.
|
||||||
this.json.encodeToString(json)
|
val data = try {
|
||||||
} catch (exception: Exception) {
|
jsonSerializer.encodeToString(json)
|
||||||
throw JsonCodingException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw JsonCodingException(root = exception)
|
||||||
// Write the alteration into a file.
|
}
|
||||||
try {
|
// Write the alteration into a file.
|
||||||
file.writeText(
|
try {
|
||||||
text = data,
|
file.writeText(
|
||||||
charset = Charsets.UTF_8,
|
text = data,
|
||||||
)
|
charset = Charsets.UTF_8,
|
||||||
} catch (exception: Exception) {
|
)
|
||||||
throw FileWriteException(root = exception)
|
} catch (exception: Exception) {
|
||||||
}
|
throw FileWriteException(root = exception)
|
||||||
// Update the dataflow.
|
}
|
||||||
itemFlow.update { items ->
|
// Update the dataflow.
|
||||||
val index = items.indexOfFirst { it.id == json.id }
|
itemFlow.update { items ->
|
||||||
items.toMutableList()
|
val index = items.indexOfFirst { it.id == json.id }
|
||||||
.also {
|
items.toMutableList()
|
||||||
if (index >= 0) {
|
.also {
|
||||||
it[index] = item
|
if (index >= 0) {
|
||||||
} else {
|
it[index] = item
|
||||||
it.add(item)
|
} else {
|
||||||
|
it.add(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.sortedWith(compareBy(Collator.getInstance()) {
|
||||||
.sortedWith(compareBy(Collator.getInstance()) {
|
it.metadata.label
|
||||||
it.metadata.label
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(BusinessException::class)
|
@Throws(BusinessException::class)
|
||||||
fun delete(id: String) {
|
suspend fun delete(id: String) {
|
||||||
val file = itemFile(id = id)
|
withContext(Dispatchers.IO) {
|
||||||
// Guard case on the file existence.
|
val file = itemFile(id = id)
|
||||||
if (file.exists().not()) {
|
// Guard case on the file existence.
|
||||||
throw BusinessException(
|
if (file.exists().not()) {
|
||||||
message = "Item doesn't not exist, deletion is impossible.",
|
throw BusinessException(
|
||||||
)
|
message = "Item doesn't not exist, deletion is impossible.",
|
||||||
}
|
)
|
||||||
// Guard case on the file deletion
|
}
|
||||||
if (file.delete().not()) {
|
// Guard case on the file deletion
|
||||||
throw BusinessException(
|
if (file.delete().not()) {
|
||||||
message = "Item file have not been deleted for unknown reason.",
|
throw BusinessException(
|
||||||
)
|
message = "Item file have not been deleted for unknown reason.",
|
||||||
}
|
)
|
||||||
// Update the data model with the deleted alteration.
|
}
|
||||||
itemFlow.update { items ->
|
// Update the data model with the deleted alteration.
|
||||||
items.toMutableList()
|
itemFlow.update { items ->
|
||||||
.also { item ->
|
items.toMutableList()
|
||||||
item.removeIf { it.id == id }
|
.also { item ->
|
||||||
}
|
item.removeIf { it.id == id }
|
||||||
.sortedWith(compareBy(Collator.getInstance()) {
|
}
|
||||||
it.metadata.label
|
.sortedWith(compareBy(Collator.getInstance()) {
|
||||||
})
|
it.metadata.label
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ private const val ITEM = "item"
|
||||||
|
|
||||||
class TagStore(
|
class TagStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
private val json: Json,
|
private val jsonSerializer: Json,
|
||||||
|
scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val alterationTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
private val alterationTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
||||||
private val characterTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
private val characterTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
||||||
|
|
@ -29,8 +30,6 @@ class TagStore(
|
||||||
init {
|
init {
|
||||||
// make the file path.
|
// make the file path.
|
||||||
File(pathProvider.tagsPath()).mkdirs()
|
File(pathProvider.tagsPath()).mkdirs()
|
||||||
// build a coroutine scope for async calls
|
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
|
||||||
// load the initial data
|
// load the initial data
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateTagFlow()
|
updateTagFlow()
|
||||||
|
|
@ -41,7 +40,7 @@ class TagStore(
|
||||||
fun characterTags(): StateFlow<Map<String, TagJson>> = characterTagsFlow
|
fun characterTags(): StateFlow<Map<String, TagJson>> = characterTagsFlow
|
||||||
fun itemTags(): StateFlow<Map<String, TagJson>> = itemTagsFlow
|
fun itemTags(): StateFlow<Map<String, TagJson>> = itemTagsFlow
|
||||||
|
|
||||||
fun updateTagFlow() {
|
suspend fun updateTagFlow() {
|
||||||
update(
|
update(
|
||||||
flow = alterationTagsFlow,
|
flow = alterationTagsFlow,
|
||||||
file = alterationFile(),
|
file = alterationFile(),
|
||||||
|
|
@ -56,7 +55,7 @@ class TagStore(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun update(
|
private suspend fun update(
|
||||||
flow: MutableStateFlow<Map<String, TagJson>>,
|
flow: MutableStateFlow<Map<String, TagJson>>,
|
||||||
file: File,
|
file: File,
|
||||||
) {
|
) {
|
||||||
|
|
@ -69,7 +68,7 @@ class TagStore(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(FileReadException::class, JsonConversionException::class)
|
@Throws(FileReadException::class, JsonConversionException::class)
|
||||||
private fun File.readTags(): List<TagJson> {
|
private suspend fun File.readTags(): List<TagJson> {
|
||||||
// read the file (force the UTF8 format)
|
// read the file (force the UTF8 format)
|
||||||
val data = try {
|
val data = try {
|
||||||
readText(charset = Charsets.UTF_8)
|
readText(charset = Charsets.UTF_8)
|
||||||
|
|
@ -81,7 +80,7 @@ class TagStore(
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
json.decodeFromString<List<TagJson>>(data)
|
jsonSerializer.decodeFromString<List<TagJson>>(data)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
throw JsonConversionException(
|
throw JsonConversionException(
|
||||||
root = exception
|
root = exception
|
||||||
|
|
@ -90,10 +89,10 @@ class TagStore(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JsonConversionException::class, FileWriteException::class)
|
@Throws(JsonConversionException::class, FileWriteException::class)
|
||||||
private fun saveAlterationTags(tags: List<TagJson>) {
|
private suspend fun saveAlterationTags(tags: List<TagJson>) {
|
||||||
// convert the data to json format
|
// convert the data to json format
|
||||||
val json = try {
|
val json = try {
|
||||||
this.json.encodeToString(tags)
|
this.jsonSerializer.encodeToString(tags)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
throw JsonConversionException(root = exception)
|
throw JsonConversionException(root = exception)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue