From 07e9a9767e15d2781247e42fc466d28644da9a7f Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Wed, 12 Aug 2020 22:26:44 +0200
Subject: [PATCH 1/6] Add a task to re-process files of an item

---
 .../scala/docspell/backend/JobFactory.scala   |  22 +++
 .../scala/docspell/backend/ops/OUpload.scala  |  28 ++++
 .../docspell/common/ReProcessItemArgs.scala   |  24 ++++
 .../scala/docspell/joex/JoexAppImpl.scala     |   8 ++
 .../docspell/joex/process/ConvertPdf.scala    |  45 +++++-
 .../docspell/joex/process/ProcessItem.scala   |  11 ++
 .../docspell/joex/process/ReProcessItem.scala | 131 ++++++++++++++++++
 .../src/main/resources/docspell-openapi.yml   |  25 ++++
 .../restserver/routes/ItemRoutes.scala        |   9 ++
 .../docspell/store/records/RAttachment.scala  |  10 ++
 .../store/records/RAttachmentSource.scala     |  17 +++
 .../docspell/store/records/RCollective.scala  |  22 +++
 .../scala/docspell/store/records/RItem.scala  |   3 +
 13 files changed, 350 insertions(+), 5 deletions(-)
 create mode 100644 modules/common/src/main/scala/docspell/common/ReProcessItemArgs.scala
 create mode 100644 modules/joex/src/main/scala/docspell/joex/process/ReProcessItem.scala

diff --git a/modules/backend/src/main/scala/docspell/backend/JobFactory.scala b/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
index d7d8fe91..96399ffa 100644
--- a/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
+++ b/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
@@ -8,6 +8,28 @@ import docspell.store.records.RJob
 
 object JobFactory {
 
+  def reprocessItem[F[_]: Sync](
+      args: ReProcessItemArgs,
+      account: AccountId,
+      prio: Priority,
+      tracker: Option[Ident]
+  ): F[RJob] =
+    for {
+      id  <- Ident.randomId[F]
+      now <- Timestamp.current[F]
+      job = RJob.newJob(
+        id,
+        ReProcessItemArgs.taskName,
+        account.collective,
+        args,
+        s"Re-process files of item ${args.itemId.id}",
+        now,
+        account.user,
+        prio,
+        tracker
+      )
+    } yield job
+
   def processItem[F[_]: Sync](
       args: ProcessItemArgs,
       account: AccountId,
diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala b/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
index a9145f72..a6fbce49 100644
--- a/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
+++ b/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
@@ -44,6 +44,19 @@ trait OUpload[F[_]] {
       case Left(srcId) =>
         submit(data, srcId, notifyJoex, itemId)
     }
+
+  /** Submits the item for re-processing. The list of attachment ids can
+    * be used to only re-process a subset of the item's attachments.
+    * If this list is empty, all attachments are reprocessed. This
+    * call only submits the job into the queue.
+    */
+  def reprocess(
+      item: Ident,
+      attachments: List[Ident],
+      account: AccountId,
+      notifyJoex: Boolean
+  ): F[OUpload.UploadResult]
+
 }
 
 object OUpload {
@@ -159,6 +172,21 @@ object OUpload {
           result <- OptionT.liftF(submit(updata, accId, notifyJoex, itemId))
         } yield result).getOrElse(UploadResult.noSource)
 
+      def reprocess(
+          item: Ident,
+          attachments: List[Ident],
+          account: AccountId,
+          notifyJoex: Boolean
+      ): F[UploadResult] =
+        (for {
+          _ <-
+            OptionT(store.transact(RItem.findByIdAndCollective(item, account.collective)))
+          args = ReProcessItemArgs(item, attachments)
+          job <-
+            OptionT.liftF(JobFactory.reprocessItem[F](args, account, Priority.Low, None))
+          res <- OptionT.liftF(submitJobs(notifyJoex)(Vector(job)))
+        } yield res).getOrElse(UploadResult.noItem)
+
       private def submitJobs(
           notifyJoex: Boolean
       )(jobs: Vector[RJob]): F[OUpload.UploadResult] =
diff --git a/modules/common/src/main/scala/docspell/common/ReProcessItemArgs.scala b/modules/common/src/main/scala/docspell/common/ReProcessItemArgs.scala
new file mode 100644
index 00000000..f8afdd58
--- /dev/null
+++ b/modules/common/src/main/scala/docspell/common/ReProcessItemArgs.scala
@@ -0,0 +1,24 @@
+package docspell.common
+
+import io.circe.generic.semiauto._
+import io.circe.{Decoder, Encoder}
+
+/** Arguments when re-processing an item.
+  *
+  * The `itemId` must exist and point to some item. If the attachment
+  * list is non-empty, only those attachments are re-processed. They
+  * must belong to the given item. If the list is empty, then all
+  * attachments are re-processed.
+  */
+case class ReProcessItemArgs(itemId: Ident, attachments: List[Ident])
+
+object ReProcessItemArgs {
+
+  val taskName: Ident = Ident.unsafe("re-process-item")
+
+  implicit val jsonEncoder: Encoder[ReProcessItemArgs] =
+    deriveEncoder[ReProcessItemArgs]
+
+  implicit val jsonDecoder: Decoder[ReProcessItemArgs] =
+    deriveDecoder[ReProcessItemArgs]
+}
diff --git a/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala b/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
index 965659b7..9882455c 100644
--- a/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
+++ b/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
@@ -14,6 +14,7 @@ import docspell.joex.fts.{MigrationTask, ReIndexTask}
 import docspell.joex.hk._
 import docspell.joex.notify._
 import docspell.joex.process.ItemHandler
+import docspell.joex.process.ReProcessItem
 import docspell.joex.scanmailbox._
 import docspell.joex.scheduler._
 import docspell.joexapi.client.JoexClient
@@ -96,6 +97,13 @@ object JoexAppImpl {
             ItemHandler.onCancel[F]
           )
         )
