diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-01-25 15:36:06 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-01-25 15:36:17 +0900 |
| commit | 3ec21511191be259370056c91549d0b981a3f4c5 (patch) | |
| tree | 5859fe079427abb12fb05bc42581871aed0f5f24 | |
| parent | 3f1df5abfe83d46d3150e7fd29e18f6531f98663 (diff) | |
| download | LunaticChat-3ec21511191be259370056c91549d0b981a3f4c5.tar.gz LunaticChat-3ec21511191be259370056c91549d0b981a3f4c5.tar.bz2 LunaticChat-3ec21511191be259370056c91549d0b981a3f4c5.zip | |
feat: Initialize Channel storage logic
9 files changed, 240 insertions, 3 deletions
diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/Channel.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/Channel.kt new file mode 100644 index 0000000..427e526 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/Channel.kt @@ -0,0 +1,40 @@ +package dev.m1sk9.lunaticChat.engine.channel.modal + +import dev.m1sk9.lunaticChat.engine.settings.UUIDSerializer +import kotlinx.serialization.Serializable +import java.util.UUID + +/** + * Represents a communication channel within the LunaticChat application. + * + * @property id Unique identifier for the channel. + * @property name Name of the channel. + * @property description Optional description of the channel. + * @property isPrivate Indicates whether the channel is private or public. + * @property ownerId UUID of the user who owns the channel. + * @property createdAt Timestamp of when the channel was created. + */ +@Serializable +data class Channel( + val id: String, + val name: String, + val description: String? = null, + val isPrivate: Boolean = false, + @Serializable(with = UUIDSerializer::class) + val ownerId: UUID, + val createdAt: Long = System.currentTimeMillis(), +) { + init { + require(id.matches(CHANNEL_ID_PATTERN)) { + "Channel ID must be 3-30 characters long and can only contain letters, numbers, underscores, and hyphens." + } + + require(name.isNotBlank()) { + "Channel name cannot be blank." + } + } + + companion object { + val CHANNEL_ID_PATTERN = Regex("^[a-zA-Z0-9_-]{3,30}$") + } +} diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelData.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelData.kt new file mode 100644 index 0000000..6196ab7 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelData.kt @@ -0,0 +1,10 @@ +package dev.m1sk9.lunaticChat.engine.channel.modal + +import kotlinx.serialization.Serializable + +@Serializable +data class ChannelData( + val version: Int = 1, + val channels: Map<String, Channel> = emptyMap(), + val members: Map<String, List<ChannelMember>> = emptyMap(), +) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelMember.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelMember.kt new file mode 100644 index 0000000..3bc2b67 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelMember.kt @@ -0,0 +1,22 @@ +package dev.m1sk9.lunaticChat.engine.channel.modal + +import dev.m1sk9.lunaticChat.engine.settings.UUIDSerializer +import kotlinx.serialization.Serializable +import java.util.UUID + +/** + * Represents a member of a channel with their role and join timestamp. + * + * @property channelId The ID of the channel. + * @property playerId The UUID of the player. + * @property role The role of the member in the channel. + * @property joinedAt The timestamp when the member joined the channel. + */ +@Serializable +data class ChannelMember( + val channelId: String, + @Serializable(with = UUIDSerializer::class) + val playerId: UUID, + val role: ChannelRole, + val joinedAt: Long = System.currentTimeMillis(), +) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelRole.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelRole.kt new file mode 100644 index 0000000..6a3309a --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/channel/modal/ChannelRole.kt @@ -0,0 +1,17 @@ +package dev.m1sk9.lunaticChat.engine.channel.modal + +import kotlinx.serialization.Serializable + +/** + * Represents the role of a user within a channel. + * + * - OWNER: The creator and primary administrator of the channel. + * - MODERATOR: A user with elevated permissions to manage channel content and users. + * - MEMBER: A regular user with standard access to the channel. + */ +@Serializable +enum class ChannelRole { + OWNER, + MODERATOR, + MEMBER, +} diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageLoadException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageLoadException.kt new file mode 100644 index 0000000..21bd654 --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageLoadException.kt @@ -0,0 +1,12 @@ +package dev.m1sk9.lunaticChat.engine.exception + +/** + * Exception thrown when there is an error loading channel storage. + * + * @param message The detail message for the exception. + * @param cause The cause of the exception, if any. + */ +class ChannelStorageLoadException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) diff --git a/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageSaveException.kt b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageSaveException.kt new file mode 100644 index 0000000..0e9990b --- /dev/null +++ b/engine/src/main/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelStorageSaveException.kt @@ -0,0 +1,12 @@ +package dev.m1sk9.lunaticChat.engine.exception + +/** + * Exception thrown when there is an error saving channel storage. + * + * @param message The detail message for the exception. + * @param cause The cause of the exception, if any. + */ +class ChannelStorageSaveException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt index 6525b55..dd6d618 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt @@ -113,7 +113,7 @@ class LunaticChat : ) // Conditionally register /reply command if quick replies are enabled - if (configuration.features.quickRepliesEnabled.enabled) { + if (configuration.features.quickReplies.enabled) { commandRegistry.registerAll( ReplyCommand(this, services.directMessageHandler, services.languageManager), ) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt index e39f04f..96dea1d 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt @@ -1,6 +1,7 @@ package dev.m1sk9.lunaticChat.paper import dev.m1sk9.lunaticChat.engine.converter.GoogleIMEClient +import dev.m1sk9.lunaticChat.paper.channel.storage.ChannelStorage import dev.m1sk9.lunaticChat.paper.command.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration import dev.m1sk9.lunaticChat.paper.converter.ConversionCache @@ -34,7 +35,8 @@ class ServiceInitializer( * 1. LanguageManager (required by all features) * 2. PlayerSettingsManager (required for DM notifications) * 3. Japanese Conversion (optional, config-dependent) - * 4. DirectMessageHandler (depends on settings manager and romaji converter) + * 4. ChannelStorage + * 5. DirectMessageHandler (depends on settings manager and romaji converter) * * @return ServiceContainer with all initialized services */ @@ -60,7 +62,12 @@ class ServiceInitializer( null } - // 4. Initialize handlers + // 4. Initialize channel storage + if (configuration.features.channelChat.enabled) { + initializeChannelStorage() + } + + // 5. Initialize handlers val directMessageHandler = DirectMessageHandler( settingsManager = playerSettingsManager, @@ -136,6 +143,24 @@ class ServiceInitializer( } /** + * Initializes channel storage by loading existing data or creating new storage. + */ + private fun initializeChannelStorage() { + val channelsFile = plugin.dataFolder.resolve("channels.json").toPath() + val storage = + ChannelStorage( + channelsFile = channelsFile, + plugin = plugin, + logger = logger, + ) + + val channelData = storage.loadFromDisk() + storage.saveToDisk(channelData) + + logger.info("Channels storage loaded successfully.") + } + + /** * Schedules periodic tasks such as cache saving. */ fun schedulePeriodicTasks() { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/storage/ChannelStorage.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/storage/ChannelStorage.kt new file mode 100644 index 0000000..7b85703 --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/channel/storage/ChannelStorage.kt @@ -0,0 +1,99 @@ +package dev.m1sk9.lunaticChat.paper.channel.storage + +import dev.m1sk9.lunaticChat.engine.channel.modal.ChannelData +import dev.m1sk9.lunaticChat.engine.exception.ChannelStorageLoadException +import dev.m1sk9.lunaticChat.engine.exception.ChannelStorageSaveException +import kotlinx.serialization.json.Json +import org.bukkit.plugin.java.JavaPlugin +import java.nio.file.Path +import java.util.logging.Logger +import kotlin.io.path.bufferedReader +import kotlin.io.path.exists +import kotlin.io.path.writeText + +/** + * Manages the storage of channel data on disk. + * + * @property channelsFile The path to the file where channel data is stored. + * @property plugin The JavaPlugin instance for accessing plugin resources. + * @property logger The logger for logging messages. + */ +class ChannelStorage( + private val channelsFile: Path, + private val plugin: JavaPlugin, + private val logger: Logger, +) { + private val json = + Json { + prettyPrint = true + ignoreUnknownKeys = true + } + + /** + * Loads channel data from disk. + * + * @return The loaded ChannelData. + * @throws ChannelStorageLoadException if there is an error loading the data. + */ + fun loadFromDisk(): ChannelData { + if (!channelsFile.exists()) { + logger.warning("Channel storage not found, will create a new one.") + return ChannelData() + } + + return try { + val jsonContent = + channelsFile.bufferedReader().use { + it.readText() + } + json.decodeFromString(ChannelData.serializer(), jsonContent).also { + logger.info("Successfully loaded channels from ${channelsFile.fileName}.") + } + } catch (e: Exception) { + throw ChannelStorageLoadException( + "Failed to load channels from ${channelsFile.fileName}: ${e.message}", + e, + ) + } + } + + /** + * Saves channel data to disk. + * + * @param data The ChannelData to save. + * @throws ChannelStorageSaveException if there is an error saving the data. + */ + fun saveToDisk(data: ChannelData) { + try { + val jsonContent = json.encodeToString(ChannelData.serializer(), data) + channelsFile.writeText(jsonContent).also { + logger.fine("Successfully saved channels from ${channelsFile.fileName}.") + } + } catch (e: Exception) { + throw ChannelStorageSaveException( + "Failed to save channels to ${channelsFile.fileName}: ${e.message}", + e, + ) + } + } + + /** + * Queues an asynchronous save of channel data to disk. + * + * @param data The ChannelData to save. + * @throws ChannelStorageSaveException if there is an error saving the data. + */ + fun queueAsyncSave(data: ChannelData) { + plugin.server.scheduler.runTaskAsynchronously( + plugin, + Runnable { + try { + saveToDisk(data) + } catch (e: ChannelStorageSaveException) { + logger.severe("Error saving channel data asynchronously: ${e.message}") + e.printStackTrace() + } + }, + ) + } +} |
