Hooking the new pubsub impl into the application

This commit is contained in:
eikek
2021-11-05 21:01:02 +01:00
parent 4d5c695882
commit f38d520a1d
25 changed files with 379 additions and 188 deletions

View File

@ -6,8 +6,6 @@
package docspell.backend
import scala.concurrent.ExecutionContext
import cats.effect._
import docspell.backend.auth.Login
@ -15,15 +13,13 @@ import docspell.backend.fulltext.CreateIndex
import docspell.backend.ops._
import docspell.backend.signup.OSignup
import docspell.ftsclient.FtsClient
import docspell.joexapi.client.JoexClient
import docspell.pubsub.api.PubSubT
import docspell.store.Store
import docspell.store.queue.JobQueue
import docspell.store.usertask.UserTaskStore
import docspell.totp.Totp
import emil.javamail.{JavaMailEmil, Settings}
import org.http4s.blaze.client.BlazeClientBuilder
import org.http4s.client.Client
trait BackendApp[F[_]] {
@ -49,6 +45,7 @@ trait BackendApp[F[_]] {
def clientSettings: OClientSettings[F]
def totp: OTotp[F]
def share: OShare[F]
def pubSub: PubSubT[F]
}
object BackendApp {
@ -56,8 +53,8 @@ object BackendApp {
def create[F[_]: Async](
cfg: Config,
store: Store[F],
httpClient: Client[F],
ftsClient: FtsClient[F]
ftsClient: FtsClient[F],
pubSubT: PubSubT[F]
): Resource[F, BackendApp[F]] =
for {
utStore <- UserTaskStore(store)
@ -65,7 +62,7 @@ object BackendApp {
totpImpl <- OTotp(store, Totp.default)
loginImpl <- Login[F](store, Totp.default)
signupImpl <- OSignup[F](store)
joexImpl <- OJoex(JoexClient(httpClient), store)
joexImpl <- OJoex(pubSubT)
collImpl <- OCollective[F](store, utStore, queue, joexImpl)
sourceImpl <- OSource[F](store)
tagImpl <- OTag[F](store)
@ -90,6 +87,7 @@ object BackendApp {
OShare(store, itemSearchImpl, simpleSearchImpl, javaEmil)
)
} yield new BackendApp[F] {
val pubSub = pubSubT
val login = loginImpl
val signup = signupImpl
val collective = collImpl
@ -113,15 +111,4 @@ object BackendApp {
val totp = totpImpl
val share = shareImpl
}
def apply[F[_]: Async](
cfg: Config,
connectEC: ExecutionContext
)(ftsFactory: Client[F] => Resource[F, FtsClient[F]]): Resource[F, BackendApp[F]] =
for {
store <- Store.create(cfg.jdbc, cfg.files.chunkSize, connectEC)
httpClient <- BlazeClientBuilder[F].resource
ftsClient <- ftsFactory(httpClient)
backend <- create(cfg, store, httpClient, ftsClient)
} yield backend
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.backend.msg
import docspell.common._
import docspell.pubsub.api.{Topic, TypedTopic}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
/** Message to request to cancel a job. */
final case class CancelJob(jobId: Ident, nodeId: Ident)
object CancelJob {
implicit val jsonDecoder: Decoder[CancelJob] =
deriveDecoder[CancelJob]
implicit val jsonEncoder: Encoder[CancelJob] =
deriveEncoder[CancelJob]
val topic: TypedTopic[CancelJob] =
TypedTopic(Topic("job-cancel-request"))
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.backend.msg
import docspell.common._
import docspell.pubsub.api.{Topic, TypedTopic}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
/** Message to notify about finished jobs. They have a final state. */
final case class JobDone(jobId: Ident, task: Ident, args: String, state: JobState)
object JobDone {
implicit val jsonDecoder: Decoder[JobDone] =
deriveDecoder[JobDone]
implicit val jsonEncoder: Encoder[JobDone] =
deriveEncoder[JobDone]
val topic: TypedTopic[JobDone] =
TypedTopic(Topic("job-finished"))
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.backend.msg
import java.util.concurrent.atomic.AtomicLong
import docspell.pubsub.api.{Topic, TypedTopic}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
final case class Ping(sender: String, num: Long)
object Ping {
implicit val jsonDecoder: Decoder[Ping] =
deriveDecoder[Ping]
implicit val jsonEncoder: Encoder[Ping] =
deriveEncoder[Ping]
private[this] val counter = new AtomicLong(0)
def next(sender: String): Ping =
Ping(sender, counter.getAndIncrement())
val topic: TypedTopic[Ping] =
TypedTopic[Ping](Topic("ping"))
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.backend.msg
import cats.data.NonEmptyList
import docspell.pubsub.api.{Topic, TypedTopic}
/** All topics used in Docspell. */
object Topics {
/** A generic notification to the job executors to look for new work. */
val jobsNotify: TypedTopic[Unit] =
TypedTopic[Unit](Topic("jobs-notify"))
/** A list of all topics. It is required to list every topic in use here! */
val all: NonEmptyList[TypedTopic[_]] =
NonEmptyList.of(Ping.topic, JobDone.topic, CancelJob.topic, jobsNotify)
}

View File

@ -11,8 +11,7 @@ import cats.effect._
import cats.implicits._
import docspell.backend.ops.OJob.{CollectiveQueueState, JobCancelResult}
import docspell.common.Priority
import docspell.common.{Ident, JobState}
import docspell.common._
import docspell.store.Store
import docspell.store.UpdateResult
import docspell.store.queries.QJob
@ -55,6 +54,7 @@ object OJob {
joex: OJoex[F]
): Resource[F, OJob[F]] =
Resource.pure[F, OJob[F]](new OJob[F] {
private[this] val logger = Logger.log4s(org.log4s.getLogger(OJob.getClass))
def queueState(collective: Ident, maxResults: Int): F[CollectiveQueueState] =
store
@ -77,11 +77,9 @@ object OJob {
job.worker match {
case Some(worker) =>
for {
flag <- joex.cancelJob(job.id, worker)
res <-
if (flag) JobCancelResult.cancelRequested.pure[F]
else remove(job)
} yield res
_ <- logger.debug(s"Attempt to cancel job: ${job.id.id}")
_ <- joex.cancelJob(job.id, worker)
} yield JobCancelResult.cancelRequested
case None =>
remove(job)
}

View File

@ -6,41 +6,27 @@
package docspell.backend.ops
import cats.data.OptionT
import cats.effect._
import cats.implicits._
import docspell.common.{Ident, NodeType}
import docspell.joexapi.client.JoexClient
import docspell.store.Store
import docspell.store.records.RNode
import docspell.backend.msg.{CancelJob, Topics}
import docspell.common.Ident
import docspell.pubsub.api.PubSubT
trait OJoex[F[_]] {
def notifyAllNodes: F[Unit]
def cancelJob(job: Ident, worker: Ident): F[Boolean]
def cancelJob(job: Ident, worker: Ident): F[Unit]
}
object OJoex {
def apply[F[_]: Sync](client: JoexClient[F], store: Store[F]): Resource[F, OJoex[F]] =
def apply[F[_]](pubSub: PubSubT[F]): Resource[F, OJoex[F]] =
Resource.pure[F, OJoex[F]](new OJoex[F] {
def notifyAllNodes: F[Unit] =
for {
nodes <- store.transact(RNode.findAll(NodeType.Joex))
_ <- nodes.toList.traverse(n => client.notifyJoexIgnoreErrors(n.url))
} yield ()
pubSub.publish1IgnoreErrors(Topics.jobsNotify, ())
def cancelJob(job: Ident, worker: Ident): F[Boolean] =
(for {
node <- OptionT(store.transact(RNode.findById(worker)))
cancel <- OptionT.liftF(client.cancelJob(node.url, job))
} yield cancel.success).getOrElse(false)
def cancelJob(job: Ident, worker: Ident): F[Unit] =
pubSub.publish1IgnoreErrors(CancelJob.topic, CancelJob(job, worker))
})
def create[F[_]: Async](store: Store[F]): Resource[F, OJoex[F]] =
JoexClient.resource.flatMap(client => apply(client, store))
}