Skip to content

Commit

Permalink
Added maintenance functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
darkfrog26 committed Apr 6, 2023
1 parent 1b77edd commit 27a729d
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 1 deletion.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name := "spice"
ThisBuild / organization := "com.outr"
ThisBuild / version := "0.0.23"
ThisBuild / version := "0.0.24-SNAPSHOT"

val scala213: String = "2.13.10"
val scala3: String = "3.2.2"
Expand Down
85 changes: 85 additions & 0 deletions core/shared/src/main/scala/spice/maintenance/Maintenance.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package spice.maintenance

import cats.effect.IO
import cats.effect.unsafe.implicits.global

import java.util.{Calendar, TimeZone}
import scala.concurrent.duration._

object Maintenance {
def schedule(name: String,
schedule: => FiniteDuration,
initialDelay: Option[FiniteDuration] = None,
onFail: TaskResult = TaskResult.Continue)
(action: TaskStatus => IO[TaskResult]): MaintenanceTaskInstance = {
var normalSchedule = () => schedule
var stat = TaskStatus().schedule(initialDelay.getOrElse(schedule))
var cancelled = false
val taskName = name
val task = new MaintenanceTaskInstance {
override def name: String = taskName

override def status: TaskStatus = stat

override def cancel(): Unit = cancelled = true
}

def scheduleNext(resultOption: Option[TaskResult]): IO[TaskResult] = {
stat = stat.copy(lastRun = Some(System.currentTimeMillis()), timesRun = stat.timesRun + 1)
if (cancelled) {
stat = stat.copy(nextRun = None, nextSchedule = None)
IO.pure(TaskResult.Stop)
} else {
val nextRunOption = resultOption match {
case None => Some(initialDelay.getOrElse(schedule))
case Some(TaskResult.Continue) => Some(normalSchedule())
case Some(TaskResult.Stop) => None
case Some(TaskResult.RunAgain) => Some(0.seconds)
case Some(TaskResult.ChangeSchedule(delay)) =>
normalSchedule = delay
Some(delay())
case Some(TaskResult.NextSchedule(delay)) => Some(delay)
}
nextRunOption match {
case Some(nextRun) =>
stat = stat.schedule(nextRun)
IO.sleep(nextRun).flatMap { _ =>
val io = action(stat).handleError { throwable =>
scribe.error(s"$name maintenance task failed, will $onFail", throwable)
onFail
}
io.flatMap { result =>
scheduleNext(Some(result))
}
}
case None =>
stat = stat.copy(nextRun = None, nextSchedule = None)
IO.pure(TaskResult.Stop)
}
}
}

scheduleNext(None).unsafeRunAndForget()

task
}

def fromTime(hour: Int,
minute: Int = 0,
second: Int = 0,
millisecond: Int = 0,
rollToNextDay: Boolean = true,
timeZone: TimeZone = TimeZone.getDefault): FiniteDuration = {
val c = Calendar.getInstance(timeZone)
c.set(Calendar.HOUR_OF_DAY, hour)
c.set(Calendar.MINUTE, minute)
c.set(Calendar.SECOND, second)
c.set(Calendar.MILLISECOND, millisecond)
val l = c.getTimeInMillis - System.currentTimeMillis()
if (l <= 0 && rollToNextDay) {
(l + 24.hours.toMillis).millis
} else {
l.millis
}
}
}
24 changes: 24 additions & 0 deletions core/shared/src/main/scala/spice/maintenance/MaintenanceTask.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package spice.maintenance

import cats.effect.IO

import scala.concurrent.duration.FiniteDuration

trait MaintenanceTask {
def name: String = getClass.getSimpleName.replace("$", "")

def nextRun: FiniteDuration

def initialDelay: FiniteDuration = nextRun

def onFail: TaskResult = TaskResult.Continue

def apply(status: TaskStatus): IO[TaskResult]

def schedule(): MaintenanceTaskInstance = Maintenance.schedule(
name = name,
schedule = nextRun,
initialDelay = Some(initialDelay),
onFail = onFail
)(apply)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package spice.maintenance

trait MaintenanceTaskInstance {
def name: String

def status: TaskStatus

def cancel(): Unit
}
35 changes: 35 additions & 0 deletions core/shared/src/main/scala/spice/maintenance/TaskResult.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package spice.maintenance

import scala.concurrent.duration.FiniteDuration

sealed trait TaskResult

object TaskResult {
/**
* Should continue on the normal schedule.
*/
case object Continue extends TaskResult

/**
* Should stop and not run again.
*/
case object Stop extends TaskResult

/**
* Should run again immediately.
*/
case object RunAgain extends TaskResult

/**
* Configures a new scheduling delay for the next and all future schedulings.
*/
case class ChangeSchedule(delay: () => FiniteDuration) extends TaskResult
object ChangeSchedule {
def to(delay: => FiniteDuration): ChangeSchedule = new ChangeSchedule(() => delay)
}

/**
* Schedules the next run on a new schedule and then will return to the normal schedule.
*/
case class NextSchedule(delay: FiniteDuration) extends TaskResult
}
13 changes: 13 additions & 0 deletions core/shared/src/main/scala/spice/maintenance/TaskStatus.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package spice.maintenance

import scala.concurrent.duration.FiniteDuration

case class TaskStatus(lastRun: Option[Long] = None,
timesRun: Int = 0,
nextRun: Option[Long] = None,
nextSchedule: Option[FiniteDuration] = None) {
def schedule(duration: FiniteDuration): TaskStatus = copy(
nextRun = Some(System.currentTimeMillis() + duration.toMillis),
nextSchedule = Some(duration)
)
}

0 comments on commit 27a729d

Please sign in to comment.