+        .withTask(
+          JobTask.json(
+            ReProcessItemArgs.taskName,
+            ReProcessItem[F](cfg, fts),
+            ReProcessItem.onCancel[F]
+          )
+        )
         .withTask(
           JobTask.json(
             NotifyDueItemsArgs.taskName,
diff --git a/modules/joex/src/main/scala/docspell/joex/process/ConvertPdf.scala b/modules/joex/src/main/scala/docspell/joex/process/ConvertPdf.scala
index ba75ec3a..572a18bb 100644
--- a/modules/joex/src/main/scala/docspell/joex/process/ConvertPdf.scala
+++ b/modules/joex/src/main/scala/docspell/joex/process/ConvertPdf.scala
@@ -126,11 +126,46 @@ object ConvertPdf {
       .compile
       .lastOrError
       .map(fm => Ident.unsafe(fm.id))
-      .flatMap(fmId =>
-        ctx.store
-          .transact(RAttachment.updateFileIdAndName(ra.id, fmId, newName))
-          .map(_ => fmId)
-      )
+      .flatMap(fmId => updateAttachment[F](ctx, ra, fmId, newName).map(_ => fmId))
       .map(fmId => ra.copy(fileId = fmId, name = newName))
   }
+
+  private def updateAttachment[F[_]: Sync](
+      ctx: Context[F, _],
+      ra: RAttachment,
+      fmId: Ident,
+      newName: Option[String]
+  ): F[Unit] =
+    for {
+      oldFile <- ctx.store.transact(RAttachment.findById(ra.id))
+      _ <-
+        ctx.store
+          .transact(RAttachment.updateFileIdAndName(ra.id, fmId, newName))
+      _ <- oldFile match {
+        case Some(raPrev) =>
+          for {
+            sameFile <-
+              ctx.store
+                .transact(RAttachmentSource.isSameFile(ra.id, raPrev.fileId))
+            _ <-
+              if (sameFile) ().pure[F]
+              else
+                ctx.logger.info("Deleting previous attachment file") *>
+                  ctx.store.bitpeace
+                    .delete(raPrev.fileId.id)
+                    .compile
+                    .drain
+                    .attempt
+                    .flatMap {
+                      case Right(_) => ().pure[F]
+                      case Left(ex) =>
+                        ctx.logger
+                          .error(ex)(s"Cannot delete previous attachment file: ${raPrev}")
+
+                    }
+          } yield ()
+        case None =>
+          ().pure[F]
+      }
+    } yield ()
 }
diff --git a/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala b/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala
index 139ec8f6..72eefa39 100644
--- a/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala
+++ b/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala
@@ -27,6 +27,17 @@ object ProcessItem {
       .flatMap(SetGivenData[F](itemOps))
       .flatMap(Task.setProgress(99))
 
+  def processAttachments[F[_]: ConcurrentEffect: ContextShift](
+      cfg: Config,
+      fts: FtsClient[F]
+  )(item: ItemData): Task[F, ProcessItemArgs, ItemData] =
+    ConvertPdf(cfg.convert, item)
+      .flatMap(Task.setProgress(30))
+      .flatMap(TextExtraction(cfg.extraction, fts))
+      .flatMap(Task.setProgress(60))
+      .flatMap(analysisOnly[F](cfg))
+      .flatMap(Task.setProgress(90))
+
   def analysisOnly[F[_]: Sync](
       cfg: Config
   )(item: ItemData): Task[F, ProcessItemArgs, ItemData] =
diff --git a/modules/joex/src/main/scala/docspell/joex/process/ReProcessItem.scala b/modules/joex/src/main/scala/docspell/joex/process/ReProcessItem.scala
new file mode 100644
index 00000000..8f5e11f2
--- /dev/null
+++ b/modules/joex/src/main/scala/docspell/joex/process/ReProcessItem.scala
@@ -0,0 +1,131 @@
+package docspell.joex.process
+
+import cats.data.OptionT
+import cats.effect._
+import cats.implicits._
+
+import docspell.common._
+import docspell.ftsclient.FtsClient
+import docspell.joex.Config
+import docspell.joex.scheduler.Context
+import docspell.joex.scheduler.Task
+import docspell.store.records.RAttachment
+import docspell.store.records.RAttachmentSource
+import docspell.store.records.RCollective
+import docspell.store.records.RItem
+
+object ReProcessItem {
+  type Args = ReProcessItemArgs
+
+  def apply[F[_]: ConcurrentEffect: ContextShift](
+      cfg: Config,
+      fts: FtsClient[F]
+  ): Task[F, Args, Unit] =
+    loadItem[F]
+      .flatMap(safeProcess[F](cfg, fts))
+      .map(_ => ())
+
+  def onCancel[F[_]: Sync: ContextShift]: Task[F, Args, Unit] =
+    logWarn("Now cancelling re-processing.")
+
+  // --- Helpers
+
+  private def contains[F[_]](ctx: Context[F, Args]): RAttachment => Boolean = {
+    val selection = ctx.args.attachments.toSet
+    if (selection.isEmpty) (_ => true)
+    else ra => selection.contains(ra.id)
+  }
+
+  def loadItem[F[_]: Sync]: Task[F, Args, ItemData] =
+    Task { ctx =>
+      (for {
+        item   <- OptionT(ctx.store.transact(RItem.findById(ctx.args.itemId)))
+        attach <- OptionT.liftF(ctx.store.transact(RAttachment.findByItem(item.id)))
+        asrc <-
+          OptionT.liftF(ctx.store.transact(RAttachmentSource.findByItem(ctx.args.itemId)))
+        asrcMap = asrc.map(s => s.id -> s).toMap
+        // copy the original files over to attachments to run the default processing task
+        // the processing doesn't touch the original files, only RAttachments
+        attachSrc =
+          attach
+            .filter(contains(ctx))
+            .flatMap(a =>
+              asrcMap.get(a.id).map { src =>
+                a.copy(fileId = src.fileId, name = src.name)
+              }
+            )
+      } yield ItemData(
+        item,
+        attachSrc,
+        Vector.empty,
+        Vector.empty,
+        asrcMap.view.mapValues(_.fileId).toMap,
+        MetaProposalList.empty,
+        Nil
+      )).getOrElseF(
+        Sync[F].raiseError(new Exception(s"Item not found: ${ctx.args.itemId.id}"))
+      )
+    }
+
+  def processFiles[F[_]: ConcurrentEffect: ContextShift](
+      cfg: Config,
+      fts: FtsClient[F],
+      data: ItemData
+  ): Task[F, Args, ItemData] = {
+
+    val convertArgs: Language => Args => F[ProcessItemArgs] =
+      lang =>
+        args =>
+          ProcessItemArgs(
+            ProcessItemArgs.ProcessMeta(
+              data.item.cid,
+              args.itemId.some,
+              lang,
+              None, //direction
+              "",   //source-id
+              None, //folder
+              Seq.empty
+            ),
+            Nil
+          ).pure[F]
+
+    getLanguage[F].flatMap { lang =>
+      ProcessItem
+        .processAttachments[F](cfg, fts)(data)
+        .contramap[Args](convertArgs(lang))
+    }
+  }
+
+  def getLanguage[F[_]: Sync]: Task[F, Args, Language] =
+    Task { ctx =>
+      (for {
+        coll <- OptionT(ctx.store.transact(RCollective.findByItem(ctx.args.itemId)))
+        lang = coll.language
+      } yield lang).getOrElse(Language.German)
+    }
+
+  def isLastRetry[F[_]: Sync]: Task[F, Args, Boolean] =
+    Task(_.isLastRetry)
+
+  def safeProcess[F[_]: ConcurrentEffect: ContextShift](
+      cfg: Config,
+      fts: FtsClient[F]
+  )(data: ItemData): Task[F, Args, ItemData] =
+    isLastRetry[F].flatMap {
+      case true =>
+        processFiles[F](cfg, fts, data).attempt
+          .flatMap({
+            case Right(d) =>
+              Task.pure(d)
+            case Left(ex) =>
+              logWarn[F](
+                "Processing failed on last retry."
+              ).andThen(_ => Sync[F].raiseError(ex))
+          })
+      case false =>
+        processFiles[F](cfg, fts, data)
+    }
+
+  private def logWarn[F[_]](msg: => String): Task[F, Args, Unit] =
+    Task(_.logger.warn(msg))
+}
diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml
index 7833b28e..c8831ed4 100644
--- a/modules/restapi/src/main/resources/docspell-openapi.yml
+++ b/modules/restapi/src/main/resources/docspell-openapi.yml
@@ -1796,6 +1796,31 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/ItemProposals"
+  /sec/item/{itemId}/reprocess:
+    post:
+      tags: [ Item ]
+      summary: Start reprocessing the files of the item.
+      description: |
+        This submits a job that will re-process the files (either all
+        or the ones specified) of the item and replace the metadata.
+      security:
+        - authTokenHeader: []
+      parameters:
+        - $ref: "#/components/parameters/id"
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/StringList"
+      responses:
+        200:
+          description: Ok
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/BasicResult"
+
+
   /sec/item/{itemId}/attachment/movebefore:
     post:
       tags: [ Item ]
diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
index 8f51d79a..d932d407 100644
--- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
+++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
@@ -279,6 +279,15 @@ object ItemRoutes {
           resp <- Ok(Conversions.basicResult(res, "Attachment moved."))
         } yield resp
 
+      case req @ POST -> Root / Ident(id) / "reprocess" =>
+        for {
+          data <- req.as[StringList]
+          ids = data.items.flatMap(s => Ident.fromString(s).toOption)
+          _    <- logger.fdebug(s"Re-process item ${id.id}")
+          res  <- backend.upload.reprocess(id, ids, user.account, true)
+          resp <- Ok(Conversions.basicResult(res))
+        } yield resp
+
       case DELETE -> Root / Ident(id) =>
         for {
           n <- backend.item.deleteItem(id, user.account.collective)
diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachment.scala b/modules/store/src/main/scala/docspell/store/records/RAttachment.scala
index 61d676b6..8c93de42 100644
--- a/modules/store/src/main/scala/docspell/store/records/RAttachment.scala
+++ b/modules/store/src/main/scala/docspell/store/records/RAttachment.scala
@@ -71,6 +71,16 @@ object RAttachment {
       commas(fileId.setTo(fId), name.setTo(fname))
     ).update.run
 
+  def updateFileId(
+      attachId: Ident,
+      fId: Ident
+  ): ConnectionIO[Int] =
+    updateRow(
+      table,
+      id.is(attachId),
+      fileId.setTo(fId)
+    ).update.run
+
   def updatePosition(attachId: Ident, pos: Int): ConnectionIO[Int] =
     updateRow(table, id.is(attachId), position.setTo(pos)).update.run
 
diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala b/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala
index 58b0a6c7..d732ecff 100644
--- a/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala
+++ b/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala
@@ -42,6 +42,12 @@ object RAttachmentSource {
   def findById(attachId: Ident): ConnectionIO[Option[RAttachmentSource]] =
     selectSimple(all, table, id.is(attachId)).query[RAttachmentSource].option
 
+  def isSameFile(attachId: Ident, file: Ident): ConnectionIO[Boolean] =
+    selectCount(id, table, and(id.is(attachId), fileId.is(file)))
+      .query[Int]
+      .unique
+      .map(_ > 0)
+
   def delete(attachId: Ident): ConnectionIO[Int] =
     deleteFrom(table, id.is(attachId)).update.run
 
@@ -64,6 +70,17 @@ object RAttachmentSource {
     selectSimple(all.map(_.prefix("a")), from, where).query[RAttachmentSource].option
   }
 
+  def findByItem(itemId: Ident): ConnectionIO[Vector[RAttachmentSource]] = {
+    val sId   = Columns.id.prefix("s")
+    val aId   = RAttachment.Columns.id.prefix("a")
+    val aItem = RAttachment.Columns.itemId.prefix("a")
+
+    val from = table ++ fr"s INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ sId.is(aId)
+    selectSimple(all.map(_.prefix("s")), from, aItem.is(itemId))
+      .query[RAttachmentSource]
+      .to[Vector]
+  }
+
   def findByItemWithMeta(
       id: Ident
   ): ConnectionIO[Vector[(RAttachmentSource, FileMeta)]] = {
diff --git a/modules/store/src/main/scala/docspell/store/records/RCollective.scala b/modules/store/src/main/scala/docspell/store/records/RCollective.scala
index 9d27bd1e..22115d5e 100644
--- a/modules/store/src/main/scala/docspell/store/records/RCollective.scala
+++ b/modules/store/src/main/scala/docspell/store/records/RCollective.scala
@@ -75,6 +75,14 @@ object RCollective {
     sql.query[RCollective].option
   }
 
+  def findByItem(itemId: Ident): ConnectionIO[Option[RCollective]] = {
+    val iColl = RItem.Columns.cid.prefix("i")
+    val iId   = RItem.Columns.id.prefix("i")
+    val cId   = id.prefix("c")
+    val from  = RItem.table ++ fr"i INNER JOIN" ++ table ++ fr"c ON" ++ iColl.is(cId)
+    selectSimple(all.map(_.prefix("c")), from, iId.is(itemId)).query[RCollective].option
+  }
+
   def existsById(cid: Ident): ConnectionIO[Boolean] = {
     val sql = selectCount(id, table, id.is(cid))
     sql.query[Int].unique.map(_ > 0)
@@ -90,5 +98,19 @@ object RCollective {
     sql.query[RCollective].stream
   }
 
+  def findByAttachment(attachId: Ident): ConnectionIO[Option[RCollective]] = {
+    val iColl = RItem.Columns.cid.prefix("i")
+    val iId   = RItem.Columns.id.prefix("i")
+    val aItem = RAttachment.Columns.itemId.prefix("a")
+    val aId   = RAttachment.Columns.id.prefix("a")
+    val cId   = Columns.id.prefix("c")
+
+    val from = table ++ fr"c INNER JOIN" ++
+      RItem.table ++ fr"i ON" ++ cId.is(iColl) ++ fr"INNER JOIN" ++
+      RAttachment.table ++ fr"a ON" ++ aItem.is(iId)
+
+    selectSimple(all, from, aId.is(attachId)).query[RCollective].option
+  }
+
   case class Settings(language: Language, integrationEnabled: Boolean)
 }
diff --git a/modules/store/src/main/scala/docspell/store/records/RItem.scala b/modules/store/src/main/scala/docspell/store/records/RItem.scala
index e961e8b2..a0025ddb 100644
--- a/modules/store/src/main/scala/docspell/store/records/RItem.scala
+++ b/modules/store/src/main/scala/docspell/store/records/RItem.scala
@@ -314,6 +314,9 @@ object RItem {
   def findByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[RItem]] =
     selectSimple(all, table, and(id.is(itemId), cid.is(coll))).query[RItem].option
 
+  def findById(itemId: Ident): ConnectionIO[Option[RItem]] =
+    selectSimple(all, table, id.is(itemId)).query[RItem].option
+
   def checkByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[Ident]] =
     selectSimple(Seq(id), table, and(id.is(itemId), cid.is(coll))).query[Ident].option
 

From 41ea071555722e17046c5d9e9ace8c29f36f28b5 Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Wed, 12 Aug 2020 23:56:29 +0200
Subject: [PATCH 2/6] Add a task to convert all pdfs that have not been
 converted

---
 .../docspell/common/ConvertAllPdfArgs.scala   |  14 ++
 .../scala/docspell/joex/JoexAppImpl.scala     |  16 ++
 .../joex/pdfconv/ConvertAllPdfTask.scala      |  68 ++++++++
 .../docspell/joex/pdfconv/PdfConvTask.scala   | 155 ++++++++++++++++++
 .../docspell/store/records/RAttachment.scala  |  29 ++++
 .../docspell/store/records/RCollective.scala  |   2 +-
 6 files changed, 283 insertions(+), 1 deletion(-)
 create mode 100644 modules/common/src/main/scala/docspell/common/ConvertAllPdfArgs.scala
 create mode 100644 modules/joex/src/main/scala/docspell/joex/pdfconv/ConvertAllPdfTask.scala
 create mode 100644 modules/joex/src/main/scala/docspell/joex/pdfconv/PdfConvTask.scala

diff --git a/modules/common/src/main/scala/docspell/common/ConvertAllPdfArgs.scala b/modules/common/src/main/scala/docspell/common/ConvertAllPdfArgs.scala
new file mode 100644
index 00000000..d4ae5ba7
--- /dev/null
+++ b/modules/common/src/main/scala/docspell/common/ConvertAllPdfArgs.scala
@@ -0,0 +1,14 @@
+package docspell.common
+
+import io.circe._
+import io.circe.generic.semiauto._
+
+case class ConvertAllPdfArgs(collective: Option[Ident])
+
+object ConvertAllPdfArgs {
+  val taskName = Ident.unsafe("submit-pdf-migration-tasks")
+  implicit val jsonDecoder: Decoder[ConvertAllPdfArgs] =
+    deriveDecoder[ConvertAllPdfArgs]
+  implicit val jsonEncoder: Encoder[ConvertAllPdfArgs] =
+    deriveEncoder[ConvertAllPdfArgs]
+}
diff --git a/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala b/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
index 9882455c..f07e089e 100644
--- a/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
+++ b/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
@@ -13,6 +13,8 @@ import docspell.ftssolr.SolrFtsClient
 import docspell.joex.fts.{MigrationTask, ReIndexTask}
 import docspell.joex.hk._
 import docspell.joex.notify._
+import docspell.joex.pdfconv.ConvertAllPdfTask
+import docspell.joex.pdfconv.PdfConvTask
 import docspell.joex.process.ItemHandler
 import docspell.joex.process.ReProcessItem
 import docspell.joex.scanmailbox._
@@ -139,6 +141,20 @@ object JoexAppImpl {
             HouseKeepingTask.onCancel[F]
           )
         )
+        .withTask(
+          JobTask.json(
+            PdfConvTask.taskName,
+            PdfConvTask[F](cfg),
+            PdfConvTask.onCancel[F]
+          )
+        )
+        .withTask(
+          JobTask.json(
+            ConvertAllPdfArgs.taskName,
+            ConvertAllPdfTask[F](queue, joex),
+            ConvertAllPdfTask.onCancel[F]
+          )
+        )
         .resource
       psch <- PeriodicScheduler.create(
         cfg.periodicScheduler,
diff --git a/modules/joex/src/main/scala/docspell/joex/pdfconv/ConvertAllPdfTask.scala b/modules/joex/src/main/scala/docspell/joex/pdfconv/ConvertAllPdfTask.scala
new file mode 100644
index 00000000..c40d0783
--- /dev/null
+++ b/modules/joex/src/main/scala/docspell/joex/pdfconv/ConvertAllPdfTask.scala
@@ -0,0 +1,68 @@
+package docspell.joex.pdfconv
+
+import cats.effect._
+import cats.implicits._
+import fs2.{Chunk, Stream}
+
+import docspell.backend.ops.OJoex
+import docspell.common._
+import docspell.joex.scheduler.{Context, Task}
+import docspell.store.queue.JobQueue
+import docspell.store.records.RAttachment
+import docspell.store.records._
+
+object ConvertAllPdfTask {
+  type Args = ConvertAllPdfArgs
+
+  def apply[F[_]: Sync](queue: JobQueue[F], joex: OJoex[F]): Task[F, Args, Unit] =
+    Task { ctx =>
+      for {
+        _ <- ctx.logger.info("Converting older pdfs using ocrmypdf")
+        n <- submitConversionJobs(ctx, queue)
+        _ <- ctx.logger.info(s"Submitted $n jobs for file conversion")
+        _ <- joex.notifyAllNodes
+      } yield ()
+    }
+
+  def onCancel[F[_]: Sync]: Task[F, Args, Unit] =
+    Task.log(_.warn("Cancelling convert-old-pdf task"))
+
+  def submitConversionJobs[F[_]: Sync](
+      ctx: Context[F, Args],
+      queue: JobQueue[F]
+  ): F[Int] =
+    ctx.store
+      .transact(RAttachment.findNonConvertedPdf(ctx.args.collective, 50))
+      .chunks
+      .flatMap(createJobs[F](ctx))
+      .chunks
+      .evalMap(jobs => queue.insertAll(jobs.toVector).map(_ => jobs.size))
+      .evalTap(n => ctx.logger.debug(s"Submitted $n jobs …"))
+      .compile
+      .foldMonoid
+
+  private def createJobs[F[_]: Sync](
+      ctx: Context[F, Args]
+  )(ras: Chunk[RAttachment]): Stream[F, RJob] = {
+    val collectiveOrSystem = ctx.args.collective.getOrElse(DocspellSystem.taskGroup)
+
+    def mkJob(ra: RAttachment): F[RJob] =
+      for {
+        id  <- Ident.randomId[F]
+        now <- Timestamp.current[F]
+      } yield RJob.newJob(
+        id,
+        PdfConvTask.taskName,
+        collectiveOrSystem,
+        PdfConvTask.Args(ra.id),
+        s"Convert pdf ${ra.id.id}/${ra.name.getOrElse("-")}",
+        now,
+        collectiveOrSystem,
+        Priority.Low,
+        Some(ra.id)
+      )
+
+    val jobs = ras.traverse(mkJob)
+    Stream.evalUnChunk(jobs)
+  }
+}
diff --git a/modules/joex/src/main/scala/docspell/joex/pdfconv/PdfConvTask.scala b/modules/joex/src/main/scala/docspell/joex/pdfconv/PdfConvTask.scala
new file mode 100644
index 00000000..07cc7c36
--- /dev/null
+++ b/modules/joex/src/main/scala/docspell/joex/pdfconv/PdfConvTask.scala
@@ -0,0 +1,155 @@
+package docspell.joex.pdfconv
+
+import cats.data.Kleisli
+import cats.data.OptionT
+import cats.effect._
+import cats.implicits._
+import fs2.Stream
+
+import docspell.common._
+import docspell.convert.ConversionResult
+import docspell.convert.extern.OcrMyPdf
+import docspell.joex.Config
+import docspell.joex.scheduler.{Context, Task}
+import docspell.store.records._
+
+import bitpeace.FileMeta
+import bitpeace.Mimetype
+import bitpeace.MimetypeHint
+import bitpeace.RangeDef
+import io.circe.generic.semiauto._
+import io.circe.{Decoder, Encoder}
+
+/** Converts the given attachment file using ocrmypdf if it is a pdf
+  * and has not already been converted (the source file is the same as
+  * in the attachment).
+  */
+object PdfConvTask {
+  case class Args(attachId: Ident)
+  object Args {
+    implicit val jsonDecoder: Decoder[Args] =
+      deriveDecoder[Args]
+    implicit val jsonEncoder: Encoder[Args] =
+      deriveEncoder[Args]
+  }
+
+  val taskName = Ident.unsafe("pdf-files-migration")
+
+  def apply[F[_]: Sync: ContextShift](cfg: Config): Task[F, Args, Unit] =
+    Task { ctx =>
+      for {
+        _    <- ctx.logger.info(s"Converting pdf file ${ctx.args} using ocrmypdf")
+        meta <- checkInputs(cfg, ctx)
+        _    <- meta.traverse(fm => convert(cfg, ctx, fm))
+      } yield ()
+    }
+
+  def onCancel[F[_]: Sync]: Task[F, Args, Unit] =
+    Task.log(_.warn("Cancelling pdfconv task"))
+
+  // --- Helper
+
+  // check if file exists and if it is pdf and if source id is the same and if ocrmypdf is enabled
+  def checkInputs[F[_]: Sync](cfg: Config, ctx: Context[F, Args]): F[Option[FileMeta]] = {
+    val none: Option[FileMeta] = None
+    val checkSameFiles =
+      (for {
+        ra <- OptionT(ctx.store.transact(RAttachment.findById(ctx.args.attachId)))
+        isSame <- OptionT.liftF(
+          ctx.store.transact(RAttachmentSource.isSameFile(ra.id, ra.fileId))
+        )
+      } yield isSame).getOrElse(false)
+    val existsPdf =
+      for {
+        meta <- ctx.store.transact(RAttachment.findMeta(ctx.args.attachId))
+        res = meta.filter(_.mimetype.matches(Mimetype.`application/pdf`))
+        _ <-
+          if (res.isEmpty)
+            ctx.logger.info(
+              s"The attachment ${ctx.args.attachId} doesn't exist or is no pdf: $meta"
+            )
+          else ().pure[F]
+      } yield res
+
+    if (cfg.convert.ocrmypdf.enabled)
+      checkSameFiles.flatMap {
+        case true => existsPdf
+        case false =>
+          ctx.logger.info(
+            s"The attachment ${ctx.args.attachId} already has been converted. Skipping."
+          ) *>
+            none.pure[F]
+      }
+    else none.pure[F]
+  }
+
+  def convert[F[_]: Sync: ContextShift](
+      cfg: Config,
+      ctx: Context[F, Args],
+      in: FileMeta
+  ): F[Unit] = {
+    val bp = ctx.store.bitpeace
+    val data = Stream
+      .emit(in)
+      .through(bp.fetchData2(RangeDef.all))
+
+    val storeResult: ConversionResult.Handler[F, Unit] =
+      Kleisli({
+        case ConversionResult.SuccessPdf(file) =>
+          storeToAttachment(ctx, in, file)
+
+        case ConversionResult.SuccessPdfTxt(file, _) =>
+          storeToAttachment(ctx, in, file)
+
+        case ConversionResult.UnsupportedFormat(mime) =>
+          ctx.logger.warn(
+            s"Unable to convert '${mime}' file ${ctx.args}: unsupported format."
+          )
+
+        case ConversionResult.InputMalformed(mime, reason) =>
+          ctx.logger.warn(s"Unable to convert '${mime}' file ${ctx.args}: $reason")
+
+        case ConversionResult.Failure(ex) =>
+          ctx.logger.error(ex)(s"Failure converting file ${ctx.args}: ${ex.getMessage}")
+      })
+
+    def ocrMyPdf(lang: Language): F[Unit] =
+      OcrMyPdf.toPDF[F, Unit](
+        cfg.convert.ocrmypdf,
+        lang,
+        in.chunksize,
+        ctx.blocker,
+        ctx.logger
+      )(data, storeResult)
+
+    for {
+      lang <- getLanguage(ctx)
+      _    <- ocrMyPdf(lang)
+    } yield ()
+  }
+
+  def getLanguage[F[_]: Sync](ctx: Context[F, Args]): F[Language] =
+    (for {
+      coll <- OptionT(ctx.store.transact(RCollective.findByAttachment(ctx.args.attachId)))
+      lang = coll.language
+    } yield lang).getOrElse(Language.German)
+
+  def storeToAttachment[F[_]: Sync](
+      ctx: Context[F, Args],
+      meta: FileMeta,
+      newFile: Stream[F, Byte]
+  ): F[Unit] = {
+    val mimeHint = MimetypeHint.advertised(meta.mimetype.asString)
+    for {
+      time <- Timestamp.current[F]
+      fid  <- Ident.randomId[F]
+      _ <-
+        ctx.store.bitpeace
+          .saveNew(newFile, meta.chunksize, mimeHint, Some(fid.id), time.value)
+          .compile
+          .lastOrError
+      _ <- ctx.store.transact(RAttachment.updateFileId(ctx.args.attachId, fid))
+    } yield ()
+  }
+
+}
diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachment.scala b/modules/store/src/main/scala/docspell/store/records/RAttachment.scala
index 8c93de42..fa6bb724 100644
--- a/modules/store/src/main/scala/docspell/store/records/RAttachment.scala
+++ b/modules/store/src/main/scala/docspell/store/records/RAttachment.scala
@@ -1,6 +1,7 @@
 package docspell.store.records
 
 import cats.implicits._
+import fs2.Stream
 
 import docspell.common._
 import docspell.store.impl.Implicits._
@@ -197,4 +198,32 @@ object RAttachment {
 
   def findItemId(attachId: Ident): ConnectionIO[Option[Ident]] =
     selectSimple(Seq(itemId), table, id.is(attachId)).query[Ident].option
+
+  def findNonConvertedPdf(
+      coll: Option[Ident],
+      chunkSize: Int
+  ): Stream[ConnectionIO, RAttachment] = {
+    val aId     = Columns.id.prefix("a")
+    val aItem   = Columns.itemId.prefix("a")
+    val aFile   = Columns.fileId.prefix("a")
+    val sId     = RAttachmentSource.Columns.id.prefix("s")
+    val sFile   = RAttachmentSource.Columns.fileId.prefix("s")
+    val iId     = RItem.Columns.id.prefix("i")
+    val iColl   = RItem.Columns.cid.prefix("i")
+    val mId     = RFileMeta.Columns.id.prefix("m")
+    val mType   = RFileMeta.Columns.mimetype.prefix("m")
+    val pdfType = "application/pdf%"
+
+    val from = table ++ fr"a INNER JOIN" ++
+      RAttachmentSource.table ++ fr"s ON" ++ sId.is(aId) ++ fr"INNER JOIN" ++
+      RItem.table ++ fr"i ON" ++ iId.is(aItem) ++ fr"INNER JOIN" ++
+      RFileMeta.table ++ fr"m ON" ++ aFile.is(mId)
+    val where = coll match {
+      case Some(cid) => and(iColl.is(cid), aFile.is(sFile), mType.lowerLike(pdfType))
+      case None      => and(aFile.is(sFile), mType.lowerLike(pdfType))
+    }
+    selectSimple(all.map(_.prefix("a")), from, where)
+      .query[RAttachment]
+      .streamWithChunkSize(chunkSize)
+  }
 }
diff --git a/modules/store/src/main/scala/docspell/store/records/RCollective.scala b/modules/store/src/main/scala/docspell/store/records/RCollective.scala
index 22115d5e..fa40e374 100644
--- a/modules/store/src/main/scala/docspell/store/records/RCollective.scala
+++ b/modules/store/src/main/scala/docspell/store/records/RCollective.scala
@@ -109,7 +109,7 @@ object RCollective {
       RItem.table ++ fr"i ON" ++ cId.is(iColl) ++ fr"INNER JOIN" ++
       RAttachment.table ++ fr"a ON" ++ aItem.is(iId)
 
-    selectSimple(all, from, aId.is(attachId)).query[RCollective].option
+    selectSimple(all.map(_.prefix("c")), from, aId.is(attachId)).query[RCollective].option
   }
 
   case class Settings(language: Language, integrationEnabled: Boolean)

From 69674eb485c00bced53e334570287bf17eac2499 Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Thu, 13 Aug 2020 01:01:02 +0200
Subject: [PATCH 3/6] Improve job-queue query to make sure jobs across all
 states show up

---
 .../scala/docspell/backend/ops/OJob.scala     |  4 ++-
 .../restserver/routes/JobQueueRoutes.scala    |  2 +-
 .../scala/docspell/store/queries/QJob.scala   | 33 ++++++++++++++-----
 3 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OJob.scala b/modules/backend/src/main/scala/docspell/backend/ops/OJob.scala
index 9a05337c..ade2fda0 100644
--- a/modules/backend/src/main/scala/docspell/backend/ops/OJob.scala
+++ b/modules/backend/src/main/scala/docspell/backend/ops/OJob.scala
@@ -48,7 +48,9 @@ object OJob {
 
       def queueState(collective: Ident, maxResults: Int): F[CollectiveQueueState] =
         store
-          .transact(QJob.queueStateSnapshot(collective).take(maxResults.toLong))
+          .transact(
+            QJob.queueStateSnapshot(collective, maxResults.toLong)
+          )
           .map(t => JobDetail(t._1, t._2))
           .compile
           .toVector
diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/JobQueueRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/JobQueueRoutes.scala
index fc605f74..4a34b219 100644
--- a/modules/restserver/src/main/scala/docspell/restserver/routes/JobQueueRoutes.scala
+++ b/modules/restserver/src/main/scala/docspell/restserver/routes/JobQueueRoutes.scala
@@ -21,7 +21,7 @@ object JobQueueRoutes {
     HttpRoutes.of {
       case GET -> Root / "state" =>
         for {
-          js <- backend.job.queueState(user.account.collective, 200)
+          js <- backend.job.queueState(user.account.collective, 40)
           res = Conversions.mkJobQueueState(js)
           resp <- Ok(res)
         } yield resp
diff --git a/modules/store/src/main/scala/docspell/store/queries/QJob.scala b/modules/store/src/main/scala/docspell/store/queries/QJob.scala
index 99f94b67..f3521ed9 100644
--- a/modules/store/src/main/scala/docspell/store/queries/QJob.scala
+++ b/modules/store/src/main/scala/docspell/store/queries/QJob.scala
@@ -209,7 +209,8 @@ object QJob {
     store.transact(RJob.findFromIds(ids))
 
   def queueStateSnapshot(
-      collective: Ident
+      collective: Ident,
+      max: Long
   ): Stream[ConnectionIO, (RJob, Vector[RJobLog])] = {
     val JC                     = RJob.Columns
     val waiting: Set[JobState] = Set(JobState.Waiting, JobState.Stuck, JobState.Scheduled)
@@ -218,18 +219,34 @@ object QJob {
 
     def selectJobs(now: Timestamp): Stream[ConnectionIO, RJob] = {
       val refDate = now.minusHours(24)
-      val sql = selectSimple(
+
+      val runningJobs = (selectSimple(
+        JC.all,
+        RJob.table,
+        and(JC.group.is(collective), JC.state.isOneOf(running.toSeq))
+      ) ++ orderBy(JC.submitted.desc)).query[RJob].stream
+
+      val waitingJobs = (selectSimple(
         JC.all,
         RJob.table,
         and(
           JC.group.is(collective),
-          or(
-            and(JC.state.isOneOf(done.toSeq), JC.submitted.isGt(refDate)),
-            JC.state.isOneOf((running ++ waiting).toSeq)
-          )
+          JC.state.isOneOf(waiting.toSeq),
+          JC.submitted.isGt(refDate)
         )
-      )
-      (sql ++ orderBy(JC.submitted.desc)).query[RJob].stream
+      ) ++ orderBy(JC.submitted.desc)).query[RJob].stream.take(max)
+
+      val doneJobs = (selectSimple(
+        JC.all,
+        RJob.table,
+        and(
+          JC.group.is(collective),
+          JC.state.isOneOf(done.toSeq),
+          JC.submitted.isGt(refDate)
+        )
+      ) ++ orderBy(JC.submitted.desc)).query[RJob].stream.take(max)
+
+      runningJobs ++ waitingJobs ++ doneJobs
     }
 
     def selectLogs(job: RJob): ConnectionIO[Vector[RJobLog]] =

From 081c4da903f8c78e4e13f9df900bcae220a97a3f Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Thu, 13 Aug 2020 01:03:42 +0200
Subject: [PATCH 4/6] Add a route to trigger the convert-all-pdf task for a
 collective

---
 .../scala/docspell/backend/JobFactory.scala   | 21 +++++++++++++++++++
 .../scala/docspell/backend/ops/OUpload.scala  | 15 +++++++++++++
 .../restserver/routes/ItemRoutes.scala        |  7 +++++++
 3 files changed, 43 insertions(+)

diff --git a/modules/backend/src/main/scala/docspell/backend/JobFactory.scala b/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
index 96399ffa..396352b4 100644
--- a/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
+++ b/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
@@ -8,6 +8,27 @@ import docspell.store.records.RJob
 
 object JobFactory {
 
+  def convertAllPdfs[F[_]: Sync](
+      collective: Option[Ident],
+      account: AccountId,
+      prio: Priority
+  ): F[RJob] =
+    for {
+      id  <- Ident.randomId[F]
+      now <- Timestamp.current[F]
+      job = RJob.newJob(
+        id,
+        ConvertAllPdfArgs.taskName,
+        account.collective,
+        ConvertAllPdfArgs(collective),
+        s"Convert all pdfs not yet converted",
+        now,
+        account.user,
+        prio,
+        None
+      )
+    } yield job
+
   def reprocessItem[F[_]: Sync](
       args: ReProcessItemArgs,
       account: AccountId,
diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala b/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
index a6fbce49..c6edbfb2 100644
--- a/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
+++ b/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
@@ -57,6 +57,11 @@ trait OUpload[F[_]] {
       notifyJoex: Boolean
   ): F[OUpload.UploadResult]
 
+  def convertAllPdf(
+      collective: Option[Ident],
+      account: AccountId,
+      notifyJoex: Boolean
+  ): F[OUpload.UploadResult]
 }
 
 object OUpload {
@@ -187,6 +192,16 @@ object OUpload {
           res <- OptionT.liftF(submitJobs(notifyJoex)(Vector(job)))
         } yield res).getOrElse(UploadResult.noItem)
 
+      def convertAllPdf(
+          collective: Option[Ident],
+          account: AccountId,
+          notifyJoex: Boolean
+      ): F[OUpload.UploadResult] =
+        for {
+          job <- JobFactory.convertAllPdfs(collective, account, Priority.Low)
+          res <- submitJobs(notifyJoex)(Vector(job))
+        } yield res
+
       private def submitJobs(
           notifyJoex: Boolean
       )(jobs: Vector[RJob]): F[OUpload.UploadResult] =
diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
index d932d407..49363696 100644
--- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
+++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
@@ -31,6 +31,13 @@ object ItemRoutes {
     import dsl._
 
     HttpRoutes.of {
+      case POST -> Root / "convertallpdfs" =>
+        for {
+          res <-
+            backend.upload.convertAllPdf(user.account.collective.some, user.account, true)
+          resp <- Ok(Conversions.basicResult(res))
+        } yield resp
+
       case req @ POST -> Root / "search" =>
         for {
           mask <- req.as[ItemSearch]

From 3986487f11ecdcef03973e1bcd204cb9800ed404 Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Thu, 13 Aug 2020 20:52:43 +0200
Subject: [PATCH 5/6] Add api docs and cleanup

---
 .../scala/docspell/backend/BackendApp.scala   |  2 +-
 .../scala/docspell/backend/JobFactory.scala   |  9 +--
 .../scala/docspell/backend/ops/OItem.scala    | 60 ++++++++++++++++++-
 .../scala/docspell/backend/ops/OUpload.scala  | 43 -------------
 .../docspell/common/ConvertAllPdfArgs.scala   | 12 ++++
 .../scala/docspell/joex/JoexAppImpl.scala     |  2 +-
 .../joex/pdfconv/ConvertAllPdfTask.scala      | 12 ++--
 .../docspell/joex/process/ProcessItem.scala   | 26 ++++----
 .../src/main/resources/docspell-openapi.yml   | 40 ++++++++++++-
 .../restserver/routes/ItemRoutes.scala        | 12 ++--
 .../scala/docspell/store/queue/JobQueue.scala | 10 ++++
 11 files changed, 155 insertions(+), 73 deletions(-)

diff --git a/modules/backend/src/main/scala/docspell/backend/BackendApp.scala b/modules/backend/src/main/scala/docspell/backend/BackendApp.scala
index 72ce0138..6ff3c73e 100644
--- a/modules/backend/src/main/scala/docspell/backend/BackendApp.scala
+++ b/modules/backend/src/main/scala/docspell/backend/BackendApp.scala
@@ -61,7 +61,7 @@ object BackendApp {
       uploadImpl     <- OUpload(store, queue, cfg.files, joexImpl)
       nodeImpl       <- ONode(store)
       jobImpl        <- OJob(store, joexImpl)
-      itemImpl       <- OItem(store, ftsClient)
+      itemImpl       <- OItem(store, ftsClient, queue, joexImpl)
       itemSearchImpl <- OItemSearch(store)
       fulltextImpl   <- OFulltext(itemSearchImpl, ftsClient, store, queue, joexImpl)
       javaEmil =
diff --git a/modules/backend/src/main/scala/docspell/backend/JobFactory.scala b/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
index 396352b4..bc05a188 100644
--- a/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
+++ b/modules/backend/src/main/scala/docspell/backend/JobFactory.scala
@@ -25,15 +25,16 @@ object JobFactory {
         now,
         account.user,
         prio,
-        None
+        collective
+          .map(c => c / ConvertAllPdfArgs.taskName)
+          .orElse(ConvertAllPdfArgs.taskName.some)
       )
     } yield job
 
   def reprocessItem[F[_]: Sync](
       args: ReProcessItemArgs,
       account: AccountId,
-      prio: Priority,
-      tracker: Option[Ident]
+      prio: Priority
   ): F[RJob] =
     for {
       id  <- Ident.randomId[F]
@@ -47,7 +48,7 @@ object JobFactory {
         now,
         account.user,
         prio,
-        tracker
+        Some(ReProcessItemArgs.taskName / args.itemId)
       )
     } yield job
 
diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala
index 4919fdfe..da3efce2 100644
--- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala
+++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala
@@ -4,10 +4,12 @@ import cats.data.OptionT
 import cats.effect.{Effect, Resource}
 import cats.implicits._
 
+import docspell.backend.JobFactory
 import docspell.common._
 import docspell.ftsclient.FtsClient
 import docspell.store.UpdateResult
 import docspell.store.queries.{QAttachment, QItem}
+import docspell.store.queue.JobQueue
 import docspell.store.records._
 import docspell.store.{AddResult, Store}
 
@@ -76,11 +78,38 @@ trait OItem[F[_]] {
       name: Option[String],
       collective: Ident
   ): F[AddResult]
+
+  /** Submits the item for re-processing. The list of attachment ids can
+    * be used to only re-process a subset of the item's attachments.
+    * If this list is empty, all attachments are reprocessed. This
+    * call only submits the job into the queue.
+    */
+  def reprocess(
+      item: Ident,
+      attachments: List[Ident],
+      account: AccountId,
+      notifyJoex: Boolean
+  ): F[UpdateResult]
+
+  /** Submits a task that finds all non-converted pdfs and triggers
+    * converting them using ocrmypdf. Each file is converted by a
+    * separate task.
+    */
+  def convertAllPdf(
+      collective: Option[Ident],
+      account: AccountId,
+      notifyJoex: Boolean
+  ): F[UpdateResult]
 }
 
 object OItem {
 
-  def apply[F[_]: Effect](store: Store[F], fts: FtsClient[F]): Resource[F, OItem[F]] =
+  def apply[F[_]: Effect](
+      store: Store[F],
+      fts: FtsClient[F],
+      queue: JobQueue[F],
+      joex: OJoex[F]
+  ): Resource[F, OItem[F]] =
     for {
       otag   <- OTag(store)
       oorg   <- OOrganization(store)
@@ -400,6 +429,35 @@ object OItem {
               )
             )
 
+        def reprocess(
+            item: Ident,
+            attachments: List[Ident],
+            account: AccountId,
+            notifyJoex: Boolean
+        ): F[UpdateResult] =
+          (for {
+            _ <- OptionT(
+              store.transact(RItem.findByIdAndCollective(item, account.collective))
+            )
+            args = ReProcessItemArgs(item, attachments)
+            job <- OptionT.liftF(
+              JobFactory.reprocessItem[F](args, account, Priority.Low)
+            )
+            _ <- OptionT.liftF(queue.insertIfNew(job))
+            _ <- OptionT.liftF(if (notifyJoex) joex.notifyAllNodes else ().pure[F])
+          } yield UpdateResult.success).getOrElse(UpdateResult.notFound)
+
+        def convertAllPdf(
+            collective: Option[Ident],
+            account: AccountId,
+            notifyJoex: Boolean
+        ): F[UpdateResult] =
+          for {
+            job <- JobFactory.convertAllPdfs[F](collective, account, Priority.Low)
+            _   <- queue.insertIfNew(job)
+            _   <- if (notifyJoex) joex.notifyAllNodes else ().pure[F]
+          } yield UpdateResult.success
+
         private def onSuccessIgnoreError(update: F[Unit])(ar: AddResult): F[Unit] =
           ar match {
             case AddResult.Success =>
diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala b/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
index c6edbfb2..a9145f72 100644
--- a/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
+++ b/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala
@@ -44,24 +44,6 @@ trait OUpload[F[_]] {
       case Left(srcId) =>
         submit(data, srcId, notifyJoex, itemId)
     }
-
-  /** Submits the item for re-processing. The list of attachment ids can
-    * be used to only re-process a subset of the item's attachments.
-    * If this list is empty, all attachments are reprocessed. This
-    * call only submits the job into the queue.
-    */
-  def reprocess(
-      item: Ident,
-      attachments: List[Ident],
-      account: AccountId,
-      notifyJoex: Boolean
-  ): F[OUpload.UploadResult]
-
-  def convertAllPdf(
-      collective: Option[Ident],
-      account: AccountId,
-      notifyJoex: Boolean
-  ): F[OUpload.UploadResult]
 }
 
 object OUpload {
@@ -177,31 +159,6 @@ object OUpload {
           result <- OptionT.liftF(submit(updata, accId, notifyJoex, itemId))
         } yield result).getOrElse(UploadResult.noSource)
 
-      def reprocess(
-          item: Ident,
-          attachments: List[Ident],
-          account: AccountId,
-          notifyJoex: Boolean
-      ): F[UploadResult] =
-        (for {
-          _ <-
-            OptionT(store.transact(RItem.findByIdAndCollective(item, account.collective)))
-          args = ReProcessItemArgs(item, attachments)
-          job <-
-            OptionT.liftF(JobFactory.reprocessItem[F](args, account, Priority.Low, None))
-          res <- OptionT.liftF(submitJobs(notifyJoex)(Vector(job)))
-        } yield res).getOrElse(UploadResult.noItem)
-
-      def convertAllPdf(
-          collective: Option[Ident],
-          account: AccountId,
-          notifyJoex: Boolean
-      ): F[OUpload.UploadResult] =
-        for {
-          job <- JobFactory.convertAllPdfs(collective, account, Priority.Low)
-          res <- submitJobs(notifyJoex)(Vector(job))
-        } yield res
-
       private def submitJobs(
           notifyJoex: Boolean
       )(jobs: Vector[RJob]): F[OUpload.UploadResult] =
diff --git a/modules/common/src/main/scala/docspell/common/ConvertAllPdfArgs.scala b/modules/common/src/main/scala/docspell/common/ConvertAllPdfArgs.scala
index d4ae5ba7..eb2978d7 100644
--- a/modules/common/src/main/scala/docspell/common/ConvertAllPdfArgs.scala
+++ b/modules/common/src/main/scala/docspell/common/ConvertAllPdfArgs.scala
@@ -3,12 +3,24 @@ package docspell.common
 import io.circe._
 import io.circe.generic.semiauto._
 
+/** Arguments for the task that finds all pdf files that have not been
+  * converted and submits for each a job that will convert the file
+  * using ocrmypdf.
+  *
+  * If the `collective` argument is present, then this task and the
+  * ones that are submitted by this task run in the realm of the
+  * collective (and only their files are considered). If it is empty,
+  * it is a system task and all files are considered.
+  */
 case class ConvertAllPdfArgs(collective: Option[Ident])
 
 object ConvertAllPdfArgs {
+
   val taskName = Ident.unsafe("submit-pdf-migration-tasks")
+
   implicit val jsonDecoder: Decoder[ConvertAllPdfArgs] =
     deriveDecoder[ConvertAllPdfArgs]
+
   implicit val jsonEncoder: Encoder[ConvertAllPdfArgs] =
     deriveEncoder[ConvertAllPdfArgs]
 }
diff --git a/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala b/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
index f07e089e..bc415446 100644
--- a/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
+++ b/modules/joex/src/main/scala/docspell/joex/JoexAppImpl.scala
@@ -87,7 +87,7 @@ object JoexAppImpl {
       joex    <- OJoex(client, store)
       upload  <- OUpload(store, queue, cfg.files, joex)
       fts     <- createFtsClient(cfg)(httpClient)
-      itemOps <- OItem(store, fts)
+      itemOps <- OItem(store, fts, queue, joex)
       javaEmil =
         JavaMailEmil(blocker, Settings.defaultSettings.copy(debug = cfg.mailDebug))
       sch <- SchedulerBuilder(cfg.scheduler, blocker, store)
diff --git a/modules/joex/src/main/scala/docspell/joex/pdfconv/ConvertAllPdfTask.scala b/modules/joex/src/main/scala/docspell/joex/pdfconv/ConvertAllPdfTask.scala
index c40d0783..019894fa 100644
--- a/modules/joex/src/main/scala/docspell/joex/pdfconv/ConvertAllPdfTask.scala
+++ b/modules/joex/src/main/scala/docspell/joex/pdfconv/ConvertAllPdfTask.scala
@@ -11,15 +11,19 @@ import docspell.store.queue.JobQueue
 import docspell.store.records.RAttachment
 import docspell.store.records._
 
+/* A task to find all non-converted pdf files (of a collective, or
+ * all) and converting them using ocrmypdf by submitting a job for
+ * each found file.
+ */
 object ConvertAllPdfTask {
   type Args = ConvertAllPdfArgs
 
   def apply[F[_]: Sync](queue: JobQueue[F], joex: OJoex[F]): Task[F, Args, Unit] =
     Task { ctx =>
       for {
-        _ <- ctx.logger.info("Converting older pdfs using ocrmypdf")
+        _ <- ctx.logger.info("Converting pdfs using ocrmypdf")
         n <- submitConversionJobs(ctx, queue)
-        _ <- ctx.logger.info(s"Submitted $n jobs for file conversion")
+        _ <- ctx.logger.info(s"Submitted $n file conversion jobs")
         _ <- joex.notifyAllNodes
       } yield ()
     }
@@ -36,7 +40,7 @@ object ConvertAllPdfTask {
       .chunks
       .flatMap(createJobs[F](ctx))
       .chunks
-      .evalMap(jobs => queue.insertAll(jobs.toVector).map(_ => jobs.size))
+      .evalMap(jobs => queue.insertAllIfNew(jobs.toVector).map(_ => jobs.size))
       .evalTap(n => ctx.logger.debug(s"Submitted $n jobs …"))
       .compile
       .foldMonoid
@@ -59,7 +63,7 @@ object ConvertAllPdfTask {
         now,
         collectiveOrSystem,
         Priority.Low,
-        Some(ra.id)
+        Some(PdfConvTask.taskName / ra.id)
       )
 
     val jobs = ras.traverse(mkJob)
diff --git a/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala b/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala
index 72eefa39..9b4d050f 100644
--- a/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala
+++ b/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala
@@ -17,12 +17,7 @@ object ProcessItem {
   )(item: ItemData): Task[F, ProcessItemArgs, ItemData] =
     ExtractArchive(item)
       .flatMap(Task.setProgress(20))
-      .flatMap(ConvertPdf(cfg.convert, _))
-      .flatMap(Task.setProgress(40))
-      .flatMap(TextExtraction(cfg.extraction, fts))
-      .flatMap(Task.setProgress(60))
-      .flatMap(analysisOnly[F](cfg))
-      .flatMap(Task.setProgress(80))
+      .flatMap(processAttachments0(cfg, fts, (40, 60, 80)))
       .flatMap(LinkProposal[F])
       .flatMap(SetGivenData[F](itemOps))
       .flatMap(Task.setProgress(99))
@@ -31,12 +26,7 @@ object ProcessItem {
       cfg: Config,
       fts: FtsClient[F]
   )(item: ItemData): Task[F, ProcessItemArgs, ItemData] =
-    ConvertPdf(cfg.convert, item)
-      .flatMap(Task.setProgress(30))
-      .flatMap(TextExtraction(cfg.extraction, fts))
-      .flatMap(Task.setProgress(60))
-      .flatMap(analysisOnly[F](cfg))
-      .flatMap(Task.setProgress(90))
+    processAttachments0[F](cfg, fts, (30, 60, 90))(item)
 
   def analysisOnly[F[_]: Sync](
       cfg: Config
@@ -45,4 +35,16 @@ object ProcessItem {
       .flatMap(FindProposal[F](cfg.processing))
       .flatMap(EvalProposals[F])
       .flatMap(SaveProposals[F])
+
+  private def processAttachments0[F[_]: ConcurrentEffect: ContextShift](
+      cfg: Config,
+      fts: FtsClient[F],
+      progress: (Int, Int, Int)
+  )(item: ItemData): Task[F, ProcessItemArgs, ItemData] =
+    ConvertPdf(cfg.convert, item)
+      .flatMap(Task.setProgress(progress._1))
+      .flatMap(TextExtraction(cfg.extraction, fts))
+      .flatMap(Task.setProgress(progress._2))
+      .flatMap(analysisOnly[F](cfg))
+      .flatMap(Task.setProgress(progress._3))
 }
diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml
index c8831ed4..94f84dd0 100644
--- a/modules/restapi/src/main/resources/docspell-openapi.yml
+++ b/modules/restapi/src/main/resources/docspell-openapi.yml
@@ -1213,6 +1213,33 @@ paths:
               schema:
                 $ref: "#/components/schemas/BasicResult"
 
+  /sec/item/convertallpdfs:
+    post:
+      tags: [ Item ]
+      summary: Convert all non-converted pdfs.
+      description: |
+        Submits a job that will find all pdf files that have not been
+        converted and converts them using the ocrmypdf tool (if
+        enabled). This tool has been added in version 0.9.0 and so
+        older files can be "migrated" this way, or maybe after
+        enabling the tool.
+
+        The task finds all files of the current collective and submits
+        task for each file to convert. These tasks are submitted with
+        a low priority so that normal processing can still proceed.
+
+        The body of the request should be empty.
+      security:
+        - authTokenHeader: []
+      responses:
+        200:
+          description: Ok
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/BasicResult"
+
+
   /sec/item/search:
     post:
       tags: [ Item ]
@@ -1811,7 +1838,7 @@ paths:
         content:
           application/json:
             schema:
-              $ref: "#/components/schemas/StringList"
+              $ref: "#/components/schemas/IdList"
       responses:
         200:
           description: Ok
@@ -2629,6 +2656,17 @@ paths:
 
 components:
   schemas:
+    IdList:
+      description:
+        A list of identifiers.
+      required:
+        - ids
+      properties:
+        ids:
+          type: array
+          items:
+            type: string
+            format: ident
     StringList:
       description: |
         A simple list of strings.
diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
index 49363696..a033791d 100644
--- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
+++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala
@@ -34,8 +34,8 @@ object ItemRoutes {
       case POST -> Root / "convertallpdfs" =>
         for {
           res <-
-            backend.upload.convertAllPdf(user.account.collective.some, user.account, true)
-          resp <- Ok(Conversions.basicResult(res))
+            backend.item.convertAllPdf(user.account.collective.some, user.account, true)
+          resp <- Ok(Conversions.basicResult(res, "Task submitted"))
         } yield resp
 
       case req @ POST -> Root / "search" =>
@@ -288,11 +288,11 @@ object ItemRoutes {
 
       case req @ POST -> Root / Ident(id) / "reprocess" =>
         for {
-          data <- req.as[StringList]
-          ids = data.items.flatMap(s => Ident.fromString(s).toOption)
+          data <- req.as[IdList]
+          ids = data.ids.flatMap(s => Ident.fromString(s).toOption)
           _    <- logger.fdebug(s"Re-process item ${id.id}")
-          res  <- backend.upload.reprocess(id, ids, user.account, true)
-          resp <- Ok(Conversions.basicResult(res))
+          res  <- backend.item.reprocess(id, ids, user.account, true)
+          resp <- Ok(Conversions.basicResult(res, "Re-process task submitted."))
         } yield resp
 
       case DELETE -> Root / Ident(id) =>
diff --git a/modules/store/src/main/scala/docspell/store/queue/JobQueue.scala b/modules/store/src/main/scala/docspell/store/queue/JobQueue.scala
index f7d15ed5..127a45e1 100644
--- a/modules/store/src/main/scala/docspell/store/queue/JobQueue.scala
+++ b/modules/store/src/main/scala/docspell/store/queue/JobQueue.scala
@@ -28,6 +28,8 @@ trait JobQueue[F[_]] {
 
   def insertAll(jobs: Seq[RJob]): F[Unit]
 
+  def insertAllIfNew(jobs: Seq[RJob]): F[Unit]
+
   def nextJob(
       prio: Ident => F[Priority],
       worker: Ident,
@@ -81,5 +83,13 @@ object JobQueue {
               logger.error(ex)("Could not insert job. Skipping it.")
           })
 
+      def insertAllIfNew(jobs: Seq[RJob]): F[Unit] =
+        jobs.toList
+          .traverse(j => insertIfNew(j).attempt)
+          .map(_.foreach {
+            case Right(()) =>
+            case Left(ex) =>
+              logger.error(ex)("Could not insert job. Skipping it.")
+          })
     })
 }

From f1288d384e7fa4c48f0c897af8dc269c3da6e26c Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Thu, 13 Aug 2020 22:08:15 +0200
Subject: [PATCH 6/6] Update documentation

---
 tools/convert-all-pdfs.sh                     |  29 +++++++++++
 website/site/content/docs/joex/_index.md      |   2 +-
 .../content/docs/tools/convert-all-pdf.md     |  46 ++++++++++++++++++
 .../site/content/docs/webapp/sources-edit.png | Bin 0 -> 47084 bytes
 website/site/content/docs/webapp/uploading.md |  19 +++++---
 5 files changed, 87 insertions(+), 9 deletions(-)
 create mode 100755 tools/convert-all-pdfs.sh
 create mode 100644 website/site/content/docs/tools/convert-all-pdf.md
 create mode 100644 website/site/content/docs/webapp/sources-edit.png

diff --git a/tools/convert-all-pdfs.sh b/tools/convert-all-pdfs.sh
new file mode 100755
index 00000000..5e47e2e1
--- /dev/null
+++ b/tools/convert-all-pdfs.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+#
+# Simple script to authenticate with docspell and trigger the "convert
+# all pdf" route that submits a task to convert all pdf files using
+# ocrmypdf.
+
+set -e
+
+BASE_URL="${1:-http://localhost:7880}"
+LOGIN_URL="$BASE_URL/api/v1/open/auth/login"
+TRIGGER_URL="$BASE_URL/api/v1/sec/item/convertallpdfs"
+
+echo "Login to trigger converting all pdfs."
+echo "Using url: $BASE_URL"
+echo -n "Account: "
+read USER
+echo -n "Password: "
+read -s PASS
+echo
+
+auth=$(curl --fail -XPOST --silent --data-binary "{\"account\":\"$USER\", \"password\":\"$PASS\"}" "$LOGIN_URL")
+
+if [ "$(echo $auth | jq .success)" == "true" ]; then
+    echo "Login successful"
+    auth_token=$(echo $auth | jq -r .token)
+    curl --fail -XPOST -H "X-Docspell-Auth: $auth_token" "$TRIGGER_URL"
+else
+    echo "Login failed."
+fi
diff --git a/website/site/content/docs/joex/_index.md b/website/site/content/docs/joex/_index.md
index c9ac7fd7..bc0bd517 100644
--- a/website/site/content/docs/joex/_index.md
+++ b/website/site/content/docs/joex/_index.md
@@ -67,7 +67,7 @@ logged in.
 The relevant part of the config file regarding the scheduler is shown
 below with some explanations.
 
-```
+``` conf
 docspell.joex {
   # other settings left out for brevity
 
diff --git a/website/site/content/docs/tools/convert-all-pdf.md b/website/site/content/docs/tools/convert-all-pdf.md
new file mode 100644
index 00000000..a0b91aea
--- /dev/null
+++ b/website/site/content/docs/tools/convert-all-pdf.md
@@ -0,0 +1,46 @@
++++
+title = "Convert All PDFs"
+description = "Convert all PDF files using OcrMyPdf."
+weight = 60
++++
+
+# convert-all-pdf.sh
+
+With version 0.9.0 there was support added for another external tool,
+[OCRMyPdf](https://github.com/jbarlow83/OCRmyPDF), that can convert
+PDF files such that they contain the OCR-ed text layer. This tool is
+optional and can be disabled.
+
+In order to convert all previously processed files with this tool,
+there is an
+[endpoint](/openapi/docspell-openapi.html#api-Item-secItemConvertallpdfsPost)
+that submits a task to convert all PDF files not already converted for
+your collective.
+
+There is no UI part to trigger this route, so you need to use curl or
+the script `convert-all-pdfs.sh` in the `tools/` directory.
+
+
+# Usage
+
+```
+./convert-all-pdfs.sh [docspell-base-url]
+```
+
+For example, if docspell is at `http://localhost:7880`:
+
+```
+./convert-all-pdfs.sh http://localhost:7880
+```
+
+The script asks for your account name and password. It then logs in
+and triggers the said endpoint. After this you should see a few tasks
+running.
+
+There will be one task per file to convert. All these tasks are
+submitted with a low priority. So files uploaded through the webapp or
+a [source](@/docs/webapp/uploading.md#anonymous-upload) with a high
+priority, will be preferred as [configured in the job
+executor](@/docs/joex/_index.md#scheduler-config). This is to not
+disturb normal processing when many conversion tasks are being
+executed.
diff --git a/website/site/content/docs/webapp/sources-edit.png b/website/site/content/docs/webapp/sources-edit.png
new file mode 100644
index 0000000000000000000000000000000000000000..23804991a36c3ee113473244324c6eb0458fd444
GIT binary patch
literal 47084
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
DREHPh

literal 0
HcmV?d00001

diff --git a/website/site/content/docs/webapp/uploading.md b/website/site/content/docs/webapp/uploading.md
index 4f729435..a8f37381 100644
--- a/website/site/content/docs/webapp/uploading.md
+++ b/website/site/content/docs/webapp/uploading.md
@@ -29,6 +29,8 @@ scripts. For this the next variant exists.
 
 It is also possible to upload files without authentication. This
 should make tools that interact with docspell much easier to write.
+The [Android Client App](@/docs/tools/android.md) uses these urls to
+upload files.
 
 Go to "Collective Settings" and then to the "Source" tab. A *Source*
 identifies an endpoint where files can be uploaded anonymously.
@@ -41,7 +43,7 @@ username is not visible.
 
 Example screenshot:
 
-{{ figure(file="sources-form.png") }}
+{{ figure(file="sources-edit.png") }}
 
 This example shows a source with name "test". Besides a description
 and a name that is only used for displaying purposes, a priority and a
@@ -58,25 +60,26 @@ The source endpoint defines two urls:
 - `/app/upload/<id>`
 - `/api/v1/open/upload/item/<id>`
 
+{{ figure(file="sources-form.png") }}
+
 The first points to a web page where everyone could upload files into
 your account. You could give this url to people for sending files
 directly into your docspell.
 
 The second url is the API url, which accepts the requests to upload
-files (it is used by the upload page, the first url).
+files. This second url can be used with the [Android Client
+App](@/docs/tools/android.md) to upload files.
 
-For example, the api url can be used to upload files with curl:
+Another example is to use curl for uploading files from the command
+line::
 
 ``` bash
 $ curl -XPOST -F file=@test.pdf http://192.168.1.95:7880/api/v1/open/upload/item/3H7hvJcDJuk-NrAW4zxsdfj-K6TMPyb6BGP-xKptVxUdqWa
 {"success":true,"message":"Files submitted."}
 ```
 
-You could add more `-F file=@/path/to/your/file.pdf` to upload
-multiple files (note, the `@` is required by curl, so it knows that
-the following is a file). There is a [script
-provided](@/docs/tools/ds.md) that uses this to upload files from the
-command line.
+There is a [script provided](@/docs/tools/ds.md) that uses curl to
+upload files from the command line more conveniently.
 
 When files are uploaded to an source endpoint, the items resulting
 from this uploads are marked with the name of the source. So you know