`_
for doing most of the heavy lifting. This is a task queue that accepts tasks from
multiple sources and processes these in parallel. It also comes with a scheduler that executes
certain commands periodically.
@@ -62,13 +62,6 @@ Paperless consists of the following components:
tasks fail and inspect the errors (i.e., wrong email credentials, errors during consuming a specific
file, etc).
- You may start the task processor by executing:
-
- .. code:: shell-session
-
- $ cd /path/to/paperless/src/
- $ python3 manage.py qcluster
-
* A `redis `_ message broker: This is a really lightweight service that is responsible
for getting the tasks from the webserver and the consumer to the task scheduler. These run in a different
process (maybe even on different machines!), and therefore, this is necessary.
@@ -291,7 +284,20 @@ Build the Docker image yourself
.. code:: yaml
webserver:
- build: .
+ build:
+ context: .
+ args:
+ QPDF_VERSION: x.y.x
+ PIKEPDF_VERSION: x.y.z
+ PSYCOPG2_VERSION: x.y.z
+ JBIG2ENC_VERSION: 0.29
+
+ .. note::
+
+ You should match the build argument versions to the version for the release you have
+ checked out. These are pre-built images with certain, more updated software.
+ If you want to build these images your self, that is possible, but beyond
+ the scope of these steps.
4. Follow steps 3 to 8 of :ref:`setup-docker_hub`. When asked to run
``docker-compose pull`` to pull the image, do
@@ -332,7 +338,7 @@ writing. Windows is not and will never be supported.
.. code::
- python3 python3-pip python3-dev imagemagick fonts-liberation gnupg libpq-dev libmagic-dev mime-support libzbar0 poppler-utils
+ python3 python3-pip python3-dev imagemagick fonts-liberation gnupg libpq-dev default-libmysqlclient-dev libmagic-dev mime-support libzbar0 poppler-utils
These dependencies are required for OCRmyPDF, which is used for text recognition.
@@ -361,7 +367,7 @@ writing. Windows is not and will never be supported.
You will also need ``build-essential``, ``python3-setuptools`` and ``python3-wheel``
for installing some of the python dependencies.
-2. Install ``redis`` >= 5.0 and configure it to start automatically.
+2. Install ``redis`` >= 6.0 and configure it to start automatically.
3. Optional. Install ``postgresql`` and configure a database, user and password for paperless. If you do not wish
to use PostgreSQL, MariaDB and SQLite are available as well.
@@ -461,8 +467,9 @@ writing. Windows is not and will never be supported.
as a starting point.
Paperless needs the ``webserver`` script to run the webserver, the
- ``consumer`` script to watch the input folder, and the ``scheduler``
- script to run tasks such as email checking and document consumption.
+ ``consumer`` script to watch the input folder, ``taskqueue`` for the background workers
+ used to handle things like document consumption and the ``scheduler`` script to run tasks such as
+ email checking at certain times .
The ``socket`` script enables ``gunicorn`` to run on port 80 without
root privileges. For this you need to uncomment the ``Require=paperless-webserver.socket``
diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst
index 0e8f6a682..2d10bb15f 100644
--- a/docs/troubleshooting.rst
+++ b/docs/troubleshooting.rst
@@ -19,7 +19,7 @@ Check for the following issues:
.. code:: shell-session
- $ python3 manage.py qcluster
+ $ celery --app paperless worker
* Look at the output of paperless and inspect it for any errors.
* Go to the admin interface, and check if there are failed tasks. If so, the
diff --git a/scripts/paperless-scheduler.service b/scripts/paperless-scheduler.service
index b1c82a38e..b15c53e79 100644
--- a/scripts/paperless-scheduler.service
+++ b/scripts/paperless-scheduler.service
@@ -1,12 +1,12 @@
[Unit]
-Description=Paperless scheduler
+Description=Paperless Celery Beat
Requires=redis.service
[Service]
User=paperless
Group=paperless
WorkingDirectory=/opt/paperless/src
-ExecStart=python3 manage.py qcluster
+ExecStart=celery --app paperless beat --loglevel INFO
[Install]
WantedBy=multi-user.target
diff --git a/scripts/paperless-task-queue.service b/scripts/paperless-task-queue.service
new file mode 100644
index 000000000..5fade360a
--- /dev/null
+++ b/scripts/paperless-task-queue.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Paperless Celery Workers
+Requires=redis.service
+
+[Service]
+User=paperless
+Group=paperless
+WorkingDirectory=/opt/paperless/src
+ExecStart=celery --app paperless worker --loglevel INFO
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src-ui/cypress/fixtures/tasks/tasks.json b/src-ui/cypress/fixtures/tasks/tasks.json
index ceb334b9d..eeccfe424 100644
--- a/src-ui/cypress/fixtures/tasks/tasks.json
+++ b/src-ui/cypress/fixtures/tasks/tasks.json
@@ -1 +1,290 @@
-[{"id":141,"type":"file","result":"sample 2.pdf: Not consuming sample 2.pdf: It is a duplicate. : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ngx.nosync-udqDZzaE/lib/python3.8/site-packages/django_q/cluster.py\", line 432, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/paperless-ngx/src/documents/tasks.py\", line 316, in consume_file\n document = Consumer().try_consume_file(\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 218, in try_consume_file\n self.pre_check_duplicate()\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 113, in pre_check_duplicate\n self._fail(\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 84, in _fail\n raise ConsumerError(f\"{self.filename}: {log_message or message}\")\ndocuments.consumer.ConsumerError: sample 2.pdf: Not consuming sample 2.pdf: It is a duplicate.\n","status":"failed","task_id":"d8ddbe298a42427d82553206ddf0bc94","name":"sample 2.pdf","created":"2022-05-26T23:17:38.333474-07:00","acknowledged":false,"attempted_task":{"id":"d8ddbe298a42427d82553206ddf0bc94","name":"sample 2.pdf","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtanJxNGs1aHOUhZQu","kwargs":"gAWVzQAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwMc2FtcGxlIDIucGRmlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDcyMGExYjI5LWI2OTYtNDY3My05Y2ZmLTJkY2ZiZWNmNWViMpSMEG92ZXJyaWRlX2NyZWF0ZWSUTnUu","result":"gAWVMQQAAAAAAABYKgQAAHNhbXBsZSAyLnBkZjogTm90IGNvbnN1bWluZyBzYW1wbGUgMi5wZGY6IEl0IGlzIGEgZHVwbGljYXRlLiA6IFRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3Mtbmd4Lm5vc3luYy11ZHFEWnphRS9saWIvcHl0aG9uMy44L3NpdGUtcGFja2FnZXMvZGphbmdvX3EvY2x1c3Rlci5weSIsIGxpbmUgNDMyLCBpbiB3b3JrZXIKICAgIHJlcyA9IGYoKnRhc2tbImFyZ3MiXSwgKip0YXNrWyJrd2FyZ3MiXSkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvdGFza3MucHkiLCBsaW5lIDMxNiwgaW4gY29uc3VtZV9maWxlCiAgICBkb2N1bWVudCA9IENvbnN1bWVyKCkudHJ5X2NvbnN1bWVfZmlsZSgKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDIxOCwgaW4gdHJ5X2NvbnN1bWVfZmlsZQogICAgc2VsZi5wcmVfY2hlY2tfZHVwbGljYXRlKCkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDExMywgaW4gcHJlX2NoZWNrX2R1cGxpY2F0ZQogICAgc2VsZi5fZmFpbCgKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDg0LCBpbiBfZmFpbAogICAgcmFpc2UgQ29uc3VtZXJFcnJvcihmIntzZWxmLmZpbGVuYW1lfToge2xvZ19tZXNzYWdlIG9yIG1lc3NhZ2V9IikKZG9jdW1lbnRzLmNvbnN1bWVyLkNvbnN1bWVyRXJyb3I6IHNhbXBsZSAyLnBkZjogTm90IGNvbnN1bWluZyBzYW1wbGUgMi5wZGY6IEl0IGlzIGEgZHVwbGljYXRlLgqULg==","group":null,"started":"2022-05-26T23:17:37.702432-07:00","stopped":"2022-05-26T23:17:38.313306-07:00","success":false,"attempt_count":1}},{"id":132,"type":"file","result":" : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 131, in get_version\n env=env,\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 68, in run\n proc = subprocess_run(args, env=env, **kwargs)\n File \"/Users/admin/opt/anaconda3/envs/paperless-ng/lib/python3.6/subprocess.py\", line 423, in run\n with Popen(*popenargs, **kwargs) as process:\n File \"/Users/admin/opt/anaconda3/envs/paperless-ng/lib/python3.6/subprocess.py\", line 729, in __init__\n restore_signals, start_new_session)\n File \"/Users/admin/opt/anaconda3/envs/paperless-ng/lib/python3.6/subprocess.py\", line 1364, in _execute_child\n raise child_exception_type(errno_num, err_msg, err_filename)\nFileNotFoundError: [Errno 2] No such file or directory: 'unpaper': 'unpaper'\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 287, in check_external_program\n found_version = version_checker()\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/_exec/unpaper.py\", line 34, in version\n return get_version('unpaper')\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 137, in get_version\n ) from e\nocrmypdf.exceptions.MissingDependencyError: Could not find program 'unpaper' on the PATH\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/paperless_tesseract/parsers.py\", line 176, in parse\n ocrmypdf.ocr(**ocr_args)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/api.py\", line 315, in ocr\n check_options(options, plugin_manager)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/_validation.py\", line 260, in check_options\n _check_options(options, plugin_manager, ocr_engine_languages)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/_validation.py\", line 250, in _check_options\n check_options_preprocessing(options)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/_validation.py\", line 128, in check_options_preprocessing\n required_for=['--clean, --clean-final'],\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 293, in check_external_program\n raise MissingDependencyError()\nocrmypdf.exceptions.MissingDependencyError\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 179, in try_consume_file\n document_parser.parse(self.path, mime_type, self.filename)\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/paperless_tesseract/parsers.py\", line 197, in parse\n raise ParseError(e)\ndocuments.parsers.ParseError\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/django_q/cluster.py\", line 436, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/tasks.py\", line 73, in consume_file\n override_tag_ids=override_tag_ids)\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 196, in try_consume_file\n raise ConsumerError(e)\ndocuments.consumer.ConsumerError\n","status":"failed","task_id":"4c554075552c4cc985abd76e6f274c90","name":"pdf-sample 10.24.48 PM.pdf","created":"2022-05-26T14:26:07.846365-07:00","acknowledged":null,"attempted_task":{"id":"4c554075552c4cc985abd76e6f274c90","name":"pdf-sample 10.24.48 PM.pdf","func":"documents.tasks.consume_file","hook":null,"args":"gAWVKwAAAAAAAACMJS4uL2NvbnN1bWUvcGRmLXNhbXBsZSAxMC4yNC40OCBQTS5wZGaUhZQu","kwargs":"gAWVGAAAAAAAAAB9lIwQb3ZlcnJpZGVfdGFnX2lkc5ROcy4=","result":"gAWVzA8AAAAAAABYxQ8AACA6IFRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL29jcm15cGRmL3N1YnByb2Nlc3MucHkiLCBsaW5lIDEzMSwgaW4gZ2V0X3ZlcnNpb24KICAgIGVudj1lbnYsCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9zdWJwcm9jZXNzLnB5IiwgbGluZSA2OCwgaW4gcnVuCiAgICBwcm9jID0gc3VicHJvY2Vzc19ydW4oYXJncywgZW52PWVudiwgKiprd2FyZ3MpCiAgRmlsZSAiL1VzZXJzL21vb25lci9vcHQvYW5hY29uZGEzL2VudnMvcGFwZXJsZXNzLW5nL2xpYi9weXRob24zLjYvc3VicHJvY2Vzcy5weSIsIGxpbmUgNDIzLCBpbiBydW4KICAgIHdpdGggUG9wZW4oKnBvcGVuYXJncywgKiprd2FyZ3MpIGFzIHByb2Nlc3M6CiAgRmlsZSAiL1VzZXJzL21vb25lci9vcHQvYW5hY29uZGEzL2VudnMvcGFwZXJsZXNzLW5nL2xpYi9weXRob24zLjYvc3VicHJvY2Vzcy5weSIsIGxpbmUgNzI5LCBpbiBfX2luaXRfXwogICAgcmVzdG9yZV9zaWduYWxzLCBzdGFydF9uZXdfc2Vzc2lvbikKICBGaWxlICIvVXNlcnMvbW9vbmVyL29wdC9hbmFjb25kYTMvZW52cy9wYXBlcmxlc3MtbmcvbGliL3B5dGhvbjMuNi9zdWJwcm9jZXNzLnB5IiwgbGluZSAxMzY0LCBpbiBfZXhlY3V0ZV9jaGlsZAogICAgcmFpc2UgY2hpbGRfZXhjZXB0aW9uX3R5cGUoZXJybm9fbnVtLCBlcnJfbXNnLCBlcnJfZmlsZW5hbWUpCkZpbGVOb3RGb3VuZEVycm9yOiBbRXJybm8gMl0gTm8gc3VjaCBmaWxlIG9yIGRpcmVjdG9yeTogJ3VucGFwZXInOiAndW5wYXBlcicKClRoZSBhYm92ZSBleGNlcHRpb24gd2FzIHRoZSBkaXJlY3QgY2F1c2Ugb2YgdGhlIGZvbGxvd2luZyBleGNlcHRpb246CgpUcmFjZWJhY2sgKG1vc3QgcmVjZW50IGNhbGwgbGFzdCk6CiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9zdWJwcm9jZXNzLnB5IiwgbGluZSAyODcsIGluIGNoZWNrX2V4dGVybmFsX3Byb2dyYW0KICAgIGZvdW5kX3ZlcnNpb24gPSB2ZXJzaW9uX2NoZWNrZXIoKQogIEZpbGUgIi9Vc2Vycy9tb29uZXIvLmxvY2FsL3NoYXJlL3ZpcnR1YWxlbnZzL3BhcGVybGVzcy1uZy03NkJ1SmxFSS9saWIvcHl0aG9uMy42L3NpdGUtcGFja2FnZXMvb2NybXlwZGYvX2V4ZWMvdW5wYXBlci5weSIsIGxpbmUgMzQsIGluIHZlcnNpb24KICAgIHJldHVybiBnZXRfdmVyc2lvbigndW5wYXBlcicpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9zdWJwcm9jZXNzLnB5IiwgbGluZSAxMzcsIGluIGdldF92ZXJzaW9uCiAgICApIGZyb20gZQpvY3JteXBkZi5leGNlcHRpb25zLk1pc3NpbmdEZXBlbmRlbmN5RXJyb3I6IENvdWxkIG5vdCBmaW5kIHByb2dyYW0gJ3VucGFwZXInIG9uIHRoZSBQQVRICgpEdXJpbmcgaGFuZGxpbmcgb2YgdGhlIGFib3ZlIGV4Y2VwdGlvbiwgYW5vdGhlciBleGNlcHRpb24gb2NjdXJyZWQ6CgpUcmFjZWJhY2sgKG1vc3QgcmVjZW50IGNhbGwgbGFzdCk6CiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvcGFwZXJsZXNzX3Rlc3NlcmFjdC9wYXJzZXJzLnB5IiwgbGluZSAxNzYsIGluIHBhcnNlCiAgICBvY3JteXBkZi5vY3IoKipvY3JfYXJncykKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL29jcm15cGRmL2FwaS5weSIsIGxpbmUgMzE1LCBpbiBvY3IKICAgIGNoZWNrX29wdGlvbnMob3B0aW9ucywgcGx1Z2luX21hbmFnZXIpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9fdmFsaWRhdGlvbi5weSIsIGxpbmUgMjYwLCBpbiBjaGVja19vcHRpb25zCiAgICBfY2hlY2tfb3B0aW9ucyhvcHRpb25zLCBwbHVnaW5fbWFuYWdlciwgb2NyX2VuZ2luZV9sYW5ndWFnZXMpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9fdmFsaWRhdGlvbi5weSIsIGxpbmUgMjUwLCBpbiBfY2hlY2tfb3B0aW9ucwogICAgY2hlY2tfb3B0aW9uc19wcmVwcm9jZXNzaW5nKG9wdGlvbnMpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9fdmFsaWRhdGlvbi5weSIsIGxpbmUgMTI4LCBpbiBjaGVja19vcHRpb25zX3ByZXByb2Nlc3NpbmcKICAgIHJlcXVpcmVkX2Zvcj1bJy0tY2xlYW4sIC0tY2xlYW4tZmluYWwnXSwKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL29jcm15cGRmL3N1YnByb2Nlc3MucHkiLCBsaW5lIDI5MywgaW4gY2hlY2tfZXh0ZXJuYWxfcHJvZ3JhbQogICAgcmFpc2UgTWlzc2luZ0RlcGVuZGVuY3lFcnJvcigpCm9jcm15cGRmLmV4Y2VwdGlvbnMuTWlzc2luZ0RlcGVuZGVuY3lFcnJvcgoKRHVyaW5nIGhhbmRsaW5nIG9mIHRoZSBhYm92ZSBleGNlcHRpb24sIGFub3RoZXIgZXhjZXB0aW9uIG9jY3VycmVkOgoKVHJhY2ViYWNrIChtb3N0IHJlY2VudCBjYWxsIGxhc3QpOgogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmcvc3JjL2RvY3VtZW50cy9jb25zdW1lci5weSIsIGxpbmUgMTc5LCBpbiB0cnlfY29uc3VtZV9maWxlCiAgICBkb2N1bWVudF9wYXJzZXIucGFyc2Uoc2VsZi5wYXRoLCBtaW1lX3R5cGUsIHNlbGYuZmlsZW5hbWUpCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvcGFwZXJsZXNzX3Rlc3NlcmFjdC9wYXJzZXJzLnB5IiwgbGluZSAxOTcsIGluIHBhcnNlCiAgICByYWlzZSBQYXJzZUVycm9yKGUpCmRvY3VtZW50cy5wYXJzZXJzLlBhcnNlRXJyb3IKCkR1cmluZyBoYW5kbGluZyBvZiB0aGUgYWJvdmUgZXhjZXB0aW9uLCBhbm90aGVyIGV4Y2VwdGlvbiBvY2N1cnJlZDoKClRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL2RqYW5nb19xL2NsdXN0ZXIucHkiLCBsaW5lIDQzNiwgaW4gd29ya2VyCiAgICByZXMgPSBmKCp0YXNrWyJhcmdzIl0sICoqdGFza1sia3dhcmdzIl0pCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvZG9jdW1lbnRzL3Rhc2tzLnB5IiwgbGluZSA3MywgaW4gY29uc3VtZV9maWxlCiAgICBvdmVycmlkZV90YWdfaWRzPW92ZXJyaWRlX3RhZ19pZHMpCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvZG9jdW1lbnRzL2NvbnN1bWVyLnB5IiwgbGluZSAxOTYsIGluIHRyeV9jb25zdW1lX2ZpbGUKICAgIHJhaXNlIENvbnN1bWVyRXJyb3IoZSkKZG9jdW1lbnRzLmNvbnN1bWVyLkNvbnN1bWVyRXJyb3IKlC4=","group":null,"started":"2021-01-20T10:47:34.535478-08:00","stopped":"2021-01-20T10:49:55.568010-08:00","success":false,"attempt_count":1}},{"id":115,"type":"file","result":"2021-01-24 2021-01-20 sample_wide_orange.pdf: Document is a duplicate : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/django_q/cluster.py\", line 436, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/tasks.py\", line 75, in consume_file\n task_id=task_id\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 168, in try_consume_file\n self.pre_check_duplicate()\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 85, in pre_check_duplicate\n self._fail(\"Document is a duplicate\")\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 53, in _fail\n raise ConsumerError(f\"{self.filename}: {message}\")\ndocuments.consumer.ConsumerError: 2021-01-24 2021-01-20 sample_wide_orange.pdf: Document is a duplicate\n","status":"failed","task_id":"86494713646a4364b01da17aadca071d","name":"2021-01-24 2021-01-20 sample_wide_orange.pdf","created":"2022-05-26T14:26:07.817608-07:00","acknowledged":null,"attempted_task":{"id":"86494713646a4364b01da17aadca071d","name":"2021-01-24 2021-01-20 sample_wide_orange.pdf","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtcTJ6NDlnbzaUhZQu","kwargs":"gAWV2QAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwsMjAyMS0wMS0yNCAyMDIxLTAxLTIwIHNhbXBsZV93aWRlX29yYW5nZS5wZGaUjA5vdmVycmlkZV90aXRsZZROjBlvdmVycmlkZV9jb3JyZXNwb25kZW50X2lklE6MGW92ZXJyaWRlX2RvY3VtZW50X3R5cGVfaWSUTowQb3ZlcnJpZGVfdGFnX2lkc5ROjAd0YXNrX2lklIwkN2MwZTY1MmQtZDhkYy00OWU4LWI1ZmUtOGM3ZTkyZDlmOTI0lHUu","result":"gAWV/AMAAAAAAABY9QMAADIwMjEtMDEtMjQgMjAyMS0wMS0yMCBzYW1wbGVfd2lkZV9vcmFuZ2UucGRmOiBEb2N1bWVudCBpcyBhIGR1cGxpY2F0ZSA6IFRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL2RqYW5nb19xL2NsdXN0ZXIucHkiLCBsaW5lIDQzNiwgaW4gd29ya2VyCiAgICByZXMgPSBmKCp0YXNrWyJhcmdzIl0sICoqdGFza1sia3dhcmdzIl0pCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvZG9jdW1lbnRzL3Rhc2tzLnB5IiwgbGluZSA3NSwgaW4gY29uc3VtZV9maWxlCiAgICB0YXNrX2lkPXRhc2tfaWQKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5nL3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDE2OCwgaW4gdHJ5X2NvbnN1bWVfZmlsZQogICAgc2VsZi5wcmVfY2hlY2tfZHVwbGljYXRlKCkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5nL3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDg1LCBpbiBwcmVfY2hlY2tfZHVwbGljYXRlCiAgICBzZWxmLl9mYWlsKCJEb2N1bWVudCBpcyBhIGR1cGxpY2F0ZSIpCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvZG9jdW1lbnRzL2NvbnN1bWVyLnB5IiwgbGluZSA1MywgaW4gX2ZhaWwKICAgIHJhaXNlIENvbnN1bWVyRXJyb3IoZiJ7c2VsZi5maWxlbmFtZX06IHttZXNzYWdlfSIpCmRvY3VtZW50cy5jb25zdW1lci5Db25zdW1lckVycm9yOiAyMDIxLTAxLTI0IDIwMjEtMDEtMjAgc2FtcGxlX3dpZGVfb3JhbmdlLnBkZjogRG9jdW1lbnQgaXMgYSBkdXBsaWNhdGUKlC4=","group":null,"started":"2021-01-26T00:21:05.379583-08:00","stopped":"2021-01-26T00:21:06.449626-08:00","success":false,"attempt_count":1}},{"id":85,"type":"file","result":"cannot open resource : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/django_q/cluster.py\", line 436, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/tasks.py\", line 81, in consume_file\n task_id=task_id\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 244, in try_consume_file\n self.path, mime_type, self.filename)\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/parsers.py\", line 302, in get_optimised_thumbnail\n thumbnail = self.get_thumbnail(document_path, mime_type, file_name)\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/paperless_text/parsers.py\", line 29, in get_thumbnail\n layout_engine=ImageFont.LAYOUT_BASIC)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/PIL/ImageFont.py\", line 852, in truetype\n return freetype(font)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/PIL/ImageFont.py\", line 849, in freetype\n return FreeTypeFont(font, size, index, encoding, layout_engine)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/PIL/ImageFont.py\", line 210, in __init__\n font, size, index, encoding, layout_engine=layout_engine\nOSError: cannot open resource\n","status":"failed","task_id":"abca803fa46342e1ac81f3d3f2080e79","name":"simple.txt","created":"2022-05-26T14:26:07.771541-07:00","acknowledged":null,"attempted_task":{"id":"abca803fa46342e1ac81f3d3f2080e79","name":"simple.txt","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtd2RhbnB5NnGUhZQu","kwargs":"gAWVtwAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwKc2ltcGxlLnR4dJSMDm92ZXJyaWRlX3RpdGxllE6MGW92ZXJyaWRlX2NvcnJlc3BvbmRlbnRfaWSUTowZb3ZlcnJpZGVfZG9jdW1lbnRfdHlwZV9pZJROjBBvdmVycmlkZV90YWdfaWRzlE6MB3Rhc2tfaWSUjCQ3ZGE0OTU4ZC0zM2UwLTQ1OGMtYTE0ZC1kMmU0NmE0NWY4Y2SUdS4=","result":"gAWV5QUAAAAAAABY3gUAAGNhbm5vdCBvcGVuIHJlc291cmNlIDogVHJhY2ViYWNrIChtb3N0IHJlY2VudCBjYWxsIGxhc3QpOgogIEZpbGUgIi9Vc2Vycy9tb29uZXIvLmxvY2FsL3NoYXJlL3ZpcnR1YWxlbnZzL3BhcGVybGVzcy1uZy03NkJ1SmxFSS9saWIvcHl0aG9uMy42L3NpdGUtcGFja2FnZXMvZGphbmdvX3EvY2x1c3Rlci5weSIsIGxpbmUgNDM2LCBpbiB3b3JrZXIKICAgIHJlcyA9IGYoKnRhc2tbImFyZ3MiXSwgKip0YXNrWyJrd2FyZ3MiXSkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5nL3NyYy9kb2N1bWVudHMvdGFza3MucHkiLCBsaW5lIDgxLCBpbiBjb25zdW1lX2ZpbGUKICAgIHRhc2tfaWQ9dGFza19pZAogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmcvc3JjL2RvY3VtZW50cy9jb25zdW1lci5weSIsIGxpbmUgMjQ0LCBpbiB0cnlfY29uc3VtZV9maWxlCiAgICBzZWxmLnBhdGgsIG1pbWVfdHlwZSwgc2VsZi5maWxlbmFtZSkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5nL3NyYy9kb2N1bWVudHMvcGFyc2Vycy5weSIsIGxpbmUgMzAyLCBpbiBnZXRfb3B0aW1pc2VkX3RodW1ibmFpbAogICAgdGh1bWJuYWlsID0gc2VsZi5nZXRfdGh1bWJuYWlsKGRvY3VtZW50X3BhdGgsIG1pbWVfdHlwZSwgZmlsZV9uYW1lKQogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmcvc3JjL3BhcGVybGVzc190ZXh0L3BhcnNlcnMucHkiLCBsaW5lIDI5LCBpbiBnZXRfdGh1bWJuYWlsCiAgICBsYXlvdXRfZW5naW5lPUltYWdlRm9udC5MQVlPVVRfQkFTSUMpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9QSUwvSW1hZ2VGb250LnB5IiwgbGluZSA4NTIsIGluIHRydWV0eXBlCiAgICByZXR1cm4gZnJlZXR5cGUoZm9udCkKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL1BJTC9JbWFnZUZvbnQucHkiLCBsaW5lIDg0OSwgaW4gZnJlZXR5cGUKICAgIHJldHVybiBGcmVlVHlwZUZvbnQoZm9udCwgc2l6ZSwgaW5kZXgsIGVuY29kaW5nLCBsYXlvdXRfZW5naW5lKQogIEZpbGUgIi9Vc2Vycy9tb29uZXIvLmxvY2FsL3NoYXJlL3ZpcnR1YWxlbnZzL3BhcGVybGVzcy1uZy03NkJ1SmxFSS9saWIvcHl0aG9uMy42L3NpdGUtcGFja2FnZXMvUElML0ltYWdlRm9udC5weSIsIGxpbmUgMjEwLCBpbiBfX2luaXRfXwogICAgZm9udCwgc2l6ZSwgaW5kZXgsIGVuY29kaW5nLCBsYXlvdXRfZW5naW5lPWxheW91dF9lbmdpbmUKT1NFcnJvcjogY2Fubm90IG9wZW4gcmVzb3VyY2UKlC4=","group":null,"started":"2021-03-06T14:23:56.974715-08:00","stopped":"2021-03-06T14:24:28.011772-08:00","success":false,"attempt_count":1}},{"id":41,"type":"file","result":"commands.txt: Not consuming commands.txt: It is a duplicate. : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ngx.nosync-udqDZzaE/lib/python3.8/site-packages/django_q/cluster.py\", line 432, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/paperless-ngx/src/documents/tasks.py\", line 70, in consume_file\n document = Consumer().try_consume_file(\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 199, in try_consume_file\n self.pre_check_duplicate()\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 97, in pre_check_duplicate\n self._fail(\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 69, in _fail\n raise ConsumerError(f\"{self.filename}: {log_message or message}\")\ndocuments.consumer.ConsumerError: commands.txt: Not consuming commands.txt: It is a duplicate.\n","status":"failed","task_id":"0af67672e8e14404b060d4cf8f69313d","name":"commands.txt","created":"2022-05-26T14:26:07.704247-07:00","acknowledged":null,"attempted_task":{"id":"0af67672e8e14404b060d4cf8f69313d","name":"commands.txt","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtZ3h4YjNxODaUhZQu","kwargs":"gAWVuQAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwMY29tbWFuZHMudHh0lIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDRkMjhmMmJiLTJkMzAtNGQzNi1iNjM5LWU2YzQ5OTU3OGVlY5R1Lg==","result":"gAWVLwQAAAAAAABYKAQAAGNvbW1hbmRzLnR4dDogTm90IGNvbnN1bWluZyBjb21tYW5kcy50eHQ6IEl0IGlzIGEgZHVwbGljYXRlLiA6IFRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3Mtbmd4Lm5vc3luYy11ZHFEWnphRS9saWIvcHl0aG9uMy44L3NpdGUtcGFja2FnZXMvZGphbmdvX3EvY2x1c3Rlci5weSIsIGxpbmUgNDMyLCBpbiB3b3JrZXIKICAgIHJlcyA9IGYoKnRhc2tbImFyZ3MiXSwgKip0YXNrWyJrd2FyZ3MiXSkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvdGFza3MucHkiLCBsaW5lIDcwLCBpbiBjb25zdW1lX2ZpbGUKICAgIGRvY3VtZW50ID0gQ29uc3VtZXIoKS50cnlfY29uc3VtZV9maWxlKAogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvRGV2Lm5vc3luYy9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZ3gvc3JjL2RvY3VtZW50cy9jb25zdW1lci5weSIsIGxpbmUgMTk5LCBpbiB0cnlfY29uc3VtZV9maWxlCiAgICBzZWxmLnByZV9jaGVja19kdXBsaWNhdGUoKQogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvRGV2Lm5vc3luYy9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZ3gvc3JjL2RvY3VtZW50cy9jb25zdW1lci5weSIsIGxpbmUgOTcsIGluIHByZV9jaGVja19kdXBsaWNhdGUKICAgIHNlbGYuX2ZhaWwoCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9EZXYubm9zeW5jL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5neC9zcmMvZG9jdW1lbnRzL2NvbnN1bWVyLnB5IiwgbGluZSA2OSwgaW4gX2ZhaWwKICAgIHJhaXNlIENvbnN1bWVyRXJyb3IoZiJ7c2VsZi5maWxlbmFtZX06IHtsb2dfbWVzc2FnZSBvciBtZXNzYWdlfSIpCmRvY3VtZW50cy5jb25zdW1lci5Db25zdW1lckVycm9yOiBjb21tYW5kcy50eHQ6IE5vdCBjb25zdW1pbmcgY29tbWFuZHMudHh0OiBJdCBpcyBhIGR1cGxpY2F0ZS4KlC4=","group":null,"started":"2022-03-10T22:26:32.548772-08:00","stopped":"2022-03-10T22:26:32.879916-08:00","success":false,"attempt_count":1}},{"id":10,"type":"file","result":"Success. New document id 260 created","status":"complete","task_id":"b7629a0f41bd40c7a3ea4680341321b5","name":"2022-03-24+Sonstige+ScanPC2022-03-24_081058.pdf","created":"2022-05-26T14:26:07.670577-07:00","acknowledged":false,"attempted_task":{"id":"b7629a0f41bd40c7a3ea4680341321b5","name":"2022-03-24+Sonstige+ScanPC2022-03-24_081058.pdf","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtc25mOW11ZW+UhZQu","kwargs":"gAWV3AAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwvMjAyMi0wMy0yNCtTb25zdGlnZStTY2FuUEMyMDIyLTAzLTI0XzA4MTA1OC5wZGaUjA5vdmVycmlkZV90aXRsZZROjBlvdmVycmlkZV9jb3JyZXNwb25kZW50X2lklE6MGW92ZXJyaWRlX2RvY3VtZW50X3R5cGVfaWSUTowQb3ZlcnJpZGVfdGFnX2lkc5ROjAd0YXNrX2lklIwkNTdmMmQwMGItY2Q0Ny00YzQ3LTlmOTctODFlOTllMTJhMjMylHUu","result":"gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjAgY3JlYXRlZJQu","group":null,"started":"2022-03-24T08:19:32.117861-07:00","stopped":"2022-03-24T08:20:10.239201-07:00","success":true,"attempt_count":1}},{"id":9,"type":"file","result":"Success. New document id 261 created","status":"complete","task_id":"02e276a86a424ccfb83309df5d8594be","name":"2sample-pdf-with-images.pdf","created":"2022-05-26T14:26:07.668987-07:00","acknowledged":false,"attempted_task":{"id":"02e276a86a424ccfb83309df5d8594be","name":"2sample-pdf-with-images.pdf","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtaXJ3cjZzOGeUhZQu","kwargs":"gAWVyAAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwbMnNhbXBsZS1wZGYtd2l0aC1pbWFnZXMucGRmlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDFlYTczMjhhLTk3MjctNDJiMC1iMTEyLTAzZjU3MzQ2MmRiNpR1Lg==","result":"gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjEgY3JlYXRlZJQu","group":null,"started":"2022-03-28T23:12:41.286318-07:00","stopped":"2022-03-28T23:13:00.523505-07:00","success":true,"attempt_count":1}},{"id":8,"type":"file","result":"Success. New document id 262 created","status":"complete","task_id":"41229b8be9b445c0a523697d0f58f13e","name":"2sample-pdf-with-images_pw.pdf","created":"2022-05-26T14:26:07.667993-07:00","acknowledged":false,"attempted_task":{"id":"41229b8be9b445c0a523697d0f58f13e","name":"2sample-pdf-with-images_pw.pdf","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtN2tfejA0MTGUhZQu","kwargs":"gAWVywAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIweMnNhbXBsZS1wZGYtd2l0aC1pbWFnZXNfcHcucGRmlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDk5YTgyOTc3LWU1MWUtNGJjYS04MjM4LTNkNzdhZTJhNjZmYZR1Lg==","result":"gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjIgY3JlYXRlZJQu","group":null,"started":"2022-03-28T23:43:53.171963-07:00","stopped":"2022-03-28T23:43:56.965257-07:00","success":true,"attempt_count":1}},{"id":6,"type":"file","result":"Success. New document id 264 created","status":"complete","task_id":"bbbca32d408c4619bd0b512a8327c773","name":"homebridge.log","created":"2022-05-26T14:26:07.665560-07:00","acknowledged":false,"attempted_task":{"id":"bbbca32d408c4619bd0b512a8327c773","name":"homebridge.log","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQteGo4aW9zYXaUhZQu","kwargs":"gAWVuwAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwOaG9tZWJyaWRnZS5sb2eUjA5vdmVycmlkZV90aXRsZZROjBlvdmVycmlkZV9jb3JyZXNwb25kZW50X2lklE6MGW92ZXJyaWRlX2RvY3VtZW50X3R5cGVfaWSUTowQb3ZlcnJpZGVfdGFnX2lkc5ROjAd0YXNrX2lklIwkNzY0NzdhNWEtNzk0Ni00NWU0LWE3MDktNzQzNDg0ZDE2YTUxlHUu","result":"gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjQgY3JlYXRlZJQu","group":null,"started":"2022-03-29T22:56:16.053026-07:00","stopped":"2022-03-29T22:56:21.196179-07:00","success":true,"attempt_count":1}},{"id":5,"type":"file","result":"Success. New document id 265 created","status":"complete","task_id":"00ab285ab4bf482ab30c7d580b252ecb","name":"IMG_7459.PNG","created":"2022-05-26T14:26:07.664506-07:00","acknowledged":false,"attempted_task":{"id":"00ab285ab4bf482ab30c7d580b252ecb","name":"IMG_7459.PNG","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtOGF5NDNfZjeUhZQu","kwargs":"gAWVuQAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwMSU1HXzc0NTkuUE5HlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDYxMTNhNzRlLTAwOWMtNGJhYi1hMjk1LTFmNjMwMzZmMTc4ZpR1Lg==","result":"gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjUgY3JlYXRlZJQu","group":null,"started":"2022-04-05T13:19:47.490282-07:00","stopped":"2022-04-05T13:21:36.782264-07:00","success":true,"attempt_count":1}},{"id":3,"type":"file","result":"Success. New document id 267 created","status":"complete","task_id":"289c5163cfec410db42948a0cacbeb9c","name":"IMG_7459.PNG","created":"2022-05-26T14:26:07.659661-07:00","acknowledged":false,"attempted_task":{"id":"289c5163cfec410db42948a0cacbeb9c","name":"IMG_7459.PNG","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtNzRuY2p2aXGUhZQu","kwargs":"gAWVuQAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwMSU1HXzc0NTkuUE5HlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJGZjZDljMmFlLWFhZmEtNGJmMC05M2Y5LWE3ZGQxYmEzYWM1NZR1Lg==","result":"gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjcgY3JlYXRlZJQu","group":null,"started":"2022-04-05T13:29:59.264441-07:00","stopped":"2022-04-05T13:30:28.336185-07:00","success":true,"attempt_count":1}},{"id":1,"type":"file","result":"Success. New document id 268 created","status":"complete","task_id":"7a4ebdb2bde04311935284027ef8ca65","name":"2019-08-04 DSA Questionnaire - 5-8-19.pdf","created":"2022-05-26T14:26:07.655276-07:00","acknowledged":false,"attempted_task":{"id":"7a4ebdb2bde04311935284027ef8ca65","name":"2019-08-04 DSA Questionnaire - 5-8-19.pdf","func":"documents.tasks.consume_file","hook":null,"args":"gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtdXpscHl2NnmUhZQu","kwargs":"gAWV1gAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwpMjAxOS0wOC0wNCBEU0EgUXVlc3Rpb25uYWlyZSAtIDUtOC0xOS5wZGaUjA5vdmVycmlkZV90aXRsZZROjBlvdmVycmlkZV9jb3JyZXNwb25kZW50X2lklE6MGW92ZXJyaWRlX2RvY3VtZW50X3R5cGVfaWSUTowQb3ZlcnJpZGVfdGFnX2lkc5ROjAd0YXNrX2lklIwkY2Q3YzBhZjgtN2Q4Ni00OGM0LTliNjgtNDQwMmQ4ZDZlOTNmlHUu","result":"gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjggY3JlYXRlZJQu","group":null,"started":"2022-04-28T21:01:04.275850-07:00","stopped":"2022-04-28T21:01:10.136884-07:00","success":true,"attempt_count":1}}]
+[
+ {
+ "id": 141,
+ "type": "file",
+ "result": "sample 2.pdf: Not consuming sample 2.pdf: It is a duplicate. : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ngx.nosync-udqDZzaE/lib/python3.8/site-packages/django_q/cluster.py\", line 432, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/paperless-ngx/src/documents/tasks.py\", line 316, in consume_file\n document = Consumer().try_consume_file(\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 218, in try_consume_file\n self.pre_check_duplicate()\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 113, in pre_check_duplicate\n self._fail(\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 84, in _fail\n raise ConsumerError(f\"{self.filename}: {log_message or message}\")\ndocuments.consumer.ConsumerError: sample 2.pdf: Not consuming sample 2.pdf: It is a duplicate.\n",
+ "status": "FAILURE",
+ "task_id": "d8ddbe298a42427d82553206ddf0bc94",
+ "name": "sample 2.pdf",
+ "created": "2022-05-26T23:17:38.333474-07:00",
+ "acknowledged": false,
+ "attempted_task": {
+ "id": "d8ddbe298a42427d82553206ddf0bc94",
+ "name": "sample 2.pdf",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtanJxNGs1aHOUhZQu",
+ "kwargs": "gAWVzQAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwMc2FtcGxlIDIucGRmlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDcyMGExYjI5LWI2OTYtNDY3My05Y2ZmLTJkY2ZiZWNmNWViMpSMEG92ZXJyaWRlX2NyZWF0ZWSUTnUu",
+ "result": "gAWVMQQAAAAAAABYKgQAAHNhbXBsZSAyLnBkZjogTm90IGNvbnN1bWluZyBzYW1wbGUgMi5wZGY6IEl0IGlzIGEgZHVwbGljYXRlLiA6IFRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3Mtbmd4Lm5vc3luYy11ZHFEWnphRS9saWIvcHl0aG9uMy44L3NpdGUtcGFja2FnZXMvZGphbmdvX3EvY2x1c3Rlci5weSIsIGxpbmUgNDMyLCBpbiB3b3JrZXIKICAgIHJlcyA9IGYoKnRhc2tbImFyZ3MiXSwgKip0YXNrWyJrd2FyZ3MiXSkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvdGFza3MucHkiLCBsaW5lIDMxNiwgaW4gY29uc3VtZV9maWxlCiAgICBkb2N1bWVudCA9IENvbnN1bWVyKCkudHJ5X2NvbnN1bWVfZmlsZSgKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDIxOCwgaW4gdHJ5X2NvbnN1bWVfZmlsZQogICAgc2VsZi5wcmVfY2hlY2tfZHVwbGljYXRlKCkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDExMywgaW4gcHJlX2NoZWNrX2R1cGxpY2F0ZQogICAgc2VsZi5fZmFpbCgKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDg0LCBpbiBfZmFpbAogICAgcmFpc2UgQ29uc3VtZXJFcnJvcihmIntzZWxmLmZpbGVuYW1lfToge2xvZ19tZXNzYWdlIG9yIG1lc3NhZ2V9IikKZG9jdW1lbnRzLmNvbnN1bWVyLkNvbnN1bWVyRXJyb3I6IHNhbXBsZSAyLnBkZjogTm90IGNvbnN1bWluZyBzYW1wbGUgMi5wZGY6IEl0IGlzIGEgZHVwbGljYXRlLgqULg==",
+ "group": null,
+ "started": "2022-05-26T23:17:37.702432-07:00",
+ "stopped": "2022-05-26T23:17:38.313306-07:00",
+ "success": false,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 132,
+ "type": "file",
+ "result": " : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 131, in get_version\n env=env,\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 68, in run\n proc = subprocess_run(args, env=env, **kwargs)\n File \"/Users/admin/opt/anaconda3/envs/paperless-ng/lib/python3.6/subprocess.py\", line 423, in run\n with Popen(*popenargs, **kwargs) as process:\n File \"/Users/admin/opt/anaconda3/envs/paperless-ng/lib/python3.6/subprocess.py\", line 729, in __init__\n restore_signals, start_new_session)\n File \"/Users/admin/opt/anaconda3/envs/paperless-ng/lib/python3.6/subprocess.py\", line 1364, in _execute_child\n raise child_exception_type(errno_num, err_msg, err_filename)\nFileNotFoundError: [Errno 2] No such file or directory: 'unpaper': 'unpaper'\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 287, in check_external_program\n found_version = version_checker()\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/_exec/unpaper.py\", line 34, in version\n return get_version('unpaper')\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 137, in get_version\n ) from e\nocrmypdf.exceptions.MissingDependencyError: Could not find program 'unpaper' on the PATH\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/paperless_tesseract/parsers.py\", line 176, in parse\n ocrmypdf.ocr(**ocr_args)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/api.py\", line 315, in ocr\n check_options(options, plugin_manager)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/_validation.py\", line 260, in check_options\n _check_options(options, plugin_manager, ocr_engine_languages)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/_validation.py\", line 250, in _check_options\n check_options_preprocessing(options)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/_validation.py\", line 128, in check_options_preprocessing\n required_for=['--clean, --clean-final'],\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/ocrmypdf/subprocess.py\", line 293, in check_external_program\n raise MissingDependencyError()\nocrmypdf.exceptions.MissingDependencyError\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 179, in try_consume_file\n document_parser.parse(self.path, mime_type, self.filename)\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/paperless_tesseract/parsers.py\", line 197, in parse\n raise ParseError(e)\ndocuments.parsers.ParseError\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/django_q/cluster.py\", line 436, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/tasks.py\", line 73, in consume_file\n override_tag_ids=override_tag_ids)\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 196, in try_consume_file\n raise ConsumerError(e)\ndocuments.consumer.ConsumerError\n",
+ "status": "FAILURE",
+ "task_id": "4c554075552c4cc985abd76e6f274c90",
+ "name": "pdf-sample 10.24.48 PM.pdf",
+ "created": "2022-05-26T14:26:07.846365-07:00",
+ "acknowledged": null,
+ "attempted_task": {
+ "id": "4c554075552c4cc985abd76e6f274c90",
+ "name": "pdf-sample 10.24.48 PM.pdf",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVKwAAAAAAAACMJS4uL2NvbnN1bWUvcGRmLXNhbXBsZSAxMC4yNC40OCBQTS5wZGaUhZQu",
+ "kwargs": "gAWVGAAAAAAAAAB9lIwQb3ZlcnJpZGVfdGFnX2lkc5ROcy4=",
+ "result": "gAWVzA8AAAAAAABYxQ8AACA6IFRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL29jcm15cGRmL3N1YnByb2Nlc3MucHkiLCBsaW5lIDEzMSwgaW4gZ2V0X3ZlcnNpb24KICAgIGVudj1lbnYsCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9zdWJwcm9jZXNzLnB5IiwgbGluZSA2OCwgaW4gcnVuCiAgICBwcm9jID0gc3VicHJvY2Vzc19ydW4oYXJncywgZW52PWVudiwgKiprd2FyZ3MpCiAgRmlsZSAiL1VzZXJzL21vb25lci9vcHQvYW5hY29uZGEzL2VudnMvcGFwZXJsZXNzLW5nL2xpYi9weXRob24zLjYvc3VicHJvY2Vzcy5weSIsIGxpbmUgNDIzLCBpbiBydW4KICAgIHdpdGggUG9wZW4oKnBvcGVuYXJncywgKiprd2FyZ3MpIGFzIHByb2Nlc3M6CiAgRmlsZSAiL1VzZXJzL21vb25lci9vcHQvYW5hY29uZGEzL2VudnMvcGFwZXJsZXNzLW5nL2xpYi9weXRob24zLjYvc3VicHJvY2Vzcy5weSIsIGxpbmUgNzI5LCBpbiBfX2luaXRfXwogICAgcmVzdG9yZV9zaWduYWxzLCBzdGFydF9uZXdfc2Vzc2lvbikKICBGaWxlICIvVXNlcnMvbW9vbmVyL29wdC9hbmFjb25kYTMvZW52cy9wYXBlcmxlc3MtbmcvbGliL3B5dGhvbjMuNi9zdWJwcm9jZXNzLnB5IiwgbGluZSAxMzY0LCBpbiBfZXhlY3V0ZV9jaGlsZAogICAgcmFpc2UgY2hpbGRfZXhjZXB0aW9uX3R5cGUoZXJybm9fbnVtLCBlcnJfbXNnLCBlcnJfZmlsZW5hbWUpCkZpbGVOb3RGb3VuZEVycm9yOiBbRXJybm8gMl0gTm8gc3VjaCBmaWxlIG9yIGRpcmVjdG9yeTogJ3VucGFwZXInOiAndW5wYXBlcicKClRoZSBhYm92ZSBleGNlcHRpb24gd2FzIHRoZSBkaXJlY3QgY2F1c2Ugb2YgdGhlIGZvbGxvd2luZyBleGNlcHRpb246CgpUcmFjZWJhY2sgKG1vc3QgcmVjZW50IGNhbGwgbGFzdCk6CiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9zdWJwcm9jZXNzLnB5IiwgbGluZSAyODcsIGluIGNoZWNrX2V4dGVybmFsX3Byb2dyYW0KICAgIGZvdW5kX3ZlcnNpb24gPSB2ZXJzaW9uX2NoZWNrZXIoKQogIEZpbGUgIi9Vc2Vycy9tb29uZXIvLmxvY2FsL3NoYXJlL3ZpcnR1YWxlbnZzL3BhcGVybGVzcy1uZy03NkJ1SmxFSS9saWIvcHl0aG9uMy42L3NpdGUtcGFja2FnZXMvb2NybXlwZGYvX2V4ZWMvdW5wYXBlci5weSIsIGxpbmUgMzQsIGluIHZlcnNpb24KICAgIHJldHVybiBnZXRfdmVyc2lvbigndW5wYXBlcicpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9zdWJwcm9jZXNzLnB5IiwgbGluZSAxMzcsIGluIGdldF92ZXJzaW9uCiAgICApIGZyb20gZQpvY3JteXBkZi5leGNlcHRpb25zLk1pc3NpbmdEZXBlbmRlbmN5RXJyb3I6IENvdWxkIG5vdCBmaW5kIHByb2dyYW0gJ3VucGFwZXInIG9uIHRoZSBQQVRICgpEdXJpbmcgaGFuZGxpbmcgb2YgdGhlIGFib3ZlIGV4Y2VwdGlvbiwgYW5vdGhlciBleGNlcHRpb24gb2NjdXJyZWQ6CgpUcmFjZWJhY2sgKG1vc3QgcmVjZW50IGNhbGwgbGFzdCk6CiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvcGFwZXJsZXNzX3Rlc3NlcmFjdC9wYXJzZXJzLnB5IiwgbGluZSAxNzYsIGluIHBhcnNlCiAgICBvY3JteXBkZi5vY3IoKipvY3JfYXJncykKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL29jcm15cGRmL2FwaS5weSIsIGxpbmUgMzE1LCBpbiBvY3IKICAgIGNoZWNrX29wdGlvbnMob3B0aW9ucywgcGx1Z2luX21hbmFnZXIpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9fdmFsaWRhdGlvbi5weSIsIGxpbmUgMjYwLCBpbiBjaGVja19vcHRpb25zCiAgICBfY2hlY2tfb3B0aW9ucyhvcHRpb25zLCBwbHVnaW5fbWFuYWdlciwgb2NyX2VuZ2luZV9sYW5ndWFnZXMpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9fdmFsaWRhdGlvbi5weSIsIGxpbmUgMjUwLCBpbiBfY2hlY2tfb3B0aW9ucwogICAgY2hlY2tfb3B0aW9uc19wcmVwcm9jZXNzaW5nKG9wdGlvbnMpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9vY3JteXBkZi9fdmFsaWRhdGlvbi5weSIsIGxpbmUgMTI4LCBpbiBjaGVja19vcHRpb25zX3ByZXByb2Nlc3NpbmcKICAgIHJlcXVpcmVkX2Zvcj1bJy0tY2xlYW4sIC0tY2xlYW4tZmluYWwnXSwKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL29jcm15cGRmL3N1YnByb2Nlc3MucHkiLCBsaW5lIDI5MywgaW4gY2hlY2tfZXh0ZXJuYWxfcHJvZ3JhbQogICAgcmFpc2UgTWlzc2luZ0RlcGVuZGVuY3lFcnJvcigpCm9jcm15cGRmLmV4Y2VwdGlvbnMuTWlzc2luZ0RlcGVuZGVuY3lFcnJvcgoKRHVyaW5nIGhhbmRsaW5nIG9mIHRoZSBhYm92ZSBleGNlcHRpb24sIGFub3RoZXIgZXhjZXB0aW9uIG9jY3VycmVkOgoKVHJhY2ViYWNrIChtb3N0IHJlY2VudCBjYWxsIGxhc3QpOgogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmcvc3JjL2RvY3VtZW50cy9jb25zdW1lci5weSIsIGxpbmUgMTc5LCBpbiB0cnlfY29uc3VtZV9maWxlCiAgICBkb2N1bWVudF9wYXJzZXIucGFyc2Uoc2VsZi5wYXRoLCBtaW1lX3R5cGUsIHNlbGYuZmlsZW5hbWUpCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvcGFwZXJsZXNzX3Rlc3NlcmFjdC9wYXJzZXJzLnB5IiwgbGluZSAxOTcsIGluIHBhcnNlCiAgICByYWlzZSBQYXJzZUVycm9yKGUpCmRvY3VtZW50cy5wYXJzZXJzLlBhcnNlRXJyb3IKCkR1cmluZyBoYW5kbGluZyBvZiB0aGUgYWJvdmUgZXhjZXB0aW9uLCBhbm90aGVyIGV4Y2VwdGlvbiBvY2N1cnJlZDoKClRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL2RqYW5nb19xL2NsdXN0ZXIucHkiLCBsaW5lIDQzNiwgaW4gd29ya2VyCiAgICByZXMgPSBmKCp0YXNrWyJhcmdzIl0sICoqdGFza1sia3dhcmdzIl0pCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvZG9jdW1lbnRzL3Rhc2tzLnB5IiwgbGluZSA3MywgaW4gY29uc3VtZV9maWxlCiAgICBvdmVycmlkZV90YWdfaWRzPW92ZXJyaWRlX3RhZ19pZHMpCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvZG9jdW1lbnRzL2NvbnN1bWVyLnB5IiwgbGluZSAxOTYsIGluIHRyeV9jb25zdW1lX2ZpbGUKICAgIHJhaXNlIENvbnN1bWVyRXJyb3IoZSkKZG9jdW1lbnRzLmNvbnN1bWVyLkNvbnN1bWVyRXJyb3IKlC4=",
+ "group": null,
+ "started": "2021-01-20T10:47:34.535478-08:00",
+ "stopped": "2021-01-20T10:49:55.568010-08:00",
+ "success": false,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 115,
+ "type": "file",
+ "result": "2021-01-24 2021-01-20 sample_wide_orange.pdf: Document is a duplicate : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/django_q/cluster.py\", line 436, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/tasks.py\", line 75, in consume_file\n task_id=task_id\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 168, in try_consume_file\n self.pre_check_duplicate()\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 85, in pre_check_duplicate\n self._fail(\"Document is a duplicate\")\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 53, in _fail\n raise ConsumerError(f\"{self.filename}: {message}\")\ndocuments.consumer.ConsumerError: 2021-01-24 2021-01-20 sample_wide_orange.pdf: Document is a duplicate\n",
+ "status": "FAILURE",
+ "task_id": "86494713646a4364b01da17aadca071d",
+ "name": "2021-01-24 2021-01-20 sample_wide_orange.pdf",
+ "created": "2022-05-26T14:26:07.817608-07:00",
+ "acknowledged": null,
+ "attempted_task": {
+ "id": "86494713646a4364b01da17aadca071d",
+ "name": "2021-01-24 2021-01-20 sample_wide_orange.pdf",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtcTJ6NDlnbzaUhZQu",
+ "kwargs": "gAWV2QAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwsMjAyMS0wMS0yNCAyMDIxLTAxLTIwIHNhbXBsZV93aWRlX29yYW5nZS5wZGaUjA5vdmVycmlkZV90aXRsZZROjBlvdmVycmlkZV9jb3JyZXNwb25kZW50X2lklE6MGW92ZXJyaWRlX2RvY3VtZW50X3R5cGVfaWSUTowQb3ZlcnJpZGVfdGFnX2lkc5ROjAd0YXNrX2lklIwkN2MwZTY1MmQtZDhkYy00OWU4LWI1ZmUtOGM3ZTkyZDlmOTI0lHUu",
+ "result": "gAWV/AMAAAAAAABY9QMAADIwMjEtMDEtMjQgMjAyMS0wMS0yMCBzYW1wbGVfd2lkZV9vcmFuZ2UucGRmOiBEb2N1bWVudCBpcyBhIGR1cGxpY2F0ZSA6IFRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL2RqYW5nb19xL2NsdXN0ZXIucHkiLCBsaW5lIDQzNiwgaW4gd29ya2VyCiAgICByZXMgPSBmKCp0YXNrWyJhcmdzIl0sICoqdGFza1sia3dhcmdzIl0pCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvZG9jdW1lbnRzL3Rhc2tzLnB5IiwgbGluZSA3NSwgaW4gY29uc3VtZV9maWxlCiAgICB0YXNrX2lkPXRhc2tfaWQKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5nL3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDE2OCwgaW4gdHJ5X2NvbnN1bWVfZmlsZQogICAgc2VsZi5wcmVfY2hlY2tfZHVwbGljYXRlKCkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5nL3NyYy9kb2N1bWVudHMvY29uc3VtZXIucHkiLCBsaW5lIDg1LCBpbiBwcmVfY2hlY2tfZHVwbGljYXRlCiAgICBzZWxmLl9mYWlsKCJEb2N1bWVudCBpcyBhIGR1cGxpY2F0ZSIpCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZy9zcmMvZG9jdW1lbnRzL2NvbnN1bWVyLnB5IiwgbGluZSA1MywgaW4gX2ZhaWwKICAgIHJhaXNlIENvbnN1bWVyRXJyb3IoZiJ7c2VsZi5maWxlbmFtZX06IHttZXNzYWdlfSIpCmRvY3VtZW50cy5jb25zdW1lci5Db25zdW1lckVycm9yOiAyMDIxLTAxLTI0IDIwMjEtMDEtMjAgc2FtcGxlX3dpZGVfb3JhbmdlLnBkZjogRG9jdW1lbnQgaXMgYSBkdXBsaWNhdGUKlC4=",
+ "group": null,
+ "started": "2021-01-26T00:21:05.379583-08:00",
+ "stopped": "2021-01-26T00:21:06.449626-08:00",
+ "success": false,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 85,
+ "type": "file",
+ "result": "cannot open resource : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/django_q/cluster.py\", line 436, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/tasks.py\", line 81, in consume_file\n task_id=task_id\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/consumer.py\", line 244, in try_consume_file\n self.path, mime_type, self.filename)\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/documents/parsers.py\", line 302, in get_optimised_thumbnail\n thumbnail = self.get_thumbnail(document_path, mime_type, file_name)\n File \"/Users/admin/Documents/Work/Contributions/paperless-ng/src/paperless_text/parsers.py\", line 29, in get_thumbnail\n layout_engine=ImageFont.LAYOUT_BASIC)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/PIL/ImageFont.py\", line 852, in truetype\n return freetype(font)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/PIL/ImageFont.py\", line 849, in freetype\n return FreeTypeFont(font, size, index, encoding, layout_engine)\n File \"/Users/admin/.local/share/virtualenvs/paperless-ng/lib/python3.6/site-packages/PIL/ImageFont.py\", line 210, in __init__\n font, size, index, encoding, layout_engine=layout_engine\nOSError: cannot open resource\n",
+ "status": "FAILURE",
+ "task_id": "abca803fa46342e1ac81f3d3f2080e79",
+ "name": "simple.txt",
+ "created": "2022-05-26T14:26:07.771541-07:00",
+ "acknowledged": null,
+ "attempted_task": {
+ "id": "abca803fa46342e1ac81f3d3f2080e79",
+ "name": "simple.txt",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtd2RhbnB5NnGUhZQu",
+ "kwargs": "gAWVtwAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwKc2ltcGxlLnR4dJSMDm92ZXJyaWRlX3RpdGxllE6MGW92ZXJyaWRlX2NvcnJlc3BvbmRlbnRfaWSUTowZb3ZlcnJpZGVfZG9jdW1lbnRfdHlwZV9pZJROjBBvdmVycmlkZV90YWdfaWRzlE6MB3Rhc2tfaWSUjCQ3ZGE0OTU4ZC0zM2UwLTQ1OGMtYTE0ZC1kMmU0NmE0NWY4Y2SUdS4=",
+ "result": "gAWV5QUAAAAAAABY3gUAAGNhbm5vdCBvcGVuIHJlc291cmNlIDogVHJhY2ViYWNrIChtb3N0IHJlY2VudCBjYWxsIGxhc3QpOgogIEZpbGUgIi9Vc2Vycy9tb29uZXIvLmxvY2FsL3NoYXJlL3ZpcnR1YWxlbnZzL3BhcGVybGVzcy1uZy03NkJ1SmxFSS9saWIvcHl0aG9uMy42L3NpdGUtcGFja2FnZXMvZGphbmdvX3EvY2x1c3Rlci5weSIsIGxpbmUgNDM2LCBpbiB3b3JrZXIKICAgIHJlcyA9IGYoKnRhc2tbImFyZ3MiXSwgKip0YXNrWyJrd2FyZ3MiXSkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5nL3NyYy9kb2N1bWVudHMvdGFza3MucHkiLCBsaW5lIDgxLCBpbiBjb25zdW1lX2ZpbGUKICAgIHRhc2tfaWQ9dGFza19pZAogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmcvc3JjL2RvY3VtZW50cy9jb25zdW1lci5weSIsIGxpbmUgMjQ0LCBpbiB0cnlfY29uc3VtZV9maWxlCiAgICBzZWxmLnBhdGgsIG1pbWVfdHlwZSwgc2VsZi5maWxlbmFtZSkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5nL3NyYy9kb2N1bWVudHMvcGFyc2Vycy5weSIsIGxpbmUgMzAyLCBpbiBnZXRfb3B0aW1pc2VkX3RodW1ibmFpbAogICAgdGh1bWJuYWlsID0gc2VsZi5nZXRfdGh1bWJuYWlsKGRvY3VtZW50X3BhdGgsIG1pbWVfdHlwZSwgZmlsZV9uYW1lKQogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmcvc3JjL3BhcGVybGVzc190ZXh0L3BhcnNlcnMucHkiLCBsaW5lIDI5LCBpbiBnZXRfdGh1bWJuYWlsCiAgICBsYXlvdXRfZW5naW5lPUltYWdlRm9udC5MQVlPVVRfQkFTSUMpCiAgRmlsZSAiL1VzZXJzL21vb25lci8ubG9jYWwvc2hhcmUvdmlydHVhbGVudnMvcGFwZXJsZXNzLW5nLTc2QnVKbEVJL2xpYi9weXRob24zLjYvc2l0ZS1wYWNrYWdlcy9QSUwvSW1hZ2VGb250LnB5IiwgbGluZSA4NTIsIGluIHRydWV0eXBlCiAgICByZXR1cm4gZnJlZXR5cGUoZm9udCkKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3MtbmctNzZCdUpsRUkvbGliL3B5dGhvbjMuNi9zaXRlLXBhY2thZ2VzL1BJTC9JbWFnZUZvbnQucHkiLCBsaW5lIDg0OSwgaW4gZnJlZXR5cGUKICAgIHJldHVybiBGcmVlVHlwZUZvbnQoZm9udCwgc2l6ZSwgaW5kZXgsIGVuY29kaW5nLCBsYXlvdXRfZW5naW5lKQogIEZpbGUgIi9Vc2Vycy9tb29uZXIvLmxvY2FsL3NoYXJlL3ZpcnR1YWxlbnZzL3BhcGVybGVzcy1uZy03NkJ1SmxFSS9saWIvcHl0aG9uMy42L3NpdGUtcGFja2FnZXMvUElML0ltYWdlRm9udC5weSIsIGxpbmUgMjEwLCBpbiBfX2luaXRfXwogICAgZm9udCwgc2l6ZSwgaW5kZXgsIGVuY29kaW5nLCBsYXlvdXRfZW5naW5lPWxheW91dF9lbmdpbmUKT1NFcnJvcjogY2Fubm90IG9wZW4gcmVzb3VyY2UKlC4=",
+ "group": null,
+ "started": "2021-03-06T14:23:56.974715-08:00",
+ "stopped": "2021-03-06T14:24:28.011772-08:00",
+ "success": false,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 41,
+ "type": "file",
+ "result": "commands.txt: Not consuming commands.txt: It is a duplicate. : Traceback (most recent call last):\n File \"/Users/admin/.local/share/virtualenvs/paperless-ngx.nosync-udqDZzaE/lib/python3.8/site-packages/django_q/cluster.py\", line 432, in worker\n res = f(*task[\"args\"], **task[\"kwargs\"])\n File \"/Users/admin/Documents/paperless-ngx/src/documents/tasks.py\", line 70, in consume_file\n document = Consumer().try_consume_file(\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 199, in try_consume_file\n self.pre_check_duplicate()\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 97, in pre_check_duplicate\n self._fail(\n File \"/Users/admin/Documents/paperless-ngx/src/documents/consumer.py\", line 69, in _fail\n raise ConsumerError(f\"{self.filename}: {log_message or message}\")\ndocuments.consumer.ConsumerError: commands.txt: Not consuming commands.txt: It is a duplicate.\n",
+ "status": "FAILURE",
+ "task_id": "0af67672e8e14404b060d4cf8f69313d",
+ "name": "commands.txt",
+ "created": "2022-05-26T14:26:07.704247-07:00",
+ "acknowledged": null,
+ "attempted_task": {
+ "id": "0af67672e8e14404b060d4cf8f69313d",
+ "name": "commands.txt",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtZ3h4YjNxODaUhZQu",
+ "kwargs": "gAWVuQAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwMY29tbWFuZHMudHh0lIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDRkMjhmMmJiLTJkMzAtNGQzNi1iNjM5LWU2YzQ5OTU3OGVlY5R1Lg==",
+ "result": "gAWVLwQAAAAAAABYKAQAAGNvbW1hbmRzLnR4dDogTm90IGNvbnN1bWluZyBjb21tYW5kcy50eHQ6IEl0IGlzIGEgZHVwbGljYXRlLiA6IFRyYWNlYmFjayAobW9zdCByZWNlbnQgY2FsbCBsYXN0KToKICBGaWxlICIvVXNlcnMvbW9vbmVyLy5sb2NhbC9zaGFyZS92aXJ0dWFsZW52cy9wYXBlcmxlc3Mtbmd4Lm5vc3luYy11ZHFEWnphRS9saWIvcHl0aG9uMy44L3NpdGUtcGFja2FnZXMvZGphbmdvX3EvY2x1c3Rlci5weSIsIGxpbmUgNDMyLCBpbiB3b3JrZXIKICAgIHJlcyA9IGYoKnRhc2tbImFyZ3MiXSwgKip0YXNrWyJrd2FyZ3MiXSkKICBGaWxlICIvVXNlcnMvbW9vbmVyL0RvY3VtZW50cy9Xb3JrL0Rldi5ub3N5bmMvQ29udHJpYnV0aW9ucy9wYXBlcmxlc3Mtbmd4L3NyYy9kb2N1bWVudHMvdGFza3MucHkiLCBsaW5lIDcwLCBpbiBjb25zdW1lX2ZpbGUKICAgIGRvY3VtZW50ID0gQ29uc3VtZXIoKS50cnlfY29uc3VtZV9maWxlKAogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvRGV2Lm5vc3luYy9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZ3gvc3JjL2RvY3VtZW50cy9jb25zdW1lci5weSIsIGxpbmUgMTk5LCBpbiB0cnlfY29uc3VtZV9maWxlCiAgICBzZWxmLnByZV9jaGVja19kdXBsaWNhdGUoKQogIEZpbGUgIi9Vc2Vycy9tb29uZXIvRG9jdW1lbnRzL1dvcmsvRGV2Lm5vc3luYy9Db250cmlidXRpb25zL3BhcGVybGVzcy1uZ3gvc3JjL2RvY3VtZW50cy9jb25zdW1lci5weSIsIGxpbmUgOTcsIGluIHByZV9jaGVja19kdXBsaWNhdGUKICAgIHNlbGYuX2ZhaWwoCiAgRmlsZSAiL1VzZXJzL21vb25lci9Eb2N1bWVudHMvV29yay9EZXYubm9zeW5jL0NvbnRyaWJ1dGlvbnMvcGFwZXJsZXNzLW5neC9zcmMvZG9jdW1lbnRzL2NvbnN1bWVyLnB5IiwgbGluZSA2OSwgaW4gX2ZhaWwKICAgIHJhaXNlIENvbnN1bWVyRXJyb3IoZiJ7c2VsZi5maWxlbmFtZX06IHtsb2dfbWVzc2FnZSBvciBtZXNzYWdlfSIpCmRvY3VtZW50cy5jb25zdW1lci5Db25zdW1lckVycm9yOiBjb21tYW5kcy50eHQ6IE5vdCBjb25zdW1pbmcgY29tbWFuZHMudHh0OiBJdCBpcyBhIGR1cGxpY2F0ZS4KlC4=",
+ "group": null,
+ "started": "2022-03-10T22:26:32.548772-08:00",
+ "stopped": "2022-03-10T22:26:32.879916-08:00",
+ "success": false,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 10,
+ "type": "file",
+ "result": "Success. New document id 260 created",
+ "status": "SUCCESS",
+ "task_id": "b7629a0f41bd40c7a3ea4680341321b5",
+ "name": "2022-03-24+Sonstige+ScanPC2022-03-24_081058.pdf",
+ "created": "2022-05-26T14:26:07.670577-07:00",
+ "acknowledged": false,
+ "attempted_task": {
+ "id": "b7629a0f41bd40c7a3ea4680341321b5",
+ "name": "2022-03-24+Sonstige+ScanPC2022-03-24_081058.pdf",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtc25mOW11ZW+UhZQu",
+ "kwargs": "gAWV3AAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwvMjAyMi0wMy0yNCtTb25zdGlnZStTY2FuUEMyMDIyLTAzLTI0XzA4MTA1OC5wZGaUjA5vdmVycmlkZV90aXRsZZROjBlvdmVycmlkZV9jb3JyZXNwb25kZW50X2lklE6MGW92ZXJyaWRlX2RvY3VtZW50X3R5cGVfaWSUTowQb3ZlcnJpZGVfdGFnX2lkc5ROjAd0YXNrX2lklIwkNTdmMmQwMGItY2Q0Ny00YzQ3LTlmOTctODFlOTllMTJhMjMylHUu",
+ "result": "gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjAgY3JlYXRlZJQu",
+ "group": null,
+ "started": "2022-03-24T08:19:32.117861-07:00",
+ "stopped": "2022-03-24T08:20:10.239201-07:00",
+ "success": true,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 9,
+ "type": "file",
+ "result": "Success. New document id 261 created",
+ "status": "SUCCESS",
+ "task_id": "02e276a86a424ccfb83309df5d8594be",
+ "name": "2sample-pdf-with-images.pdf",
+ "created": "2022-05-26T14:26:07.668987-07:00",
+ "acknowledged": false,
+ "attempted_task": {
+ "id": "02e276a86a424ccfb83309df5d8594be",
+ "name": "2sample-pdf-with-images.pdf",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtaXJ3cjZzOGeUhZQu",
+ "kwargs": "gAWVyAAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwbMnNhbXBsZS1wZGYtd2l0aC1pbWFnZXMucGRmlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDFlYTczMjhhLTk3MjctNDJiMC1iMTEyLTAzZjU3MzQ2MmRiNpR1Lg==",
+ "result": "gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjEgY3JlYXRlZJQu",
+ "group": null,
+ "started": "2022-03-28T23:12:41.286318-07:00",
+ "stopped": "2022-03-28T23:13:00.523505-07:00",
+ "success": true,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 8,
+ "type": "file",
+ "result": "Success. New document id 262 created",
+ "status": "SUCCESS",
+ "task_id": "41229b8be9b445c0a523697d0f58f13e",
+ "name": "2sample-pdf-with-images_pw.pdf",
+ "created": "2022-05-26T14:26:07.667993-07:00",
+ "acknowledged": false,
+ "attempted_task": {
+ "id": "41229b8be9b445c0a523697d0f58f13e",
+ "name": "2sample-pdf-with-images_pw.pdf",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtN2tfejA0MTGUhZQu",
+ "kwargs": "gAWVywAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIweMnNhbXBsZS1wZGYtd2l0aC1pbWFnZXNfcHcucGRmlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDk5YTgyOTc3LWU1MWUtNGJjYS04MjM4LTNkNzdhZTJhNjZmYZR1Lg==",
+ "result": "gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjIgY3JlYXRlZJQu",
+ "group": null,
+ "started": "2022-03-28T23:43:53.171963-07:00",
+ "stopped": "2022-03-28T23:43:56.965257-07:00",
+ "success": true,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 6,
+ "type": "file",
+ "result": "Success. New document id 264 created",
+ "status": "SUCCESS",
+ "task_id": "bbbca32d408c4619bd0b512a8327c773",
+ "name": "homebridge.log",
+ "created": "2022-05-26T14:26:07.665560-07:00",
+ "acknowledged": false,
+ "attempted_task": {
+ "id": "bbbca32d408c4619bd0b512a8327c773",
+ "name": "homebridge.log",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQteGo4aW9zYXaUhZQu",
+ "kwargs": "gAWVuwAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwOaG9tZWJyaWRnZS5sb2eUjA5vdmVycmlkZV90aXRsZZROjBlvdmVycmlkZV9jb3JyZXNwb25kZW50X2lklE6MGW92ZXJyaWRlX2RvY3VtZW50X3R5cGVfaWSUTowQb3ZlcnJpZGVfdGFnX2lkc5ROjAd0YXNrX2lklIwkNzY0NzdhNWEtNzk0Ni00NWU0LWE3MDktNzQzNDg0ZDE2YTUxlHUu",
+ "result": "gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjQgY3JlYXRlZJQu",
+ "group": null,
+ "started": "2022-03-29T22:56:16.053026-07:00",
+ "stopped": "2022-03-29T22:56:21.196179-07:00",
+ "success": true,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 5,
+ "type": "file",
+ "result": "Success. New document id 265 created",
+ "status": "SUCCESS",
+ "task_id": "00ab285ab4bf482ab30c7d580b252ecb",
+ "name": "IMG_7459.PNG",
+ "created": "2022-05-26T14:26:07.664506-07:00",
+ "acknowledged": false,
+ "attempted_task": {
+ "id": "00ab285ab4bf482ab30c7d580b252ecb",
+ "name": "IMG_7459.PNG",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtOGF5NDNfZjeUhZQu",
+ "kwargs": "gAWVuQAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwMSU1HXzc0NTkuUE5HlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJDYxMTNhNzRlLTAwOWMtNGJhYi1hMjk1LTFmNjMwMzZmMTc4ZpR1Lg==",
+ "result": "gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjUgY3JlYXRlZJQu",
+ "group": null,
+ "started": "2022-04-05T13:19:47.490282-07:00",
+ "stopped": "2022-04-05T13:21:36.782264-07:00",
+ "success": true,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 3,
+ "type": "file",
+ "result": "Success. New document id 267 created",
+ "status": "SUCCESS",
+ "task_id": "289c5163cfec410db42948a0cacbeb9c",
+ "name": "IMG_7459.PNG",
+ "created": "2022-05-26T14:26:07.659661-07:00",
+ "acknowledged": false,
+ "attempted_task": {
+ "id": "289c5163cfec410db42948a0cacbeb9c",
+ "name": "IMG_7459.PNG",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtNzRuY2p2aXGUhZQu",
+ "kwargs": "gAWVuQAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwMSU1HXzc0NTkuUE5HlIwOb3ZlcnJpZGVfdGl0bGWUTowZb3ZlcnJpZGVfY29ycmVzcG9uZGVudF9pZJROjBlvdmVycmlkZV9kb2N1bWVudF90eXBlX2lklE6MEG92ZXJyaWRlX3RhZ19pZHOUTowHdGFza19pZJSMJGZjZDljMmFlLWFhZmEtNGJmMC05M2Y5LWE3ZGQxYmEzYWM1NZR1Lg==",
+ "result": "gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjcgY3JlYXRlZJQu",
+ "group": null,
+ "started": "2022-04-05T13:29:59.264441-07:00",
+ "stopped": "2022-04-05T13:30:28.336185-07:00",
+ "success": true,
+ "attempt_count": 1
+ }
+ },
+ {
+ "id": 1,
+ "type": "file",
+ "result": "Success. New document id 268 created",
+ "status": "SUCCESS",
+ "task_id": "7a4ebdb2bde04311935284027ef8ca65",
+ "name": "2019-08-04 DSA Questionnaire - 5-8-19.pdf",
+ "created": "2022-05-26T14:26:07.655276-07:00",
+ "acknowledged": false,
+ "attempted_task": {
+ "id": "7a4ebdb2bde04311935284027ef8ca65",
+ "name": "2019-08-04 DSA Questionnaire - 5-8-19.pdf",
+ "func": "documents.tasks.consume_file",
+ "hook": null,
+ "args": "gAWVLgAAAAAAAACMKC90bXAvcGFwZXJsZXNzL3BhcGVybGVzcy11cGxvYWQtdXpscHl2NnmUhZQu",
+ "kwargs": "gAWV1gAAAAAAAAB9lCiMEW92ZXJyaWRlX2ZpbGVuYW1llIwpMjAxOS0wOC0wNCBEU0EgUXVlc3Rpb25uYWlyZSAtIDUtOC0xOS5wZGaUjA5vdmVycmlkZV90aXRsZZROjBlvdmVycmlkZV9jb3JyZXNwb25kZW50X2lklE6MGW92ZXJyaWRlX2RvY3VtZW50X3R5cGVfaWSUTowQb3ZlcnJpZGVfdGFnX2lkc5ROjAd0YXNrX2lklIwkY2Q3YzBhZjgtN2Q4Ni00OGM0LTliNjgtNDQwMmQ4ZDZlOTNmlHUu",
+ "result": "gAWVKAAAAAAAAACMJFN1Y2Nlc3MuIE5ldyBkb2N1bWVudCBpZCAyNjggY3JlYXRlZJQu",
+ "group": null,
+ "started": "2022-04-28T21:01:04.275850-07:00",
+ "stopped": "2022-04-28T21:01:10.136884-07:00",
+ "success": true,
+ "attempt_count": 1
+ }
+ }
+]
diff --git a/src-ui/src/app/components/manage/tasks/tasks.component.html b/src-ui/src/app/components/manage/tasks/tasks.component.html
index 91dad4ec8..961b8b091 100644
--- a/src-ui/src/app/components/manage/tasks/tasks.component.html
+++ b/src-ui/src/app/components/manage/tasks/tasks.component.html
@@ -54,7 +54,7 @@
{{ task.name }} |
- {{ task.created | customDate:'short' }} |
+ {{ task.date_created | customDate:'short' }} |
50" class="result" (click)="expandTask(task); $event.stopPropagation();"
[ngbPopover]="resultPopover" popoverClass="shadow small mobile" triggers="mouseenter:mouseleave" container="body">
@@ -74,11 +74,18 @@
|
-
+
+
+
+
|
diff --git a/src-ui/src/app/components/manage/tasks/tasks.component.ts b/src-ui/src/app/components/manage/tasks/tasks.component.ts
index 3779e7281..a2601dd8b 100644
--- a/src-ui/src/app/components/manage/tasks/tasks.component.ts
+++ b/src-ui/src/app/components/manage/tasks/tasks.component.ts
@@ -1,6 +1,7 @@
import { Component, OnInit, OnDestroy } from '@angular/core'
+import { Router } from '@angular/router'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { takeUntil, Subject, first } from 'rxjs'
+import { Subject, first } from 'rxjs'
import { PaperlessTask } from 'src/app/data/paperless-task'
import { TasksService } from 'src/app/services/tasks.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
@@ -24,7 +25,8 @@ export class TasksComponent implements OnInit, OnDestroy {
constructor(
public tasksService: TasksService,
- private modalService: NgbModal
+ private modalService: NgbModal,
+ private readonly router: Router
) {}
ngOnInit() {
@@ -64,6 +66,11 @@ export class TasksComponent implements OnInit, OnDestroy {
}
}
+ dismissAndGo(task: PaperlessTask) {
+ this.dismissTask(task)
+ this.router.navigate(['documents', task.related_document])
+ }
+
expandTask(task: PaperlessTask) {
this.expandedTask = this.expandedTask == task.id ? undefined : task.id
}
diff --git a/src-ui/src/app/data/paperless-task.ts b/src-ui/src/app/data/paperless-task.ts
index 5984725f9..ccf09bb6f 100644
--- a/src-ui/src/app/data/paperless-task.ts
+++ b/src-ui/src/app/data/paperless-task.ts
@@ -6,11 +6,10 @@ export enum PaperlessTaskType {
}
export enum PaperlessTaskStatus {
- Queued = 'queued',
- Started = 'started',
- Complete = 'complete',
- Failed = 'failed',
- Unknown = 'unknown',
+ Pending = 'PENDING',
+ Started = 'STARTED',
+ Complete = 'SUCCESS',
+ Failed = 'FAILURE',
}
export interface PaperlessTask extends ObjectWithId {
@@ -24,9 +23,11 @@ export interface PaperlessTask extends ObjectWithId {
name: string
- created: Date
+ date_created: Date
- started?: Date
+ done?: Date
result: string
+
+ related_document?: number
}
diff --git a/src-ui/src/app/services/tasks.service.ts b/src-ui/src/app/services/tasks.service.ts
index 8518d6f0e..4607128a1 100644
--- a/src-ui/src/app/services/tasks.service.ts
+++ b/src-ui/src/app/services/tasks.service.ts
@@ -1,6 +1,6 @@
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
-import { first, map } from 'rxjs/operators'
+import { first } from 'rxjs/operators'
import {
PaperlessTask,
PaperlessTaskStatus,
@@ -27,7 +27,7 @@ export class TasksService {
}
public get queuedFileTasks(): PaperlessTask[] {
- return this.fileTasks.filter((t) => t.status == PaperlessTaskStatus.Queued)
+ return this.fileTasks.filter((t) => t.status == PaperlessTaskStatus.Pending)
}
public get startedFileTasks(): PaperlessTask[] {
diff --git a/src/documents/bulk_edit.py b/src/documents/bulk_edit.py
index 0cf0daf3e..663e96809 100644
--- a/src/documents/bulk_edit.py
+++ b/src/documents/bulk_edit.py
@@ -1,11 +1,12 @@
import itertools
from django.db.models import Q
-from django_q.tasks import async_task
from documents.models import Correspondent
from documents.models import Document
from documents.models import DocumentType
from documents.models import StoragePath
+from documents.tasks import bulk_update_documents
+from documents.tasks import update_document_archive_file
def set_correspondent(doc_ids, correspondent):
@@ -16,7 +17,7 @@ def set_correspondent(doc_ids, correspondent):
affected_docs = [doc.id for doc in qs]
qs.update(correspondent=correspondent)
- async_task("documents.tasks.bulk_update_documents", document_ids=affected_docs)
+ bulk_update_documents.delay(document_ids=affected_docs)
return "OK"
@@ -31,8 +32,7 @@ def set_storage_path(doc_ids, storage_path):
affected_docs = [doc.id for doc in qs]
qs.update(storage_path=storage_path)
- async_task(
- "documents.tasks.bulk_update_documents",
+ bulk_update_documents.delay(
document_ids=affected_docs,
)
@@ -47,7 +47,7 @@ def set_document_type(doc_ids, document_type):
affected_docs = [doc.id for doc in qs]
qs.update(document_type=document_type)
- async_task("documents.tasks.bulk_update_documents", document_ids=affected_docs)
+ bulk_update_documents.delay(document_ids=affected_docs)
return "OK"
@@ -63,7 +63,7 @@ def add_tag(doc_ids, tag):
[DocumentTagRelationship(document_id=doc, tag_id=tag) for doc in affected_docs],
)
- async_task("documents.tasks.bulk_update_documents", document_ids=affected_docs)
+ bulk_update_documents.delay(document_ids=affected_docs)
return "OK"
@@ -79,7 +79,7 @@ def remove_tag(doc_ids, tag):
Q(document_id__in=affected_docs) & Q(tag_id=tag),
).delete()
- async_task("documents.tasks.bulk_update_documents", document_ids=affected_docs)
+ bulk_update_documents.delay(document_ids=affected_docs)
return "OK"
@@ -103,7 +103,7 @@ def modify_tags(doc_ids, add_tags, remove_tags):
ignore_conflicts=True,
)
- async_task("documents.tasks.bulk_update_documents", document_ids=affected_docs)
+ bulk_update_documents.delay(document_ids=affected_docs)
return "OK"
@@ -123,8 +123,7 @@ def delete(doc_ids):
def redo_ocr(doc_ids):
for document_id in doc_ids:
- async_task(
- "documents.tasks.update_document_archive_file",
+ update_document_archive_file.delay(
document_id=document_id,
)
diff --git a/src/documents/management/commands/document_consumer.py b/src/documents/management/commands/document_consumer.py
index 3a3b8a163..3dce17263 100644
--- a/src/documents/management/commands/document_consumer.py
+++ b/src/documents/management/commands/document_consumer.py
@@ -11,9 +11,9 @@ from typing import Final
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
-from django_q.tasks import async_task
from documents.models import Tag
from documents.parsers import is_file_ext_supported
+from documents.tasks import consume_file
from watchdog.events import FileSystemEventHandler
from watchdog.observers.polling import PollingObserver
@@ -92,11 +92,9 @@ def _consume(filepath):
try:
logger.info(f"Adding {filepath} to the task queue.")
- async_task(
- "documents.tasks.consume_file",
+ consume_file.delay(
filepath,
override_tag_ids=tag_ids if tag_ids else None,
- task_name=os.path.basename(filepath)[:100],
)
except Exception:
# Catch all so that the consumer won't crash.
diff --git a/src/documents/migrations/1001_auto_20201109_1636.py b/src/documents/migrations/1001_auto_20201109_1636.py
index 0558ee640..2558180bb 100644
--- a/src/documents/migrations/1001_auto_20201109_1636.py
+++ b/src/documents/migrations/1001_auto_20201109_1636.py
@@ -1,34 +1,14 @@
# Generated by Django 3.1.3 on 2020-11-09 16:36
from django.db import migrations
-from django.db.migrations import RunPython
-from django_q.models import Schedule
-from django_q.tasks import schedule
-
-
-def add_schedules(apps, schema_editor):
- schedule(
- "documents.tasks.train_classifier",
- name="Train the classifier",
- schedule_type=Schedule.HOURLY,
- )
- schedule(
- "documents.tasks.index_optimize",
- name="Optimize the index",
- schedule_type=Schedule.DAILY,
- )
-
-
-def remove_schedules(apps, schema_editor):
- Schedule.objects.filter(func="documents.tasks.train_classifier").delete()
- Schedule.objects.filter(func="documents.tasks.index_optimize").delete()
class Migration(migrations.Migration):
dependencies = [
("documents", "1000_update_paperless_all"),
- ("django_q", "0013_task_attempt_count"),
]
- operations = [RunPython(add_schedules, remove_schedules)]
+ operations = [
+ migrations.RunPython(migrations.RunPython.noop, migrations.RunPython.noop)
+ ]
diff --git a/src/documents/migrations/1004_sanity_check_schedule.py b/src/documents/migrations/1004_sanity_check_schedule.py
index 61d617dde..0437fbd57 100644
--- a/src/documents/migrations/1004_sanity_check_schedule.py
+++ b/src/documents/migrations/1004_sanity_check_schedule.py
@@ -2,27 +2,12 @@
from django.db import migrations
from django.db.migrations import RunPython
-from django_q.models import Schedule
-from django_q.tasks import schedule
-
-
-def add_schedules(apps, schema_editor):
- schedule(
- "documents.tasks.sanity_check",
- name="Perform sanity check",
- schedule_type=Schedule.WEEKLY,
- )
-
-
-def remove_schedules(apps, schema_editor):
- Schedule.objects.filter(func="documents.tasks.sanity_check").delete()
class Migration(migrations.Migration):
dependencies = [
("documents", "1003_mime_types"),
- ("django_q", "0013_task_attempt_count"),
]
- operations = [RunPython(add_schedules, remove_schedules)]
+ operations = [RunPython(migrations.RunPython.noop, migrations.RunPython.noop)]
diff --git a/src/documents/migrations/1022_paperlesstask.py b/src/documents/migrations/1022_paperlesstask.py
index f1ecb244f..b89e9bbac 100644
--- a/src/documents/migrations/1022_paperlesstask.py
+++ b/src/documents/migrations/1022_paperlesstask.py
@@ -4,28 +4,9 @@ from django.db import migrations, models
import django.db.models.deletion
-def init_paperless_tasks(apps, schema_editor):
- PaperlessTask = apps.get_model("documents", "PaperlessTask")
- Task = apps.get_model("django_q", "Task")
-
- for task in Task.objects.filter(func="documents.tasks.consume_file"):
- if not hasattr(task, "paperlesstask"):
- paperlesstask = PaperlessTask.objects.create(
- attempted_task=task,
- task_id=task.id,
- name=task.name,
- created=task.started,
- started=task.started,
- acknowledged=True,
- )
- task.paperlesstask = paperlesstask
- task.save()
-
-
class Migration(migrations.Migration):
dependencies = [
- ("django_q", "0014_schedule_cluster"),
("documents", "1021_webp_thumbnail_conversion"),
]
@@ -60,10 +41,12 @@ class Migration(migrations.Migration):
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="attempted_task",
- to="django_q.task",
+ # This is a dummy field, 1026 will fix up the column
+ # This manual change is required, as django doesn't django doesn't really support
+ # removing an app which has migration deps like this
+ to="documents.document",
),
),
],
- ),
- migrations.RunPython(init_paperless_tasks, migrations.RunPython.noop),
+ )
]
diff --git a/src/documents/migrations/1026_transition_to_celery.py b/src/documents/migrations/1026_transition_to_celery.py
new file mode 100644
index 000000000..76c6edf11
--- /dev/null
+++ b/src/documents/migrations/1026_transition_to_celery.py
@@ -0,0 +1,57 @@
+# Generated by Django 4.1.1 on 2022-09-27 19:31
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("django_celery_results", "0011_taskresult_periodic_task_name"),
+ ("documents", "1025_alter_savedviewfilterrule_rule_type"),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name="paperlesstask",
+ name="created",
+ ),
+ migrations.RemoveField(
+ model_name="paperlesstask",
+ name="name",
+ ),
+ migrations.RemoveField(
+ model_name="paperlesstask",
+ name="started",
+ ),
+ # Remove the field from the model
+ migrations.RemoveField(
+ model_name="paperlesstask",
+ name="attempted_task",
+ ),
+ # Add the field back, pointing to the correct model
+ # This resolves a problem where the temporary change in 1022
+ # results in a type mismatch
+ migrations.AddField(
+ model_name="paperlesstask",
+ name="attempted_task",
+ field=models.OneToOneField(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="attempted_task",
+ to="django_celery_results.taskresult",
+ ),
+ ),
+ # Drop the django-q tables entirely
+ # Must be done last or there could be references here
+ migrations.RunSQL(
+ "DROP TABLE IF EXISTS django_q_ormq", reverse_sql=migrations.RunSQL.noop
+ ),
+ migrations.RunSQL(
+ "DROP TABLE IF EXISTS django_q_schedule", reverse_sql=migrations.RunSQL.noop
+ ),
+ migrations.RunSQL(
+ "DROP TABLE IF EXISTS django_q_task", reverse_sql=migrations.RunSQL.noop
+ ),
+ ]
diff --git a/src/documents/models.py b/src/documents/models.py
index fc1d0cb7d..5a84c467b 100644
--- a/src/documents/models.py
+++ b/src/documents/models.py
@@ -12,7 +12,7 @@ from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
-from django_q.tasks import Task
+from django_celery_results.models import TaskResult
from documents.parsers import get_default_file_extension
@@ -527,19 +527,16 @@ class UiSettings(models.Model):
class PaperlessTask(models.Model):
-
task_id = models.CharField(max_length=128)
- name = models.CharField(max_length=256)
- created = models.DateTimeField(_("created"), auto_now=True)
- started = models.DateTimeField(_("started"), null=True)
+ acknowledged = models.BooleanField(default=False)
+
attempted_task = models.OneToOneField(
- Task,
+ TaskResult,
on_delete=models.CASCADE,
related_name="attempted_task",
null=True,
blank=True,
)
- acknowledged = models.BooleanField(default=False)
class Comment(models.Model):
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index ca28240b0..63da721d3 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -1,6 +1,14 @@
import datetime
import math
import re
+from ast import literal_eval
+from asyncio.log import logger
+from pathlib import Path
+from typing import Dict
+from typing import Optional
+from typing import Tuple
+
+from celery import states
try:
import zoneinfo
@@ -18,12 +26,12 @@ from .models import Correspondent
from .models import Document
from .models import DocumentType
from .models import MatchingModel
-from .models import PaperlessTask
from .models import SavedView
from .models import SavedViewFilterRule
from .models import StoragePath
from .models import Tag
from .models import UiSettings
+from .models import PaperlessTask
from .parsers import is_mime_type_supported
@@ -629,7 +637,19 @@ class TasksViewSerializer(serializers.ModelSerializer):
class Meta:
model = PaperlessTask
depth = 1
- fields = "__all__"
+ fields = (
+ "id",
+ "task_id",
+ "date_created",
+ "date_done",
+ "type",
+ "status",
+ "result",
+ "acknowledged",
+ "task_name",
+ "name",
+ "related_document",
+ )
type = serializers.SerializerMethodField()
@@ -641,24 +661,108 @@ class TasksViewSerializer(serializers.ModelSerializer):
def get_result(self, obj):
result = ""
- if hasattr(obj, "attempted_task") and obj.attempted_task:
- result = obj.attempted_task.result
+ if (
+ hasattr(obj, "attempted_task")
+ and obj.attempted_task
+ and obj.attempted_task.result
+ ):
+ try:
+ result: str = obj.attempted_task.result
+ if "exc_message" in result:
+ # This is a dict in this case
+ result: Dict = literal_eval(result)
+ # This is a list, grab the first item (most recent)
+ result = result["exc_message"][0]
+ except Exception as e: # pragma: no cover
+ # Extra security if something is malformed
+ logger.warn(f"Error getting task result: {e}", exc_info=True)
return result
status = serializers.SerializerMethodField()
def get_status(self, obj):
- if obj.attempted_task is None:
- if obj.started:
- return "started"
- else:
- return "queued"
- elif obj.attempted_task.success:
- return "complete"
- elif not obj.attempted_task.success:
- return "failed"
- else:
- return "unknown"
+ result = "unknown"
+ if hasattr(obj, "attempted_task") and obj.attempted_task:
+ result = obj.attempted_task.status
+ return result
+
+ date_created = serializers.SerializerMethodField()
+
+ def get_date_created(self, obj):
+ result = ""
+ if hasattr(obj, "attempted_task") and obj.attempted_task:
+ result = obj.attempted_task.date_created
+ return result
+
+ date_done = serializers.SerializerMethodField()
+
+ def get_date_done(self, obj):
+ result = ""
+ if hasattr(obj, "attempted_task") and obj.attempted_task:
+ result = obj.attempted_task.date_done
+ return result
+
+ task_id = serializers.SerializerMethodField()
+
+ def get_task_id(self, obj):
+ result = ""
+ if hasattr(obj, "attempted_task") and obj.attempted_task:
+ result = obj.attempted_task.task_id
+ return result
+
+ task_name = serializers.SerializerMethodField()
+
+ def get_task_name(self, obj):
+ result = ""
+ if hasattr(obj, "attempted_task") and obj.attempted_task:
+ result = obj.attempted_task.task_name
+ return result
+
+ name = serializers.SerializerMethodField()
+
+ def get_name(self, obj):
+ result = ""
+ if hasattr(obj, "attempted_task") and obj.attempted_task:
+ try:
+ task_kwargs: Optional[str] = obj.attempted_task.task_kwargs
+ # Try the override filename first (this is a webui created task?)
+ if task_kwargs is not None:
+ # It's a string, string of a dict. Who knows why...
+ kwargs = literal_eval(literal_eval(task_kwargs))
+ if "override_filename" in kwargs:
+ result = kwargs["override_filename"]
+
+ # Nothing was found, report the task first argument
+ if not len(result):
+ # There are always some arguments to the consume
+ task_args: Tuple = literal_eval(
+ literal_eval(obj.attempted_task.task_args),
+ )
+ filepath = Path(task_args[0])
+ result = filepath.name
+ except Exception as e: # pragma: no cover
+ # Extra security if something is malformed
+ logger.warn(f"Error getting file name from task: {e}", exc_info=True)
+
+ return result
+
+ related_document = serializers.SerializerMethodField()
+
+ def get_related_document(self, obj):
+ result = ""
+ regexp = r"New document id (\d+) created"
+ if (
+ hasattr(obj, "attempted_task")
+ and obj.attempted_task
+ and obj.attempted_task.result
+ and obj.attempted_task.status == states.SUCCESS
+ ):
+ try:
+ result = re.search(regexp, obj.attempted_task.result).group(1)
+ except Exception:
+ pass
+
+ return result
class AcknowledgeTasksViewSerializer(serializers.Serializer):
diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py
index 2e7c2369c..76ae974c6 100644
--- a/src/documents/signals/handlers.py
+++ b/src/documents/signals/handlers.py
@@ -2,7 +2,6 @@ import logging
import os
import shutil
-import django_q
from django.conf import settings
from django.contrib.admin.models import ADDITION
from django.contrib.admin.models import LogEntry
@@ -14,6 +13,7 @@ from django.db.models import Q
from django.dispatch import receiver
from django.utils import termcolors
from django.utils import timezone
+from django_celery_results.models import TaskResult
from filelock import FileLock
from .. import matching
@@ -25,7 +25,6 @@ from ..models import MatchingModel
from ..models import PaperlessTask
from ..models import Tag
-
logger = logging.getLogger("paperless.handlers")
@@ -503,47 +502,19 @@ def add_to_index(sender, document, **kwargs):
index.add_or_update_document(document)
-@receiver(django_q.signals.pre_enqueue)
-def init_paperless_task(sender, task, **kwargs):
- if task["func"] == "documents.tasks.consume_file":
- try:
- paperless_task, created = PaperlessTask.objects.get_or_create(
- task_id=task["id"],
- )
- paperless_task.name = task["name"]
- paperless_task.created = task["started"]
- paperless_task.save()
- except Exception as e:
- # Don't let an exception in the signal handlers prevent
- # a document from being consumed.
- logger.error(f"Creating PaperlessTask failed: {e}")
-
-
-@receiver(django_q.signals.pre_execute)
-def paperless_task_started(sender, task, **kwargs):
+@receiver(models.signals.post_save, sender=TaskResult)
+def update_paperless_task(sender, instance: TaskResult, **kwargs):
try:
- if task["func"] == "documents.tasks.consume_file":
- paperless_task, created = PaperlessTask.objects.get_or_create(
- task_id=task["id"],
- )
- paperless_task.started = timezone.now()
- paperless_task.save()
- except PaperlessTask.DoesNotExist:
- pass
- except Exception as e:
- logger.error(f"Creating PaperlessTask failed: {e}")
-
-
-@receiver(models.signals.post_save, sender=django_q.models.Task)
-def update_paperless_task(sender, instance, **kwargs):
- try:
- if instance.func == "documents.tasks.consume_file":
- paperless_task, created = PaperlessTask.objects.get_or_create(
- task_id=instance.id,
+ if instance.task_name == "documents.tasks.consume_file":
+ paperless_task, _ = PaperlessTask.objects.get_or_create(
+ task_id=instance.task_id,
)
+ paperless_task.name = instance.task_name
+ paperless_task.created = instance.date_created
+ paperless_task.completed = instance.date_done
paperless_task.attempted_task = instance
paperless_task.save()
- except PaperlessTask.DoesNotExist:
- pass
except Exception as e:
+ # Don't let an exception in the signal handlers prevent
+ # a document from being consumed.
logger.error(f"Creating PaperlessTask failed: {e}")
diff --git a/src/documents/tasks.py b/src/documents/tasks.py
index 72b8b5244..d41b19b51 100644
--- a/src/documents/tasks.py
+++ b/src/documents/tasks.py
@@ -8,6 +8,7 @@ from typing import Type
import tqdm
from asgiref.sync import async_to_sync
+from celery import shared_task
from channels.layers import get_channel_layer
from django.conf import settings
from django.db import transaction
@@ -36,6 +37,7 @@ from whoosh.writing import AsyncWriter
logger = logging.getLogger("paperless.tasks")
+@shared_task
def index_optimize():
ix = index.open_index()
writer = AsyncWriter(ix)
@@ -52,6 +54,7 @@ def index_reindex(progress_bar_disable=False):
index.update_document(writer, document)
+@shared_task
def train_classifier():
if (
not Tag.objects.filter(matching_algorithm=Tag.MATCH_AUTO).exists()
@@ -80,6 +83,7 @@ def train_classifier():
logger.warning("Classifier error: " + str(e))
+@shared_task
def consume_file(
path,
override_filename=None,
@@ -183,6 +187,7 @@ def consume_file(
)
+@shared_task
def sanity_check():
messages = sanity_checker.check_sanity()
@@ -198,6 +203,7 @@ def sanity_check():
return "No issues detected."
+@shared_task
def bulk_update_documents(document_ids):
documents = Document.objects.filter(id__in=document_ids)
@@ -211,6 +217,7 @@ def bulk_update_documents(document_ids):
index.update_document(writer, doc)
+@shared_task
def update_document_archive_file(document_id):
"""
Re-creates the archive file of a document, including new OCR content and thumbnail
diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py
index 9ad8dd118..0a8d72155 100644
--- a/src/documents/tests/test_api.py
+++ b/src/documents/tests/test_api.py
@@ -10,6 +10,8 @@ import zipfile
from unittest import mock
from unittest.mock import MagicMock
+import celery
+
try:
import zoneinfo
except ImportError:
@@ -20,7 +22,6 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.test import override_settings
from django.utils import timezone
-from django_q.models import Task
from documents import bulk_edit
from documents import index
from documents.models import Correspondent
@@ -31,7 +32,7 @@ from documents.models import PaperlessTask
from documents.models import SavedView
from documents.models import StoragePath
from documents.models import Tag
-from documents.models import UiSettings
+from django_celery_results.models import TaskResult
from documents.models import Comment
from documents.models import StoragePath
from documents.tests.utils import DirectoriesMixin
@@ -790,7 +791,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["documents_inbox"], None)
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload(self, m):
with open(
@@ -813,7 +814,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertIsNone(kwargs["override_document_type_id"])
self.assertIsNone(kwargs["override_tag_ids"])
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_empty_metadata(self, m):
with open(
@@ -836,7 +837,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertIsNone(kwargs["override_document_type_id"])
self.assertIsNone(kwargs["override_tag_ids"])
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_invalid_form(self, m):
with open(
@@ -850,7 +851,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, 400)
m.assert_not_called()
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_invalid_file(self, m):
with open(
@@ -864,7 +865,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, 400)
m.assert_not_called()
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_with_title(self, async_task):
with open(
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
@@ -882,7 +883,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(kwargs["override_title"], "my custom title")
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_with_correspondent(self, async_task):
c = Correspondent.objects.create(name="test-corres")
with open(
@@ -901,7 +902,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(kwargs["override_correspondent_id"], c.id)
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_with_invalid_correspondent(self, async_task):
with open(
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
@@ -915,7 +916,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
async_task.assert_not_called()
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_with_document_type(self, async_task):
dt = DocumentType.objects.create(name="invoice")
with open(
@@ -934,7 +935,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(kwargs["override_document_type_id"], dt.id)
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_with_invalid_document_type(self, async_task):
with open(
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
@@ -948,7 +949,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
async_task.assert_not_called()
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_with_tags(self, async_task):
t1 = Tag.objects.create(name="tag1")
t2 = Tag.objects.create(name="tag2")
@@ -968,7 +969,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertCountEqual(kwargs["override_tag_ids"], [t1.id, t2.id])
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_with_invalid_tags(self, async_task):
t1 = Tag.objects.create(name="tag1")
t2 = Tag.objects.create(name="tag2")
@@ -984,7 +985,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
async_task.assert_not_called()
- @mock.patch("documents.views.async_task")
+ @mock.patch("documents.views.consume_file.delay")
def test_upload_with_created(self, async_task):
created = datetime.datetime(
2022,
@@ -1619,7 +1620,7 @@ class TestBulkEdit(DirectoriesMixin, APITestCase):
user = User.objects.create_superuser(username="temp_admin")
self.client.force_authenticate(user=user)
- patcher = mock.patch("documents.bulk_edit.async_task")
+ patcher = mock.patch("documents.bulk_edit.bulk_update_documents.delay")
self.async_task = patcher.start()
self.addCleanup(patcher.stop)
self.c1 = Correspondent.objects.create(name="c1")
@@ -2738,7 +2739,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
class TestTasks(APITestCase):
ENDPOINT = "/api/tasks/"
- ENDPOINT_ACKOWLEDGE = "/api/acknowledge_tasks/"
+ ENDPOINT_ACKNOWLEDGE = "/api/acknowledge_tasks/"
def setUp(self):
super().setUp()
@@ -2747,16 +2748,27 @@ class TestTasks(APITestCase):
self.client.force_authenticate(user=self.user)
def test_get_tasks(self):
- task_id1 = str(uuid.uuid4())
- PaperlessTask.objects.create(task_id=task_id1)
- Task.objects.create(
- id=task_id1,
- started=timezone.now() - datetime.timedelta(seconds=30),
- stopped=timezone.now(),
- func="documents.tasks.consume_file",
+ """
+ GIVEN:
+ - Attempted celery tasks
+ WHEN:
+ - API call is made to get tasks
+ THEN:
+ - Attempting and pending tasks are serialized and provided
+ """
+ result1 = TaskResult.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_name="documents.tasks.some_great_task",
+ status=celery.states.PENDING,
)
- task_id2 = str(uuid.uuid4())
- PaperlessTask.objects.create(task_id=task_id2)
+ PaperlessTask.objects.create(attempted_task=result1)
+
+ result2 = TaskResult.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_name="documents.tasks.some_awesome_task",
+ status=celery.states.STARTED,
+ )
+ PaperlessTask.objects.create(attempted_task=result2)
response = self.client.get(self.ENDPOINT)
@@ -2764,25 +2776,155 @@ class TestTasks(APITestCase):
self.assertEqual(len(response.data), 2)
returned_task1 = response.data[1]
returned_task2 = response.data[0]
- self.assertEqual(returned_task1["task_id"], task_id1)
- self.assertEqual(returned_task1["status"], "complete")
- self.assertIsNotNone(returned_task1["attempted_task"])
- self.assertEqual(returned_task2["task_id"], task_id2)
- self.assertEqual(returned_task2["status"], "queued")
- self.assertIsNone(returned_task2["attempted_task"])
+
+ self.assertEqual(returned_task1["task_id"], result1.task_id)
+ self.assertEqual(returned_task1["status"], celery.states.PENDING)
+ self.assertEqual(returned_task1["task_name"], result1.task_name)
+
+ self.assertEqual(returned_task2["task_id"], result2.task_id)
+ self.assertEqual(returned_task2["status"], celery.states.STARTED)
+ self.assertEqual(returned_task2["task_name"], result2.task_name)
def test_acknowledge_tasks(self):
- task_id = str(uuid.uuid4())
- task = PaperlessTask.objects.create(task_id=task_id)
+ """
+ GIVEN:
+ - Attempted celery tasks
+ WHEN:
+ - API call is made to get mark task as acknowledged
+ THEN:
+ - Task is marked as acknowledged
+ """
+ result1 = TaskResult.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_name="documents.tasks.some_task",
+ status=celery.states.PENDING,
+ )
+ task = PaperlessTask.objects.create(attempted_task=result1)
response = self.client.get(self.ENDPOINT)
self.assertEqual(len(response.data), 1)
response = self.client.post(
- self.ENDPOINT_ACKOWLEDGE,
+ self.ENDPOINT_ACKNOWLEDGE,
{"tasks": [task.id]},
)
self.assertEqual(response.status_code, 200)
response = self.client.get(self.ENDPOINT)
self.assertEqual(len(response.data), 0)
+
+ def test_task_result_no_error(self):
+ """
+ GIVEN:
+ - A celery task completed without error
+ WHEN:
+ - API call is made to get tasks
+ THEN:
+ - The returned data includes the task result
+ """
+ result1 = TaskResult.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_name="documents.tasks.some_task",
+ status=celery.states.SUCCESS,
+ result="Success. New document id 1 created",
+ )
+ _ = PaperlessTask.objects.create(attempted_task=result1)
+
+ response = self.client.get(self.ENDPOINT)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.data), 1)
+
+ returned_data = response.data[0]
+
+ self.assertEqual(returned_data["result"], "Success. New document id 1 created")
+ self.assertEqual(returned_data["related_document"], "1")
+
+ def test_task_result_with_error(self):
+ """
+ GIVEN:
+ - A celery task completed with an exception
+ WHEN:
+ - API call is made to get tasks
+ THEN:
+ - The returned result is the exception info
+ """
+ result1 = TaskResult.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_name="documents.tasks.some_task",
+ status=celery.states.SUCCESS,
+ result={
+ "exc_type": "ConsumerError",
+ "exc_message": ["test.pdf: Not consuming test.pdf: It is a duplicate."],
+ "exc_module": "documents.consumer",
+ },
+ )
+ _ = PaperlessTask.objects.create(attempted_task=result1)
+
+ response = self.client.get(self.ENDPOINT)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.data), 1)
+
+ returned_data = response.data[0]
+
+ self.assertEqual(
+ returned_data["result"],
+ "test.pdf: Not consuming test.pdf: It is a duplicate.",
+ )
+
+ def test_task_name_webui(self):
+ """
+ GIVEN:
+ - Attempted celery task
+ - Task was created through the webui
+ WHEN:
+ - API call is made to get tasks
+ THEN:
+ - Returned data include the filename
+ """
+ result1 = TaskResult.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_name="documents.tasks.some_task",
+ status=celery.states.SUCCESS,
+ task_args="\"('/tmp/paperless/paperless-upload-5iq7skzc',)\"",
+ task_kwargs="\"{'override_filename': 'test.pdf', 'override_title': None, 'override_correspondent_id': None, 'override_document_type_id': None, 'override_tag_ids': None, 'task_id': '466e8fe7-7193-4698-9fff-72f0340e2082', 'override_created': None}\"",
+ )
+ _ = PaperlessTask.objects.create(attempted_task=result1)
+
+ response = self.client.get(self.ENDPOINT)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.data), 1)
+
+ returned_data = response.data[0]
+
+ self.assertEqual(returned_data["name"], "test.pdf")
+
+ def test_task_name_consume_folder(self):
+ """
+ GIVEN:
+ - Attempted celery task
+ - Task was created through the consume folder
+ WHEN:
+ - API call is made to get tasks
+ THEN:
+ - Returned data include the filename
+ """
+ result1 = TaskResult.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_name="documents.tasks.some_task",
+ status=celery.states.SUCCESS,
+ task_args="\"('/consume/anothertest.pdf',)\"",
+ task_kwargs="\"{'override_tag_ids': None}\"",
+ )
+ _ = PaperlessTask.objects.create(attempted_task=result1)
+
+ response = self.client.get(self.ENDPOINT)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.data), 1)
+
+ returned_data = response.data[0]
+
+ self.assertEqual(returned_data["name"], "anothertest.pdf")
diff --git a/src/documents/tests/test_management_consumer.py b/src/documents/tests/test_management_consumer.py
index e8f6f55f6..822a7ed07 100644
--- a/src/documents/tests/test_management_consumer.py
+++ b/src/documents/tests/test_management_consumer.py
@@ -43,7 +43,7 @@ class ConsumerMixin:
super().setUp()
self.t = None
patcher = mock.patch(
- "documents.management.commands.document_consumer.async_task",
+ "documents.tasks.consume_file.delay",
)
self.task_mock = patcher.start()
self.addCleanup(patcher.stop)
@@ -76,7 +76,7 @@ class ConsumerMixin:
# A bogus async_task that will simply check the file for
# completeness and raise an exception otherwise.
- def bogus_task(self, func, filename, **kwargs):
+ def bogus_task(self, filename, **kwargs):
eq = filecmp.cmp(filename, self.sample_file, shallow=False)
if not eq:
print("Consumed an INVALID file.")
@@ -115,7 +115,7 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
self.task_mock.assert_called_once()
args, kwargs = self.task_mock.call_args
- self.assertEqual(args[1], f)
+ self.assertEqual(args[0], f)
def test_consume_file_invalid_ext(self):
self.t_start()
@@ -135,7 +135,7 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
self.task_mock.assert_called_once()
args, kwargs = self.task_mock.call_args
- self.assertEqual(args[1], f)
+ self.assertEqual(args[0], f)
@mock.patch("documents.management.commands.document_consumer.logger.error")
def test_slow_write_pdf(self, error_logger):
@@ -155,7 +155,7 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
self.task_mock.assert_called_once()
args, kwargs = self.task_mock.call_args
- self.assertEqual(args[1], fname)
+ self.assertEqual(args[0], fname)
@mock.patch("documents.management.commands.document_consumer.logger.error")
def test_slow_write_and_move(self, error_logger):
@@ -175,7 +175,7 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
self.task_mock.assert_called_once()
args, kwargs = self.task_mock.call_args
- self.assertEqual(args[1], fname2)
+ self.assertEqual(args[0], fname2)
error_logger.assert_not_called()
@@ -193,7 +193,7 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
self.task_mock.assert_called_once()
args, kwargs = self.task_mock.call_args
- self.assertEqual(args[1], fname)
+ self.assertEqual(args[0], fname)
# assert that we have an error logged with this invalid file.
error_logger.assert_called_once()
@@ -241,7 +241,7 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
self.assertEqual(2, self.task_mock.call_count)
fnames = [
- os.path.basename(args[1]) for args, _ in self.task_mock.call_args_list
+ os.path.basename(args[0]) for args, _ in self.task_mock.call_args_list
]
self.assertCountEqual(fnames, ["my_file.pdf", "my_second_file.pdf"])
@@ -338,7 +338,7 @@ class TestConsumerTags(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
tag_ids.append(Tag.objects.get(name=tag_names[1]).pk)
args, kwargs = self.task_mock.call_args
- self.assertEqual(args[1], f)
+ self.assertEqual(args[0], f)
# assertCountEqual has a bad name, but test that the first
# sequence contains the same elements as second, regardless of
diff --git a/src/documents/views.py b/src/documents/views.py
index e32a93725..025ff2f67 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -28,7 +28,7 @@ from django.utils.translation import get_language
from django.views.decorators.cache import cache_control
from django.views.generic import TemplateView
from django_filters.rest_framework import DjangoFilterBackend
-from django_q.tasks import async_task
+from documents.tasks import consume_file
from packaging import version as packaging_version
from paperless import version
from paperless.db import GnuPG
@@ -615,8 +615,7 @@ class PostDocumentView(GenericAPIView):
task_id = str(uuid.uuid4())
- async_task(
- "documents.tasks.consume_file",
+ consume_file.delay(
temp_filename,
override_filename=doc_name,
override_title=title,
@@ -624,7 +623,6 @@ class PostDocumentView(GenericAPIView):
override_document_type_id=document_type_id,
override_tag_ids=tag_ids,
task_id=task_id,
- task_name=os.path.basename(doc_name)[:100],
override_created=created,
)
@@ -888,8 +886,9 @@ class TasksViewSet(ReadOnlyModelViewSet):
queryset = (
PaperlessTask.objects.filter(
acknowledged=False,
+ attempted_task__isnull=False,
)
- .order_by("created")
+ .order_by("attempted_task__date_created")
.reverse()
)
diff --git a/src/paperless/__init__.py b/src/paperless/__init__.py
index 1c7f09cbe..3635cbe9d 100644
--- a/src/paperless/__init__.py
+++ b/src/paperless/__init__.py
@@ -1,5 +1,11 @@
+from .celery import app as celery_app
from .checks import binaries_check
from .checks import paths_check
from .checks import settings_values_check
-__all__ = ["binaries_check", "paths_check", "settings_values_check"]
+__all__ = [
+ "celery_app",
+ "binaries_check",
+ "paths_check",
+ "settings_values_check",
+]
diff --git a/src/paperless/celery.py b/src/paperless/celery.py
new file mode 100644
index 000000000..a9a853521
--- /dev/null
+++ b/src/paperless/celery.py
@@ -0,0 +1,17 @@
+import os
+
+from celery import Celery
+
+# Set the default Django settings module for the 'celery' program.
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "paperless.settings")
+
+app = Celery("paperless")
+
+# Using a string here means the worker doesn't have to serialize
+# the configuration object to child processes.
+# - namespace='CELERY' means all celery-related configuration keys
+# should have a `CELERY_` prefix.
+app.config_from_object("django.conf:settings", namespace="CELERY")
+
+# Load task modules from all registered Django apps.
+app.autodiscover_tasks()
diff --git a/src/paperless/settings.py b/src/paperless/settings.py
index e092b3f3e..6836f4ea0 100644
--- a/src/paperless/settings.py
+++ b/src/paperless/settings.py
@@ -10,6 +10,7 @@ from typing import Optional
from typing import Set
from urllib.parse import urlparse
+from celery.schedules import crontab
from concurrent_log_handler.queue import setup_logging_queues
from django.utils.translation import gettext_lazy as _
from dotenv import load_dotenv
@@ -128,7 +129,7 @@ INSTALLED_APPS = [
"rest_framework",
"rest_framework.authtoken",
"django_filters",
- "django_q",
+ "django_celery_results",
] + env_apps
if DEBUG:
@@ -179,6 +180,8 @@ ASGI_APPLICATION = "paperless.asgi.application"
STATIC_URL = os.getenv("PAPERLESS_STATIC_URL", BASE_URL + "static/")
WHITENOISE_STATIC_PREFIX = "/static/"
+_REDIS_URL = os.getenv("PAPERLESS_REDIS", "redis://localhost:6379")
+
# TODO: what is this used for?
TEMPLATES = [
{
@@ -200,7 +203,7 @@ CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
- "hosts": [os.getenv("PAPERLESS_REDIS", "redis://localhost:6379")],
+ "hosts": [_REDIS_URL],
"capacity": 2000, # default 100
"expiry": 15, # default 60
},
@@ -456,24 +459,53 @@ TASK_WORKERS = __get_int("PAPERLESS_TASK_WORKERS", 1)
WORKER_TIMEOUT: Final[int] = __get_int("PAPERLESS_WORKER_TIMEOUT", 1800)
-# Per django-q docs, timeout must be smaller than retry
-# We default retry to 10s more than the timeout to silence the
-# warning, as retry functionality isn't used.
-WORKER_RETRY: Final[int] = __get_int(
- "PAPERLESS_WORKER_RETRY",
- WORKER_TIMEOUT + 10,
-)
+CELERY_BROKER_URL = _REDIS_URL
+CELERY_TIMEZONE = TIME_ZONE
-Q_CLUSTER = {
- "name": "paperless",
- "guard_cycle": 5,
- "catch_up": False,
- "recycle": 1,
- "retry": WORKER_RETRY,
- "timeout": WORKER_TIMEOUT,
- "workers": TASK_WORKERS,
- "redis": os.getenv("PAPERLESS_REDIS", "redis://localhost:6379"),
- "log_level": "DEBUG" if DEBUG else "INFO",
+CELERY_WORKER_HIJACK_ROOT_LOGGER = False
+CELERY_WORKER_CONCURRENCY = TASK_WORKERS
+CELERY_WORKER_MAX_TASKS_PER_CHILD = 1
+CELERY_WORKER_SEND_TASK_EVENTS = True
+
+CELERY_SEND_TASK_SENT_EVENT = True
+
+CELERY_TASK_TRACK_STARTED = True
+CELERY_TASK_TIME_LIMIT = WORKER_TIMEOUT
+
+CELERY_RESULT_EXTENDED = True
+CELERY_RESULT_BACKEND = "django-db"
+CELERY_CACHE_BACKEND = "default"
+
+CELERY_BEAT_SCHEDULE = {
+ # Every ten minutes
+ "Check all e-mail accounts": {
+ "task": "paperless_mail.tasks.process_mail_accounts",
+ "schedule": crontab(minute="*/10"),
+ },
+ # Hourly at 5 minutes past the hour
+ "Train the classifier": {
+ "task": "documents.tasks.train_classifier",
+ "schedule": crontab(minute="5", hour="*/1"),
+ },
+ # Daily at midnight
+ "Optimize the index": {
+ "task": "documents.tasks.index_optimize",
+ "schedule": crontab(minute=0, hour=0),
+ },
+ # Weekly, Sunday at 00:30
+ "Perform sanity check": {
+ "task": "documents.tasks.sanity_check",
+ "schedule": crontab(minute=30, hour=0, day_of_week="sun"),
+ },
+}
+CELERY_BEAT_SCHEDULE_FILENAME = os.path.join(DATA_DIR, "celerybeat-schedule.db")
+
+# django setting.
+CACHES = {
+ "default": {
+ "BACKEND": "django.core.cache.backends.redis.RedisCache",
+ "LOCATION": _REDIS_URL,
+ },
}
diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py
index f28586a2a..67631ac5e 100644
--- a/src/paperless_mail/mail.py
+++ b/src/paperless_mail/mail.py
@@ -10,10 +10,10 @@ import magic
import pathvalidate
from django.conf import settings
from django.db import DatabaseError
-from django_q.tasks import async_task
from documents.loggers import LoggingMixin
from documents.models import Correspondent
from documents.parsers import is_mime_type_supported
+from documents.tasks import consume_file
from imap_tools import AND
from imap_tools import MailBox
from imap_tools import MailboxFolderSelectError
@@ -414,8 +414,7 @@ class MailAccountHandler(LoggingMixin):
f"{message.subject} from {message.from_}",
)
- async_task(
- "documents.tasks.consume_file",
+ consume_file.delay(
path=temp_filename,
override_filename=pathvalidate.sanitize_filename(
att.filename,
diff --git a/src/paperless_mail/migrations/0002_auto_20201117_1334.py b/src/paperless_mail/migrations/0002_auto_20201117_1334.py
index 5b29b3072..72e37e342 100644
--- a/src/paperless_mail/migrations/0002_auto_20201117_1334.py
+++ b/src/paperless_mail/migrations/0002_auto_20201117_1334.py
@@ -2,28 +2,12 @@
from django.db import migrations
from django.db.migrations import RunPython
-from django_q.models import Schedule
-from django_q.tasks import schedule
-
-
-def add_schedules(apps, schema_editor):
- schedule(
- "paperless_mail.tasks.process_mail_accounts",
- name="Check all e-mail accounts",
- schedule_type=Schedule.MINUTES,
- minutes=10,
- )
-
-
-def remove_schedules(apps, schema_editor):
- Schedule.objects.filter(func="paperless_mail.tasks.process_mail_accounts").delete()
class Migration(migrations.Migration):
dependencies = [
("paperless_mail", "0001_initial"),
- ("django_q", "0013_task_attempt_count"),
]
- operations = [RunPython(add_schedules, remove_schedules)]
+ operations = [RunPython(migrations.RunPython.noop, migrations.RunPython.noop)]
diff --git a/src/paperless_mail/tasks.py b/src/paperless_mail/tasks.py
index faa0300e8..5c92233de 100644
--- a/src/paperless_mail/tasks.py
+++ b/src/paperless_mail/tasks.py
@@ -1,13 +1,14 @@
import logging
+from celery import shared_task
from paperless_mail.mail import MailAccountHandler
from paperless_mail.mail import MailError
from paperless_mail.models import MailAccount
-
logger = logging.getLogger("paperless.mail.tasks")
+@shared_task
def process_mail_accounts():
total_new_documents = 0
for account in MailAccount.objects.all():
@@ -20,11 +21,3 @@ def process_mail_accounts():
return f"Added {total_new_documents} document(s)."
else:
return "No new documents were added."
-
-
-def process_mail_account(name):
- try:
- account = MailAccount.objects.get(name=name)
- MailAccountHandler().handle_mail_account(account)
- except MailAccount.DoesNotExist:
- logger.error(f"Unknown mail acccount: {name}")
diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py
index 997184fd2..28d40be7c 100644
--- a/src/paperless_mail/tests/test_mail.py
+++ b/src/paperless_mail/tests/test_mail.py
@@ -248,7 +248,7 @@ class TestMail(DirectoriesMixin, TestCase):
m.return_value = self.bogus_mailbox
self.addCleanup(patcher.stop)
- patcher = mock.patch("paperless_mail.mail.async_task")
+ patcher = mock.patch("paperless_mail.mail.consume_file.delay")
self.async_task = patcher.start()
self.addCleanup(patcher.stop)
@@ -1032,20 +1032,3 @@ class TestTasks(TestCase):
m.side_effect = lambda account: 0
result = tasks.process_mail_accounts()
self.assertIn("No new", result)
-
- @mock.patch("paperless_mail.tasks.MailAccountHandler.handle_mail_account")
- def test_single_accounts(self, m):
- MailAccount.objects.create(
- name="A",
- imap_server="A",
- username="A",
- password="A",
- )
-
- tasks.process_mail_account("A")
-
- m.assert_called_once()
- m.reset_mock()
-
- tasks.process_mail_account("B")
- m.assert_not_called()