diff --git a/foundation-core/build.gradle.kts b/foundation-core/build.gradle.kts index b1b3fb9..edc921b 100644 --- a/foundation-core/build.gradle.kts +++ b/foundation-core/build.gradle.kts @@ -2,4 +2,5 @@ dependencies { // TODO: might be able to ship all dependencies in core? are we duplicating classes in JARs? implementation("software.amazon.awssdk:s3:2.17.102") + implementation("org.quartz-scheduler:quartz:2.3.2") } diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/FoundationCorePlugin.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/FoundationCorePlugin.kt index 4528dc5..6c8e341 100644 --- a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/FoundationCorePlugin.kt +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/FoundationCorePlugin.kt @@ -8,6 +8,7 @@ import cloud.kubelet.foundation.core.features.stats.StatsFeature import cloud.kubelet.foundation.core.features.update.UpdateFeature import cloud.kubelet.foundation.core.features.world.WorldFeature import cloud.kubelet.foundation.core.features.persist.PersistenceFeature +import cloud.kubelet.foundation.core.features.scheduler.SchedulerFeature import org.koin.dsl.module import java.nio.file.Path @@ -38,6 +39,7 @@ class FoundationCorePlugin : FoundationPlugin() { } override fun createFeatures() = listOf( + SchedulerFeature(), PersistenceFeature(), BackupFeature(), DevFeature(), diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/CoreFeature.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/CoreFeature.kt new file mode 100644 index 0000000..391cea7 --- /dev/null +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/CoreFeature.kt @@ -0,0 +1,7 @@ +package cloud.kubelet.foundation.core.abstraction + +interface CoreFeature { + fun enable() + fun disable() + fun module() = org.koin.dsl.module {} +} diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/Feature.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/Feature.kt index 61b9a31..1969ce2 100644 --- a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/Feature.kt +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/Feature.kt @@ -7,13 +7,15 @@ import org.bukkit.event.Listener import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.koin.dsl.module +import org.quartz.Scheduler -abstract class Feature : KoinComponent, Listener { +abstract class Feature : CoreFeature, KoinComponent, Listener { private val plugin by inject() + protected val scheduler by inject() - open fun enable() {} - open fun disable() {} - open fun module() = module {} + override fun enable() {} + override fun disable() {} + override fun module() = module {} protected fun registerCommandExecutor(name: String, executor: CommandExecutor) { registerCommandExecutor(listOf(name), executor) diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/FoundationPlugin.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/FoundationPlugin.kt index c3b178e..2343b90 100644 --- a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/FoundationPlugin.kt +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/abstraction/FoundationPlugin.kt @@ -10,7 +10,7 @@ import org.koin.dsl.module abstract class FoundationPlugin : JavaPlugin() { private lateinit var pluginModule: Module private lateinit var pluginApplication: KoinApplication - private lateinit var features: List + private lateinit var features: List private lateinit var module: Module override fun onEnable() { @@ -45,7 +45,10 @@ abstract class FoundationPlugin : JavaPlugin() { try { slF4JLogger.info("Enabling feature: ${it.javaClass.simpleName}") it.enable() - server.pluginManager.registerEvents(it, this) + // TODO: May replace this check with a method in the interface, CoreFeature would no-op. + if (it is Feature) { + server.pluginManager.registerEvents(it, this) + } } catch (e: Exception) { slF4JLogger.error("Failed to enable feature: ${it.javaClass.simpleName}", e) } @@ -61,5 +64,5 @@ abstract class FoundationPlugin : JavaPlugin() { } protected open fun createModule() = module {} - protected abstract fun createFeatures(): List + protected abstract fun createFeatures(): List } diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/backup/BackupConfig.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/backup/BackupConfig.kt index 203d51b..cb2ffff 100644 --- a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/backup/BackupConfig.kt +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/backup/BackupConfig.kt @@ -4,9 +4,15 @@ import kotlinx.serialization.Serializable @Serializable data class BackupConfig( + val schedule: ScheduleConfig = ScheduleConfig(), val s3: S3Config = S3Config(), ) +@Serializable +data class ScheduleConfig( + val cron: String = "", +) + @Serializable data class S3Config( val accessKeyId: String = "", diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/backup/BackupFeature.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/backup/BackupFeature.kt index ff07d42..14f1f87 100644 --- a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/backup/BackupFeature.kt +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/backup/BackupFeature.kt @@ -3,8 +3,9 @@ package cloud.kubelet.foundation.core.features.backup import cloud.kubelet.foundation.core.FoundationCorePlugin import cloud.kubelet.foundation.core.Util import cloud.kubelet.foundation.core.abstraction.Feature +import cloud.kubelet.foundation.core.features.scheduler.cancel +import cloud.kubelet.foundation.core.features.scheduler.cron import com.charleskorn.kaml.Yaml -import org.koin.core.KoinApplication import org.koin.core.component.inject import org.koin.dsl.module import software.amazon.awssdk.auth.credentials.AwsSessionCredentials @@ -18,6 +19,7 @@ class BackupFeature : Feature() { private val plugin by inject() private val s3Client by inject() private val config by inject() + private lateinit var scheduleId: String override fun enable() { // Create backup directory. @@ -25,6 +27,20 @@ class BackupFeature : Feature() { backupPath.toFile().mkdir() registerCommandExecutor("fbackup", BackupCommand(plugin, backupPath, config, s3Client)) + + if (config.schedule.cron.isNotEmpty()) { + scheduleId = scheduler.cron("${config.schedule.cron} ?") { + plugin.server.scheduler.runTask(plugin) { -> + plugin.server.dispatchCommand(plugin.server.consoleSender, "fbackup") + } + } + } + } + + override fun disable() { + if (::scheduleId.isInitialized) { + scheduler.cancel(scheduleId) + } } override fun module() = module { diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerExtensions.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerExtensions.kt new file mode 100644 index 0000000..ed2dc8c --- /dev/null +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerExtensions.kt @@ -0,0 +1,30 @@ +package cloud.kubelet.foundation.core.features.scheduler + +import org.quartz.CronScheduleBuilder.cronSchedule +import org.quartz.JobBuilder.newJob +import org.quartz.JobDataMap +import org.quartz.Scheduler +import org.quartz.TriggerBuilder.newTrigger +import org.quartz.TriggerKey.triggerKey +import java.util.UUID + +fun Scheduler.cron(cronExpression: String, f: () -> Unit): String { + val id = UUID.randomUUID().toString() + val job = newJob(SchedulerRunner::class.java).apply { + setJobData(JobDataMap().apply { + set("function", f) + }) + }.build() + + val trigger = newTrigger() + .withIdentity(triggerKey(id)) + .withSchedule(cronSchedule(cronExpression)) + .build() + + scheduleJob(job, trigger) + return id +} + +fun Scheduler.cancel(id: String) { + unscheduleJob(triggerKey(id)) +} diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerFeature.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerFeature.kt new file mode 100644 index 0000000..23651a3 --- /dev/null +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerFeature.kt @@ -0,0 +1,22 @@ +package cloud.kubelet.foundation.core.features.scheduler + +import cloud.kubelet.foundation.core.abstraction.CoreFeature +import org.koin.dsl.module +import org.quartz.Scheduler +import org.quartz.impl.StdSchedulerFactory + +class SchedulerFeature : CoreFeature { + private val scheduler: Scheduler = StdSchedulerFactory.getDefaultScheduler() + + override fun enable() { + scheduler.start() + } + + override fun disable() { + scheduler.shutdown(true) + } + + override fun module() = module { + single { scheduler } + } +} diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerRunner.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerRunner.kt new file mode 100644 index 0000000..385bc89 --- /dev/null +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/features/scheduler/SchedulerRunner.kt @@ -0,0 +1,11 @@ +package cloud.kubelet.foundation.core.features.scheduler + +import org.quartz.Job +import org.quartz.JobExecutionContext + +class SchedulerRunner : Job { + override fun execute(context: JobExecutionContext) { + val f = context.jobDetail.jobDataMap["function"] as () -> Unit + f() + } +} \ No newline at end of file diff --git a/foundation-core/src/main/resources/backup.yaml b/foundation-core/src/main/resources/backup.yaml index d043b8e..fbb7591 100644 --- a/foundation-core/src/main/resources/backup.yaml +++ b/foundation-core/src/main/resources/backup.yaml @@ -1,3 +1,11 @@ +# Configuration of backup scheduling, expressed by cron expressions. +schedule: + # Cron expression to use for the backup schedule. + # Examples: + # "0 3 * * *" -> every day at 3 AM + # "0 3 * * SUN" -> every Sunday at 3 AM + cron: "" + # Configuration of S3 service to upload back-ups to. s3: # The access key ID from your S3-compliant storage provider. diff --git a/foundation-core/src/main/resources/quartz.properties b/foundation-core/src/main/resources/quartz.properties new file mode 100644 index 0000000..798770c --- /dev/null +++ b/foundation-core/src/main/resources/quartz.properties @@ -0,0 +1,3 @@ +org.quartz.scheduler.instanceName = Foundation +org.quartz.threadPool.threadCount = 2 +org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore \ No newline at end of file