11package app.simplecloud.plugin.api.shared.config
22
3+ import app.simplecloud.plugin.api.shared.exception.ConfigurationException
34import 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.*
106import org.spongepowered.configurate.ConfigurationOptions
117import org.spongepowered.configurate.kotlin.objectMapperFactory
12- import org.spongepowered.configurate.kotlin.toNode
138import org.spongepowered.configurate.yaml.NodeStyle
149import org.spongepowered.configurate.yaml.YamlConfigurationLoader
1510import 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}
0 commit comments