diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f7952ad0..24f563c7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,11 @@ If you feel like contributing to the project, please do! Bug fixes and improvements are always welcome. +⚠️ Please note: Pull requests that implement a new feature or enhancement _should almost always target an existing feature request_ with evidence of community interest and discussion. This is in order to balance the work of implementing and maintaining new features / enhancements. Pull requests that are opened without meeting this requirement may not be merged. + If you want to implement something big: -- Please start a discussion about that in the issues! Maybe something similar is already in development and we can make it happen together. +- As above, please start with a discussion! Maybe something similar is already in development and we can make it happen together. - When making additions to the project, consider if the majority of users will benefit from your change. If not, you're probably better of forking the project. - Also consider if your change will get in the way of other users. A good change is a change that enhances the experience of some users who want that change and does not affect users who do not care about the change. - Please see the [paperless-ngx merge process](#merging-prs) below. diff --git a/docs/configuration.md b/docs/configuration.md index 33b464022..20d964ddc 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1759,6 +1759,11 @@ started by the container. : Path to an image file in the /media/logo directory, must include 'logo', e.g. `/logo/Atari_logo.svg` +!!! note + + The logo file will be viewable by anyone with access to the Paperless instance login page, + so consider your choice of logo carefully and removing exif data from images before uploading. + #### [`PAPERLESS_ENABLE_UPDATE_CHECK=`](#PAPERLESS_ENABLE_UPDATE_CHECK) {#PAPERLESS_ENABLE_UPDATE_CHECK} !!! note diff --git a/src-ui/src/app/components/manage/tag-list/tag-list.component.spec.ts b/src-ui/src/app/components/manage/tag-list/tag-list.component.spec.ts index 4e723993c..b4694e145 100644 --- a/src-ui/src/app/components/manage/tag-list/tag-list.component.spec.ts +++ b/src-ui/src/app/components/manage/tag-list/tag-list.component.spec.ts @@ -71,4 +71,20 @@ describe('TagListComponent', () => { 'Do you really want to delete the tag "Tag1"?' ) }) + + it('should filter out child tags if name filter is empty, otherwise show all', () => { + const tags = [ + { id: 1, name: 'Tag1', parent: null }, + { id: 2, name: 'Tag2', parent: 1 }, + { id: 3, name: 'Tag3', parent: null }, + ] + component['_nameFilter'] = null // Simulate empty name filter + const filtered = component.filterData(tags as any) + expect(filtered.length).toBe(2) + expect(filtered.find((t) => t.id === 2)).toBeUndefined() + + component['_nameFilter'] = 'Tag2' // Simulate non-empty name filter + const filteredWithName = component.filterData(tags as any) + expect(filteredWithName.length).toBe(3) + }) }) diff --git a/src-ui/src/app/components/manage/tag-list/tag-list.component.ts b/src-ui/src/app/components/manage/tag-list/tag-list.component.ts index 12481594f..77d42e020 100644 --- a/src-ui/src/app/components/manage/tag-list/tag-list.component.ts +++ b/src-ui/src/app/components/manage/tag-list/tag-list.component.ts @@ -62,6 +62,8 @@ export class TagListComponent extends ManagementListComponent { } filterData(data: Tag[]) { - return data.filter((tag) => !tag.parent) + return this.nameFilter?.length + ? [...data] + : data.filter((tag) => !tag.parent) } } diff --git a/src/documents/management/commands/document_consumer.py b/src/documents/management/commands/document_consumer.py index 35d79288e..97027e02d 100644 --- a/src/documents/management/commands/document_consumer.py +++ b/src/documents/management/commands/document_consumer.py @@ -82,6 +82,13 @@ def _is_ignored(filepath: Path) -> bool: def _consume(filepath: Path) -> None: + # Check permissions early + try: + filepath.stat() + except (PermissionError, OSError): + logger.warning(f"Not consuming file {filepath}: Permission denied.") + return + if filepath.is_dir() or _is_ignored(filepath): return @@ -323,7 +330,12 @@ class Command(BaseCommand): # Also make sure the file exists still, some scanners might write a # temporary file first - file_still_exists = filepath.exists() and filepath.is_file() + try: + file_still_exists = filepath.exists() and filepath.is_file() + except (PermissionError, OSError): # pragma: no cover + # If we can't check, let it fail in the _consume function + file_still_exists = True + continue if waited_long_enough and file_still_exists: _consume(filepath) diff --git a/src/documents/tests/test_management_consumer.py b/src/documents/tests/test_management_consumer.py index 821fd82e0..38b9eadda 100644 --- a/src/documents/tests/test_management_consumer.py +++ b/src/documents/tests/test_management_consumer.py @@ -209,6 +209,26 @@ class TestConsumer(DirectoriesMixin, ConsumerThreadMixin, TransactionTestCase): # assert that we have an error logged with this invalid file. error_logger.assert_called_once() + @mock.patch("documents.management.commands.document_consumer.logger.warning") + def test_permission_error_on_prechecks(self, warning_logger): + filepath = Path(self.dirs.consumption_dir) / "selinux.txt" + filepath.touch() + + original_stat = Path.stat + + def raising_stat(self, *args, **kwargs): + if self == filepath: + raise PermissionError("Permission denied") + return original_stat(self, *args, **kwargs) + + with mock.patch("pathlib.Path.stat", new=raising_stat): + document_consumer._consume(filepath) + + warning_logger.assert_called_once() + (args, _) = warning_logger.call_args + self.assertIn("Permission denied", args[0]) + self.consume_file_mock.assert_not_called() + @override_settings(CONSUMPTION_DIR="does_not_exist") def test_consumption_directory_invalid(self): self.assertRaises(CommandError, call_command, "document_consumer", "--oneshot") diff --git a/src/paperless/settings.py b/src/paperless/settings.py index 45203d969..80a235b4c 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -923,7 +923,7 @@ CELERY_ACCEPT_CONTENT = ["application/json", "application/x-python-serialize"] CELERY_BEAT_SCHEDULE = _parse_beat_schedule() # https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-schedule-filename -CELERY_BEAT_SCHEDULE_FILENAME = DATA_DIR / "celerybeat-schedule.db" +CELERY_BEAT_SCHEDULE_FILENAME = str(DATA_DIR / "celerybeat-schedule.db") # Cachalot: Database read cache.