mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Use a minimum age of items to remove
In order to keep deleted items for a while, the periodic task can now use a duration to only remove items with a certain age. This can be used to ensure that a deleted item stays at least X days before it will be removed from the database. Refs: #347
This commit is contained in:
@ -0,0 +1,8 @@
|
||||
ALTER TABLE "empty_trash_setting"
|
||||
ADD COLUMN "min_age" bigint;
|
||||
|
||||
UPDATE "empty_trash_setting"
|
||||
SET "min_age" = 604800000;
|
||||
|
||||
ALTER TABLE "empty_trash_setting"
|
||||
ALTER COLUMN "min_age" SET NOT NULL;
|
@ -0,0 +1,8 @@
|
||||
ALTER TABLE `empty_trash_setting`
|
||||
ADD COLUMN (`min_age` bigint);
|
||||
|
||||
UPDATE `empty_trash_setting`
|
||||
SET `min_age` = 604800000;
|
||||
|
||||
ALTER TABLE `empty_trash_setting`
|
||||
MODIFY `min_age` bigint NOT NULL;
|
@ -0,0 +1,8 @@
|
||||
ALTER TABLE "empty_trash_setting"
|
||||
ADD COLUMN "min_age" bigint;
|
||||
|
||||
UPDATE "empty_trash_setting"
|
||||
SET "min_age" = 604800000;
|
||||
|
||||
ALTER TABLE "empty_trash_setting"
|
||||
ALTER COLUMN "min_age" SET NOT NULL;
|
@ -34,6 +34,9 @@ trait DoobieMeta extends EmilDoobieMeta {
|
||||
e.apply(a).noSpaces
|
||||
)
|
||||
|
||||
implicit val metaDuration: Meta[Duration] =
|
||||
Meta[Long].imap(Duration.millis)(_.millis)
|
||||
|
||||
implicit val metaCollectiveState: Meta[CollectiveState] =
|
||||
Meta[String].imap(CollectiveState.unsafe)(CollectiveState.asString)
|
||||
|
||||
|
@ -54,15 +54,23 @@ object QUserTask {
|
||||
)
|
||||
).query[RPeriodicTask].option.map(_.map(makeUserTask))
|
||||
|
||||
def insert(scope: UserTaskScope, task: UserTask[String]): ConnectionIO[Int] =
|
||||
def insert(
|
||||
scope: UserTaskScope,
|
||||
subject: Option[String],
|
||||
task: UserTask[String]
|
||||
): ConnectionIO[Int] =
|
||||
for {
|
||||
r <- task.toPeriodicTask[ConnectionIO](scope)
|
||||
r <- task.toPeriodicTask[ConnectionIO](scope, subject)
|
||||
n <- RPeriodicTask.insert(r)
|
||||
} yield n
|
||||
|
||||
def update(scope: UserTaskScope, task: UserTask[String]): ConnectionIO[Int] =
|
||||
def update(
|
||||
scope: UserTaskScope,
|
||||
subject: Option[String],
|
||||
task: UserTask[String]
|
||||
): ConnectionIO[Int] =
|
||||
for {
|
||||
r <- task.toPeriodicTask[ConnectionIO](scope)
|
||||
r <- task.toPeriodicTask[ConnectionIO](scope, subject)
|
||||
n <- RPeriodicTask.update(r)
|
||||
} yield n
|
||||
|
||||
|
@ -13,7 +13,6 @@ import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import com.github.eikek.calev._
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
@ -75,16 +74,15 @@ object RCollective {
|
||||
)
|
||||
)
|
||||
now <- Timestamp.current[ConnectionIO]
|
||||
cls = settings.classifier.map(_.toRecord(cid, now))
|
||||
n2 <- cls match {
|
||||
case Some(cr) =>
|
||||
RClassifierSetting.update(cr)
|
||||
n2 <- settings.classifier match {
|
||||
case Some(cls) =>
|
||||
RClassifierSetting.update(cls.toRecord(cid, now))
|
||||
case None =>
|
||||
RClassifierSetting.delete(cid)
|
||||
}
|
||||
n3 <- settings.emptyTrash match {
|
||||
case Some(trashSchedule) =>
|
||||
REmptyTrashSetting.update(REmptyTrashSetting(cid, trashSchedule, now))
|
||||
case Some(trash) =>
|
||||
REmptyTrashSetting.update(trash.toRecord(cid, now))
|
||||
case None =>
|
||||
REmptyTrashSetting.delete(cid)
|
||||
}
|
||||
@ -114,7 +112,8 @@ object RCollective {
|
||||
cs.itemCount.s,
|
||||
cs.categories.s,
|
||||
cs.listType.s,
|
||||
es.schedule.s
|
||||
es.schedule.s,
|
||||
es.minAge.s
|
||||
),
|
||||
from(c).leftJoin(cs, cs.cid === c.id).leftJoin(es, es.cid === c.id),
|
||||
c.id === coll
|
||||
@ -168,7 +167,7 @@ object RCollective {
|
||||
language: Language,
|
||||
integrationEnabled: Boolean,
|
||||
classifier: Option[RClassifierSetting.Classifier],
|
||||
emptyTrash: Option[CalEvent]
|
||||
emptyTrash: Option[REmptyTrashSetting.EmptyTrash]
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import doobie.implicits._
|
||||
final case class REmptyTrashSetting(
|
||||
cid: Ident,
|
||||
schedule: CalEvent,
|
||||
minAge: Duration,
|
||||
created: Timestamp
|
||||
)
|
||||
|
||||
@ -31,8 +32,9 @@ object REmptyTrashSetting {
|
||||
|
||||
val cid = Column[Ident]("cid", this)
|
||||
val schedule = Column[CalEvent]("schedule", this)
|
||||
val minAge = Column[Duration]("min_age", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
val all = NonEmptyList.of[Column[_]](cid, schedule, created)
|
||||
val all = NonEmptyList.of[Column[_]](cid, schedule, minAge, created)
|
||||
}
|
||||
|
||||
val T = Table(None)
|
||||
@ -43,7 +45,7 @@ object REmptyTrashSetting {
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
fr"${v.cid},${v.schedule},${v.created}"
|
||||
fr"${v.cid},${v.schedule},${v.minAge},${v.created}"
|
||||
)
|
||||
|
||||
def update(v: REmptyTrashSetting): ConnectionIO[Int] =
|
||||
@ -52,7 +54,8 @@ object REmptyTrashSetting {
|
||||
T,
|
||||
T.cid === v.cid,
|
||||
DML.set(
|
||||
T.schedule.setTo(v.schedule)
|
||||
T.schedule.setTo(v.schedule),
|
||||
T.minAge.setTo(v.minAge)
|
||||
)
|
||||
)
|
||||
n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO]
|
||||
@ -64,7 +67,7 @@ object REmptyTrashSetting {
|
||||
}
|
||||
|
||||
def findForAllCollectives(
|
||||
default: CalEvent,
|
||||
default: EmptyTrash,
|
||||
chunkSize: Int
|
||||
): Stream[ConnectionIO, REmptyTrashSetting] = {
|
||||
val c = RCollective.as("c")
|
||||
@ -72,7 +75,8 @@ object REmptyTrashSetting {
|
||||
val sql = run(
|
||||
select(
|
||||
c.id.s,
|
||||
coalesce(e.schedule.s, const(default)).s,
|
||||
coalesce(e.schedule.s, const(default.schedule)).s,
|
||||
coalesce(e.minAge.s, const(default.minAge)).s,
|
||||
coalesce(e.created.s, c.created.s).s
|
||||
),
|
||||
from(c).leftJoin(e, e.cid === c.id)
|
||||
@ -83,4 +87,13 @@ object REmptyTrashSetting {
|
||||
def delete(coll: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.cid === coll)
|
||||
|
||||
final case class EmptyTrash(schedule: CalEvent, minAge: Duration) {
|
||||
def toRecord(coll: Ident, created: Timestamp): REmptyTrashSetting =
|
||||
REmptyTrashSetting(coll, schedule, minAge, created)
|
||||
}
|
||||
object EmptyTrash {
|
||||
val default = EmptyTrash(EmptyTrashArgs.defaultSchedule, Duration.days(7))
|
||||
def fromRecord(r: REmptyTrashSetting): EmptyTrash =
|
||||
EmptyTrash(r.schedule, r.minAge)
|
||||
}
|
||||
}
|
||||
|
@ -389,8 +389,16 @@ object RItem {
|
||||
def findById(itemId: Ident): ConnectionIO[Option[RItem]] =
|
||||
run(select(T.all), from(T), T.id === itemId).query[RItem].option
|
||||
|
||||
def findDeleted(collective: Ident, chunkSize: Int): Stream[ConnectionIO, RItem] =
|
||||
run(select(T.all), from(T), T.cid === collective && T.state === ItemState.deleted)
|
||||
def findDeleted(
|
||||
collective: Ident,
|
||||
maxUpdated: Timestamp,
|
||||
chunkSize: Int
|
||||
): Stream[ConnectionIO, RItem] =
|
||||
run(
|
||||
select(T.all),
|
||||
from(T),
|
||||
T.cid === collective && T.state === ItemState.deleted && T.updated < maxUpdated
|
||||
)
|
||||
.query[RItem]
|
||||
.streamWithChunkSize(chunkSize)
|
||||
|
||||
|
@ -43,7 +43,8 @@ object UserTask {
|
||||
.map(a => ut.copy(args = a))
|
||||
|
||||
def toPeriodicTask[F[_]: Sync](
|
||||
scope: UserTaskScope
|
||||
scope: UserTaskScope,
|
||||
subject: Option[String]
|
||||
): F[RPeriodicTask] =
|
||||
RPeriodicTask
|
||||
.create[F](
|
||||
@ -51,7 +52,7 @@ object UserTask {
|
||||
scope,
|
||||
ut.name,
|
||||
ut.args,
|
||||
s"${scope.fold(_.user.id, _.id)}: ${ut.name.id}",
|
||||
subject.getOrElse(s"${scope.fold(_.user.id, _.id)}: ${ut.name.id}"),
|
||||
Priority.Low,
|
||||
ut.timer,
|
||||
ut.summary
|
||||
|
@ -61,7 +61,9 @@ trait UserTaskStore[F[_]] {
|
||||
* exists, a new one is created. Otherwise the existing task is
|
||||
* updated.
|
||||
*/
|
||||
def updateTask[A](scope: UserTaskScope, ut: UserTask[A])(implicit E: Encoder[A]): F[Int]
|
||||
def updateTask[A](scope: UserTaskScope, subject: Option[String], ut: UserTask[A])(
|
||||
implicit E: Encoder[A]
|
||||
): F[Int]
|
||||
|
||||
/** Delete the task with the given id of the given user.
|
||||
*/
|
||||
@ -92,8 +94,8 @@ trait UserTaskStore[F[_]] {
|
||||
* the user `account`, they will all be removed and the given task
|
||||
* inserted!
|
||||
*/
|
||||
def updateOneTask[A](scope: UserTaskScope, ut: UserTask[A])(implicit
|
||||
E: Encoder[A]
|
||||
def updateOneTask[A](scope: UserTaskScope, subject: Option[String], ut: UserTask[A])(
|
||||
implicit E: Encoder[A]
|
||||
): F[UserTask[String]]
|
||||
|
||||
/** Delete all tasks of the given user that have name `name'.
|
||||
@ -123,16 +125,16 @@ object UserTaskStore {
|
||||
case Left(err) => Stream.raiseError[F](new Exception(err))
|
||||
})
|
||||
|
||||
def updateTask[A](scope: UserTaskScope, ut: UserTask[A])(implicit
|
||||
E: Encoder[A]
|
||||
def updateTask[A](scope: UserTaskScope, subject: Option[String], ut: UserTask[A])(
|
||||
implicit E: Encoder[A]
|
||||
): F[Int] = {
|
||||
val exists = QUserTask.exists(ut.id)
|
||||
val insert = QUserTask.insert(scope, ut.encode)
|
||||
val insert = QUserTask.insert(scope, subject, ut.encode)
|
||||
store.add(insert, exists).flatMap {
|
||||
case AddResult.Success =>
|
||||
1.pure[F]
|
||||
case AddResult.EntityExists(_) =>
|
||||
store.transact(QUserTask.update(scope, ut.encode))
|
||||
store.transact(QUserTask.update(scope, subject, ut.encode))
|
||||
case AddResult.Failure(ex) =>
|
||||
Async[F].raiseError(ex)
|
||||
}
|
||||
@ -166,21 +168,25 @@ object UserTaskStore {
|
||||
case Left(err) => Async[F].raiseError(new Exception(err))
|
||||
})
|
||||
|
||||
def updateOneTask[A](scope: UserTaskScope, ut: UserTask[A])(implicit
|
||||
def updateOneTask[A](
|
||||
scope: UserTaskScope,
|
||||
subject: Option[String],
|
||||
ut: UserTask[A]
|
||||
)(implicit
|
||||
E: Encoder[A]
|
||||
): F[UserTask[String]] =
|
||||
getByNameRaw(scope, ut.name).compile.toList.flatMap {
|
||||
case a :: rest =>
|
||||
val task = ut.copy(id = a.id).encode
|
||||
for {
|
||||
_ <- store.transact(QUserTask.update(scope, task))
|
||||
_ <- store.transact(QUserTask.update(scope, subject, task))
|
||||
_ <- store.transact(
|
||||
rest.traverse(t => QUserTask.delete(scope.toAccountId, t.id))
|
||||
)
|
||||
} yield task
|
||||
case Nil =>
|
||||
val task = ut.encode
|
||||
store.transact(QUserTask.insert(scope, task)).map(_ => task)
|
||||
store.transact(QUserTask.insert(scope, subject, task)).map(_ => task)
|
||||
}
|
||||
|
||||
def deleteAll(scope: UserTaskScope, name: Ident): F[Int] =
|
||||
|
Reference in New Issue
Block a user