diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala b/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala index 14377f35..db217e0c 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala @@ -185,7 +185,7 @@ object OCollective { None, EmptyTrashArgs(coll) ) - _ <- uts.updateOneTask(AccountId(coll, EmptyTrashArgs.taskName), ut) + _ <- uts.updateOneTask(AccountId(coll, coll), ut) _ <- joex.notifyAllNodes } yield () @@ -215,7 +215,7 @@ object OCollective { CalEvent(WeekdayComponent.All, DateEvent.All, TimeEvent.All), None, EmptyTrashArgs(collective) - ).encode.toPeriodicTask(AccountId(collective, EmptyTrashArgs.taskName)) + ).encode.toPeriodicTask(AccountId(collective, collective)) job <- ut.toJob _ <- queue.insert(job) _ <- joex.notifyAllNodes diff --git a/modules/common/src/main/scala/docspell/common/EmptyTrashArgs.scala b/modules/common/src/main/scala/docspell/common/EmptyTrashArgs.scala index 2c85fddf..9ce432bf 100644 --- a/modules/common/src/main/scala/docspell/common/EmptyTrashArgs.scala +++ b/modules/common/src/main/scala/docspell/common/EmptyTrashArgs.scala @@ -6,8 +6,8 @@ package docspell.common +import com.github.eikek.calev.CalEvent import docspell.common.syntax.all._ - import io.circe._ import io.circe.generic.semiauto._ @@ -21,7 +21,7 @@ case class EmptyTrashArgs( ) { def makeSubject: String = - "Empty trash " + "Empty trash" } @@ -29,6 +29,8 @@ object EmptyTrashArgs { val taskName = Ident.unsafe("empty-trash") + val defaultSchedule = CalEvent.unsafe("*-*-1/7 03:00:00") + implicit val jsonEncoder: Encoder[EmptyTrashArgs] = deriveEncoder[EmptyTrashArgs] implicit val jsonDecoder: Decoder[EmptyTrashArgs] = diff --git a/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala b/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala index cd2a114c..ad7ec625 100644 --- a/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala +++ b/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala @@ -7,11 +7,9 @@ package docspell.joex import scala.concurrent.ExecutionContext - import cats.effect._ import cats.implicits._ import fs2.concurrent.SignallingRef - import docspell.analysis.TextAnalyser import docspell.backend.ops._ import docspell.common._ @@ -34,8 +32,7 @@ import docspell.joex.scheduler._ import docspell.joexapi.client.JoexClient import docspell.store.Store import docspell.store.queue._ -import docspell.store.records.RJobLog - +import docspell.store.records.{REmptyTrashSetting, RJobLog} import emil.javamail._ import org.http4s.blaze.client.BlazeClientBuilder import org.http4s.client.Client @@ -77,11 +74,23 @@ final class JoexAppImpl[F[_]: Async]( HouseKeepingTask .periodicTask[F](cfg.houseKeeping.schedule) .flatMap(pstore.insert) *> + scheduleEmptyTrashTasks *> MigrationTask.job.flatMap(queue.insertIfNew) *> AllPreviewsTask .job(MakePreviewArgs.StoreMode.WhenMissing, None) .flatMap(queue.insertIfNew) *> AllPageCountTask.job.flatMap(queue.insertIfNew) + + private def scheduleEmptyTrashTasks: F[Unit] = + store + .transact( + REmptyTrashSetting.findForAllCollectives(EmptyTrashArgs.defaultSchedule, 50) + ) + .evalMap(es => EmptyTrashTask.periodicTask(es.cid, es.schedule)) + .evalMap(pstore.insert) + .compile + .drain + } object JoexAppImpl { diff --git a/modules/joex/src/main/scala/docspell/joex/emptytrash/EmptyTrashTask.scala b/modules/joex/src/main/scala/docspell/joex/emptytrash/EmptyTrashTask.scala index 12173cb2..fda6fb98 100644 --- a/modules/joex/src/main/scala/docspell/joex/emptytrash/EmptyTrashTask.scala +++ b/modules/joex/src/main/scala/docspell/joex/emptytrash/EmptyTrashTask.scala @@ -8,15 +8,15 @@ package docspell.joex.emptytrash import cats.effect._ import cats.implicits._ +import com.github.eikek.calev.CalEvent import fs2.Stream - import docspell.backend.ops.{OItem, OItemSearch} import docspell.common._ import docspell.joex.scheduler._ -import docspell.store.records.RItem +import docspell.store.records.{RItem, RPeriodicTask} +import docspell.store.usertask.UserTask object EmptyTrashTask { - type Args = EmptyTrashArgs def onCancel[F[_]]: Task[F, Args, Unit] = @@ -24,6 +24,19 @@ object EmptyTrashTask { private val pageSize = 20 + def periodicTask[F[_]: Sync](collective: Ident, ce: CalEvent): F[RPeriodicTask] = { + Ident.randomId[F].flatMap( id => + UserTask( + id, + EmptyTrashArgs.taskName, + true, + ce, + None, + EmptyTrashArgs(collective) + ).encode.toPeriodicTask(AccountId(collective, collective))) + } + + def apply[F[_]: Async]( itemOps: OItem[F], itemSearchOps: OItemSearch[F] diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala index bc7c3ef0..71747f5c 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala @@ -8,15 +8,13 @@ package docspell.restserver.routes import cats.effect._ import cats.implicits._ - import docspell.backend.BackendApp import docspell.backend.auth.AuthToken import docspell.backend.ops.OCollective -import docspell.common.ListType +import docspell.common.{EmptyTrashArgs, ListType} import docspell.restapi.model._ import docspell.restserver.conv.Conversions import docspell.restserver.http4s._ - import com.github.eikek.calev.CalEvent import org.http4s.HttpRoutes import org.http4s.circe.CirceEntityDecoder._ @@ -71,7 +69,7 @@ object CollectiveRoutes { CollectiveSettings( c.language, c.integrationEnabled, - c.emptyTrash.getOrElse(CalEvent.unsafe("*-*-1/7 03:00:00")), + c.emptyTrash.getOrElse(EmptyTrashArgs.defaultSchedule), ClassifierSetting( c.classifier.map(_.itemCount).getOrElse(0), c.classifier diff --git a/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala b/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala index f4df6900..f08079e5 100644 --- a/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala +++ b/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala @@ -8,6 +8,7 @@ package docspell.store.records import cats.data.NonEmptyList import cats.implicits._ +import fs2.Stream import docspell.common._ import docspell.store.qb.DSL._ @@ -62,6 +63,23 @@ object REmptyTrashSetting { sql.query[REmptyTrashSetting].option } + def findForAllCollectives( + default: CalEvent, + chunkSize: Int + ): Stream[ConnectionIO, REmptyTrashSetting] = { + val c = RCollective.as("c") + val e = REmptyTrashSetting.as("e") + val sql = run( + select( + c.id.s, + coalesce(e.schedule.s, const(default)).s, + coalesce(e.created.s, c.created.s).s + ), + from(c).leftJoin(e, e.cid === c.id) + ) + sql.query[REmptyTrashSetting].streamWithChunkSize(chunkSize) + } + def delete(coll: Ident): ConnectionIO[Int] = DML.delete(T, T.cid === coll)