Skip to content

Commit 860c6b8

Browse files
committed
feat: ConfigFactory.kt
1 parent 18cdeef commit 860c6b8

File tree

2 files changed

+84
-53
lines changed

2 files changed

+84
-53
lines changed
Lines changed: 80 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,28 @@
11
package app.simplecloud.plugin.api.shared.config
22

3+
import app.simplecloud.plugin.api.shared.exception.ConfigurationException
34
import app.simplecloud.plugin.api.shared.repository.GenericEnumSerializer
4-
import kotlinx.coroutines.CoroutineScope
5-
import kotlinx.coroutines.Dispatchers
6-
import kotlinx.coroutines.Job
7-
import kotlinx.coroutines.delay
8-
import kotlinx.coroutines.isActive
9-
import kotlinx.coroutines.launch
5+
import kotlinx.coroutines.*
106
import org.spongepowered.configurate.ConfigurationOptions
117
import org.spongepowered.configurate.kotlin.objectMapperFactory
12-
import org.spongepowered.configurate.kotlin.toNode
138
import org.spongepowered.configurate.yaml.NodeStyle
149
import org.spongepowered.configurate.yaml.YamlConfigurationLoader
1510
import java.io.File
16-
import java.nio.file.FileSystems
17-
import java.nio.file.Files
18-
import java.nio.file.Path
19-
import java.nio.file.StandardWatchEventKinds
11+
import java.nio.file.*
12+
import kotlin.coroutines.CoroutineContext
2013

21-
/**
22-
* @author Niklas Nieberler
23-
*/
24-
25-
class ConfigFactory<E>(
14+
class ConfigFactory(
2615
private val file: File,
27-
private val defaultConfig: E
28-
) {
16+
private val configClass: Class<*>,
17+
private val coroutineContext: CoroutineContext = Dispatchers.IO
18+
) : AutoCloseable {
2919

30-
private var config = defaultConfig
31-
private val path = file.toPath()
20+
private var config: Any? = null
21+
private val path: Path = file.toPath()
22+
private var watchJob: Job? = null
3223

3324
private val configurationLoader = YamlConfigurationLoader.builder()
34-
.path(this.path)
25+
.path(path)
3526
.nodeStyle(NodeStyle.BLOCK)
3627
.defaultOptions { options ->
3728
options.serializers { builder ->
@@ -41,60 +32,96 @@ class ConfigFactory<E>(
4132
}
4233
.build()
4334

44-
fun loadOrCreate() {
45-
registerWatcher()
46-
if (this.file.exists()) {
35+
fun loadOrCreate(defaultConfig: Any) {
36+
if (!configClass.isInstance(defaultConfig)) {
37+
throw IllegalArgumentException("Default config must be an instance of ${configClass.name}")
38+
}
39+
40+
if (file.exists()) {
4741
loadConfig()
48-
return
42+
} else {
43+
createDefaultConfig(defaultConfig)
4944
}
50-
createDefaultConfig()
45+
startWatching()
5146
}
5247

53-
private fun createDefaultConfig() {
54-
this.path.parent?.let { Files.createDirectories(it) }
55-
Files.createFile(this.path)
48+
private fun createDefaultConfig(defaultConfig: Any) {
49+
path.parent?.let { Files.createDirectories(it) }
50+
Files.createFile(path)
5651

57-
val configurationNode = this.configurationLoader.load(ConfigurationOptions.defaults())
58-
this.defaultConfig!!.toNode(configurationNode)
59-
this.configurationLoader.save(configurationNode)
52+
val node = configurationLoader.createNode()
53+
node.set(configClass, defaultConfig)
54+
configurationLoader.save(node)
55+
config = defaultConfig
6056
}
6157

62-
fun getConfig(): E = this.config
58+
@Suppress("UNCHECKED_CAST")
59+
fun <T> getConfig(): T {
60+
return config as? T ?: throw IllegalStateException("Configuration not loaded or invalid type")
61+
}
6362

63+
@Throws(ConfigurationException::class)
6464
private fun loadConfig() {
65-
val configurationNode = this.configurationLoader.load(ConfigurationOptions.defaults())
66-
this.config = configurationNode.get(this.defaultConfig!!::class.java)
67-
?: throw IllegalStateException("Config could not be loaded")
65+
try {
66+
val node = configurationLoader.load(ConfigurationOptions.defaults())
67+
config = node.get(configClass)
68+
?: throw ConfigurationException("Failed to parse configuration file")
69+
} catch (e: Exception) {
70+
throw ConfigurationException("Failed to load configuration", e)
71+
}
6872
}
6973

70-
private fun registerWatcher(): Job {
74+
private fun startWatching() {
7175
val watchService = FileSystems.getDefault().newWatchService()
72-
this.path.register(
76+
path.parent?.register(
7377
watchService,
7478
StandardWatchEventKinds.ENTRY_CREATE,
7579
StandardWatchEventKinds.ENTRY_MODIFY
7680
)
7781

78-
return CoroutineScope(Dispatchers.IO).launch {
79-
while (isActive) {
80-
val key = watchService.take()
81-
82-
key.pollEvents().forEach { event ->
83-
val path = event.context() as? Path ?: return@forEach
84-
if (!file.name.contains(path.toString())) return@launch
85-
86-
when (event.kind()) {
87-
StandardWatchEventKinds.ENTRY_CREATE,
88-
StandardWatchEventKinds.ENTRY_MODIFY -> {
89-
delay(100)
90-
loadConfig()
91-
}
82+
watchJob = CoroutineScope(coroutineContext).launch {
83+
watchService.use { watchService ->
84+
while (isActive) {
85+
val key = watchService.take()
86+
key.pollEvents().forEach { event ->
87+
handleWatchEvent(event)
88+
}
89+
if (!key.reset()) {
90+
break
9291
}
9392
}
93+
}
94+
}
95+
}
9496

95-
key.reset()
97+
/**
98+
* Handles a single watch event.
99+
*/
100+
private suspend fun handleWatchEvent(event: WatchEvent<*>) {
101+
val path = event.context() as? Path ?: return
102+
if (!file.name.contains(path.toString())) return
103+
104+
when (event.kind()) {
105+
StandardWatchEventKinds.ENTRY_CREATE,
106+
StandardWatchEventKinds.ENTRY_MODIFY -> {
107+
delay(100)
108+
try {
109+
loadConfig()
110+
} catch (e: ConfigurationException) {
111+
println("Failed to reload configuration: ${e.message}")
112+
}
96113
}
97114
}
98115
}
99116

117+
override fun close() {
118+
watchJob?.cancel()
119+
}
120+
121+
companion object {
122+
inline fun <reified T : Any> create(
123+
file: File,
124+
coroutineContext: CoroutineContext = Dispatchers.IO
125+
): ConfigFactory = ConfigFactory(file, T::class.java, coroutineContext)
126+
}
100127
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package app.simplecloud.plugin.api.shared.exception
2+
3+
class ConfigurationException(message: String, cause: Throwable? = null) :
4+
Exception(message, cause)

0 commit comments

Comments
 (0)