mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-11 10:00:48 -05:00
Merge branch 'main' into dev
This commit is contained in:
commit
ef2789cf57
26
.github/release-drafter.yml
vendored
26
.github/release-drafter.yml
vendored
@ -1,3 +1,14 @@
|
|||||||
|
autolabeler:
|
||||||
|
- label: "bug"
|
||||||
|
branch:
|
||||||
|
- '/^fix/'
|
||||||
|
title:
|
||||||
|
- "/^fix/i"
|
||||||
|
- label: "enhancement"
|
||||||
|
branch:
|
||||||
|
- '/^feature/'
|
||||||
|
title:
|
||||||
|
- "/^feature/i"
|
||||||
categories:
|
categories:
|
||||||
- title: 'Breaking Changes'
|
- title: 'Breaking Changes'
|
||||||
labels:
|
labels:
|
||||||
@ -15,9 +26,15 @@ categories:
|
|||||||
- 'chore'
|
- 'chore'
|
||||||
- 'deployment'
|
- 'deployment'
|
||||||
- 'translation'
|
- 'translation'
|
||||||
|
- 'ci-cd'
|
||||||
- title: 'Dependencies'
|
- title: 'Dependencies'
|
||||||
collapse-after: 3
|
collapse-after: 3
|
||||||
label: 'dependencies'
|
label: 'dependencies'
|
||||||
|
- title: 'All App Changes'
|
||||||
|
labels:
|
||||||
|
- 'frontend'
|
||||||
|
- 'backend'
|
||||||
|
collapse-after: 0
|
||||||
include-labels:
|
include-labels:
|
||||||
- 'enhancement'
|
- 'enhancement'
|
||||||
- 'bug'
|
- 'bug'
|
||||||
@ -25,11 +42,12 @@ include-labels:
|
|||||||
- 'deployment'
|
- 'deployment'
|
||||||
- 'translation'
|
- 'translation'
|
||||||
- 'dependencies'
|
- 'dependencies'
|
||||||
replacers: # Changes "Feature: Update checker" to "Update checker"
|
- 'documentation'
|
||||||
- search: '/Feature:|Feat:|\[feature\]/gi'
|
- 'frontend'
|
||||||
replace: ''
|
- 'backend'
|
||||||
|
- 'ci-cd'
|
||||||
category-template: '### $TITLE'
|
category-template: '### $TITLE'
|
||||||
change-template: '- $TITLE [@$AUTHOR](https://github.com/$AUTHOR) ([#$NUMBER]($URL))'
|
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
||||||
change-title-escapes: '\<*_&#@'
|
change-title-escapes: '\<*_&#@'
|
||||||
template: |
|
template: |
|
||||||
## paperless-ngx $RESOLVED_VERSION
|
## paperless-ngx $RESOLVED_VERSION
|
||||||
|
512
.github/scripts/cleanup-tags.py
vendored
512
.github/scripts/cleanup-tags.py
vendored
@ -1,167 +1,41 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import functools
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from typing import List
|
from typing import List
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from common import get_log_level
|
from common import get_log_level
|
||||||
|
from github import ContainerPackage
|
||||||
|
from github import GithubBranchApi
|
||||||
|
from github import GithubContainerRegistryApi
|
||||||
|
|
||||||
logger = logging.getLogger("cleanup-tags")
|
logger = logging.getLogger("cleanup-tags")
|
||||||
|
|
||||||
|
|
||||||
class ContainerPackage:
|
class DockerManifest2:
|
||||||
def __init__(self, data: Dict):
|
"""
|
||||||
|
Data class wrapping the Docker Image Manifest Version 2.
|
||||||
|
|
||||||
|
See https://docs.docker.com/registry/spec/manifest-v2-2/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data: Dict) -> None:
|
||||||
self._data = data
|
self._data = data
|
||||||
self.name = self._data["name"]
|
# This is the sha256: digest string. Corresponds to Github API name
|
||||||
self.id = self._data["id"]
|
# if the package is an untagged package
|
||||||
self.url = self._data["url"]
|
self.digest = self._data["digest"]
|
||||||
self.tags = self._data["metadata"]["container"]["tags"]
|
platform_data_os = self._data["platform"]["os"]
|
||||||
|
platform_arch = self._data["platform"]["architecture"]
|
||||||
@functools.cached_property
|
platform_variant = self._data["platform"].get(
|
||||||
def untagged(self) -> bool:
|
"variant",
|
||||||
return len(self.tags) == 0
|
"",
|
||||||
|
|
||||||
@functools.cache
|
|
||||||
def tag_matches(self, pattern: str) -> bool:
|
|
||||||
for tag in self.tags:
|
|
||||||
if re.match(pattern, tag) is not None:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"Package {self.name}"
|
|
||||||
|
|
||||||
|
|
||||||
class GithubContainerRegistry:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
session: requests.Session,
|
|
||||||
token: str,
|
|
||||||
owner_or_org: str,
|
|
||||||
):
|
|
||||||
self._session: requests.Session = session
|
|
||||||
self._token = token
|
|
||||||
self._owner_or_org = owner_or_org
|
|
||||||
# https://docs.github.com/en/rest/branches/branches
|
|
||||||
self._BRANCHES_ENDPOINT = "https://api.github.com/repos/{OWNER}/{REPO}/branches"
|
|
||||||
if self._owner_or_org == "paperless-ngx":
|
|
||||||
# https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-an-organization
|
|
||||||
self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions"
|
|
||||||
# https://docs.github.com/en/rest/packages#delete-package-version-for-an-organization
|
|
||||||
self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}"
|
|
||||||
else:
|
|
||||||
# https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-the-authenticated-user
|
|
||||||
self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions"
|
|
||||||
# https://docs.github.com/en/rest/packages#delete-a-package-version-for-the-authenticated-user
|
|
||||||
self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}"
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""
|
|
||||||
Sets up the required headers for auth and response
|
|
||||||
type from the API
|
|
||||||
"""
|
|
||||||
self._session.headers.update(
|
|
||||||
{
|
|
||||||
"Accept": "application/vnd.github.v3+json",
|
|
||||||
"Authorization": f"token {self._token}",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return self
|
self.platform = f"{platform_data_os}/{platform_arch}{platform_variant}"
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
"""
|
|
||||||
Ensures the authorization token is cleaned up no matter
|
|
||||||
the reason for the exit
|
|
||||||
"""
|
|
||||||
if "Accept" in self._session.headers:
|
|
||||||
del self._session.headers["Accept"]
|
|
||||||
if "Authorization" in self._session.headers:
|
|
||||||
del self._session.headers["Authorization"]
|
|
||||||
|
|
||||||
def _read_all_pages(self, endpoint):
|
|
||||||
"""
|
|
||||||
Internal function to read all pages of an endpoint, utilizing the
|
|
||||||
next.url until exhausted
|
|
||||||
"""
|
|
||||||
internal_data = []
|
|
||||||
|
|
||||||
while True:
|
|
||||||
resp = self._session.get(endpoint)
|
|
||||||
if resp.status_code == 200:
|
|
||||||
internal_data += resp.json()
|
|
||||||
if "next" in resp.links:
|
|
||||||
endpoint = resp.links["next"]["url"]
|
|
||||||
else:
|
|
||||||
logger.debug("Exiting pagination loop")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.warning(f"Request to {endpoint} return HTTP {resp.status_code}")
|
|
||||||
break
|
|
||||||
|
|
||||||
return internal_data
|
|
||||||
|
|
||||||
def get_branches(self, repo: str):
|
|
||||||
"""
|
|
||||||
Returns all current branches of the given repository
|
|
||||||
"""
|
|
||||||
endpoint = self._BRANCHES_ENDPOINT.format(OWNER=self._owner_or_org, REPO=repo)
|
|
||||||
internal_data = self._read_all_pages(endpoint)
|
|
||||||
return internal_data
|
|
||||||
|
|
||||||
def filter_branches_by_name_pattern(self, branch_data, pattern: str):
|
|
||||||
"""
|
|
||||||
Filters the given list of branches to those which start with the given
|
|
||||||
pattern. Future enhancement could use regex patterns instead.
|
|
||||||
"""
|
|
||||||
matches = {}
|
|
||||||
|
|
||||||
for branch in branch_data:
|
|
||||||
if branch["name"].startswith(pattern):
|
|
||||||
matches[branch["name"]] = branch
|
|
||||||
|
|
||||||
return matches
|
|
||||||
|
|
||||||
def get_package_versions(
|
|
||||||
self,
|
|
||||||
package_name: str,
|
|
||||||
package_type: str = "container",
|
|
||||||
) -> List[ContainerPackage]:
|
|
||||||
"""
|
|
||||||
Returns all the versions of a given package (container images) from
|
|
||||||
the API
|
|
||||||
"""
|
|
||||||
package_name = quote(package_name, safe="")
|
|
||||||
endpoint = self._PACKAGES_VERSIONS_ENDPOINT.format(
|
|
||||||
ORG=self._owner_or_org,
|
|
||||||
PACKAGE_TYPE=package_type,
|
|
||||||
PACKAGE_NAME=package_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
pkgs = []
|
|
||||||
|
|
||||||
for data in self._read_all_pages(endpoint):
|
|
||||||
pkgs.append(ContainerPackage(data))
|
|
||||||
|
|
||||||
return pkgs
|
|
||||||
|
|
||||||
def delete_package_version(self, package_data: ContainerPackage):
|
|
||||||
"""
|
|
||||||
Deletes the given package version from the GHCR
|
|
||||||
"""
|
|
||||||
resp = self._session.delete(package_data.url)
|
|
||||||
if resp.status_code != 204:
|
|
||||||
logger.warning(
|
|
||||||
f"Request to delete {package_data.url} returned HTTP {resp.status_code}",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _main():
|
def _main():
|
||||||
@ -187,6 +61,15 @@ def _main():
|
|||||||
help="If provided, delete untagged containers as well",
|
help="If provided, delete untagged containers as well",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# If given, the package is assumed to be a multi-arch manifest. Cache packages are
|
||||||
|
# not multi-arch, all other types are
|
||||||
|
parser.add_argument(
|
||||||
|
"--is-manifest",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="If provided, the package is assumed to be a multi-arch manifest following schema v2",
|
||||||
|
)
|
||||||
|
|
||||||
# Allows configuration of log level for debugging
|
# Allows configuration of log level for debugging
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--loglevel",
|
"--loglevel",
|
||||||
@ -194,6 +77,12 @@ def _main():
|
|||||||
help="Configures the logging level",
|
help="Configures the logging level",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Get the name of the package being processed this round
|
||||||
|
parser.add_argument(
|
||||||
|
"package",
|
||||||
|
help="The package to process",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@ -207,181 +96,194 @@ def _main():
|
|||||||
repo: Final[str] = os.environ["GITHUB_REPOSITORY"]
|
repo: Final[str] = os.environ["GITHUB_REPOSITORY"]
|
||||||
gh_token: Final[str] = os.environ["TOKEN"]
|
gh_token: Final[str] = os.environ["TOKEN"]
|
||||||
|
|
||||||
with requests.session() as sess:
|
# Find all branches named feature-*
|
||||||
with GithubContainerRegistry(sess, gh_token, repo_owner) as gh_api:
|
# Note: Only relevant to the main application, but simpler to
|
||||||
|
# leave in for all packages
|
||||||
|
with GithubBranchApi(gh_token) as branch_api:
|
||||||
|
feature_branches = {}
|
||||||
|
for branch in branch_api.get_branches(
|
||||||
|
repo=repo,
|
||||||
|
):
|
||||||
|
if branch.name.startswith("feature-"):
|
||||||
|
logger.debug(f"Found feature branch {branch.name}")
|
||||||
|
feature_branches[branch.name] = branch
|
||||||
|
|
||||||
# Step 1 - Get branch information
|
logger.info(f"Located {len(feature_branches)} feature branches")
|
||||||
|
|
||||||
# Step 1.1 - Locate all branches of the repo
|
with GithubContainerRegistryApi(gh_token, repo_owner) as container_api:
|
||||||
all_branches = gh_api.get_branches("paperless-ngx")
|
# Get the information about all versions of the given package
|
||||||
logger.info(f"Located {len(all_branches)} branches of {repo_owner}/{repo} ")
|
all_package_versions: List[
|
||||||
|
ContainerPackage
|
||||||
|
] = container_api.get_package_versions(args.package)
|
||||||
|
|
||||||
# Step 1.2 - Filter branches to those starting with "feature-"
|
all_pkgs_tags_to_version: Dict[str, ContainerPackage] = {}
|
||||||
feature_branches = gh_api.filter_branches_by_name_pattern(
|
for pkg in all_package_versions:
|
||||||
all_branches,
|
for tag in pkg.tags:
|
||||||
"feature-",
|
all_pkgs_tags_to_version[tag] = pkg
|
||||||
)
|
logger.info(
|
||||||
logger.info(f"Located {len(feature_branches)} feature branches")
|
f"Located {len(all_package_versions)} versions of package {args.package}",
|
||||||
|
)
|
||||||
|
|
||||||
# Step 2 - Deal with package information
|
# Filter to packages which are tagged with feature-*
|
||||||
for package_name in ["paperless-ngx", "paperless-ngx/builder/cache/app"]:
|
packages_tagged_feature: List[ContainerPackage] = []
|
||||||
|
for package in all_package_versions:
|
||||||
|
if package.tag_matches("feature-"):
|
||||||
|
packages_tagged_feature.append(package)
|
||||||
|
|
||||||
# Step 2.1 - Location all versions of the given package
|
feature_pkgs_tags_to_versions: Dict[str, ContainerPackage] = {}
|
||||||
all_package_versions = gh_api.get_package_versions(package_name)
|
for pkg in packages_tagged_feature:
|
||||||
|
for tag in pkg.tags:
|
||||||
|
feature_pkgs_tags_to_versions[tag] = pkg
|
||||||
|
|
||||||
# Faster lookup, map the tag to their container
|
logger.info(
|
||||||
all_pkgs_tags_to_version = {}
|
f'Located {len(feature_pkgs_tags_to_versions)} versions of package {args.package} tagged "feature-"',
|
||||||
for pkg in all_package_versions:
|
)
|
||||||
for tag in pkg.tags:
|
|
||||||
all_pkgs_tags_to_version[tag] = pkg
|
# All the feature tags minus all the feature branches leaves us feature tags
|
||||||
|
# with no corresponding branch
|
||||||
|
tags_to_delete = list(
|
||||||
|
set(feature_pkgs_tags_to_versions.keys()) - set(feature_branches.keys()),
|
||||||
|
)
|
||||||
|
|
||||||
|
# All the tags minus the set of going to be deleted tags leaves us the
|
||||||
|
# tags which will be kept around
|
||||||
|
tags_to_keep = list(
|
||||||
|
set(all_pkgs_tags_to_version.keys()) - set(tags_to_delete),
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Located {len(tags_to_delete)} versions of package {args.package} to delete",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete certain package versions for which no branch existed
|
||||||
|
for tag_to_delete in tags_to_delete:
|
||||||
|
package_version_info = feature_pkgs_tags_to_versions[tag_to_delete]
|
||||||
|
|
||||||
|
if args.delete:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Located {len(all_package_versions)} versions of package {package_name}",
|
f"Deleting {tag_to_delete} (id {package_version_info.id})",
|
||||||
|
)
|
||||||
|
container_api.delete_package_version(
|
||||||
|
package_version_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 2.2 - Location package versions which have a tag of "feature-"
|
else:
|
||||||
packages_tagged_feature = []
|
logger.info(
|
||||||
|
f"Would delete {tag_to_delete} (id {package_version_info.id})",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Deal with untagged package versions
|
||||||
|
if args.untagged:
|
||||||
|
|
||||||
|
logger.info("Handling untagged image packages")
|
||||||
|
|
||||||
|
if not args.is_manifest:
|
||||||
|
# If the package is not a multi-arch manifest, images without tags are safe to delete.
|
||||||
|
# They are not referred to by anything. This will leave all with at least 1 tag
|
||||||
|
|
||||||
for package in all_package_versions:
|
for package in all_package_versions:
|
||||||
if package.tag_matches("feature-"):
|
if package.untagged:
|
||||||
packages_tagged_feature.append(package)
|
if args.delete:
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f'Located {len(packages_tagged_feature)} versions of package {package_name} tagged "feature-"',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Faster lookup, map feature- tags to their container
|
|
||||||
feature_pkgs_tags_to_versions = {}
|
|
||||||
for pkg in packages_tagged_feature:
|
|
||||||
for tag in pkg.tags:
|
|
||||||
feature_pkgs_tags_to_versions[tag] = pkg
|
|
||||||
|
|
||||||
# Step 2.3 - Determine which package versions have no matching branch and which tags we're keeping
|
|
||||||
tags_to_delete = list(
|
|
||||||
set(feature_pkgs_tags_to_versions.keys())
|
|
||||||
- set(feature_branches.keys()),
|
|
||||||
)
|
|
||||||
tags_to_keep = list(
|
|
||||||
set(all_pkgs_tags_to_version.keys()) - set(tags_to_delete),
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"Located {len(tags_to_delete)} versions of package {package_name} to delete",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Step 2.4 - Delete certain package versions
|
|
||||||
for tag_to_delete in tags_to_delete:
|
|
||||||
package_version_info = feature_pkgs_tags_to_versions[tag_to_delete]
|
|
||||||
|
|
||||||
if args.delete:
|
|
||||||
logger.info(
|
|
||||||
f"Deleting {tag_to_delete} (id {package_version_info.id})",
|
|
||||||
)
|
|
||||||
gh_api.delete_package_version(
|
|
||||||
package_version_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.info(
|
|
||||||
f"Would delete {tag_to_delete} (id {package_version_info.id})",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Step 3 - Deal with untagged and dangling packages
|
|
||||||
if args.untagged:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Ok, bear with me, these are annoying.
|
|
||||||
|
|
||||||
Our images are multi-arch, so the manifest is more like a pointer to a sha256 digest.
|
|
||||||
These images are untagged, but pointed to, and so should not be removed (or every pull fails).
|
|
||||||
|
|
||||||
So for each image getting kept, parse the manifest to find the digest(s) it points to. Then
|
|
||||||
remove those from the list of untagged images. The final result is the untagged, not pointed to
|
|
||||||
version which should be safe to remove.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
Tag: ghcr.io/paperless-ngx/paperless-ngx:1.7.1 refers to
|
|
||||||
amd64: sha256:b9ed4f8753bbf5146547671052d7e91f68cdfc9ef049d06690b2bc866fec2690
|
|
||||||
armv7: sha256:81605222df4ba4605a2ba4893276e5d08c511231ead1d5da061410e1bbec05c3
|
|
||||||
arm64: sha256:374cd68db40734b844705bfc38faae84cc4182371de4bebd533a9a365d5e8f3b
|
|
||||||
each of which appears as untagged image
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Step 3.1 - Simplify the untagged data, mapping name (which is a digest) to the version
|
|
||||||
untagged_versions = {}
|
|
||||||
for x in all_package_versions:
|
|
||||||
if x.untagged:
|
|
||||||
untagged_versions[x.name] = x
|
|
||||||
|
|
||||||
skips = 0
|
|
||||||
# Extra security to not delete on an unexpected error
|
|
||||||
actually_delete = True
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Located {len(tags_to_keep)} tags of package {package_name} to keep",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Step 3.2 - Parse manifests to locate digests pointed to
|
|
||||||
for tag in tags_to_keep:
|
|
||||||
full_name = f"ghcr.io/{repo_owner}/{package_name}:{tag}"
|
|
||||||
logger.info(f"Checking manifest for {full_name}")
|
|
||||||
try:
|
|
||||||
proc = subprocess.run(
|
|
||||||
[
|
|
||||||
shutil.which("docker"),
|
|
||||||
"manifest",
|
|
||||||
"inspect",
|
|
||||||
full_name,
|
|
||||||
],
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
manifest_list = json.loads(proc.stdout)
|
|
||||||
for manifest in manifest_list["manifests"]:
|
|
||||||
digest = manifest["digest"]
|
|
||||||
platform_data_os = manifest["platform"]["os"]
|
|
||||||
platform_arch = manifest["platform"]["architecture"]
|
|
||||||
platform_variant = manifest["platform"].get(
|
|
||||||
"variant",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
platform = f"{platform_data_os}/{platform_arch}{platform_variant}"
|
|
||||||
|
|
||||||
if digest in untagged_versions:
|
|
||||||
logger.debug(
|
|
||||||
f"Skipping deletion of {digest}, referred to by {full_name} for {platform}",
|
|
||||||
)
|
|
||||||
del untagged_versions[digest]
|
|
||||||
skips += 1
|
|
||||||
|
|
||||||
except json.decoder.JSONDecodeError as err:
|
|
||||||
# This is probably for a cache image, which isn't a multi-arch digest
|
|
||||||
# These are ok to delete all on
|
|
||||||
logger.debug(f"{err} on {full_name}")
|
|
||||||
continue
|
|
||||||
except Exception as err:
|
|
||||||
actually_delete = False
|
|
||||||
logger.exception(err)
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.info(f"Skipping deletion of {skips} packages")
|
|
||||||
|
|
||||||
# Step 3.3 - Delete the untagged and not pointed at packages
|
|
||||||
logger.info(f"Deleting untagged packages of {package_name}")
|
|
||||||
for to_delete_name in untagged_versions:
|
|
||||||
to_delete_version = untagged_versions[to_delete_name]
|
|
||||||
|
|
||||||
if args.delete and actually_delete:
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Deleting id {to_delete_version.id} named {to_delete_version.name}",
|
f"Deleting id {package.id} named {package.name}",
|
||||||
)
|
)
|
||||||
gh_api.delete_package_version(
|
container_api.delete_package_version(
|
||||||
to_delete_version,
|
package,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Would delete {to_delete_name} (id {to_delete_version.id})",
|
f"Would delete {package.name} (id {package.id})",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info("Leaving untagged images untouched")
|
logger.info(
|
||||||
|
f"Not deleting tag {package.tags[0]} of package {args.package}",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ok, bear with me, these are annoying.
|
||||||
|
|
||||||
|
Our images are multi-arch, so the manifest is more like a pointer to a sha256 digest.
|
||||||
|
These images are untagged, but pointed to, and so should not be removed (or every pull fails).
|
||||||
|
|
||||||
|
So for each image getting kept, parse the manifest to find the digest(s) it points to. Then
|
||||||
|
remove those from the list of untagged images. The final result is the untagged, not pointed to
|
||||||
|
version which should be safe to remove.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Tag: ghcr.io/paperless-ngx/paperless-ngx:1.7.1 refers to
|
||||||
|
amd64: sha256:b9ed4f8753bbf5146547671052d7e91f68cdfc9ef049d06690b2bc866fec2690
|
||||||
|
armv7: sha256:81605222df4ba4605a2ba4893276e5d08c511231ead1d5da061410e1bbec05c3
|
||||||
|
arm64: sha256:374cd68db40734b844705bfc38faae84cc4182371de4bebd533a9a365d5e8f3b
|
||||||
|
each of which appears as untagged image, but isn't really.
|
||||||
|
|
||||||
|
So from the list of untagged packages, remove those digests. Once all tags which
|
||||||
|
are being kept are checked, the remaining untagged packages are actually untagged
|
||||||
|
with no referrals in a manifest to them.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Simplify the untagged data, mapping name (which is a digest) to the version
|
||||||
|
untagged_versions = {}
|
||||||
|
for x in all_package_versions:
|
||||||
|
if x.untagged:
|
||||||
|
untagged_versions[x.name] = x
|
||||||
|
|
||||||
|
skips = 0
|
||||||
|
# Extra security to not delete on an unexpected error
|
||||||
|
actually_delete = True
|
||||||
|
|
||||||
|
# Parse manifests to locate digests pointed to
|
||||||
|
for tag in sorted(tags_to_keep):
|
||||||
|
full_name = f"ghcr.io/{repo_owner}/{args.package}:{tag}"
|
||||||
|
logger.info(f"Checking manifest for {full_name}")
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
[
|
||||||
|
shutil.which("docker"),
|
||||||
|
"manifest",
|
||||||
|
"inspect",
|
||||||
|
full_name,
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
manifest_list = json.loads(proc.stdout)
|
||||||
|
for manifest_data in manifest_list["manifests"]:
|
||||||
|
manifest = DockerManifest2(manifest_data)
|
||||||
|
|
||||||
|
if manifest.digest in untagged_versions:
|
||||||
|
logger.debug(
|
||||||
|
f"Skipping deletion of {manifest.digest}, referred to by {full_name} for {manifest.platform}",
|
||||||
|
)
|
||||||
|
del untagged_versions[manifest.digest]
|
||||||
|
skips += 1
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
actually_delete = False
|
||||||
|
logger.exception(err)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Skipping deletion of {skips} packages referred to by a manifest",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 3.3 - Delete the untagged and not pointed at packages
|
||||||
|
logger.info(f"Deleting untagged packages of {args.package}")
|
||||||
|
for to_delete_name in untagged_versions:
|
||||||
|
to_delete_version = untagged_versions[to_delete_name]
|
||||||
|
|
||||||
|
if args.delete and actually_delete:
|
||||||
|
logger.info(
|
||||||
|
f"Deleting id {to_delete_version.id} named {to_delete_version.name}",
|
||||||
|
)
|
||||||
|
container_api.delete_package_version(
|
||||||
|
to_delete_version,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
f"Would delete {to_delete_name} (id {to_delete_version.id})",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info("Leaving untagged images untouched")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
1
.github/scripts/common.py
vendored
1
.github/scripts/common.py
vendored
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import logging
|
import logging
|
||||||
from argparse import ArgumentError
|
|
||||||
|
|
||||||
|
|
||||||
def get_image_tag(
|
def get_image_tag(
|
||||||
|
227
.github/scripts/github.py
vendored
Normal file
227
.github/scripts/github.py
vendored
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
This module contains some useful classes for interacting with the Github API.
|
||||||
|
The full documentation for the API can be found here: https://docs.github.com/en/rest
|
||||||
|
|
||||||
|
Mostly, this focusses on two areas, repo branches and repo packages, as the use case
|
||||||
|
is cleaning up container images which are no longer referred to.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
logger = logging.getLogger("github-api")
|
||||||
|
|
||||||
|
|
||||||
|
class _GithubApiBase:
|
||||||
|
"""
|
||||||
|
A base class for interacting with the Github API. It
|
||||||
|
will handle the session and setting authorization headers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, token: str) -> None:
|
||||||
|
self._token = token
|
||||||
|
self._session: Optional[requests.Session] = None
|
||||||
|
|
||||||
|
def __enter__(self) -> "_GithubApiBase":
|
||||||
|
"""
|
||||||
|
Sets up the required headers for auth and response
|
||||||
|
type from the API
|
||||||
|
"""
|
||||||
|
self._session = requests.Session()
|
||||||
|
self._session.headers.update(
|
||||||
|
{
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"Authorization": f"token {self._token}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
"""
|
||||||
|
Ensures the authorization token is cleaned up no matter
|
||||||
|
the reason for the exit
|
||||||
|
"""
|
||||||
|
if "Accept" in self._session.headers:
|
||||||
|
del self._session.headers["Accept"]
|
||||||
|
if "Authorization" in self._session.headers:
|
||||||
|
del self._session.headers["Authorization"]
|
||||||
|
|
||||||
|
# Close the session as well
|
||||||
|
self._session.close()
|
||||||
|
self._session = None
|
||||||
|
|
||||||
|
def _read_all_pages(self, endpoint):
|
||||||
|
"""
|
||||||
|
Helper function to read all pages of an endpoint, utilizing the
|
||||||
|
next.url until exhausted. Assumes the endpoint returns a list
|
||||||
|
"""
|
||||||
|
internal_data = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
resp = self._session.get(endpoint)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
internal_data += resp.json()
|
||||||
|
if "next" in resp.links:
|
||||||
|
endpoint = resp.links["next"]["url"]
|
||||||
|
else:
|
||||||
|
logger.debug("Exiting pagination loop")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logger.warning(f"Request to {endpoint} return HTTP {resp.status_code}")
|
||||||
|
break
|
||||||
|
|
||||||
|
return internal_data
|
||||||
|
|
||||||
|
|
||||||
|
class _EndpointResponse:
|
||||||
|
"""
|
||||||
|
For all endpoint JSON responses, store the full
|
||||||
|
response data, for ease of extending later, if need be.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data: Dict) -> None:
|
||||||
|
self._data = data
|
||||||
|
|
||||||
|
|
||||||
|
class GithubBranch(_EndpointResponse):
|
||||||
|
"""
|
||||||
|
Simple wrapper for a repository branch, only extracts name information
|
||||||
|
for now.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data: Dict) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
self.name = self._data["name"]
|
||||||
|
|
||||||
|
|
||||||
|
class GithubBranchApi(_GithubApiBase):
|
||||||
|
"""
|
||||||
|
Wrapper around branch API.
|
||||||
|
|
||||||
|
See https://docs.github.com/en/rest/branches/branches
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, token: str) -> None:
|
||||||
|
super().__init__(token)
|
||||||
|
|
||||||
|
self._ENDPOINT = "https://api.github.com/repos/{REPO}/branches"
|
||||||
|
|
||||||
|
def get_branches(self, repo: str) -> List[GithubBranch]:
|
||||||
|
"""
|
||||||
|
Returns all current branches of the given repository owned by the given
|
||||||
|
owner or organization.
|
||||||
|
"""
|
||||||
|
endpoint = self._ENDPOINT.format(REPO=repo)
|
||||||
|
internal_data = self._read_all_pages(endpoint)
|
||||||
|
return [GithubBranch(branch) for branch in internal_data]
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerPackage(_EndpointResponse):
|
||||||
|
"""
|
||||||
|
Data class wrapping the JSON response from the package related
|
||||||
|
endpoints
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data: Dict):
|
||||||
|
super().__init__(data)
|
||||||
|
# This is a numerical ID, required for interactions with this
|
||||||
|
# specific package, including deletion of it or restoration
|
||||||
|
self.id: int = self._data["id"]
|
||||||
|
|
||||||
|
# A string name. This might be an actual name or it could be a
|
||||||
|
# digest string like "sha256:"
|
||||||
|
self.name: str = self._data["name"]
|
||||||
|
|
||||||
|
# URL to the package, including its ID, can be used for deletion
|
||||||
|
# or restoration without needing to build up a URL ourselves
|
||||||
|
self.url: str = self._data["url"]
|
||||||
|
|
||||||
|
# The list of tags applied to this image. Maybe an empty list
|
||||||
|
self.tags: List[str] = self._data["metadata"]["container"]["tags"]
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def untagged(self) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True if the image has no tags applied to it, False otherwise
|
||||||
|
"""
|
||||||
|
return len(self.tags) == 0
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def tag_matches(self, pattern: str) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True if the image has at least one tag which matches the given regex,
|
||||||
|
False otherwise
|
||||||
|
"""
|
||||||
|
for tag in self.tags:
|
||||||
|
if re.match(pattern, tag) is not None:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Package {self.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class GithubContainerRegistryApi(_GithubApiBase):
|
||||||
|
"""
|
||||||
|
Class wrapper to deal with the Github packages API. This class only deals with
|
||||||
|
container type packages, the only type published by paperless-ngx.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, token: str, owner_or_org: str) -> None:
|
||||||
|
super().__init__(token)
|
||||||
|
self._owner_or_org = owner_or_org
|
||||||
|
if self._owner_or_org == "paperless-ngx":
|
||||||
|
# https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-an-organization
|
||||||
|
self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions"
|
||||||
|
# https://docs.github.com/en/rest/packages#delete-package-version-for-an-organization
|
||||||
|
self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}"
|
||||||
|
else:
|
||||||
|
# https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-the-authenticated-user
|
||||||
|
self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions"
|
||||||
|
# https://docs.github.com/en/rest/packages#delete-a-package-version-for-the-authenticated-user
|
||||||
|
self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}"
|
||||||
|
|
||||||
|
def get_package_versions(
|
||||||
|
self,
|
||||||
|
package_name: str,
|
||||||
|
) -> List[ContainerPackage]:
|
||||||
|
"""
|
||||||
|
Returns all the versions of a given package (container images) from
|
||||||
|
the API
|
||||||
|
"""
|
||||||
|
|
||||||
|
package_type: str = "container"
|
||||||
|
# Need to quote this for slashes in the name
|
||||||
|
package_name = urllib.parse.quote(package_name, safe="")
|
||||||
|
|
||||||
|
endpoint = self._PACKAGES_VERSIONS_ENDPOINT.format(
|
||||||
|
ORG=self._owner_or_org,
|
||||||
|
PACKAGE_TYPE=package_type,
|
||||||
|
PACKAGE_NAME=package_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
pkgs = []
|
||||||
|
|
||||||
|
for data in self._read_all_pages(endpoint):
|
||||||
|
pkgs.append(ContainerPackage(data))
|
||||||
|
|
||||||
|
return pkgs
|
||||||
|
|
||||||
|
def delete_package_version(self, package_data: ContainerPackage):
|
||||||
|
"""
|
||||||
|
Deletes the given package version from the GHCR
|
||||||
|
"""
|
||||||
|
resp = self._session.delete(package_data.url)
|
||||||
|
if resp.status_code != 204:
|
||||||
|
logger.warning(
|
||||||
|
f"Request to delete {package_data.url} returned HTTP {resp.status_code}",
|
||||||
|
)
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -452,7 +452,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Create Release and Changelog
|
name: Create Release and Changelog
|
||||||
id: create-release
|
id: create-release
|
||||||
uses: release-drafter/release-drafter@v5
|
uses: paperless-ngx/release-drafter@master
|
||||||
with:
|
with:
|
||||||
name: Paperless-ngx ${{ steps.get_version.outputs.version }}
|
name: Paperless-ngx ${{ steps.get_version.outputs.version }}
|
||||||
tag: ${{ steps.get_version.outputs.version }}
|
tag: ${{ steps.get_version.outputs.version }}
|
||||||
@ -492,6 +492,8 @@ jobs:
|
|||||||
git branch ${{ needs.publish-release.outputs.version }}-changelog
|
git branch ${{ needs.publish-release.outputs.version }}-changelog
|
||||||
git checkout ${{ needs.publish-release.outputs.version }}-changelog
|
git checkout ${{ needs.publish-release.outputs.version }}-changelog
|
||||||
echo -e "# Changelog\n\n${{ needs.publish-release.outputs.changelog }}\n" > changelog-new.md
|
echo -e "# Changelog\n\n${{ needs.publish-release.outputs.changelog }}\n" > changelog-new.md
|
||||||
|
echo "Manually linking usernames"
|
||||||
|
sed -i -r 's|@(.+?) \(\[#|[@\1](https://github.com/\1) ([#|ig' changelog-new.md
|
||||||
CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
|
CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
|
||||||
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
||||||
mv changelog-new.md changelog.md
|
mv changelog-new.md changelog.md
|
||||||
|
56
.github/workflows/cleanup-tags.yml
vendored
56
.github/workflows/cleanup-tags.yml
vendored
@ -16,6 +16,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- ".github/workflows/cleanup-tags.yml"
|
- ".github/workflows/cleanup-tags.yml"
|
||||||
- ".github/scripts/cleanup-tags.py"
|
- ".github/scripts/cleanup-tags.py"
|
||||||
|
- ".github/scripts/github.py"
|
||||||
- ".github/scripts/common.py"
|
- ".github/scripts/common.py"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -45,14 +46,63 @@ jobs:
|
|||||||
name: Install requests
|
name: Install requests
|
||||||
run: |
|
run: |
|
||||||
python -m pip install requests
|
python -m pip install requests
|
||||||
|
# Clean up primary packages
|
||||||
-
|
-
|
||||||
name: Cleanup feature tags
|
name: Cleanup for package "paperless-ngx"
|
||||||
# Only run if the token is not empty
|
|
||||||
if: "${{ env.TOKEN != '' }}"
|
if: "${{ env.TOKEN != '' }}"
|
||||||
run: |
|
run: |
|
||||||
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx"
|
||||||
|
-
|
||||||
|
name: Cleanup for package "qpdf"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx/builder/qpdf"
|
||||||
|
-
|
||||||
|
name: Cleanup for package "pikepdf"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx/builder/pikepdf"
|
||||||
|
-
|
||||||
|
name: Cleanup for package "jbig2enc"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx/builder/jbig2enc"
|
||||||
|
-
|
||||||
|
name: Cleanup for package "psycopg2"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx/builder/psycopg2"
|
||||||
|
#
|
||||||
|
# Clean up registry cache packages
|
||||||
|
#
|
||||||
|
-
|
||||||
|
name: Cleanup for package "builder/cache/app"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/app"
|
||||||
|
-
|
||||||
|
name: Cleanup for package "builder/cache/qpdf"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/qpdf"
|
||||||
|
-
|
||||||
|
name: Cleanup for package "builder/cache/psycopg2"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/psycopg2"
|
||||||
|
-
|
||||||
|
name: Cleanup for package "builder/cache/jbig2enc"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/jbig2enc"
|
||||||
|
-
|
||||||
|
name: Cleanup for package "builder/cache/pikepdf"
|
||||||
|
if: "${{ env.TOKEN != '' }}"
|
||||||
|
run: |
|
||||||
|
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/pikepdf"
|
||||||
-
|
-
|
||||||
name: Check all tags still pull
|
name: Check all tags still pull
|
||||||
run: |
|
run: |
|
||||||
ghcr_name=$(echo "${GITHUB_REPOSITORY}" | awk '{ print tolower($0) }')
|
ghcr_name=$(echo "${GITHUB_REPOSITORY}" | awk '{ print tolower($0) }')
|
||||||
|
echo "Pulling all tags of ghcr.io/${ghcr_name}"
|
||||||
docker pull --quiet --all-tags ghcr.io/${ghcr_name}
|
docker pull --quiet --all-tags ghcr.io/${ghcr_name}
|
||||||
|
18
.github/workflows/project-actions.yml
vendored
18
.github/workflows/project-actions.yml
vendored
@ -13,6 +13,9 @@ on:
|
|||||||
- main
|
- main
|
||||||
- dev
|
- dev
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
todo: Todo
|
todo: Todo
|
||||||
done: Done
|
done: Done
|
||||||
@ -24,7 +27,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
||||||
steps:
|
steps:
|
||||||
- name: Set issue status to ${{ env.todo }}
|
- name: Add issue to project and set status to ${{ env.todo }}
|
||||||
uses: leonsteinhaeuser/project-beta-automations@v1.3.0
|
uses: leonsteinhaeuser/project-beta-automations@v1.3.0
|
||||||
with:
|
with:
|
||||||
gh_token: ${{ secrets.GH_TOKEN }}
|
gh_token: ${{ secrets.GH_TOKEN }}
|
||||||
@ -35,13 +38,20 @@ jobs:
|
|||||||
pr_opened_or_reopened:
|
pr_opened_or_reopened:
|
||||||
name: pr_opened_or_reopened
|
name: pr_opened_or_reopened
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
permissions:
|
||||||
|
# write permission is required for autolabeler
|
||||||
|
pull-requests: write
|
||||||
|
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
||||||
steps:
|
steps:
|
||||||
- name: Set PR status to ${{ env.in_progress }}
|
- name: Add PR to project and set status to "Needs Review"
|
||||||
uses: leonsteinhaeuser/project-beta-automations@v1.3.0
|
uses: leonsteinhaeuser/project-beta-automations@v1.3.0
|
||||||
with:
|
with:
|
||||||
gh_token: ${{ secrets.GH_TOKEN }}
|
gh_token: ${{ secrets.GH_TOKEN }}
|
||||||
organization: paperless-ngx
|
organization: paperless-ngx
|
||||||
project_id: 2
|
project_id: 2
|
||||||
resource_node_id: ${{ github.event.pull_request.node_id }}
|
resource_node_id: ${{ github.event.pull_request.node_id }}
|
||||||
status_value: ${{ env.in_progress }} # Target status
|
status_value: "Needs Review" # Target status
|
||||||
|
- name: Label PR with release-drafter
|
||||||
|
uses: release-drafter/release-drafter@v5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
4
Pipfile
4
Pipfile
@ -33,6 +33,8 @@ redis = "*"
|
|||||||
scikit-learn = "~=1.1"
|
scikit-learn = "~=1.1"
|
||||||
# Pin this until piwheels is building 1.9 (see https://www.piwheels.org/project/scipy/)
|
# Pin this until piwheels is building 1.9 (see https://www.piwheels.org/project/scipy/)
|
||||||
scipy = "==1.8.1"
|
scipy = "==1.8.1"
|
||||||
|
# https://github.com/paperless-ngx/paperless-ngx/issues/1364
|
||||||
|
numpy = "==1.22.3"
|
||||||
whitenoise = "~=6.2"
|
whitenoise = "~=6.2"
|
||||||
watchdog = "~=2.1"
|
watchdog = "~=2.1"
|
||||||
whoosh="~=2.7"
|
whoosh="~=2.7"
|
||||||
@ -51,8 +53,8 @@ concurrent-log-handler = "*"
|
|||||||
"importlib-resources" = {version = "*", markers = "python_version < '3.9'"}
|
"importlib-resources" = {version = "*", markers = "python_version < '3.9'"}
|
||||||
zipp = {version = "*", markers = "python_version < '3.9'"}
|
zipp = {version = "*", markers = "python_version < '3.9'"}
|
||||||
pyzbar = "*"
|
pyzbar = "*"
|
||||||
pdf2image = "*"
|
|
||||||
mysqlclient = "*"
|
mysqlclient = "*"
|
||||||
|
setproctitle = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
coveralls = "*"
|
coveralls = "*"
|
||||||
|
154
Pipfile.lock
generated
154
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "af0a0a5b996c11ad95266e98200640fd77648ec0f337ac3eb4e4b5ca1c714a0e"
|
"sha256": "896665b8ff6d8a99af44b729c581033add1ba5cbd927723ef275649491c92a4f"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
@ -39,7 +39,7 @@
|
|||||||
"sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1",
|
"sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1",
|
||||||
"sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"
|
"sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==1.2.3"
|
"version": "==1.2.3"
|
||||||
},
|
},
|
||||||
"asgiref": {
|
"asgiref": {
|
||||||
@ -55,7 +55,7 @@
|
|||||||
"sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
|
"sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
|
||||||
"sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
|
"sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==4.0.2"
|
"version": "==4.0.2"
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
@ -208,7 +208,7 @@
|
|||||||
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
||||||
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
|
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==2.1.1"
|
"version": "==2.1.1"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
@ -271,7 +271,7 @@
|
|||||||
"sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd",
|
"sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd",
|
||||||
"sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"
|
"sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==38.0.1"
|
"version": "==38.0.1"
|
||||||
},
|
},
|
||||||
"daphne": {
|
"daphne": {
|
||||||
@ -279,7 +279,7 @@
|
|||||||
"sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f",
|
"sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f",
|
||||||
"sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"
|
"sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==3.0.2"
|
"version": "==3.0.2"
|
||||||
},
|
},
|
||||||
"dateparser": {
|
"dateparser": {
|
||||||
@ -348,7 +348,7 @@
|
|||||||
"django-q": {
|
"django-q": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"git": "https://github.com/paperless-ngx/django-q.git",
|
"git": "https://github.com/paperless-ngx/django-q.git",
|
||||||
"ref": "21ef9116f6386cf985ccd812dde4111efcfd1ba9"
|
"ref": "8b5289d8caf36f67fb99448e76ead20d5b498c1b"
|
||||||
},
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -390,7 +390,7 @@
|
|||||||
"sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06",
|
"sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06",
|
||||||
"sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"
|
"sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==0.13.0"
|
"version": "==0.13.0"
|
||||||
},
|
},
|
||||||
"hiredis": {
|
"hiredis": {
|
||||||
@ -437,7 +437,7 @@
|
|||||||
"sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
|
"sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
|
||||||
"sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
|
"sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==2.0.0"
|
"version": "==2.0.0"
|
||||||
},
|
},
|
||||||
"httptools": {
|
"httptools": {
|
||||||
@ -553,7 +553,7 @@
|
|||||||
"sha256:4158fcecd13733f8be669be0683b96ebdbbd38d23559f54dca7205aea1bf1e35",
|
"sha256:4158fcecd13733f8be669be0683b96ebdbbd38d23559f54dca7205aea1bf1e35",
|
||||||
"sha256:f21f109b3c7ff9d95f8387f752d0d9c34a02aa2f7060c2135f465da0e5160ff6"
|
"sha256:f21f109b3c7ff9d95f8387f752d0d9c34a02aa2f7060c2135f465da0e5160ff6"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==1.1.0"
|
"version": "==1.1.0"
|
||||||
},
|
},
|
||||||
"langdetect": {
|
"langdetect": {
|
||||||
@ -712,37 +712,57 @@
|
|||||||
},
|
},
|
||||||
"numpy": {
|
"numpy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
"sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676",
|
||||||
|
"sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4",
|
||||||
"sha256:17e5226674f6ea79e14e3b91bfbc153fdf3ac13f5cc54ee7bc8fdbe820a32da0",
|
"sha256:17e5226674f6ea79e14e3b91bfbc153fdf3ac13f5cc54ee7bc8fdbe820a32da0",
|
||||||
|
"sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce",
|
||||||
"sha256:2bd879d3ca4b6f39b7770829f73278b7c5e248c91d538aab1e506c628353e47f",
|
"sha256:2bd879d3ca4b6f39b7770829f73278b7c5e248c91d538aab1e506c628353e47f",
|
||||||
|
"sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123",
|
||||||
|
"sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1",
|
||||||
|
"sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e",
|
||||||
"sha256:4f41f5bf20d9a521f8cab3a34557cd77b6f205ab2116651f12959714494268b0",
|
"sha256:4f41f5bf20d9a521f8cab3a34557cd77b6f205ab2116651f12959714494268b0",
|
||||||
"sha256:5593f67e66dea4e237f5af998d31a43e447786b2154ba1ad833676c788f37cde",
|
"sha256:5593f67e66dea4e237f5af998d31a43e447786b2154ba1ad833676c788f37cde",
|
||||||
|
"sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5",
|
||||||
|
"sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d",
|
||||||
"sha256:5e28cd64624dc2354a349152599e55308eb6ca95a13ce6a7d5679ebff2962913",
|
"sha256:5e28cd64624dc2354a349152599e55308eb6ca95a13ce6a7d5679ebff2962913",
|
||||||
"sha256:633679a472934b1c20a12ed0c9a6c9eb167fbb4cb89031939bfd03dd9dbc62b8",
|
"sha256:633679a472934b1c20a12ed0c9a6c9eb167fbb4cb89031939bfd03dd9dbc62b8",
|
||||||
|
"sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a",
|
||||||
"sha256:806970e69106556d1dd200e26647e9bee5e2b3f1814f9da104a943e8d548ca38",
|
"sha256:806970e69106556d1dd200e26647e9bee5e2b3f1814f9da104a943e8d548ca38",
|
||||||
"sha256:806cc25d5c43e240db709875e947076b2826f47c2c340a5a2f36da5bb10c58d6",
|
"sha256:806cc25d5c43e240db709875e947076b2826f47c2c340a5a2f36da5bb10c58d6",
|
||||||
"sha256:8247f01c4721479e482cc2f9f7d973f3f47810cbc8c65e38fd1bbd3141cc9842",
|
"sha256:8247f01c4721479e482cc2f9f7d973f3f47810cbc8c65e38fd1bbd3141cc9842",
|
||||||
|
"sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab",
|
||||||
"sha256:8ebf7e194b89bc66b78475bd3624d92980fca4e5bb86dda08d677d786fefc414",
|
"sha256:8ebf7e194b89bc66b78475bd3624d92980fca4e5bb86dda08d677d786fefc414",
|
||||||
"sha256:8ecb818231afe5f0f568c81f12ce50f2b828ff2b27487520d85eb44c71313b9e",
|
"sha256:8ecb818231afe5f0f568c81f12ce50f2b828ff2b27487520d85eb44c71313b9e",
|
||||||
"sha256:8f9d84a24889ebb4c641a9b99e54adb8cab50972f0166a3abc14c3b93163f074",
|
"sha256:8f9d84a24889ebb4c641a9b99e54adb8cab50972f0166a3abc14c3b93163f074",
|
||||||
"sha256:909c56c4d4341ec8315291a105169d8aae732cfb4c250fbc375a1efb7a844f8f",
|
"sha256:909c56c4d4341ec8315291a105169d8aae732cfb4c250fbc375a1efb7a844f8f",
|
||||||
|
"sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75",
|
||||||
|
"sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168",
|
||||||
"sha256:9b83d48e464f393d46e8dd8171687394d39bc5abfe2978896b77dc2604e8635d",
|
"sha256:9b83d48e464f393d46e8dd8171687394d39bc5abfe2978896b77dc2604e8635d",
|
||||||
|
"sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4",
|
||||||
"sha256:ac987b35df8c2a2eab495ee206658117e9ce867acf3ccb376a19e83070e69418",
|
"sha256:ac987b35df8c2a2eab495ee206658117e9ce867acf3ccb376a19e83070e69418",
|
||||||
"sha256:b78d00e48261fbbd04aa0d7427cf78d18401ee0abd89c7559bbf422e5b1c7d01",
|
"sha256:b78d00e48261fbbd04aa0d7427cf78d18401ee0abd89c7559bbf422e5b1c7d01",
|
||||||
"sha256:b8b97a8a87cadcd3f94659b4ef6ec056261fa1e1c3317f4193ac231d4df70215",
|
"sha256:b8b97a8a87cadcd3f94659b4ef6ec056261fa1e1c3317f4193ac231d4df70215",
|
||||||
"sha256:bd5b7ccae24e3d8501ee5563e82febc1771e73bd268eef82a1e8d2b4d556ae66",
|
"sha256:bd5b7ccae24e3d8501ee5563e82febc1771e73bd268eef82a1e8d2b4d556ae66",
|
||||||
"sha256:bdc02c0235b261925102b1bd586579b7158e9d0d07ecb61148a1799214a4afd5",
|
"sha256:bdc02c0235b261925102b1bd586579b7158e9d0d07ecb61148a1799214a4afd5",
|
||||||
"sha256:be6b350dfbc7f708d9d853663772a9310783ea58f6035eec649fb9c4371b5389",
|
"sha256:be6b350dfbc7f708d9d853663772a9310783ea58f6035eec649fb9c4371b5389",
|
||||||
|
"sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f",
|
||||||
"sha256:c403c81bb8ffb1c993d0165a11493fd4bf1353d258f6997b3ee288b0a48fce77",
|
"sha256:c403c81bb8ffb1c993d0165a11493fd4bf1353d258f6997b3ee288b0a48fce77",
|
||||||
"sha256:cf8c6aed12a935abf2e290860af8e77b26a042eb7f2582ff83dc7ed5f963340c",
|
"sha256:cf8c6aed12a935abf2e290860af8e77b26a042eb7f2582ff83dc7ed5f963340c",
|
||||||
"sha256:d98addfd3c8728ee8b2c49126f3c44c703e2b005d4a95998e2167af176a9e722",
|
"sha256:d98addfd3c8728ee8b2c49126f3c44c703e2b005d4a95998e2167af176a9e722",
|
||||||
|
"sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18",
|
||||||
"sha256:dc76bca1ca98f4b122114435f83f1fcf3c0fe48e4e6f660e07996abf2f53903c",
|
"sha256:dc76bca1ca98f4b122114435f83f1fcf3c0fe48e4e6f660e07996abf2f53903c",
|
||||||
"sha256:dec198619b7dbd6db58603cd256e092bcadef22a796f778bf87f8592b468441d",
|
"sha256:dec198619b7dbd6db58603cd256e092bcadef22a796f778bf87f8592b468441d",
|
||||||
"sha256:df28dda02c9328e122661f399f7655cdcbcf22ea42daa3650a26bce08a187450",
|
"sha256:df28dda02c9328e122661f399f7655cdcbcf22ea42daa3650a26bce08a187450",
|
||||||
"sha256:e603ca1fb47b913942f3e660a15e55a9ebca906857edfea476ae5f0fe9b457d5",
|
"sha256:e603ca1fb47b913942f3e660a15e55a9ebca906857edfea476ae5f0fe9b457d5",
|
||||||
"sha256:ecfdd68d334a6b97472ed032b5b37a30d8217c097acfff15e8452c710e775524"
|
"sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62",
|
||||||
|
"sha256:ecfdd68d334a6b97472ed032b5b37a30d8217c097acfff15e8452c710e775524",
|
||||||
|
"sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe",
|
||||||
|
"sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430",
|
||||||
|
"sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802",
|
||||||
|
"sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"index": "pypi",
|
||||||
"version": "==1.23.2"
|
"version": "==1.22.3"
|
||||||
},
|
},
|
||||||
"ocrmypdf": {
|
"ocrmypdf": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -757,7 +777,7 @@
|
|||||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==21.3"
|
"version": "==21.3"
|
||||||
},
|
},
|
||||||
"pathvalidate": {
|
"pathvalidate": {
|
||||||
@ -768,14 +788,6 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.5.2"
|
"version": "==2.5.2"
|
||||||
},
|
},
|
||||||
"pdf2image": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:84f79f2b8fad943e36323ea4e937fcb05f26ded0caa0a01181df66049e42fb65",
|
|
||||||
"sha256:d58ed94d978a70c73c2bb7fdf8acbaf2a7089c29ff8141be5f45433c0c4293bb"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==1.16.0"
|
|
||||||
},
|
|
||||||
"pdfminer.six": {
|
"pdfminer.six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5a64c924410ac48501d6060b21638bf401db69f5b1bd57207df7fbc070ac8ae2",
|
"sha256:5a64c924410ac48501d6060b21638bf401db69f5b1bd57207df7fbc070ac8ae2",
|
||||||
@ -894,7 +906,7 @@
|
|||||||
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
||||||
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==1.0.0"
|
"version": "==1.0.0"
|
||||||
},
|
},
|
||||||
"portalocker": {
|
"portalocker": {
|
||||||
@ -1035,6 +1047,7 @@
|
|||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
|
||||||
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
|
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
|
||||||
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
|
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
|
||||||
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
|
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
|
||||||
@ -1046,26 +1059,32 @@
|
|||||||
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
|
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
|
||||||
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
|
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
|
||||||
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
|
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
|
||||||
|
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
|
||||||
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
|
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
|
||||||
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
|
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
|
||||||
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
|
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
|
||||||
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
|
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
|
||||||
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
|
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
|
||||||
|
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
|
||||||
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
|
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
|
||||||
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
|
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
|
||||||
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
|
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
|
||||||
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
|
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
|
||||||
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
|
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
|
||||||
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
|
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
|
||||||
|
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
|
||||||
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
|
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
|
||||||
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
|
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
|
||||||
|
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
|
||||||
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
|
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
|
||||||
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
|
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
|
||||||
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
|
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
|
||||||
|
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
|
||||||
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
|
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
|
||||||
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
|
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
|
||||||
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
|
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
|
||||||
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
|
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
|
||||||
|
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
|
||||||
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
|
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
|
||||||
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
|
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
|
||||||
],
|
],
|
||||||
@ -1165,7 +1184,7 @@
|
|||||||
"sha256:fbc88d3ba402b5d041d204ec2449c4078898f89c4a6e6f0ed1c1a510ef1e221d",
|
"sha256:fbc88d3ba402b5d041d204ec2449c4078898f89c4a6e6f0ed1c1a510ef1e221d",
|
||||||
"sha256:fbd3fe37353c62fd0eb19fb76f78aa693716262bcd5f9c14bb9e5aca4b3f0dc4"
|
"sha256:fbd3fe37353c62fd0eb19fb76f78aa693716262bcd5f9c14bb9e5aca4b3f0dc4"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==2022.3.2"
|
"version": "==2022.3.2"
|
||||||
},
|
},
|
||||||
"reportlab": {
|
"reportlab": {
|
||||||
@ -1278,6 +1297,72 @@
|
|||||||
],
|
],
|
||||||
"version": "==21.1.0"
|
"version": "==21.1.0"
|
||||||
},
|
},
|
||||||
|
"setproctitle": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b",
|
||||||
|
"sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9",
|
||||||
|
"sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31",
|
||||||
|
"sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83",
|
||||||
|
"sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f",
|
||||||
|
"sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca",
|
||||||
|
"sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494",
|
||||||
|
"sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe",
|
||||||
|
"sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172",
|
||||||
|
"sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084",
|
||||||
|
"sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4",
|
||||||
|
"sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1",
|
||||||
|
"sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c",
|
||||||
|
"sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973",
|
||||||
|
"sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c",
|
||||||
|
"sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202",
|
||||||
|
"sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5",
|
||||||
|
"sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65",
|
||||||
|
"sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18",
|
||||||
|
"sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13",
|
||||||
|
"sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005",
|
||||||
|
"sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02",
|
||||||
|
"sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7",
|
||||||
|
"sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665",
|
||||||
|
"sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f",
|
||||||
|
"sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1",
|
||||||
|
"sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989",
|
||||||
|
"sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827",
|
||||||
|
"sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42",
|
||||||
|
"sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927",
|
||||||
|
"sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122",
|
||||||
|
"sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9",
|
||||||
|
"sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f",
|
||||||
|
"sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d",
|
||||||
|
"sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98",
|
||||||
|
"sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796",
|
||||||
|
"sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb",
|
||||||
|
"sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd",
|
||||||
|
"sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe",
|
||||||
|
"sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806",
|
||||||
|
"sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484",
|
||||||
|
"sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e",
|
||||||
|
"sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575",
|
||||||
|
"sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76",
|
||||||
|
"sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246",
|
||||||
|
"sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb",
|
||||||
|
"sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099",
|
||||||
|
"sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c",
|
||||||
|
"sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258",
|
||||||
|
"sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865",
|
||||||
|
"sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b",
|
||||||
|
"sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d",
|
||||||
|
"sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10",
|
||||||
|
"sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f",
|
||||||
|
"sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94",
|
||||||
|
"sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6",
|
||||||
|
"sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963",
|
||||||
|
"sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4",
|
||||||
|
"sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8",
|
||||||
|
"sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.3.2"
|
||||||
|
},
|
||||||
"setuptools": {
|
"setuptools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
|
"sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
|
||||||
@ -1315,7 +1400,7 @@
|
|||||||
"sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b",
|
"sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b",
|
||||||
"sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380"
|
"sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==3.1.0"
|
"version": "==3.1.0"
|
||||||
},
|
},
|
||||||
"tika": {
|
"tika": {
|
||||||
@ -1349,7 +1434,7 @@
|
|||||||
"sha256:2e4582b70f04b2345908254684a984206c0d9b50e3074a24a4c55aba21d24d01",
|
"sha256:2e4582b70f04b2345908254684a984206c0d9b50e3074a24a4c55aba21d24d01",
|
||||||
"sha256:41223af4a9d5726e645a8ee82480f413e5e300dd257db94bc38ae12ea48fb2e5"
|
"sha256:41223af4a9d5726e645a8ee82480f413e5e300dd257db94bc38ae12ea48fb2e5"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==22.2.1"
|
"version": "==22.2.1"
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
@ -1365,7 +1450,7 @@
|
|||||||
"sha256:21f4f0d7241572efa7f7a4fdabb052e61b55dc48274e6842697ccdf5253e5451",
|
"sha256:21f4f0d7241572efa7f7a4fdabb052e61b55dc48274e6842697ccdf5253e5451",
|
||||||
"sha256:c3119520447d68ef3eb8187a55a4f44fa455f30eb1b4238fa5691ba094f2b05b"
|
"sha256:c3119520447d68ef3eb8187a55a4f44fa455f30eb1b4238fa5691ba094f2b05b"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==2022.2"
|
"version": "==2022.2"
|
||||||
},
|
},
|
||||||
"tzlocal": {
|
"tzlocal": {
|
||||||
@ -1373,7 +1458,7 @@
|
|||||||
"sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745",
|
"sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745",
|
||||||
"sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"
|
"sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==4.2"
|
"version": "==4.2"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
@ -1757,7 +1842,7 @@
|
|||||||
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
||||||
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
|
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==2.1.1"
|
"version": "==2.1.1"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
@ -2036,7 +2121,7 @@
|
|||||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==21.3"
|
"version": "==21.3"
|
||||||
},
|
},
|
||||||
"pathspec": {
|
"pathspec": {
|
||||||
@ -2060,7 +2145,7 @@
|
|||||||
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
||||||
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==1.0.0"
|
"version": "==1.0.0"
|
||||||
},
|
},
|
||||||
"pre-commit": {
|
"pre-commit": {
|
||||||
@ -2175,6 +2260,7 @@
|
|||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
|
||||||
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
|
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
|
||||||
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
|
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
|
||||||
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
|
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
|
||||||
@ -2186,26 +2272,32 @@
|
|||||||
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
|
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
|
||||||
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
|
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
|
||||||
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
|
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
|
||||||
|
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
|
||||||
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
|
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
|
||||||
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
|
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
|
||||||
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
|
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
|
||||||
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
|
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
|
||||||
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
|
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
|
||||||
|
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
|
||||||
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
|
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
|
||||||
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
|
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
|
||||||
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
|
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
|
||||||
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
|
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
|
||||||
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
|
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
|
||||||
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
|
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
|
||||||
|
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
|
||||||
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
|
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
|
||||||
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
|
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
|
||||||
|
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
|
||||||
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
|
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
|
||||||
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
|
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
|
||||||
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
|
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
|
||||||
|
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
|
||||||
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
|
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
|
||||||
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
|
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
|
||||||
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
|
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
|
||||||
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
|
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
|
||||||
|
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
|
||||||
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
|
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
|
||||||
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
|
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
|
||||||
],
|
],
|
||||||
|
@ -36,3 +36,7 @@
|
|||||||
# The default language to use for OCR. Set this to the language most of your
|
# The default language to use for OCR. Set this to the language most of your
|
||||||
# documents are written in.
|
# documents are written in.
|
||||||
#PAPERLESS_OCR_LANGUAGE=eng
|
#PAPERLESS_OCR_LANGUAGE=eng
|
||||||
|
|
||||||
|
# Set if accessing paperless via a domain subpath e.g. https://domain.com/PATHPREFIX and using a reverse-proxy like traefik or nginx
|
||||||
|
#PAPERLESS_FORCE_SCRIPT_NAME=/PATHPREFIX
|
||||||
|
#PAPERLESS_STATIC_URL=/PATHPREFIX/static/ # trailing slash required
|
||||||
|
@ -50,6 +50,7 @@ map_folders() {
|
|||||||
# Export these so they can be used in docker-prepare.sh
|
# Export these so they can be used in docker-prepare.sh
|
||||||
export DATA_DIR="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}"
|
export DATA_DIR="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}"
|
||||||
export MEDIA_ROOT_DIR="${PAPERLESS_MEDIA_ROOT:-/usr/src/paperless/media}"
|
export MEDIA_ROOT_DIR="${PAPERLESS_MEDIA_ROOT:-/usr/src/paperless/media}"
|
||||||
|
export CONSUME_DIR="${PAPERLESS_CONSUMPTION_DIR:-/usr/src/paperless/consume}"
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -77,7 +78,11 @@ initialize() {
|
|||||||
|
|
||||||
local export_dir="/usr/src/paperless/export"
|
local export_dir="/usr/src/paperless/export"
|
||||||
|
|
||||||
for dir in "${export_dir}" "${DATA_DIR}" "${DATA_DIR}/index" "${MEDIA_ROOT_DIR}" "${MEDIA_ROOT_DIR}/documents" "${MEDIA_ROOT_DIR}/documents/originals" "${MEDIA_ROOT_DIR}/documents/thumbnails"; do
|
for dir in \
|
||||||
|
"${export_dir}" \
|
||||||
|
"${DATA_DIR}" "${DATA_DIR}/index" \
|
||||||
|
"${MEDIA_ROOT_DIR}" "${MEDIA_ROOT_DIR}/documents" "${MEDIA_ROOT_DIR}/documents/originals" "${MEDIA_ROOT_DIR}/documents/thumbnails" \
|
||||||
|
"${CONSUME_DIR}"; do
|
||||||
if [[ ! -d "${dir}" ]]; then
|
if [[ ! -d "${dir}" ]]; then
|
||||||
echo "Creating directory ${dir}"
|
echo "Creating directory ${dir}"
|
||||||
mkdir "${dir}"
|
mkdir "${dir}"
|
||||||
@ -91,7 +96,11 @@ initialize() {
|
|||||||
set +e
|
set +e
|
||||||
echo "Adjusting permissions of paperless files. This may take a while."
|
echo "Adjusting permissions of paperless files. This may take a while."
|
||||||
chown -R paperless:paperless ${tmp_dir}
|
chown -R paperless:paperless ${tmp_dir}
|
||||||
for dir in "${export_dir}" "${DATA_DIR}" "${MEDIA_ROOT_DIR}"; do
|
for dir in \
|
||||||
|
"${export_dir}" \
|
||||||
|
"${DATA_DIR}" \
|
||||||
|
"${MEDIA_ROOT_DIR}" \
|
||||||
|
"${CONSUME_DIR}"; do
|
||||||
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown paperless:paperless {} +
|
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown paperless:paperless {} +
|
||||||
done
|
done
|
||||||
set -e
|
set -e
|
||||||
|
@ -12,4 +12,4 @@ if [ "$(id -u)" == "$(id -u paperless)" ]; then
|
|||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
/usr/local/bin/supervisord -c /etc/supervisord.conf "${rootless_args[@]}"
|
exec /usr/local/bin/supervisord -c /etc/supervisord.conf "${rootless_args[@]}"
|
||||||
|
@ -27,6 +27,12 @@ PAPERLESS_REDIS=<url>
|
|||||||
This is required for processing scheduled tasks such as email fetching, index
|
This is required for processing scheduled tasks such as email fetching, index
|
||||||
optimization and for training the automatic document matcher.
|
optimization and for training the automatic document matcher.
|
||||||
|
|
||||||
|
* If your Redis server needs login credentials PAPERLESS_REDIS = ``redis://<username>:<password>@<host>:<port>``
|
||||||
|
|
||||||
|
* With the requirepass option PAPERLESS_REDIS = ``redis://:<password>@<host>:<port>``
|
||||||
|
|
||||||
|
`More information on securing your Redis Instance <https://redis.io/docs/getting-started/#securing-redis>`_.
|
||||||
|
|
||||||
Defaults to redis://localhost:6379.
|
Defaults to redis://localhost:6379.
|
||||||
|
|
||||||
PAPERLESS_DBENGINE=<engine_name>
|
PAPERLESS_DBENGINE=<engine_name>
|
||||||
@ -215,9 +221,16 @@ PAPERLESS_FORCE_SCRIPT_NAME=<path>
|
|||||||
PAPERLESS_STATIC_URL=<path>
|
PAPERLESS_STATIC_URL=<path>
|
||||||
Override the STATIC_URL here. Unless you're hosting Paperless off a
|
Override the STATIC_URL here. Unless you're hosting Paperless off a
|
||||||
subdomain like /paperless/, you probably don't need to change this.
|
subdomain like /paperless/, you probably don't need to change this.
|
||||||
|
If you do change it, be sure to include the trailing slash.
|
||||||
|
|
||||||
Defaults to "/static/".
|
Defaults to "/static/".
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When hosting paperless behind a reverse proxy like Traefik or Nginx at a subpath e.g.
|
||||||
|
example.com/paperlessngx you will also need to set ``PAPERLESS_FORCE_SCRIPT_NAME``
|
||||||
|
(see above).
|
||||||
|
|
||||||
PAPERLESS_AUTO_LOGIN_USERNAME=<username>
|
PAPERLESS_AUTO_LOGIN_USERNAME=<username>
|
||||||
Specify a username here so that paperless will automatically perform login
|
Specify a username here so that paperless will automatically perform login
|
||||||
with the selected user.
|
with the selected user.
|
||||||
@ -805,10 +818,10 @@ the program doesn't automatically execute it (ie. the program isn't in your
|
|||||||
$PATH), then you'll need to specify the literal path for that program.
|
$PATH), then you'll need to specify the literal path for that program.
|
||||||
|
|
||||||
PAPERLESS_CONVERT_BINARY=<path>
|
PAPERLESS_CONVERT_BINARY=<path>
|
||||||
Defaults to "/usr/bin/convert".
|
Defaults to "convert".
|
||||||
|
|
||||||
PAPERLESS_GS_BINARY=<path>
|
PAPERLESS_GS_BINARY=<path>
|
||||||
Defaults to "/usr/bin/gs".
|
Defaults to "gs".
|
||||||
|
|
||||||
|
|
||||||
.. _configuration-docker:
|
.. _configuration-docker:
|
||||||
|
@ -1,142 +1,8 @@
|
|||||||
|
|
||||||
.. _scanners:
|
.. _scanners:
|
||||||
|
|
||||||
***********************
|
*******************
|
||||||
Scanner recommendations
|
Scanners & Software
|
||||||
***********************
|
*******************
|
||||||
|
|
||||||
As Paperless operates by watching a folder for new files, doesn't care what
|
Paperless-ngx is compatible with many different scanners and scanning tools. A user-maintained list of scanners and other software is available on `the wiki <https://github.com/paperless-ngx/paperless-ngx/wiki/Scanner-&-Software-Recommendations>`_.
|
||||||
scanner you use, but sometimes finding a scanner that will write to an FTP,
|
|
||||||
NFS, or SMB server can be difficult. This page is here to help you find one
|
|
||||||
that works right for you based on recommendations from other Paperless users.
|
|
||||||
|
|
||||||
Physical scanners
|
|
||||||
=================
|
|
||||||
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brand | Model | Supports | Recommended By |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| | | FTP | SFTP | NFS | SMB | SMTP | API [1]_ | |
|
|
||||||
+=========+===================+=====+======+=====+==========+======+==========+================+
|
|
||||||
| Brother | `ADS-1700W`_ | yes | yes | | yes | yes | |`holzhannes`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `ADS-1600W`_ | yes | | | yes | yes | |`holzhannes`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `ADS-1500W`_ | yes | | | yes | yes | |`danielquinn`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `ADS-1100W`_ | yes | | | | | |`ytzelf`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `ADS-2800W`_ | yes | yes | | yes | yes | |`philpagel`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `MFC-J6930DW`_ | yes | | | | | |`ayounggun`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `MFC-L5850DW`_ | yes | | | | yes | |`holzhannes`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `MFC-L2750DW`_ | yes | | | yes | yes | |`muued`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `MFC-J5910DW`_ | yes | | | | | |`bmsleight`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `MFC-8950DW`_ | yes | | | yes | yes | |`philpagel`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Brother | `MFC-9142CDN`_ | yes | | | yes | | |`REOLDEV`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Canon | `Maxify MB 5350`_ | | | | yes [2]_ | yes | |`eingemaischt`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Fujitsu | `ix500`_ | yes | | | yes | | |`eonist`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Epson | `ES-580W`_ | yes | | | yes | yes | |`fignew`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Epson | `WF-7710DWF`_ | yes | | | yes | | |`Skylinar`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Fujitsu | `S1300i`_ | yes | | | yes | | |`jonaswinkler`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
| Doxie | `Q2`_ | | | | | | yes |`Unkn0wnCat`_ |
|
|
||||||
+---------+-------------------+-----+------+-----+----------+------+----------+----------------+
|
|
||||||
|
|
||||||
.. _MFC-L5850DW: https://www.brother-usa.com/products/mfcl5850dw
|
|
||||||
.. _MFC-L2750DW: https://www.brother.de/drucker/laserdrucker/mfc-l2750dw
|
|
||||||
.. _ADS-1700W: https://www.brother-usa.com/products/ads1700w
|
|
||||||
.. _ADS-1600W: https://www.brother-usa.com/products/ads1600w
|
|
||||||
.. _ADS-1500W: https://www.brother.ca/en/p/ads1500w
|
|
||||||
.. _ADS-1100W: https://support.brother.com/g/b/downloadtop.aspx?c=fr&lang=fr&prod=ads1100w_eu_as_cn
|
|
||||||
.. _ADS-2800W: https://www.brother-usa.com/products/ads2800w
|
|
||||||
.. _Maxify MB 5350: https://www.canon.de/printers/inkjet/maxify/maxify_mb5350/specification.html
|
|
||||||
.. _MFC-J6930DW: https://www.brother.ca/en/p/MFCJ6930DW
|
|
||||||
.. _MFC-J5910DW: https://www.brother.co.uk/printers/inkjet-printers/mfcj5910dw
|
|
||||||
.. _MFC-8950DW: https://www.brother-usa.com/products/mfc8950dw
|
|
||||||
.. _MFC-9142CDN: https://www.brother.co.uk/printers/laser-printers/mfc9140cdn
|
|
||||||
.. _ES-580W: https://epson.com/Support/Scanners/ES-Series/Epson-WorkForce-ES-580W/s/SPT_B11B258201
|
|
||||||
.. _WF-7710DWF: https://www.epson.de/en/products/printers/inkjet-printers/for-home/workforce-wf-7710dwf
|
|
||||||
.. _ix500: http://www.fujitsu.com/us/products/computing/peripheral/scanners/scansnap/ix500/
|
|
||||||
.. _S1300i: https://www.fujitsu.com/global/products/computing/peripheral/scanners/soho/s1300i/
|
|
||||||
.. _Q2: https://www.getdoxie.com/product/doxie-q/
|
|
||||||
|
|
||||||
.. _ayounggun: https://github.com/ayounggun
|
|
||||||
.. _bmsleight: https://github.com/bmsleight
|
|
||||||
.. _danielquinn: https://github.com/danielquinn
|
|
||||||
.. _eonist: https://github.com/eonist
|
|
||||||
.. _fignew: https://github.com/fignew
|
|
||||||
.. _holzhannes: https://github.com/holzhannes
|
|
||||||
.. _jonaswinkler: https://github.com/jonaswinkler
|
|
||||||
.. _REOLDEV: https://github.com/REOLDEV
|
|
||||||
.. _Skylinar: https://github.com/Skylinar
|
|
||||||
.. _ytzelf: https://github.com/ytzelf
|
|
||||||
.. _Unkn0wnCat: https://github.com/Unkn0wnCat
|
|
||||||
.. _muued: https://github.com/muued
|
|
||||||
.. _philpagel: https://github.com/philpagel
|
|
||||||
.. _eingemaischt: https://github.com/eingemaischt
|
|
||||||
|
|
||||||
.. [1] Scanners with API Integration allow to push scanned documents directly to :ref:`Paperless API <api-file_uploads>`, sometimes referred to as Webhook or Document POST.
|
|
||||||
.. [2] Canon Multi Function Printers show strange behavior over SMB. They close and reopen the file after every page. It's recommended to tune the
|
|
||||||
:ref:`polling <configuration-polling>` and :ref:`inotify <configuration-inotify>` configuration values for your scanner. The scanner timeout is 3 minutes, so ``180`` is a good starting point.
|
|
||||||
|
|
||||||
Mobile phone software
|
|
||||||
=====================
|
|
||||||
|
|
||||||
You can use your phone to "scan" documents. The regular camera app will work, but may have too low contrast for OCR to work well. Apps specifically for scanning are recommended.
|
|
||||||
|
|
||||||
+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+
|
|
||||||
| Name | OS | Supports | Recommended By |
|
|
||||||
+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+
|
|
||||||
| | | FTP | NFS | SMB | Email | WebDAV | |
|
|
||||||
+=============================+================+=====+=====+=====+=======+========+==================+
|
|
||||||
| `Office Lens`_ | Android | ? | ? | ? | ? | ? | `jonaswinkler`_ |
|
|
||||||
+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+
|
|
||||||
| `Genius Scan`_ | Android | yes | no | yes | yes | yes | `hannahswain`_ |
|
|
||||||
+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+
|
|
||||||
| `OpenScan`_ | Android | no | no | no | no | no | `benjaminfrank`_ |
|
|
||||||
+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+
|
|
||||||
| `OCR Scanner - QuickScan`_ | iOS | no | no | no | no | yes | `holzhannes`_ |
|
|
||||||
+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+
|
|
||||||
|
|
||||||
On Android, you can use these applications in combination with one of the :ref:`Paperless-ngx compatible apps <usage-mobile_upload>` to "Share" the documents produced by these scanner apps with paperless. On iOS, you can share the scanned documents via iOS-Sharing to other mail, WebDav or FTP apps.
|
|
||||||
|
|
||||||
There is also an iOS Shortcut that allows you to directly upload text, PDF and image documents available here: https://www.icloud.com/shortcuts/d234abc0885040129d9d75fa45fe1154
|
|
||||||
Please note this only works for documents downloaded to iCloud / the device, in other words not directly from a URL.
|
|
||||||
|
|
||||||
.. _Office Lens: https://play.google.com/store/apps/details?id=com.microsoft.office.officelens
|
|
||||||
.. _Genius Scan: https://play.google.com/store/apps/details?id=com.thegrizzlylabs.geniusscan.free
|
|
||||||
.. _OCR Scanner - QuickScan: https://apps.apple.com/us/app/quickscan-scanner-text-ocr/id1513790291
|
|
||||||
.. _OpenScan: https://github.com/Ethereal-Developers-Inc/OpenScan
|
|
||||||
|
|
||||||
.. _hannahswain: https://github.com/hannahswain
|
|
||||||
.. _benjaminfrank: https://github.com/benjaminfrank
|
|
||||||
|
|
||||||
API Scanning Setup
|
|
||||||
==================
|
|
||||||
|
|
||||||
This sections contains information on how to set up scanners to post directly to :ref:`Paperless API <api-file_uploads>`.
|
|
||||||
|
|
||||||
Doxie Q2
|
|
||||||
--------
|
|
||||||
|
|
||||||
This part assumes your Doxie is connected to WiFi and you know its IP.
|
|
||||||
|
|
||||||
1. Open your Doxie web UI by navigating to its IP address
|
|
||||||
2. Navigate to Options -> Webhook
|
|
||||||
3. Set the *URL* to ``https://[your-paperless-ngx-instance]/api/documents/post_document/``
|
|
||||||
4. Set the *File Parameter Name* to ``document``
|
|
||||||
5. Add the username and password to the respective fields (Consider creating a user just for your Doxie)
|
|
||||||
6. Click *Submit* at the bottom of the page
|
|
||||||
|
|
||||||
Congrats, you can now scan directly from your Doxie to your Paperless-ngx instance!
|
|
||||||
|
@ -224,6 +224,7 @@ Install Paperless from Docker Hub
|
|||||||
You can utilize Docker secrets for some configuration settings by
|
You can utilize Docker secrets for some configuration settings by
|
||||||
appending `_FILE` to some configuration values. This is supported currently
|
appending `_FILE` to some configuration values. This is supported currently
|
||||||
only by:
|
only by:
|
||||||
|
|
||||||
* PAPERLESS_DBUSER
|
* PAPERLESS_DBUSER
|
||||||
* PAPERLESS_DBPASS
|
* PAPERLESS_DBPASS
|
||||||
* PAPERLESS_SECRET_KEY
|
* PAPERLESS_SECRET_KEY
|
||||||
|
@ -816,7 +816,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">184</context>
|
<context context-type="linenumber">185</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context>
|
||||||
@ -1015,16 +1015,12 @@
|
|||||||
<context context-type="linenumber">13</context>
|
<context context-type="linenumber">13</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2722549756198502062" datatype="html">
|
|
||||||
<source>Add item</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
|
|
||||||
<context context-type="linenumber">11</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Used for both types, correspondents, storage paths</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="524422427194414813" datatype="html">
|
<trans-unit id="524422427194414813" datatype="html">
|
||||||
<source>Suggestions:</source>
|
<source>Suggestions:</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
||||||
|
<context context-type="linenumber">16</context>
|
||||||
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
|
||||||
<context context-type="linenumber">30</context>
|
<context context-type="linenumber">30</context>
|
||||||
@ -1034,6 +1030,14 @@
|
|||||||
<context context-type="linenumber">42</context>
|
<context context-type="linenumber">42</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="2722549756198502062" datatype="html">
|
||||||
|
<source>Add item</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
|
||||||
|
<context context-type="linenumber">11</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Used for both types, correspondents, storage paths</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="6560126119609945418" datatype="html">
|
<trans-unit id="6560126119609945418" datatype="html">
|
||||||
<source>Add tag</source>
|
<source>Add tag</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
@ -1498,7 +1502,7 @@
|
|||||||
<source>Correspondent</source>
|
<source>Correspondent</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">78</context>
|
<context context-type="linenumber">79</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||||
@ -1521,7 +1525,7 @@
|
|||||||
<source>Document type</source>
|
<source>Document type</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">80</context>
|
<context context-type="linenumber">81</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||||
@ -1544,7 +1548,7 @@
|
|||||||
<source>Storage path</source>
|
<source>Storage path</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">82</context>
|
<context context-type="linenumber">83</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||||
@ -1563,21 +1567,21 @@
|
|||||||
<source>Default</source>
|
<source>Default</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">83</context>
|
<context context-type="linenumber">84</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6205355627445317276" datatype="html">
|
<trans-unit id="6205355627445317276" datatype="html">
|
||||||
<source>Content</source>
|
<source>Content</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">90</context>
|
<context context-type="linenumber">91</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="218403386307979629" datatype="html">
|
<trans-unit id="218403386307979629" datatype="html">
|
||||||
<source>Metadata</source>
|
<source>Metadata</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">99</context>
|
<context context-type="linenumber">100</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/metadata-collapse/metadata-collapse.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/metadata-collapse/metadata-collapse.component.ts</context>
|
||||||
@ -1588,95 +1592,95 @@
|
|||||||
<source>Date modified</source>
|
<source>Date modified</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">105</context>
|
<context context-type="linenumber">106</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6392918669949841614" datatype="html">
|
<trans-unit id="6392918669949841614" datatype="html">
|
||||||
<source>Date added</source>
|
<source>Date added</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">109</context>
|
<context context-type="linenumber">110</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="146828917013192897" datatype="html">
|
<trans-unit id="146828917013192897" datatype="html">
|
||||||
<source>Media filename</source>
|
<source>Media filename</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">113</context>
|
<context context-type="linenumber">114</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4500855521601039868" datatype="html">
|
<trans-unit id="4500855521601039868" datatype="html">
|
||||||
<source>Original filename</source>
|
<source>Original filename</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">117</context>
|
<context context-type="linenumber">118</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7985558498848210210" datatype="html">
|
<trans-unit id="7985558498848210210" datatype="html">
|
||||||
<source>Original MD5 checksum</source>
|
<source>Original MD5 checksum</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">121</context>
|
<context context-type="linenumber">122</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5888243105821763422" datatype="html">
|
<trans-unit id="5888243105821763422" datatype="html">
|
||||||
<source>Original file size</source>
|
<source>Original file size</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">125</context>
|
<context context-type="linenumber">126</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2696647325713149563" datatype="html">
|
<trans-unit id="2696647325713149563" datatype="html">
|
||||||
<source>Original mime type</source>
|
<source>Original mime type</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">129</context>
|
<context context-type="linenumber">130</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="342875990758166588" datatype="html">
|
<trans-unit id="342875990758166588" datatype="html">
|
||||||
<source>Archive MD5 checksum</source>
|
<source>Archive MD5 checksum</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">133</context>
|
<context context-type="linenumber">134</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6033581412811562084" datatype="html">
|
<trans-unit id="6033581412811562084" datatype="html">
|
||||||
<source>Archive file size</source>
|
<source>Archive file size</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">137</context>
|
<context context-type="linenumber">138</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6992781481378431874" datatype="html">
|
<trans-unit id="6992781481378431874" datatype="html">
|
||||||
<source>Original document metadata</source>
|
<source>Original document metadata</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">143</context>
|
<context context-type="linenumber">144</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2846565152091361585" datatype="html">
|
<trans-unit id="2846565152091361585" datatype="html">
|
||||||
<source>Archived document metadata</source>
|
<source>Archived document metadata</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">144</context>
|
<context context-type="linenumber">145</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8191371354890763172" datatype="html">
|
<trans-unit id="8191371354890763172" datatype="html">
|
||||||
<source>Enter Password</source>
|
<source>Enter Password</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">166</context>
|
<context context-type="linenumber">167</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">202</context>
|
<context context-type="linenumber">203</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3807699453257291879" datatype="html">
|
<trans-unit id="3807699453257291879" datatype="html">
|
||||||
<source>Comments</source>
|
<source>Comments</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">173</context>
|
<context context-type="linenumber">174</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||||
@ -1687,14 +1691,14 @@
|
|||||||
<source>Discard</source>
|
<source>Discard</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">182</context>
|
<context context-type="linenumber">183</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5129524307369213584" datatype="html">
|
<trans-unit id="5129524307369213584" datatype="html">
|
||||||
<source>Save & next</source>
|
<source>Save & next</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">183</context>
|
<context context-type="linenumber">184</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9021887951960049161" datatype="html">
|
<trans-unit id="9021887951960049161" datatype="html">
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul ngbNav class="order-sm-3">
|
<ul ngbNav class="order-sm-3">
|
||||||
<li ngbDropdown class="nav-item dropdown">
|
<li ngbDropdown class="nav-item dropdown">
|
||||||
<button class="btn" id="userDropdown" ngbDropdownToggle>
|
<button class="btn border-0" id="userDropdown" ngbDropdownToggle>
|
||||||
<span class="small me-2 d-none d-sm-inline">
|
<span class="small me-2 d-none d-sm-inline">
|
||||||
{{this.settingsService.displayName}}
|
{{this.settingsService.displayName}}
|
||||||
</span>
|
</span>
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
Please enter a comment.
|
Please enter a comment.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2 d-flex justify-content-end">
|
<div class="form-group mt-2 d-flex justify-content-end align-items-center">
|
||||||
|
<div *ngIf="networkActive" class="spinner-border spinner-border-sm fw-normal me-auto" role="status"></div>
|
||||||
<button type="button" class="btn btn-primary btn-sm" [disabled]="networkActive" (click)="addComment()" i18n>Add comment</button>
|
<button type="button" class="btn btn-primary btn-sm" [disabled]="networkActive" (click)="addComment()" i18n>Add comment</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { DocumentCommentsService } from 'src/app/services/rest/document-comments.service'
|
import { DocumentCommentsService } from 'src/app/services/rest/document-comments.service'
|
||||||
import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'
|
import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'
|
||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
@ -10,7 +10,7 @@ import { ToastService } from 'src/app/services/toast.service'
|
|||||||
templateUrl: './document-comments.component.html',
|
templateUrl: './document-comments.component.html',
|
||||||
styleUrls: ['./document-comments.component.scss'],
|
styleUrls: ['./document-comments.component.scss'],
|
||||||
})
|
})
|
||||||
export class DocumentCommentsComponent implements OnInit {
|
export class DocumentCommentsComponent {
|
||||||
commentForm: FormGroup = new FormGroup({
|
commentForm: FormGroup = new FormGroup({
|
||||||
newComment: new FormControl(''),
|
newComment: new FormControl(''),
|
||||||
})
|
})
|
||||||
@ -19,19 +19,30 @@ export class DocumentCommentsComponent implements OnInit {
|
|||||||
comments: PaperlessDocumentComment[] = []
|
comments: PaperlessDocumentComment[] = []
|
||||||
newCommentError: boolean = false
|
newCommentError: boolean = false
|
||||||
|
|
||||||
|
private _documentId: number
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
documentId: number
|
set documentId(id: number) {
|
||||||
|
if (id != this._documentId) {
|
||||||
|
this._documentId = id
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private commentsService: DocumentCommentsService,
|
private commentsService: DocumentCommentsService,
|
||||||
private toastService: ToastService
|
private toastService: ToastService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
update(): void {
|
||||||
|
this.networkActive = true
|
||||||
this.commentsService
|
this.commentsService
|
||||||
.getComments(this.documentId)
|
.getComments(this._documentId)
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe((comments) => (this.comments = comments))
|
.subscribe((comments) => {
|
||||||
|
this.comments = comments
|
||||||
|
this.networkActive = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addComment() {
|
addComment() {
|
||||||
@ -45,7 +56,7 @@ export class DocumentCommentsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
this.newCommentError = false
|
this.newCommentError = false
|
||||||
this.networkActive = true
|
this.networkActive = true
|
||||||
this.commentsService.addComment(this.documentId, comment).subscribe({
|
this.commentsService.addComment(this._documentId, comment).subscribe({
|
||||||
next: (result) => {
|
next: (result) => {
|
||||||
this.comments = result
|
this.comments = result
|
||||||
this.commentForm.get('newComment').reset()
|
this.commentForm.get('newComment').reset()
|
||||||
@ -61,7 +72,7 @@ export class DocumentCommentsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteComment(commentId: number) {
|
deleteComment(commentId: number) {
|
||||||
this.commentsService.deleteComment(this.documentId, commentId).subscribe({
|
this.commentsService.deleteComment(this._documentId, commentId).subscribe({
|
||||||
next: (result) => {
|
next: (result) => {
|
||||||
this.comments = result
|
this.comments = result
|
||||||
this.networkActive = false
|
this.networkActive = false
|
||||||
|
@ -80,7 +80,7 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
top: 0;
|
top: .2rem;
|
||||||
right: 0;
|
right: 0;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
row-gap: .2rem;
|
row-gap: .2rem;
|
||||||
|
@ -5,7 +5,7 @@ export const environment = {
|
|||||||
apiBaseUrl: document.baseURI + 'api/',
|
apiBaseUrl: document.baseURI + 'api/',
|
||||||
apiVersion: '2',
|
apiVersion: '2',
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
version: '1.8.1-dev',
|
version: '1.9.0',
|
||||||
webSocketHost: window.location.host,
|
webSocketHost: window.location.host,
|
||||||
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
||||||
webSocketBaseUrl: base_url.pathname + 'ws/',
|
webSocketBaseUrl: base_url.pathname + 'ws/',
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -345,7 +345,7 @@
|
|||||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||||
<context context-type="linenumber">146</context>
|
<context context-type="linenumber">146</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Début de l'envoi …</target>
|
<target state="translated">Début du téléversement...</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2173456130768795374" datatype="html">
|
<trans-unit id="2173456130768795374" datatype="html">
|
||||||
<source>Paperless-ngx</source>
|
<source>Paperless-ngx</source>
|
||||||
|
@ -1451,7 +1451,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
|
||||||
<context context-type="linenumber">4</context>
|
<context context-type="linenumber">4</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Enter comment</target>
|
<target state="translated">Vnesite komentar</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4025397324401332794" datatype="html">
|
<trans-unit id="4025397324401332794" datatype="html">
|
||||||
<source> Please enter a comment. </source>
|
<source> Please enter a comment. </source>
|
||||||
@ -1459,7 +1459,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
|
||||||
<context context-type="linenumber">5,7</context>
|
<context context-type="linenumber">5,7</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation"> Please enter a comment. </target>
|
<target state="translated"> Prosimo, vpišite komentar. </target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2337485514607640701" datatype="html">
|
<trans-unit id="2337485514607640701" datatype="html">
|
||||||
<source>Add comment</source>
|
<source>Add comment</source>
|
||||||
@ -1467,7 +1467,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
|
||||||
<context context-type="linenumber">10</context>
|
<context context-type="linenumber">10</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Add comment</target>
|
<target state="translated">Dodaj komentar</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5438997040668245251" datatype="html">
|
<trans-unit id="5438997040668245251" datatype="html">
|
||||||
<source>Error saving comment: <x id="PH" equiv-text="e.toString()"/></source>
|
<source>Error saving comment: <x id="PH" equiv-text="e.toString()"/></source>
|
||||||
@ -1475,7 +1475,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.ts</context>
|
||||||
<context context-type="linenumber">57</context>
|
<context context-type="linenumber">57</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Error saving comment: <x id="PH" equiv-text="e.toString()"/></target>
|
<target state="translated">Napaka pri shranjevanju komentarja: <x id="PH" equiv-text="e.toString()"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7593210124183303626" datatype="html">
|
<trans-unit id="7593210124183303626" datatype="html">
|
||||||
<source>Error deleting comment: <x id="PH" equiv-text="e.toString()"/></source>
|
<source>Error deleting comment: <x id="PH" equiv-text="e.toString()"/></source>
|
||||||
@ -1483,7 +1483,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.ts</context>
|
||||||
<context context-type="linenumber">72</context>
|
<context context-type="linenumber">72</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Error deleting comment: <x id="PH" equiv-text="e.toString()"/></target>
|
<target state="translated">Napaka pri brisanju komentarja: <x id="PH" equiv-text="e.toString()"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1407560924967345762" datatype="html">
|
<trans-unit id="1407560924967345762" datatype="html">
|
||||||
<source>Page</source>
|
<source>Page</source>
|
||||||
@ -1859,7 +1859,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">128</context>
|
<context context-type="linenumber">128</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Comments</target>
|
<target state="translated">Komentarji</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3823219296477075982" datatype="html">
|
<trans-unit id="3823219296477075982" datatype="html">
|
||||||
<source>Discard</source>
|
<source>Discard</source>
|
||||||
@ -2245,7 +2245,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
<context context-type="linenumber">339</context>
|
<context context-type="linenumber">339</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">This operation will assign the storage path "<x id="PH" equiv-text="storagePath.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</target>
|
<target state="translated">Ta operacija bo dodelila pot shranjevanja "<x id="PH" equiv-text="storagePath.name"/>" v <x id="PH_1" equiv-text="this.list.selected.size"/> izbran dokument/e.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="60728365335056946" datatype="html">
|
<trans-unit id="60728365335056946" datatype="html">
|
||||||
<source>This operation will remove the storage path from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
<source>This operation will remove the storage path from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||||
@ -2253,7 +2253,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
<context context-type="linenumber">341</context>
|
<context context-type="linenumber">341</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">This operation will remove the storage path from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</target>
|
<target state="translated">Ta operacija bo odstranila pot shranjevanja iz <x id="PH" equiv-text="this.list.selected.size"/> izbranih dokumentov.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="749430623564850405" datatype="html">
|
<trans-unit id="749430623564850405" datatype="html">
|
||||||
<source>Delete confirm</source>
|
<source>Delete confirm</source>
|
||||||
@ -2285,7 +2285,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
<context context-type="linenumber">388</context>
|
<context context-type="linenumber">388</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">This operation will permanently redo OCR for <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</target>
|
<target state="translated">Ta operacija bo trajno obnovila OCR za <x id="PH" equiv-text="this.list.selected.size"/> izbrane dokumente.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8076495233090006322" datatype="html">
|
<trans-unit id="8076495233090006322" datatype="html">
|
||||||
<source>Filter by correspondent</source>
|
<source>Filter by correspondent</source>
|
||||||
@ -2373,7 +2373,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||||
<context context-type="linenumber">180</context>
|
<context context-type="linenumber">180</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Filter by document type</target>
|
<target state="translated">Filtriraj po vrsti dokumenta</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="157572966557284263" datatype="html">
|
<trans-unit id="157572966557284263" datatype="html">
|
||||||
<source>Filter by storage path</source>
|
<source>Filter by storage path</source>
|
||||||
@ -2385,7 +2385,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||||
<context context-type="linenumber">185</context>
|
<context context-type="linenumber">185</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Filter by storage path</target>
|
<target state="translated">Filtriraj po poti shrambe</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3727324658595204357" datatype="html">
|
<trans-unit id="3727324658595204357" datatype="html">
|
||||||
<source>Created: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate }}"/></source>
|
<source>Created: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate }}"/></source>
|
||||||
@ -2397,7 +2397,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||||
<context context-type="linenumber">48</context>
|
<context context-type="linenumber">48</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Created: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate }}"/></target>
|
<target state="translated">Ustvarjeno: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate }}"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2030261243264601523" datatype="html">
|
<trans-unit id="2030261243264601523" datatype="html">
|
||||||
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate }}"/></source>
|
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate }}"/></source>
|
||||||
@ -2409,7 +2409,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||||
<context context-type="linenumber">49</context>
|
<context context-type="linenumber">49</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate }}"/></target>
|
<target state="translated">Dodano: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate }}"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4235671847487610290" datatype="html">
|
<trans-unit id="4235671847487610290" datatype="html">
|
||||||
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate }}"/></source>
|
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate }}"/></source>
|
||||||
@ -2421,7 +2421,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Modified: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate }}"/></target>
|
<target state="translated">Spremenjeno: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate }}"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2332107018974972998" datatype="html">
|
<trans-unit id="2332107018974972998" datatype="html">
|
||||||
<source>Score:</source>
|
<source>Score:</source>
|
||||||
@ -2437,7 +2437,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||||
<context context-type="linenumber">14</context>
|
<context context-type="linenumber">14</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Toggle tag filter</target>
|
<target state="translated">Preklopi filter oznak</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4648526799630820486" datatype="html">
|
<trans-unit id="4648526799630820486" datatype="html">
|
||||||
<source>Toggle correspondent filter</source>
|
<source>Toggle correspondent filter</source>
|
||||||
@ -2445,7 +2445,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||||
<context context-type="linenumber">24</context>
|
<context context-type="linenumber">24</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Toggle correspondent filter</target>
|
<target state="translated">Preklop korespondenčnega filtra</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5319701482646590642" datatype="html">
|
<trans-unit id="5319701482646590642" datatype="html">
|
||||||
<source>Toggle document type filter</source>
|
<source>Toggle document type filter</source>
|
||||||
@ -2453,7 +2453,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||||
<context context-type="linenumber">31</context>
|
<context context-type="linenumber">31</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Toggle document type filter</target>
|
<target state="translated">Preklop filtra vrste dokumenta</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8950368321707344185" datatype="html">
|
<trans-unit id="8950368321707344185" datatype="html">
|
||||||
<source>Toggle storage path filter</source>
|
<source>Toggle storage path filter</source>
|
||||||
@ -2461,7 +2461,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||||
<context context-type="linenumber">38</context>
|
<context context-type="linenumber">38</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Toggle storage path filter</target>
|
<target state="translated">Preklop filtra poti za shranjevanje</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5145213156408463657" datatype="html">
|
<trans-unit id="5145213156408463657" datatype="html">
|
||||||
<source>Select none</source>
|
<source>Select none</source>
|
||||||
@ -2589,7 +2589,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||||
<context context-type="linenumber">175</context>
|
<context context-type="linenumber">175</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Edit document</target>
|
<target state="translated">Uredi dokument</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2155249406916744630" datatype="html">
|
<trans-unit id="2155249406916744630" datatype="html">
|
||||||
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
|
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
|
||||||
@ -2709,7 +2709,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">164</context>
|
<context context-type="linenumber">164</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">equals</target>
|
<target state="translated">je enako</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5325481293405718739" datatype="html">
|
<trans-unit id="5325481293405718739" datatype="html">
|
||||||
<source>is empty</source>
|
<source>is empty</source>
|
||||||
@ -2717,7 +2717,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">168</context>
|
<context context-type="linenumber">168</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">is empty</target>
|
<target state="translated">je prazno</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6166785695326182482" datatype="html">
|
<trans-unit id="6166785695326182482" datatype="html">
|
||||||
<source>is not empty</source>
|
<source>is not empty</source>
|
||||||
@ -2725,7 +2725,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">172</context>
|
<context context-type="linenumber">172</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">is not empty</target>
|
<target state="translated">ni prazno</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4686622206659266699" datatype="html">
|
<trans-unit id="4686622206659266699" datatype="html">
|
||||||
<source>greater than</source>
|
<source>greater than</source>
|
||||||
@ -2733,7 +2733,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">176</context>
|
<context context-type="linenumber">176</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">greater than</target>
|
<target state="translated">večje kot</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8014012170270529279" datatype="html">
|
<trans-unit id="8014012170270529279" datatype="html">
|
||||||
<source>less than</source>
|
<source>less than</source>
|
||||||
@ -2741,7 +2741,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">180</context>
|
<context context-type="linenumber">180</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">less than</target>
|
<target state="translated">manj kot</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7210076240260527720" datatype="html">
|
<trans-unit id="7210076240260527720" datatype="html">
|
||||||
<source>Save current view</source>
|
<source>Save current view</source>
|
||||||
@ -2805,7 +2805,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/correspondent-list/correspondent-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/correspondent-list/correspondent-list.component.ts</context>
|
||||||
<context context-type="linenumber">34</context>
|
<context context-type="linenumber">34</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">correspondents</target>
|
<target state="translated">dopisniki</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6360600151505327572" datatype="html">
|
<trans-unit id="6360600151505327572" datatype="html">
|
||||||
<source>Last used</source>
|
<source>Last used</source>
|
||||||
@ -2837,7 +2837,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/document-type-list/document-type-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/document-type-list/document-type-list.component.ts</context>
|
||||||
<context context-type="linenumber">31</context>
|
<context context-type="linenumber">31</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">document types</target>
|
<target state="translated">vrste dokumentov</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4990731724078522539" datatype="html">
|
<trans-unit id="4990731724078522539" datatype="html">
|
||||||
<source>Do you really want to delete the document type "<x id="PH" equiv-text="object.name"/>"?</source>
|
<source>Do you really want to delete the document type "<x id="PH" equiv-text="object.name"/>"?</source>
|
||||||
@ -2965,7 +2965,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||||
<context context-type="linenumber">74</context>
|
<context context-type="linenumber">74</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">{VAR_PLURAL, plural, =1 {One <x id="INTERPOLATION"/>} other {<x id="INTERPOLATION_1"/> total <x id="INTERPOLATION_2"/>}}</target>
|
<target state="translated">{VAR_PLURAL, plural, =1 {Eden <x id="INTERPOLATION"/>} other {<x id="INTERPOLATION_1"/> skupno <x id="INTERPOLATION_2"/>}}</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="810888510148304696" datatype="html">
|
<trans-unit id="810888510148304696" datatype="html">
|
||||||
<source>Automatic</source>
|
<source>Automatic</source>
|
||||||
@ -2985,7 +2985,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">140</context>
|
<context context-type="linenumber">140</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Do you really want to delete the <x id="PH" equiv-text="this.typeName"/>?</target>
|
<target state="translated">Ali res želite izbrisati <x id="PH" equiv-text="this.typeName"/>?</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8371896857609524947" datatype="html">
|
<trans-unit id="8371896857609524947" datatype="html">
|
||||||
<source>Associated documents will not be deleted.</source>
|
<source>Associated documents will not be deleted.</source>
|
||||||
@ -3001,7 +3001,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||||
<context context-type="linenumber">168,170</context>
|
<context context-type="linenumber">168,170</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Error while deleting element: <x id="PH" equiv-text="JSON.stringify( error.error )"/></target>
|
<target state="translated">Napaka pri brisanju elementa: <x id="PH" equiv-text="JSON.stringify( error.error )"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6439365426343089851" datatype="html">
|
<trans-unit id="6439365426343089851" datatype="html">
|
||||||
<source>General</source>
|
<source>General</source>
|
||||||
@ -3009,7 +3009,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">10</context>
|
<context context-type="linenumber">10</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">General</target>
|
<target state="translated">Splošno</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8671234314555525900" datatype="html">
|
<trans-unit id="8671234314555525900" datatype="html">
|
||||||
<source>Appearance</source>
|
<source>Appearance</source>
|
||||||
@ -3145,7 +3145,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">105</context>
|
<context context-type="linenumber">105</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Theme Color</target>
|
<target state="translated">Barve teme</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7808756054397155068" datatype="html">
|
<trans-unit id="7808756054397155068" datatype="html">
|
||||||
<source>Reset</source>
|
<source>Reset</source>
|
||||||
@ -3153,7 +3153,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">114</context>
|
<context context-type="linenumber">114</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Reset</target>
|
<target state="translated">Ponastavi</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8508424367627989968" datatype="html">
|
<trans-unit id="8508424367627989968" datatype="html">
|
||||||
<source>Bulk editing</source>
|
<source>Bulk editing</source>
|
||||||
@ -3193,7 +3193,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">132</context>
|
<context context-type="linenumber">132</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Enable comments</target>
|
<target state="translated">Omogoči komentarje</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5851560788527570644" datatype="html">
|
<trans-unit id="5851560788527570644" datatype="html">
|
||||||
<source>Notifications</source>
|
<source>Notifications</source>
|
||||||
@ -3281,7 +3281,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||||
<context context-type="linenumber">253</context>
|
<context context-type="linenumber">253</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Settings saved</target>
|
<target state="translated">Nastavitve shranjene</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7217000812750597833" datatype="html">
|
<trans-unit id="7217000812750597833" datatype="html">
|
||||||
<source>Settings were saved successfully.</source>
|
<source>Settings were saved successfully.</source>
|
||||||
@ -3289,7 +3289,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||||
<context context-type="linenumber">254</context>
|
<context context-type="linenumber">254</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Settings were saved successfully.</target>
|
<target state="translated">Nastavitve uspešno shranjene.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="525012668859298131" datatype="html">
|
<trans-unit id="525012668859298131" datatype="html">
|
||||||
<source>Settings were saved successfully. Reload is required to apply some changes.</source>
|
<source>Settings were saved successfully. Reload is required to apply some changes.</source>
|
||||||
@ -3297,7 +3297,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||||
<context context-type="linenumber">258</context>
|
<context context-type="linenumber">258</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Settings were saved successfully. Reload is required to apply some changes.</target>
|
<target state="translated">Nastavitve so bile uspešno shranjene. Za uveljavitev nekaterih sprememb je potreben ponovni zagon.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8491974984518503778" datatype="html">
|
<trans-unit id="8491974984518503778" datatype="html">
|
||||||
<source>Reload now</source>
|
<source>Reload now</source>
|
||||||
@ -3305,7 +3305,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||||
<context context-type="linenumber">259</context>
|
<context context-type="linenumber">259</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Reload now</target>
|
<target state="translated">Ponovno zaženi</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3011185103048412841" datatype="html">
|
<trans-unit id="3011185103048412841" datatype="html">
|
||||||
<source>An error occurred while saving settings.</source>
|
<source>An error occurred while saving settings.</source>
|
||||||
@ -3313,7 +3313,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||||
<context context-type="linenumber">269</context>
|
<context context-type="linenumber">269</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">An error occurred while saving settings.</target>
|
<target state="translated">Prišlo je do napake ob shranjevanju nastavitev.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6839066544204061364" datatype="html">
|
<trans-unit id="6839066544204061364" datatype="html">
|
||||||
<source>Use system language</source>
|
<source>Use system language</source>
|
||||||
@ -3337,7 +3337,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||||
<context context-type="linenumber">304,306</context>
|
<context context-type="linenumber">304,306</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Error while storing settings on server: <x id="PH" equiv-text="JSON.stringify( error.error )"/></target>
|
<target state="translated">Napaka pri shranjevanju nastavitev na strežnik: <x id="PH" equiv-text="JSON.stringify( error.error )"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5101757640976222639" datatype="html">
|
<trans-unit id="5101757640976222639" datatype="html">
|
||||||
<source>storage path</source>
|
<source>storage path</source>
|
||||||
@ -3345,7 +3345,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/storage-path-list/storage-path-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/storage-path-list/storage-path-list.component.ts</context>
|
||||||
<context context-type="linenumber">30</context>
|
<context context-type="linenumber">30</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">storage path</target>
|
<target state="translated">pot do shrambe</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="22235115124223314" datatype="html">
|
<trans-unit id="22235115124223314" datatype="html">
|
||||||
<source>storage paths</source>
|
<source>storage paths</source>
|
||||||
@ -3353,7 +3353,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/storage-path-list/storage-path-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/storage-path-list/storage-path-list.component.ts</context>
|
||||||
<context context-type="linenumber">31</context>
|
<context context-type="linenumber">31</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">storage paths</target>
|
<target state="translated">poti do shrambe</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1569070683025071137" datatype="html">
|
<trans-unit id="1569070683025071137" datatype="html">
|
||||||
<source>Do you really want to delete the storage path "<x id="PH" equiv-text="object.name"/>"?</source>
|
<source>Do you really want to delete the storage path "<x id="PH" equiv-text="object.name"/>"?</source>
|
||||||
@ -3361,7 +3361,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/storage-path-list/storage-path-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/storage-path-list/storage-path-list.component.ts</context>
|
||||||
<context context-type="linenumber">45</context>
|
<context context-type="linenumber">45</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Do you really want to delete the storage path "<x id="PH" equiv-text="object.name"/>"?</target>
|
<target state="translated">Ali res želite izbrisati pot shranjevanja "<x id="PH" equiv-text="object.name"/>"?</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6402703264596649214" datatype="html">
|
<trans-unit id="6402703264596649214" datatype="html">
|
||||||
<source>tag</source>
|
<source>tag</source>
|
||||||
@ -3369,7 +3369,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-list.component.ts</context>
|
||||||
<context context-type="linenumber">30</context>
|
<context context-type="linenumber">30</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">tag</target>
|
<target state="translated">oznaka</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4975748273657042999" datatype="html">
|
<trans-unit id="4975748273657042999" datatype="html">
|
||||||
<source>tags</source>
|
<source>tags</source>
|
||||||
@ -3377,7 +3377,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-list.component.ts</context>
|
||||||
<context context-type="linenumber">31</context>
|
<context context-type="linenumber">31</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">tags</target>
|
<target state="translated">oznake</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="93754014749412887" datatype="html">
|
<trans-unit id="93754014749412887" datatype="html">
|
||||||
<source>Do you really want to delete the tag "<x id="PH" equiv-text="object.name"/>"?</source>
|
<source>Do you really want to delete the tag "<x id="PH" equiv-text="object.name"/>"?</source>
|
||||||
@ -3393,7 +3393,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">1</context>
|
<context context-type="linenumber">1</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">File Tasks</target>
|
<target state="translated">Datotečne naloge</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="103921551219467537" datatype="html">
|
<trans-unit id="103921551219467537" datatype="html">
|
||||||
<source>Clear selection</source>
|
<source>Clear selection</source>
|
||||||
@ -3401,7 +3401,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">6</context>
|
<context context-type="linenumber">6</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Clear selection</target>
|
<target state="translated">Počisti izbiro</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="187187500641108332" datatype="html">
|
<trans-unit id="187187500641108332" datatype="html">
|
||||||
<source>
|
<source>
|
||||||
@ -3411,7 +3411,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">11</context>
|
<context context-type="linenumber">11</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation"><x id="INTERPOLATION" equiv-text="{{dismissButtonText}}"/></target>
|
<target state="translated"><x id="INTERPOLATION" equiv-text="{{dismissButtonText}}"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1102717806459547726" datatype="html">
|
<trans-unit id="1102717806459547726" datatype="html">
|
||||||
<source>Refresh</source>
|
<source>Refresh</source>
|
||||||
@ -3419,7 +3419,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">20</context>
|
<context context-type="linenumber">20</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Refresh</target>
|
<target state="translated">Osveži</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5968132631442328843" datatype="html">
|
<trans-unit id="5968132631442328843" datatype="html">
|
||||||
<source>Results</source>
|
<source>Results</source>
|
||||||
@ -3427,7 +3427,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">42</context>
|
<context context-type="linenumber">42</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Results</target>
|
<target state="translated">Rezultat</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8958063833276423847" datatype="html">
|
<trans-unit id="8958063833276423847" datatype="html">
|
||||||
<source>click for full output</source>
|
<source>click for full output</source>
|
||||||
@ -3435,7 +3435,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">66</context>
|
<context context-type="linenumber">66</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">click for full output</target>
|
<target state="translated">kliknite za celoten izpis</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1536087519743707362" datatype="html">
|
<trans-unit id="1536087519743707362" datatype="html">
|
||||||
<source>Dismiss</source>
|
<source>Dismiss</source>
|
||||||
@ -3447,7 +3447,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">54</context>
|
<context context-type="linenumber">54</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Dismiss</target>
|
<target state="translated">Opusti</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6798650225457993016" datatype="html">
|
<trans-unit id="6798650225457993016" datatype="html">
|
||||||
<source>Failed <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.failedFileTasks.length > 0" class="badge bg-danger ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.failedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
|
<source>Failed <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.failedFileTasks.length > 0" class="badge bg-danger ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.failedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
|
||||||
@ -3455,7 +3455,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">96</context>
|
<context context-type="linenumber">96</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Failed <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.failedFileTasks.length > 0" class="badge bg-danger ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.failedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></target>
|
<target state="translated">Neuspešno <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.failedFileTasks.length > 0" class="badge bg-danger ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.failedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2352193508676933865" datatype="html">
|
<trans-unit id="2352193508676933865" datatype="html">
|
||||||
<source>Complete <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.completedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.completedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
|
<source>Complete <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.completedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.completedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
|
||||||
@ -3463,7 +3463,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">102</context>
|
<context context-type="linenumber">102</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Complete <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.completedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.completedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></target>
|
<target state="translated">Dokončano <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.completedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.completedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1697296301417588213" datatype="html">
|
<trans-unit id="1697296301417588213" datatype="html">
|
||||||
<source>Started <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.startedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.startedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
|
<source>Started <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.startedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.startedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
|
||||||
@ -3471,7 +3471,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">108</context>
|
<context context-type="linenumber">108</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Started <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.startedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.startedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></target>
|
<target state="translated">Začetek <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.startedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.startedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6517676116023827583" datatype="html">
|
<trans-unit id="6517676116023827583" datatype="html">
|
||||||
<source>Queued <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.queuedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
|
<source>Queued <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.queuedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
|
||||||
@ -3479,7 +3479,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">114</context>
|
<context context-type="linenumber">114</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Queued <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.queuedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></target>
|
<target state="translated">V čakalni vrsti <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="tasksService.queuedFileTasks.length > 0" class="badge bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5404910960991552159" datatype="html">
|
<trans-unit id="5404910960991552159" datatype="html">
|
||||||
<source>Dismiss selected</source>
|
<source>Dismiss selected</source>
|
||||||
@ -3487,7 +3487,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">21</context>
|
<context context-type="linenumber">21</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Dismiss selected</target>
|
<target state="translated">Opusti izbrano</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8829078752502782653" datatype="html">
|
<trans-unit id="8829078752502782653" datatype="html">
|
||||||
<source>Dismiss all</source>
|
<source>Dismiss all</source>
|
||||||
@ -3499,7 +3499,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">52</context>
|
<context context-type="linenumber">52</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Dismiss all</target>
|
<target state="translated">Opusti vse</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1323591410517879795" datatype="html">
|
<trans-unit id="1323591410517879795" datatype="html">
|
||||||
<source>Confirm Dismiss All</source>
|
<source>Confirm Dismiss All</source>
|
||||||
@ -3507,7 +3507,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Confirm Dismiss All</target>
|
<target state="translated">Potrdi Opusti vse</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6566358716882976903" datatype="html">
|
<trans-unit id="6566358716882976903" datatype="html">
|
||||||
<source>tasks?</source>
|
<source>tasks?</source>
|
||||||
@ -3515,7 +3515,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">52</context>
|
<context context-type="linenumber">52</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">tasks?</target>
|
<target state="translated">naloge?</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="181464970911903082" datatype="html">
|
<trans-unit id="181464970911903082" datatype="html">
|
||||||
<source>404 Not Found</source>
|
<source>404 Not Found</source>
|
||||||
@ -3619,7 +3619,7 @@
|
|||||||
<context context-type="sourcefile">src/app/guards/dirty-doc.guard.ts</context>
|
<context context-type="sourcefile">src/app/guards/dirty-doc.guard.ts</context>
|
||||||
<context context-type="linenumber">17</context>
|
<context context-type="linenumber">17</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Warning: You have unsaved changes to your document(s).</target>
|
<target state="translated">Opozorilo: v dokumentih imate neshranjene spremembe.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="159901853873315050" datatype="html">
|
<trans-unit id="159901853873315050" datatype="html">
|
||||||
<source>Unsaved Changes</source>
|
<source>Unsaved Changes</source>
|
||||||
@ -3803,7 +3803,7 @@
|
|||||||
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
|
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
|
||||||
<context context-type="linenumber">118</context>
|
<context context-type="linenumber">118</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">You have unsaved changes to the document</target>
|
<target state="translated">Imate neshranjene spremembe dokumenta</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2089045849587358256" datatype="html">
|
<trans-unit id="2089045849587358256" datatype="html">
|
||||||
<source>Are you sure you want to close this document?</source>
|
<source>Are you sure you want to close this document?</source>
|
||||||
@ -3868,7 +3868,7 @@
|
|||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">146</context>
|
<context context-type="linenumber">146</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Belarusian</target>
|
<target state="translated">Beloruščina</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2719780722934172508" datatype="html">
|
<trans-unit id="2719780722934172508" datatype="html">
|
||||||
<source>Czech</source>
|
<source>Czech</source>
|
||||||
@ -3988,7 +3988,7 @@
|
|||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">236</context>
|
<context context-type="linenumber">236</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Slovenian</target>
|
<target state="translated">Slovenščina</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8608389829607915090" datatype="html">
|
<trans-unit id="8608389829607915090" datatype="html">
|
||||||
<source>Serbian</source>
|
<source>Serbian</source>
|
||||||
@ -3996,7 +3996,7 @@
|
|||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">242</context>
|
<context context-type="linenumber">242</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Serbian</target>
|
<target state="translated">Srbščina</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="499386805970351976" datatype="html">
|
<trans-unit id="499386805970351976" datatype="html">
|
||||||
<source>Swedish</source>
|
<source>Swedish</source>
|
||||||
@ -4012,7 +4012,7 @@
|
|||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">254</context>
|
<context context-type="linenumber">254</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Turkish</target>
|
<target state="translated">Turščina</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4689443708886954687" datatype="html">
|
<trans-unit id="4689443708886954687" datatype="html">
|
||||||
<source>Chinese Simplified</source>
|
<source>Chinese Simplified</source>
|
||||||
@ -4020,7 +4020,7 @@
|
|||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">260</context>
|
<context context-type="linenumber">260</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Chinese Simplified</target>
|
<target state="translated">Poenostavljena kitajščina</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4912706592792948707" datatype="html">
|
<trans-unit id="4912706592792948707" datatype="html">
|
||||||
<source>ISO 8601</source>
|
<source>ISO 8601</source>
|
||||||
@ -4036,7 +4036,7 @@
|
|||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">372</context>
|
<context context-type="linenumber">372</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Successfully completed one-time migratration of settings to the database!</target>
|
<target state="translated">Uspešno opravljena enkratna migracija nastavitev v bazo!</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5558341108007064934" datatype="html">
|
<trans-unit id="5558341108007064934" datatype="html">
|
||||||
<source>Unable to migrate settings to the database, please try saving manually.</source>
|
<source>Unable to migrate settings to the database, please try saving manually.</source>
|
||||||
@ -4044,7 +4044,7 @@
|
|||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">373</context>
|
<context context-type="linenumber">373</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Unable to migrate settings to the database, please try saving manually.</target>
|
<target state="translated">Nastavitev ni mogoče preseliti v bazo podatkov, poskusite jih shraniti ročno.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1519954996184640001" datatype="html">
|
<trans-unit id="1519954996184640001" datatype="html">
|
||||||
<source>Error</source>
|
<source>Error</source>
|
||||||
|
@ -3,12 +3,15 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import List # for type hinting. Can be removed, if only Python >3.8 is used
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from pdf2image import convert_from_path
|
from pikepdf import Page
|
||||||
from pikepdf import Pdf
|
from pikepdf import Pdf
|
||||||
|
from pikepdf import PdfImage
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL import ImageSequence
|
from PIL import ImageSequence
|
||||||
from pyzbar import pyzbar
|
from pyzbar import pyzbar
|
||||||
@ -31,7 +34,7 @@ def supported_file_type(mime_type) -> bool:
|
|||||||
return mime_type in supported_mime
|
return mime_type in supported_mime
|
||||||
|
|
||||||
|
|
||||||
def barcode_reader(image) -> List[str]:
|
def barcode_reader(image: Image) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Read any barcodes contained in image
|
Read any barcodes contained in image
|
||||||
Returns a list containing all found barcodes
|
Returns a list containing all found barcodes
|
||||||
@ -98,21 +101,39 @@ def convert_from_tiff_to_pdf(filepath: str) -> str:
|
|||||||
return newpath
|
return newpath
|
||||||
|
|
||||||
|
|
||||||
def scan_file_for_separating_barcodes(filepath: str) -> List[int]:
|
def scan_file_for_separating_barcodes(filepath: str) -> Tuple[Optional[str], List[int]]:
|
||||||
"""
|
"""
|
||||||
Scan the provided pdf file for page separating barcodes
|
Scan the provided pdf file for page separating barcodes
|
||||||
Returns a list of pagenumbers, which separate the file
|
Returns a PDF filepath and a list of pagenumbers,
|
||||||
|
which separate the file into new files
|
||||||
"""
|
"""
|
||||||
|
|
||||||
separator_page_numbers = []
|
separator_page_numbers = []
|
||||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
pdf_filepath = None
|
||||||
# use a temporary directory in case the file os too big to handle in memory
|
|
||||||
with tempfile.TemporaryDirectory() as path:
|
mime_type = get_file_mime_type(filepath)
|
||||||
pages_from_path = convert_from_path(filepath, output_folder=path)
|
|
||||||
for current_page_number, page in enumerate(pages_from_path):
|
if supported_file_type(mime_type):
|
||||||
current_barcodes = barcode_reader(page)
|
pdf_filepath = filepath
|
||||||
if separator_barcode in current_barcodes:
|
if mime_type == "image/tiff":
|
||||||
separator_page_numbers.append(current_page_number)
|
pdf_filepath = convert_from_tiff_to_pdf(filepath)
|
||||||
return separator_page_numbers
|
|
||||||
|
pdf = Pdf.open(pdf_filepath)
|
||||||
|
|
||||||
|
for page_num, page in enumerate(pdf.pages):
|
||||||
|
for image_key in page.images:
|
||||||
|
pdfimage = PdfImage(page.images[image_key])
|
||||||
|
pillow_img = pdfimage.as_pil_image()
|
||||||
|
|
||||||
|
detected_barcodes = barcode_reader(pillow_img)
|
||||||
|
|
||||||
|
if settings.CONSUMER_BARCODE_STRING in detected_barcodes:
|
||||||
|
separator_page_numbers.append(page_num)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"Unsupported file format for barcode reader: {str(mime_type)}",
|
||||||
|
)
|
||||||
|
return pdf_filepath, separator_page_numbers
|
||||||
|
|
||||||
|
|
||||||
def separate_pages(filepath: str, pages_to_split_on: List[int]) -> List[str]:
|
def separate_pages(filepath: str, pages_to_split_on: List[int]) -> List[str]:
|
||||||
@ -122,47 +143,56 @@ def separate_pages(filepath: str, pages_to_split_on: List[int]) -> List[str]:
|
|||||||
Returns a list of (temporary) filepaths to consume.
|
Returns a list of (temporary) filepaths to consume.
|
||||||
These will need to be deleted later.
|
These will need to be deleted later.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
document_paths = []
|
||||||
|
|
||||||
|
if not pages_to_split_on:
|
||||||
|
logger.warning("No pages to split on!")
|
||||||
|
return document_paths
|
||||||
|
|
||||||
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
|
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
|
||||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||||
fname = os.path.splitext(os.path.basename(filepath))[0]
|
fname = os.path.splitext(os.path.basename(filepath))[0]
|
||||||
pdf = Pdf.open(filepath)
|
pdf = Pdf.open(filepath)
|
||||||
document_paths = []
|
|
||||||
logger.debug(f"Temp dir is {str(tempdir)}")
|
# A list of documents, ie a list of lists of pages
|
||||||
if not pages_to_split_on:
|
documents: List[List[Page]] = []
|
||||||
logger.warning("No pages to split on!")
|
# A single document, ie a list of pages
|
||||||
else:
|
document: List[Page] = []
|
||||||
# go from the first page to the first separator page
|
|
||||||
|
for idx, page in enumerate(pdf.pages):
|
||||||
|
# Keep building the new PDF as long as it is not a
|
||||||
|
# separator index
|
||||||
|
if idx not in pages_to_split_on:
|
||||||
|
document.append(page)
|
||||||
|
# Make sure to append the very last document to the documents
|
||||||
|
if idx == (len(pdf.pages) - 1):
|
||||||
|
documents.append(document)
|
||||||
|
document = []
|
||||||
|
else:
|
||||||
|
# This is a split index, save the current PDF pages, and restart
|
||||||
|
# a new destination page listing
|
||||||
|
logger.debug(f"Starting new document at idx {idx}")
|
||||||
|
documents.append(document)
|
||||||
|
document = []
|
||||||
|
|
||||||
|
documents = [x for x in documents if len(x)]
|
||||||
|
|
||||||
|
logger.debug(f"Split into {len(documents)} new documents")
|
||||||
|
|
||||||
|
# Write the new documents out
|
||||||
|
for doc_idx, document in enumerate(documents):
|
||||||
dst = Pdf.new()
|
dst = Pdf.new()
|
||||||
for n, page in enumerate(pdf.pages):
|
dst.pages.extend(document)
|
||||||
if n < pages_to_split_on[0]:
|
|
||||||
dst.pages.append(page)
|
output_filename = f"{fname}_document_{doc_idx}.pdf"
|
||||||
output_filename = f"{fname}_document_0.pdf"
|
|
||||||
|
logger.debug(f"pdf no:{doc_idx} has {len(dst.pages)} pages")
|
||||||
savepath = os.path.join(tempdir, output_filename)
|
savepath = os.path.join(tempdir, output_filename)
|
||||||
with open(savepath, "wb") as out:
|
with open(savepath, "wb") as out:
|
||||||
dst.save(out)
|
dst.save(out)
|
||||||
document_paths = [savepath]
|
document_paths.append(savepath)
|
||||||
|
|
||||||
# iterate through the rest of the document
|
|
||||||
for count, page_number in enumerate(pages_to_split_on):
|
|
||||||
logger.debug(f"Count: {str(count)} page_number: {str(page_number)}")
|
|
||||||
dst = Pdf.new()
|
|
||||||
try:
|
|
||||||
next_page = pages_to_split_on[count + 1]
|
|
||||||
except IndexError:
|
|
||||||
next_page = len(pdf.pages)
|
|
||||||
# skip the first page_number. This contains the barcode page
|
|
||||||
for page in range(page_number + 1, next_page):
|
|
||||||
logger.debug(
|
|
||||||
f"page_number: {str(page_number)} next_page: {str(next_page)}",
|
|
||||||
)
|
|
||||||
dst.pages.append(pdf.pages[page])
|
|
||||||
output_filename = f"{fname}_document_{str(count + 1)}.pdf"
|
|
||||||
logger.debug(f"pdf no:{str(count)} has {str(len(dst.pages))} pages")
|
|
||||||
savepath = os.path.join(tempdir, output_filename)
|
|
||||||
with open(savepath, "wb") as out:
|
|
||||||
dst.save(out)
|
|
||||||
document_paths.append(savepath)
|
|
||||||
logger.debug(f"Temp files are {str(document_paths)}")
|
|
||||||
return document_paths
|
return document_paths
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,29 +96,13 @@ def consume_file(
|
|||||||
# check for separators in current document
|
# check for separators in current document
|
||||||
if settings.CONSUMER_ENABLE_BARCODES:
|
if settings.CONSUMER_ENABLE_BARCODES:
|
||||||
|
|
||||||
mime_type = barcodes.get_file_mime_type(path)
|
pdf_filepath, separators = barcodes.scan_file_for_separating_barcodes(path)
|
||||||
|
|
||||||
if not barcodes.supported_file_type(mime_type):
|
if separators:
|
||||||
# if not supported, skip this routine
|
logger.debug(
|
||||||
logger.warning(
|
f"Pages with separators found in: {str(path)}",
|
||||||
f"Unsupported file format for barcode reader: {str(mime_type)}",
|
|
||||||
)
|
)
|
||||||
else:
|
document_list = barcodes.separate_pages(pdf_filepath, separators)
|
||||||
separators = []
|
|
||||||
document_list = []
|
|
||||||
|
|
||||||
if mime_type == "image/tiff":
|
|
||||||
file_to_process = barcodes.convert_from_tiff_to_pdf(path)
|
|
||||||
else:
|
|
||||||
file_to_process = path
|
|
||||||
|
|
||||||
separators = barcodes.scan_file_for_separating_barcodes(file_to_process)
|
|
||||||
|
|
||||||
if separators:
|
|
||||||
logger.debug(
|
|
||||||
f"Pages with separators found in: {str(path)}",
|
|
||||||
)
|
|
||||||
document_list = barcodes.separate_pages(file_to_process, separators)
|
|
||||||
|
|
||||||
if document_list:
|
if document_list:
|
||||||
for n, document in enumerate(document_list):
|
for n, document in enumerate(document_list):
|
||||||
@ -134,15 +118,13 @@ def consume_file(
|
|||||||
target_dir=path.parent,
|
target_dir=path.parent,
|
||||||
)
|
)
|
||||||
|
|
||||||
# if we got here, the document was successfully split
|
# Delete the PDF file which was split
|
||||||
# and can safely be deleted
|
os.remove(pdf_filepath)
|
||||||
if mime_type == "image/tiff":
|
|
||||||
# Remove the TIFF converted to PDF file
|
# If the original was a TIFF, remove the original file as well
|
||||||
logger.debug(f"Deleting file {file_to_process}")
|
if str(pdf_filepath) != str(path):
|
||||||
os.unlink(file_to_process)
|
logger.debug(f"Deleting file {path}")
|
||||||
# Remove the original file (new file is saved above)
|
os.unlink(path)
|
||||||
logger.debug(f"Deleting file {path}")
|
|
||||||
os.unlink(path)
|
|
||||||
|
|
||||||
# notify the sender, otherwise the progress bar
|
# notify the sender, otherwise the progress bar
|
||||||
# in the UI stays stuck
|
# in the UI stays stuck
|
||||||
|
BIN
src/documents/tests/samples/barcodes/patch-code-t-double.pdf
Normal file
BIN
src/documents/tests/samples/barcodes/patch-code-t-double.pdf
Normal file
Binary file not shown.
@ -13,22 +13,23 @@ from PIL import Image
|
|||||||
|
|
||||||
|
|
||||||
class TestBarcode(DirectoriesMixin, TestCase):
|
class TestBarcode(DirectoriesMixin, TestCase):
|
||||||
|
|
||||||
|
SAMPLE_DIR = os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
"samples",
|
||||||
|
)
|
||||||
|
|
||||||
|
BARCODE_SAMPLE_DIR = os.path.join(SAMPLE_DIR, "barcodes")
|
||||||
|
|
||||||
def test_barcode_reader(self):
|
def test_barcode_reader(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(self.BARCODE_SAMPLE_DIR, "barcode-39-PATCHT.png")
|
||||||
os.path.dirname(__file__),
|
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-39-PATCHT.png",
|
|
||||||
)
|
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||||
self.assertEqual(barcodes.barcode_reader(img), [separator_barcode])
|
self.assertEqual(barcodes.barcode_reader(img), [separator_barcode])
|
||||||
|
|
||||||
def test_barcode_reader2(self):
|
def test_barcode_reader2(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t.pbm",
|
"patch-code-t.pbm",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -37,9 +38,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_barcode_reader_distorsion(self):
|
def test_barcode_reader_distorsion(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-39-PATCHT-distorsion.png",
|
"barcode-39-PATCHT-distorsion.png",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -48,9 +47,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_barcode_reader_distorsion2(self):
|
def test_barcode_reader_distorsion2(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-39-PATCHT-distorsion2.png",
|
"barcode-39-PATCHT-distorsion2.png",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -59,9 +56,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_barcode_reader_unreadable(self):
|
def test_barcode_reader_unreadable(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-39-PATCHT-unreadable.png",
|
"barcode-39-PATCHT-unreadable.png",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -69,9 +64,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_barcode_reader_qr(self):
|
def test_barcode_reader_qr(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"qr-code-PATCHT.png",
|
"qr-code-PATCHT.png",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -80,9 +73,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_barcode_reader_128(self):
|
def test_barcode_reader_128(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-128-PATCHT.png",
|
"barcode-128-PATCHT.png",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -90,15 +81,13 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
self.assertEqual(barcodes.barcode_reader(img), [separator_barcode])
|
self.assertEqual(barcodes.barcode_reader(img), [separator_barcode])
|
||||||
|
|
||||||
def test_barcode_reader_no_barcode(self):
|
def test_barcode_reader_no_barcode(self):
|
||||||
test_file = os.path.join(os.path.dirname(__file__), "samples", "simple.png")
|
test_file = os.path.join(self.SAMPLE_DIR, "simple.png")
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
self.assertEqual(barcodes.barcode_reader(img), [])
|
self.assertEqual(barcodes.barcode_reader(img), [])
|
||||||
|
|
||||||
def test_barcode_reader_custom_separator(self):
|
def test_barcode_reader_custom_separator(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-39-custom.png",
|
"barcode-39-custom.png",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -106,9 +95,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_barcode_reader_custom_qr_separator(self):
|
def test_barcode_reader_custom_qr_separator(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-qr-custom.png",
|
"barcode-qr-custom.png",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -116,9 +103,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_barcode_reader_custom_128_separator(self):
|
def test_barcode_reader_custom_128_separator(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-128-custom.png",
|
"barcode-128-custom.png",
|
||||||
)
|
)
|
||||||
img = Image.open(test_file)
|
img = Image.open(test_file)
|
||||||
@ -126,19 +111,15 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_get_mime_type(self):
|
def test_get_mime_type(self):
|
||||||
tiff_file = os.path.join(
|
tiff_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"simple.tiff",
|
"simple.tiff",
|
||||||
)
|
)
|
||||||
pdf_file = os.path.join(
|
pdf_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"simple.pdf",
|
"simple.pdf",
|
||||||
)
|
)
|
||||||
png_file = os.path.join(
|
png_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-128-custom.png",
|
"barcode-128-custom.png",
|
||||||
)
|
)
|
||||||
tiff_file_no_extension = os.path.join(settings.SCRATCH_DIR, "testfile1")
|
tiff_file_no_extension = os.path.join(settings.SCRATCH_DIR, "testfile1")
|
||||||
@ -173,8 +154,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_convert_error_from_pdf_to_pdf(self):
|
def test_convert_error_from_pdf_to_pdf(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"simple.pdf",
|
"simple.pdf",
|
||||||
)
|
)
|
||||||
dst = os.path.join(settings.SCRATCH_DIR, "simple.pdf")
|
dst = os.path.join(settings.SCRATCH_DIR, "simple.pdf")
|
||||||
@ -183,117 +163,155 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_scan_file_for_separating_barcodes(self):
|
def test_scan_file_for_separating_barcodes(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t.pdf",
|
"patch-code-t.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [0])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [0])
|
||||||
|
|
||||||
def test_scan_file_for_separating_barcodes2(self):
|
def test_scan_file_for_separating_barcodes2(self):
|
||||||
test_file = os.path.join(os.path.dirname(__file__), "samples", "simple.pdf")
|
test_file = os.path.join(self.SAMPLE_DIR, "simple.pdf")
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [])
|
||||||
|
|
||||||
def test_scan_file_for_separating_barcodes3(self):
|
def test_scan_file_for_separating_barcodes3(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t-middle.pdf",
|
"patch-code-t-middle.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [1])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [1])
|
||||||
|
|
||||||
def test_scan_file_for_separating_barcodes4(self):
|
def test_scan_file_for_separating_barcodes4(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"several-patcht-codes.pdf",
|
"several-patcht-codes.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [2, 5])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [2, 5])
|
||||||
|
|
||||||
def test_scan_file_for_separating_barcodes_upsidedown(self):
|
def test_scan_file_for_separating_barcodes_upsidedown(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t-middle_reverse.pdf",
|
"patch-code-t-middle_reverse.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [1])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [1])
|
||||||
|
|
||||||
def test_scan_file_for_separating_qr_barcodes(self):
|
def test_scan_file_for_separating_qr_barcodes(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t-qr.pdf",
|
"patch-code-t-qr.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [0])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [0])
|
||||||
|
|
||||||
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
||||||
def test_scan_file_for_separating_custom_barcodes(self):
|
def test_scan_file_for_separating_custom_barcodes(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-39-custom.pdf",
|
"barcode-39-custom.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [0])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [0])
|
||||||
|
|
||||||
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
||||||
def test_scan_file_for_separating_custom_qr_barcodes(self):
|
def test_scan_file_for_separating_custom_qr_barcodes(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-qr-custom.pdf",
|
"barcode-qr-custom.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [0])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [0])
|
||||||
|
|
||||||
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
||||||
def test_scan_file_for_separating_custom_128_barcodes(self):
|
def test_scan_file_for_separating_custom_128_barcodes(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-128-custom.pdf",
|
"barcode-128-custom.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [0])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [0])
|
||||||
|
|
||||||
def test_scan_file_for_separating_wrong_qr_barcodes(self):
|
def test_scan_file_for_separating_wrong_qr_barcodes(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"barcode-39-custom.pdf",
|
"barcode-39-custom.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.scan_file_for_separating_barcodes(test_file)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
self.assertEqual(pages, [])
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pdf_file, test_file)
|
||||||
|
self.assertListEqual(separator_page_numbers, [])
|
||||||
|
|
||||||
def test_separate_pages(self):
|
def test_separate_pages(self):
|
||||||
|
test_file = os.path.join(
|
||||||
|
self.BARCODE_SAMPLE_DIR,
|
||||||
|
"patch-code-t-middle.pdf",
|
||||||
|
)
|
||||||
|
pages = barcodes.separate_pages(test_file, [1])
|
||||||
|
|
||||||
|
self.assertEqual(len(pages), 2)
|
||||||
|
|
||||||
|
def test_separate_pages_double_code(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Input PDF with two patch code pages in a row
|
||||||
|
WHEN:
|
||||||
|
- The input file is split
|
||||||
|
THEN:
|
||||||
|
- Only two files are output
|
||||||
|
"""
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
"samples",
|
"samples",
|
||||||
"barcodes",
|
"barcodes",
|
||||||
"patch-code-t-middle.pdf",
|
"patch-code-t-double.pdf",
|
||||||
)
|
)
|
||||||
pages = barcodes.separate_pages(test_file, [1])
|
pages = barcodes.separate_pages(test_file, [1, 2])
|
||||||
|
|
||||||
self.assertEqual(len(pages), 2)
|
self.assertEqual(len(pages), 2)
|
||||||
|
|
||||||
def test_separate_pages_no_list(self):
|
def test_separate_pages_no_list(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t-middle.pdf",
|
"patch-code-t-middle.pdf",
|
||||||
)
|
)
|
||||||
with self.assertLogs("paperless.barcodes", level="WARNING") as cm:
|
with self.assertLogs("paperless.barcodes", level="WARNING") as cm:
|
||||||
@ -308,9 +326,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_save_to_dir(self):
|
def test_save_to_dir(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t.pdf",
|
"patch-code-t.pdf",
|
||||||
)
|
)
|
||||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||||
@ -320,9 +336,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_save_to_dir2(self):
|
def test_save_to_dir2(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t.pdf",
|
"patch-code-t.pdf",
|
||||||
)
|
)
|
||||||
nonexistingdir = "/nowhere"
|
nonexistingdir = "/nowhere"
|
||||||
@ -340,9 +354,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_save_to_dir3(self):
|
def test_save_to_dir3(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t.pdf",
|
"patch-code-t.pdf",
|
||||||
)
|
)
|
||||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||||
@ -352,31 +364,36 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_barcode_splitter(self):
|
def test_barcode_splitter(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t-middle.pdf",
|
"patch-code-t-middle.pdf",
|
||||||
)
|
)
|
||||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||||
separators = barcodes.scan_file_for_separating_barcodes(test_file)
|
|
||||||
self.assertTrue(separators)
|
pdf_file, separator_page_numbers = barcodes.scan_file_for_separating_barcodes(
|
||||||
document_list = barcodes.separate_pages(test_file, separators)
|
test_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(test_file, pdf_file)
|
||||||
|
self.assertTrue(len(separator_page_numbers) > 0)
|
||||||
|
|
||||||
|
document_list = barcodes.separate_pages(test_file, separator_page_numbers)
|
||||||
self.assertTrue(document_list)
|
self.assertTrue(document_list)
|
||||||
for document in document_list:
|
for document in document_list:
|
||||||
barcodes.save_to_dir(document, target_dir=tempdir)
|
barcodes.save_to_dir(document, target_dir=tempdir)
|
||||||
|
|
||||||
target_file1 = os.path.join(tempdir, "patch-code-t-middle_document_0.pdf")
|
target_file1 = os.path.join(tempdir, "patch-code-t-middle_document_0.pdf")
|
||||||
target_file2 = os.path.join(tempdir, "patch-code-t-middle_document_1.pdf")
|
target_file2 = os.path.join(tempdir, "patch-code-t-middle_document_1.pdf")
|
||||||
|
|
||||||
self.assertTrue(os.path.isfile(target_file1))
|
self.assertTrue(os.path.isfile(target_file1))
|
||||||
self.assertTrue(os.path.isfile(target_file2))
|
self.assertTrue(os.path.isfile(target_file2))
|
||||||
|
|
||||||
@override_settings(CONSUMER_ENABLE_BARCODES=True)
|
@override_settings(CONSUMER_ENABLE_BARCODES=True)
|
||||||
def test_consume_barcode_file(self):
|
def test_consume_barcode_file(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t-middle.pdf",
|
"patch-code-t-middle.pdf",
|
||||||
)
|
)
|
||||||
|
|
||||||
dst = os.path.join(settings.SCRATCH_DIR, "patch-code-t-middle.pdf")
|
dst = os.path.join(settings.SCRATCH_DIR, "patch-code-t-middle.pdf")
|
||||||
shutil.copy(test_file, dst)
|
shutil.copy(test_file, dst)
|
||||||
|
|
||||||
@ -388,9 +405,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
def test_consume_barcode_tiff_file(self):
|
def test_consume_barcode_tiff_file(self):
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t-middle.tiff",
|
"patch-code-t-middle.tiff",
|
||||||
)
|
)
|
||||||
dst = os.path.join(settings.SCRATCH_DIR, "patch-code-t-middle.tiff")
|
dst = os.path.join(settings.SCRATCH_DIR, "patch-code-t-middle.tiff")
|
||||||
@ -412,18 +427,17 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
and continue archiving the file as is.
|
and continue archiving the file as is.
|
||||||
"""
|
"""
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"simple.jpg",
|
"simple.jpg",
|
||||||
)
|
)
|
||||||
dst = os.path.join(settings.SCRATCH_DIR, "simple.jpg")
|
dst = os.path.join(settings.SCRATCH_DIR, "simple.jpg")
|
||||||
shutil.copy(test_file, dst)
|
shutil.copy(test_file, dst)
|
||||||
with self.assertLogs("paperless.tasks", level="WARNING") as cm:
|
with self.assertLogs("paperless.barcodes", level="WARNING") as cm:
|
||||||
self.assertIn("Success", tasks.consume_file(dst))
|
self.assertIn("Success", tasks.consume_file(dst))
|
||||||
self.assertListEqual(
|
self.assertListEqual(
|
||||||
cm.output,
|
cm.output,
|
||||||
[
|
[
|
||||||
"WARNING:paperless.tasks:Unsupported file format for barcode reader: image/jpeg",
|
"WARNING:paperless.barcodes:Unsupported file format for barcode reader: image/jpeg",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
m.assert_called_once()
|
m.assert_called_once()
|
||||||
@ -445,9 +459,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
|||||||
the user uploads a supported image file, but without extension
|
the user uploads a supported image file, but without extension
|
||||||
"""
|
"""
|
||||||
test_file = os.path.join(
|
test_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
self.BARCODE_SAMPLE_DIR,
|
||||||
"samples",
|
|
||||||
"barcodes",
|
|
||||||
"patch-code-t-middle.tiff",
|
"patch-code-t-middle.tiff",
|
||||||
)
|
)
|
||||||
dst = os.path.join(settings.SCRATCH_DIR, "patch-code-t-middle")
|
dst = os.path.join(settings.SCRATCH_DIR, "patch-code-t-middle")
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import grp
|
||||||
import os
|
import os
|
||||||
|
import pwd
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
|
|
||||||
@ -32,12 +34,15 @@ def path_check(var, directory):
|
|||||||
with open(test_file, "w"):
|
with open(test_file, "w"):
|
||||||
pass
|
pass
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
|
dir_stat = os.stat(directory)
|
||||||
|
dir_mode = stat.filemode(dir_stat.st_mode)
|
||||||
|
dir_owner = pwd.getpwuid(dir_stat.st_uid).pw_name
|
||||||
|
dir_group = grp.getgrgid(dir_stat.st_gid).gr_name
|
||||||
messages.append(
|
messages.append(
|
||||||
Error(
|
Error(
|
||||||
writeable_message.format(var),
|
writeable_message.format(var),
|
||||||
writeable_hint.format(
|
writeable_hint.format(
|
||||||
f"\n{stat.filemode(os.stat(directory).st_mode)} "
|
f"\n{dir_mode} {dir_owner} {dir_group} " f"{directory}\n",
|
||||||
f"{directory}\n",
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from typing import Final
|
from typing import Final
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
__version__: Final[Tuple[int, int, int]] = (1, 8, 0)
|
__version__: Final[Tuple[int, int, int]] = (1, 9, 0)
|
||||||
# Version string like X.Y.Z
|
# Version string like X.Y.Z
|
||||||
__full_version_str__: Final[str] = ".".join(map(str, __version__))
|
__full_version_str__: Final[str] = ".".join(map(str, __version__))
|
||||||
# Version string like X.Y
|
# Version string like X.Y
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import requests
|
import requests
|
||||||
@ -28,6 +29,11 @@ class TikaDocumentParser(DocumentParser):
|
|||||||
|
|
||||||
def extract_metadata(self, document_path, mime_type):
|
def extract_metadata(self, document_path, mime_type):
|
||||||
tika_server = settings.TIKA_ENDPOINT
|
tika_server = settings.TIKA_ENDPOINT
|
||||||
|
|
||||||
|
# tika does not support a PathLike, only strings
|
||||||
|
# ensure this is a string
|
||||||
|
document_path = str(document_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = parser.from_file(document_path, tika_server)
|
parsed = parser.from_file(document_path, tika_server)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -47,10 +53,14 @@ class TikaDocumentParser(DocumentParser):
|
|||||||
for key in parsed["metadata"]
|
for key in parsed["metadata"]
|
||||||
]
|
]
|
||||||
|
|
||||||
def parse(self, document_path, mime_type, file_name=None):
|
def parse(self, document_path: Path, mime_type, file_name=None):
|
||||||
self.log("info", f"Sending {document_path} to Tika server")
|
self.log("info", f"Sending {document_path} to Tika server")
|
||||||
tika_server = settings.TIKA_ENDPOINT
|
tika_server = settings.TIKA_ENDPOINT
|
||||||
|
|
||||||
|
# tika does not support a PathLike, only strings
|
||||||
|
# ensure this is a string
|
||||||
|
document_path = str(document_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = parser.from_file(document_path, tika_server)
|
parsed = parser.from_file(document_path, tika_server)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user