diff --git a/Changelog.md b/Changelog.md index 27c86a66fb..0d46c7b8a6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,9 @@ ### 🚀 Features +- Add optional `process` field to upload metadata. When `false`, create + the item without conversion/OCR/analysis; reprocess later if needed + (#3344). The upload form exposes this as "Process files". - Upload endpoints now return `fileKeys` and `jobIds` so clients can track submitted files and processing jobs immediately. - Add secured endpoint to download files by file key for retrieving uploaded diff --git a/modules/addonlib/src/main/scala/docspell/addons/out/NewFile.scala b/modules/addonlib/src/main/scala/docspell/addons/out/NewFile.scala index b378d10915..e38aa40a19 100644 --- a/modules/addonlib/src/main/scala/docspell/addons/out/NewFile.scala +++ b/modules/addonlib/src/main/scala/docspell/addons/out/NewFile.scala @@ -64,7 +64,8 @@ object NewFile { tags = None, reprocess = false, attachmentsOnly = attachmentsOnly, - customData = customData + customData = customData, + process = None ) } diff --git a/modules/addonlib/src/main/scala/docspell/addons/out/NewItem.scala b/modules/addonlib/src/main/scala/docspell/addons/out/NewItem.scala index 6cd90b2108..8df6861092 100644 --- a/modules/addonlib/src/main/scala/docspell/addons/out/NewItem.scala +++ b/modules/addonlib/src/main/scala/docspell/addons/out/NewItem.scala @@ -80,7 +80,8 @@ object NewItem { tags = tags, reprocess = false, attachmentsOnly = attachmentsOnly, - customData = customData + customData = customData, + process = None ) } 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 b75a49ce55..31e71b4cff 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OUpload.scala @@ -73,7 +73,8 @@ object OUpload { attachmentsOnly: Option[Boolean], flattenArchives: Option[Boolean], customData: Option[Json], - priority: Option[Priority] + priority: Option[Priority], + process: Option[Boolean] ) case class UploadData[F[_]]( @@ -163,7 +164,8 @@ object OUpload { data.meta.tags.some, reprocess = false, data.meta.attachmentsOnly, - data.meta.customData + data.meta.customData, + data.meta.process ) args = ProcessItemArgs(meta, files.toList) jobs <- right( diff --git a/modules/backend/src/test/scala/docspell/backend/ops/OUploadTest.scala b/modules/backend/src/test/scala/docspell/backend/ops/OUploadTest.scala index 527be1a285..f5de42474f 100644 --- a/modules/backend/src/test/scala/docspell/backend/ops/OUploadTest.scala +++ b/modules/backend/src/test/scala/docspell/backend/ops/OUploadTest.scala @@ -39,7 +39,8 @@ class OUploadTest extends DatabaseTest { attachmentsOnly = None, flattenArchives = None, customData = None, - priority = None + priority = None, + process = None ), files = Vector( OUpload.File( @@ -64,6 +65,60 @@ class OUploadTest extends DatabaseTest { } } + test("submit with process=false stores flag on process-item job args") { + val store = h2Store() + val content = "store-only".getBytes("UTF-8") + + OUpload[IO](store, JobStoreImpl(store)).use { upload => + for { + cid <- prepareCollective(store) + data = OUpload.UploadData( + multiple = true, + meta = OUpload.UploadMeta( + direction = None, + sourceAbbrev = "webapp", + folderId = None, + validFileTypes = Seq.empty, + skipDuplicates = false, + fileFilter = Glob.all, + tags = Nil, + language = Some(Language.English), + attachmentsOnly = None, + flattenArchives = None, + customData = None, + priority = None, + process = Some(false) + ), + files = Vector( + OUpload.File( + Some("big.xlsx"), + None, + Stream.emits(content).covary[IO] + ) + ), + priority = Priority.Low, + tracker = None + ) + result <- upload.submit(data, cid, None, None) + jobId <- result match { + case OUpload.UploadResult.Success(_, jobs) => + jobs.headOption match { + case Some(id) => IO.pure(id) + case None => IO.raiseError(new Exception("expected a job id")) + } + case other => + IO.raiseError(new Exception(s"expected Success, got $other")) + } + job <- store.transact(docspell.store.records.RJob.findById(jobId)) + j <- IO.fromOption(job)(new Exception("job missing")) + args <- IO.fromEither(ProcessItemArgs.parse(j.args)) + } yield { + assertEquals(args.meta.process, Some(false)) + assert(!args.isProcessingEnabled) + } + } + } + test("submitted file can be loaded by returned file key") { val store = h2Store() val content = "retrieve-me".getBytes("UTF-8") @@ -86,7 +141,8 @@ class OUploadTest extends DatabaseTest { attachmentsOnly = None, flattenArchives = None, customData = None, - priority = None + priority = None, + process = None ), files = Vector( OUpload.File( diff --git a/modules/common/src/main/scala/docspell/common/ProcessItemArgs.scala b/modules/common/src/main/scala/docspell/common/ProcessItemArgs.scala index d14265955c..ab66ce8d39 100644 --- a/modules/common/src/main/scala/docspell/common/ProcessItemArgs.scala +++ b/modules/common/src/main/scala/docspell/common/ProcessItemArgs.scala @@ -34,6 +34,9 @@ case class ProcessItemArgs(meta: ProcessMeta, files: List[File]) extends TaskArg def isNormalProcessing: Boolean = !meta.reprocess + + def isProcessingEnabled: Boolean = + meta.process.getOrElse(true) } object ProcessItemArgs { @@ -55,7 +58,8 @@ object ProcessItemArgs { tags: Option[List[String]], reprocess: Boolean, attachmentsOnly: Option[Boolean], - customData: Option[Json] + customData: Option[Json], + process: Option[Boolean] ) object ProcessMeta { diff --git a/modules/common/src/test/scala/docspell/common/ProcessItemArgsTest.scala b/modules/common/src/test/scala/docspell/common/ProcessItemArgsTest.scala new file mode 100644 index 0000000000..e3aadf45ee --- /dev/null +++ b/modules/common/src/test/scala/docspell/common/ProcessItemArgsTest.scala @@ -0,0 +1,69 @@ +/* + * Copyright 2020 Eike K. & Contributors + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package docspell.common + +import io.circe.syntax._ +import munit.FunSuite + +class ProcessItemArgsTest extends FunSuite { + + private def meta(process: Option[Boolean]): ProcessItemArgs.ProcessMeta = + ProcessItemArgs.ProcessMeta( + collective = CollectiveId(1), + itemId = None, + language = Language.English, + direction = None, + sourceAbbrev = "webapp", + folderId = None, + validFileTypes = Seq.empty, + skipDuplicate = false, + fileFilter = None, + tags = None, + reprocess = false, + attachmentsOnly = None, + customData = None, + process = process + ) + + test("isProcessingEnabled defaults to true") { + val args = ProcessItemArgs(meta(None), Nil) + assert(args.isProcessingEnabled) + } + + test("isProcessingEnabled respects process=false") { + val args = ProcessItemArgs(meta(Some(false)), Nil) + assert(!args.isProcessingEnabled) + } + + test("decode ProcessMeta without process field") { + val json = + """{ + | "collective": 1, + | "itemId": null, + | "language": "eng", + | "direction": null, + | "sourceAbbrev": "webapp", + | "folderId": null, + | "validFileTypes": [], + | "skipDuplicate": false, + | "fileFilter": null, + | "tags": null, + | "reprocess": false, + | "attachmentsOnly": null, + | "customData": null + |}""".stripMargin + + val decoded = io.circe.parser.decode[ProcessItemArgs.ProcessMeta](json).toOption.get + assertEquals(decoded.process, None) + assert(ProcessItemArgs(decoded, Nil).isProcessingEnabled) + } + + test("roundtrip process=false") { + val m = meta(Some(false)) + assertEquals(m.asJson.as[ProcessItemArgs.ProcessMeta], Right(m)) + } +} 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 9b52bcb90d..fa1f381ba2 100644 --- a/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala +++ b/modules/joex/src/main/scala/docspell/joex/process/ProcessItem.scala @@ -22,6 +22,7 @@ import docspell.scheduler.Task import docspell.store.Store object ProcessItem { + type Args = ProcessItemArgs def apply[F[_]: Async: Files]( cfg: Config, @@ -31,15 +32,22 @@ object ProcessItem { regexNer: RegexNerFile[F], addonOps: AddonOps[F], store: Store[F] - )(item: ItemData): Task[F, ProcessItemArgs, ItemData] = - ExtractArchive(store)(item) - .flatMap(Task.setProgress(20)) - .flatMap(processAttachments0(cfg, fts, analyser, regexNer, store, (40, 60, 80))) - .flatMap(LinkProposal.onlyNew[F](store)) - .flatMap(SetGivenData.onlyNew[F](itemOps)) - .flatMap(Task.setProgress(99)) - .flatMap(RemoveEmptyItem(itemOps)) - .flatMap(RunAddons(addonOps, store, AddonTriggerType.FinalProcessItem)) + )(item: ItemData): Task[F, Args, ItemData] = + isProcessingEnabled[F].flatMap { + case true => + ExtractArchive(store)(item) + .flatMap(Task.setProgress(20)) + .flatMap(processAttachments0(cfg, fts, analyser, regexNer, store, (40, 60, 80))) + .flatMap(LinkProposal.onlyNew[F](store)) + .flatMap(SetGivenData.onlyNew[F](itemOps)) + .flatMap(Task.setProgress(99)) + .flatMap(RemoveEmptyItem(itemOps)) + .flatMap(RunAddons(addonOps, store, AddonTriggerType.FinalProcessItem)) + case false => + logStoreOnly[F] + .flatMap(_ => SetGivenData.onlyNew[F](itemOps)(item)) + .flatMap(Task.setProgress(99)) + } def processAttachments[F[_]: Async: Files]( cfg: Config, @@ -47,7 +55,7 @@ object ProcessItem { analyser: TextAnalyser[F], regexNer: RegexNerFile[F], store: Store[F] - )(item: ItemData): Task[F, ProcessItemArgs, ItemData] = + )(item: ItemData): Task[F, Args, ItemData] = processAttachments0[F](cfg, fts, analyser, regexNer, store, (30, 60, 90))(item) def analysisOnly[F[_]: Async: Files]( @@ -55,7 +63,7 @@ object ProcessItem { analyser: TextAnalyser[F], regexNer: RegexNerFile[F], store: Store[F] - )(item: ItemData): Task[F, ProcessItemArgs, ItemData] = + )(item: ItemData): Task[F, Args, ItemData] = TextAnalysis[F](cfg.textAnalysis, analyser, regexNer, store)(item) .flatMap(FindProposal[F](cfg.textAnalysis, store)) .flatMap(EvalProposals[F](store)) @@ -69,7 +77,7 @@ object ProcessItem { regexNer: RegexNerFile[F], store: Store[F], progress: (Int, Int, Int) - )(item: ItemData): Task[F, ProcessItemArgs, ItemData] = + )(item: ItemData): Task[F, Args, ItemData] = ConvertPdf(cfg.convert, store, item) .flatMap(Task.setProgress(progress._1)) .flatMap(TextExtraction(cfg.extraction, fts, store)) @@ -78,4 +86,10 @@ object ProcessItem { .flatMap(Task.setProgress(progress._2)) .flatMap(analysisOnly[F](cfg, analyser, regexNer, store)) .flatMap(Task.setProgress(progress._3)) + + private def isProcessingEnabled[F[_]: Sync]: Task[F, Args, Boolean] = + Task(ctx => ctx.args.isProcessingEnabled.pure[F]) + + private def logStoreOnly[F[_]]: Task[F, Args, Unit] = + Task.log(_.info("Not processing files. Only storing the item.")) } diff --git a/modules/joex/src/main/scala/docspell/joex/process/ReProcessItem.scala b/modules/joex/src/main/scala/docspell/joex/process/ReProcessItem.scala index 39337ee7c5..d89e96be56 100644 --- a/modules/joex/src/main/scala/docspell/joex/process/ReProcessItem.scala +++ b/modules/joex/src/main/scala/docspell/joex/process/ReProcessItem.scala @@ -136,7 +136,8 @@ object ReProcessItem { None, reprocess = true, None, // attachOnly (not used when reprocessing attachments) - None // cannot retain customData from an already existing item + None, // cannot retain customData from an already existing item + None ), Nil ).pure[F] diff --git a/modules/joex/src/main/scala/docspell/joex/scanmailbox/ScanMailboxTask.scala b/modules/joex/src/main/scala/docspell/joex/scanmailbox/ScanMailboxTask.scala index 11f0f093b2..b48dd7bca2 100644 --- a/modules/joex/src/main/scala/docspell/joex/scanmailbox/ScanMailboxTask.scala +++ b/modules/joex/src/main/scala/docspell/joex/scanmailbox/ScanMailboxTask.scala @@ -330,6 +330,7 @@ object ScanMailboxTask { args.attachmentsOnly, None, None, + None, None ) data = OUpload.UploadData( diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 8e441354c5..371153e8f1 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -8476,6 +8476,15 @@ components: Processing priority for the upload jobs. If omitted, the endpoint default applies (high for secured upload, source priority for open upload, server config for integration). + process: + type: boolean + default: true + description: | + Whether to run the processing pipeline (conversion, text + extraction/OCR, preview, analysis). Defaults to `true`. If + `false`, an item is still created and given metadata is + applied, but these stages are skipped. Processing can be + started later via the reprocess endpoints. Collective: description: | diff --git a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala index 7e537fc5dc..617ccdab38 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala @@ -321,7 +321,8 @@ trait Conversions { m.attachmentsOnly, m.flattenArchives, m.customData, - m.priority + m.priority, + m.process ) ) ) @@ -341,6 +342,7 @@ trait Conversions { None, None, None, + None, None ) ) diff --git a/modules/store/src/main/scala/db/migration/common/MigrateCollectiveIdTaskArgs.scala b/modules/store/src/main/scala/db/migration/common/MigrateCollectiveIdTaskArgs.scala index 852f24f066..1086c69943 100644 --- a/modules/store/src/main/scala/db/migration/common/MigrateCollectiveIdTaskArgs.scala +++ b/modules/store/src/main/scala/db/migration/common/MigrateCollectiveIdTaskArgs.scala @@ -349,7 +349,8 @@ object MigrateCollectiveIdTaskArgs extends TransactorSupport { tags = oldArgs.meta.tags, reprocess = oldArgs.meta.reprocess, attachmentsOnly = oldArgs.meta.attachmentsOnly, - customData = None + customData = None, + process = None ), oldArgs.files.map(f => ProcessItemArgs diff --git a/modules/webapp/src/main/elm/Comp/UploadForm.elm b/modules/webapp/src/main/elm/Comp/UploadForm.elm index 92541d4ed8..100a57fb8c 100644 --- a/modules/webapp/src/main/elm/Comp/UploadForm.elm +++ b/modules/webapp/src/main/elm/Comp/UploadForm.elm @@ -47,6 +47,7 @@ type alias Model = , priorityModel : Comp.FixedDropdown.Model Priority , priority : Priority , flattenArchives : Bool + , process : Bool , uploadDone : Maybe Bool } @@ -63,6 +64,7 @@ type Msg | LanguageMsg (Comp.FixedDropdown.Msg Language) | PrioDropdownMsg (Comp.FixedDropdown.Msg Priority) | ToggleFlattenArchives + | ToggleProcess init : Model @@ -82,6 +84,7 @@ init = Comp.FixedDropdown.init Data.Priority.all , priority = Data.Priority.High , flattenArchives = False + , process = True , uploadDone = Nothing } @@ -182,6 +185,9 @@ update sourceId flags msg model = , Sub.none ) + ToggleProcess -> + ( { model | process = not model.process }, Cmd.none, Sub.none ) + SubmitUpload -> let emptyMeta = @@ -200,6 +206,7 @@ update sourceId flags msg model = , language = Maybe.map Data.Language.toIso3 model.language , flattenArchives = Just model.flattenArchives , priority = Just (Data.Priority.toName model.priority) + , process = Just model.process } fileids = @@ -523,6 +530,23 @@ renderForm texts model = ] ] ] + , div [ class "flex flex-col mb-3" ] + [ label [ class "inline-flex items-center" ] + [ input + [ type_ "checkbox" + , checked model.process + , onCheck (\_ -> ToggleProcess) + , class Styles.checkboxInput + ] + [] + , span [ class "ml-2" ] + [ text texts.processFiles + ] + ] + , div [ class "text-gray-400 text-xs mt-1" ] + [ text texts.processFilesInfo + ] + ] , div [ class "flex flex-col mb-3" ] [ label [ class "inline-flex items-center mb-2" ] [ span [ class "mr-2" ] [ text (texts.language ++ ":") ] @@ -573,6 +597,17 @@ renderErrorMsg texts model = renderSuccessMsg : Texts -> Bool -> Model -> Html Msg renderSuccessMsg texts public model = + let + sb = + texts.successBox + + ( line1, line2, line3 ) = + if model.process then + ( sb.line1, sb.line2, sb.line3 ) + + else + ( sb.storeOnlyLine1, sb.storeOnlyLine2, sb.storeOnlyLine3 ) + in div [ class "row" , classList [ ( "hidden", Maybe.map not model.uploadDone |> Maybe.withDefault True ) ] @@ -582,38 +617,39 @@ renderSuccessMsg texts public model = [ h3 [ class Styles.header2, class "text-green-800 dark:text-lime-800" ] [ i [ class "fa fa-smile font-thin" ] [] , span [ class "ml-2" ] - [ text texts.successBox.allFilesUploaded + [ text sb.allFilesUploaded ] ] , p [ classList [ ( "hidden", public ) ] ] - [ text texts.successBox.line1 + [ text line1 , a [ class Styles.successMessageLink , Page.href (SearchPage Nothing) ] - [ text texts.successBox.itemsPage + [ text sb.itemsPage ] - , text texts.successBox.line2 + , text line2 , a [ class Styles.successMessageLink + , classList [ ( "hidden", not model.process ) ] , Page.href QueuePage ] - [ text texts.successBox.processingPage + [ text sb.processingPage ] - , text texts.successBox.line3 + , text line3 ] , p [] - [ text texts.successBox.resetLine1 + [ text sb.resetLine1 , a [ class Styles.successMessageLink , href "#" , onClick Clear ] - [ text texts.successBox.reset + [ text sb.reset ] - , text texts.successBox.resetLine2 + , text sb.resetLine2 ] ] ] diff --git a/modules/webapp/src/main/elm/Messages/Comp/UploadForm.elm b/modules/webapp/src/main/elm/Messages/Comp/UploadForm.elm index 30fa6fd2ff..b0cef20de5 100644 --- a/modules/webapp/src/main/elm/Messages/Comp/UploadForm.elm +++ b/modules/webapp/src/main/elm/Messages/Comp/UploadForm.elm @@ -19,6 +19,8 @@ type alias Texts = , reset : String , allFilesOneItem : String , skipExistingFiles : String + , processFiles : String + , processFilesInfo : String , language : String , languageInfo : String , uploadErrorMessage : String @@ -29,6 +31,9 @@ type alias Texts = , line2 : String , processingPage : String , line3 : String + , storeOnlyLine1 : String + , storeOnlyLine2 : String + , storeOnlyLine3 : String , resetLine1 : String , reset : String , resetLine2 : String @@ -48,6 +53,10 @@ gb = , reset = "Reset" , allFilesOneItem = "All files are one single item" , skipExistingFiles = "Skip files already present in docspell" + , processFiles = "Process files" + , processFilesInfo = + "If unchecked, files are stored as items without conversion, OCR or analysis. " + ++ "Processing can be started later from the item." , language = "Language" , languageInfo = "Used for text extraction and analysis. The collective's " @@ -62,6 +71,10 @@ gb = , line2 = " later where the files will arrive eventually. Or go to the " , processingPage = "Processing Page" , line3 = " to view the current processing state." + , storeOnlyLine1 = + "Your files have been successfully uploaded and stored without processing. Check the " + , storeOnlyLine2 = " where they are available." + , storeOnlyLine3 = "" , resetLine1 = " Click " , reset = "Reset" , resetLine2 = " to upload more files." @@ -81,6 +94,10 @@ de = , reset = "Zurücksetzen" , allFilesOneItem = "Alle Dateien sind ein Dokument" , skipExistingFiles = "Lasse Dateien aus, die schon in Docspell sind" + , processFiles = "Dateien verarbeiten" + , processFilesInfo = + "Wenn deaktiviert, werden Dateien als Dokumente gespeichert ohne Konvertierung, OCR oder Analyse. " + ++ "Die Verarbeitung kann später am Dokument gestartet werden." , language = "Sprache" , languageInfo = "Wird für Texterkennung und -analyse verwendet. Die Standardsprache des Kollektivs " @@ -95,6 +112,10 @@ de = , line2 = " wo die Dateien als Dokumente erscheinen werden oder gehe zur " , processingPage = "Verarbeitungsseite," , line3 = " welche einen Einblick in den aktuellen Status gibt." + , storeOnlyLine1 = + "Deine Dateien wurden erfolgreich hochgeladen und ohne Verarbeitung gespeichert. Gehe zur " + , storeOnlyLine2 = " wo sie als Dokumente verfügbar sind." + , storeOnlyLine3 = "" , resetLine1 = " Klicke " , reset = "Zurücksetzen" , resetLine2 = " um weitere Dateien hochzuladen." @@ -114,6 +135,10 @@ fr = , reset = "Recommencer" , allFilesOneItem = "Tous les fichiers ne sont qu'un seul document" , skipExistingFiles = "Ignorer les fichiers déjà présents dans docspell" + , processFiles = "Traiter les fichiers" + , processFilesInfo = + "Si décoché, les fichiers sont enregistrés comme documents sans conversion, OCR ni analyse. " + ++ "Le traitement peut être démarré plus tard depuis le document." , language = "Langue" , languageInfo = "Utilisé pour l'extraction et l'analyse. Le langage par défaut" @@ -128,6 +153,10 @@ fr = , line2 = " plus tard où les fichiers arrivent. Où rendez-vous à la " , processingPage = "File de traitement" , line3 = " afin de voir l'état du traitement." + , storeOnlyLine1 = + "Les fichiers ont bien été envoyés et enregistrés sans traitement. Rendez-vous aux " + , storeOnlyLine2 = " où ils sont disponibles." + , storeOnlyLine3 = "" , resetLine1 = " Cliquer sur " , reset = "Recommencer" , resetLine2 = " pour envoyer plus de fichier." diff --git a/website/site/content/docs/api/upload.md b/website/site/content/docs/api/upload.md index b2f9ce5923..9a57895afe 100644 --- a/website/site/content/docs/api/upload.md +++ b/website/site/content/docs/api/upload.md @@ -55,6 +55,7 @@ specified via a JSON structure in a part with name `meta`: , attachmentsOnly: Maybe Bool , flattenArchives: Maybe Bool , priority: Maybe String +, process: Maybe Bool } ``` @@ -117,6 +118,11 @@ specified via a JSON structure in a part with name `meta`: source's configured priority for open uploads via a source URL, and the server configuration for the integration endpoint. When specified, it overrides the source's default priority. +- The `process` field controls whether the processing pipeline runs. It + defaults to `true`. When set to `false`, an item is still created and + given metadata is applied, but conversion, text extraction/OCR, + preview generation and analysis are skipped. Processing can be + started later via the item reprocess endpoints. # Endpoints diff --git a/website/site/content/docs/joex/file-processing.md b/website/site/content/docs/joex/file-processing.md index 5a4da05349..ba518d396f 100644 --- a/website/site/content/docs/joex/file-processing.md +++ b/website/site/content/docs/joex/file-processing.md @@ -19,6 +19,11 @@ If an error occurs during processing, the item will be created anyways, so you can see it. Depending on the error, some information may not be available. +Uploads may set `process: false` in the upload metadata. In that case +an item is still created and given metadata is applied, but the heavy +stages below are skipped. Processing can be started later via the +reprocess endpoints. + Processing files may require some resources, like memory and cpu. Many things can be configured in the config file to adapt it to the machine it is running on. diff --git a/website/site/content/docs/webapp/uploading.md b/website/site/content/docs/webapp/uploading.md index 84574e6cab..8008b52599 100644 --- a/website/site/content/docs/webapp/uploading.md +++ b/website/site/content/docs/webapp/uploading.md @@ -18,7 +18,9 @@ a separate item. When you click "Submit" the files are uploaded and stored in the database. Then the job executor(s) are notified which immediately -start processing them. +start processing them. On the upload form you can uncheck *Process +files* to only store the item and skip conversion, OCR and analysis; +processing can be started later from the item. Go to the top-right menu and click "Processing Queue" to see the current state. diff --git a/website/src/main/scala/docspell/website/ItemArgsExample.scala b/website/src/main/scala/docspell/website/ItemArgsExample.scala index 86664ac151..05674dcd18 100644 --- a/website/src/main/scala/docspell/website/ItemArgsExample.scala +++ b/website/src/main/scala/docspell/website/ItemArgsExample.scala @@ -20,7 +20,8 @@ object ItemArgsExample extends Helper { tags = List("given-tag-1").some, reprocess = false, attachmentsOnly = None, - customData = Some(Json.obj("my-id" -> Json.fromInt(42))) + customData = Some(Json.obj("my-id" -> Json.fromInt(42))), + process = None ) val exampleJson = example.asJson.spaces2