mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-12-18 01:41:14 -06:00
Compare commits
1 Commits
fix-11615
...
fix-rmpipe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55ddcd753e |
@@ -14,7 +14,7 @@
|
|||||||
@if (previewText) {
|
@if (previewText) {
|
||||||
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
|
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
|
||||||
} @else {
|
} @else {
|
||||||
<object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
<object [data]="previewURL" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
@if (requiresPassword) {
|
@if (requiresPassword) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { first, Subject, takeUntil } from 'rxjs'
|
|||||||
import { Document } from 'src/app/data/document'
|
import { Document } from 'src/app/data/document'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
|
||||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
|
|
||||||
@@ -19,7 +18,6 @@ import { SettingsService } from 'src/app/services/settings.service'
|
|||||||
NgbPopoverModule,
|
NgbPopoverModule,
|
||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
PdfViewerModule,
|
PdfViewerModule,
|
||||||
SafeUrlPipe,
|
|
||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
|
|||||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
@@ -128,7 +127,6 @@ describe('SavedViewWidgetComponent', () => {
|
|||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
CustomDatePipe,
|
CustomDatePipe,
|
||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
SafeUrlPipe,
|
|
||||||
PreviewPopupComponent,
|
PreviewPopupComponent,
|
||||||
CustomFieldDisplayComponent,
|
CustomFieldDisplayComponent,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -379,7 +379,7 @@
|
|||||||
<ng-template #previewContent>
|
<ng-template #previewContent>
|
||||||
<div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded">
|
<div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded">
|
||||||
@if (showThumbnailOverlay) {
|
@if (showThumbnailOverlay) {
|
||||||
<img [src]="thumbUrl | safeUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
|
<img [src]="thumbUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
|
||||||
}
|
}
|
||||||
<div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center">
|
<div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center">
|
||||||
<div>
|
<div>
|
||||||
@@ -406,7 +406,7 @@
|
|||||||
</pdf-viewer>
|
</pdf-viewer>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
<object [data]="previewUrl" class="preview-sticky" width="100%"></object>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@case (ContentRenderType.Text) {
|
@case (ContentRenderType.Text) {
|
||||||
@@ -414,7 +414,7 @@
|
|||||||
}
|
}
|
||||||
@case (ContentRenderType.Image) {
|
@case (ContentRenderType.Image) {
|
||||||
<div class="preview-sticky">
|
<div class="preview-sticky">
|
||||||
<img [src]="previewUrl | safeUrl" width="100%" height="100%" alt="{{title}}" />
|
<img [src]="previewUrl" width="100%" height="100%" alt="{{title}}" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case (ContentRenderType.TIFF) {
|
@case (ContentRenderType.TIFF) {
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@case (ContentRenderType.Other) {
|
@case (ContentRenderType.Other) {
|
||||||
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
<object [data]="previewUrl" class="preview-sticky" width="100%"></object>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if (requiresPassword) {
|
@if (requiresPassword) {
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
|
|||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
|
import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
|
||||||
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
|
||||||
import { ComponentRouterService } from 'src/app/services/component-router.service'
|
import { ComponentRouterService } from 'src/app/services/component-router.service'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
import { HotKeyService } from 'src/app/services/hot-key.service'
|
import { HotKeyService } from 'src/app/services/hot-key.service'
|
||||||
@@ -169,7 +168,6 @@ export enum ZoomSetting {
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NgTemplateOutlet,
|
NgTemplateOutlet,
|
||||||
SafeUrlPipe,
|
|
||||||
NgbNavModule,
|
NgbNavModule,
|
||||||
NgbDropdownModule,
|
NgbDropdownModule,
|
||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
|
|||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
||||||
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
|
||||||
import { CustomFieldDisplayComponent } from '../../common/custom-field-display/custom-field-display.component'
|
import { CustomFieldDisplayComponent } from '../../common/custom-field-display/custom-field-display.component'
|
||||||
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
||||||
import { DocumentCardLargeComponent } from './document-card-large.component'
|
import { DocumentCardLargeComponent } from './document-card-large.component'
|
||||||
@@ -53,7 +52,6 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
CustomDatePipe,
|
CustomDatePipe,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
SafeUrlPipe,
|
|
||||||
IsNumberPipe,
|
IsNumberPipe,
|
||||||
PreviewPopupComponent,
|
PreviewPopupComponent,
|
||||||
CustomFieldDisplayComponent,
|
CustomFieldDisplayComponent,
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing'
|
|
||||||
import { BrowserModule, DomSanitizer } from '@angular/platform-browser'
|
|
||||||
import { SafeUrlPipe } from './safeurl.pipe'
|
|
||||||
|
|
||||||
describe('SafeUrlPipe', () => {
|
|
||||||
let pipe: SafeUrlPipe
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [SafeUrlPipe],
|
|
||||||
imports: [BrowserModule],
|
|
||||||
})
|
|
||||||
pipe = TestBed.inject(SafeUrlPipe)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should bypass security and trust the url', () => {
|
|
||||||
const url = 'https://example.com'
|
|
||||||
const domSanitizer = TestBed.inject(DomSanitizer)
|
|
||||||
const sanitizerSpy = jest.spyOn(
|
|
||||||
domSanitizer,
|
|
||||||
'bypassSecurityTrustResourceUrl'
|
|
||||||
)
|
|
||||||
|
|
||||||
let safeResourceUrl = pipe.transform(url)
|
|
||||||
expect(safeResourceUrl).not.toBeNull()
|
|
||||||
expect(sanitizerSpy).toHaveBeenCalled()
|
|
||||||
|
|
||||||
safeResourceUrl = pipe.transform(null)
|
|
||||||
expect(safeResourceUrl).not.toBeNull()
|
|
||||||
expect(sanitizerSpy).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Pipe, PipeTransform, inject } from '@angular/core'
|
|
||||||
import { DomSanitizer } from '@angular/platform-browser'
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'safeUrl',
|
|
||||||
})
|
|
||||||
export class SafeUrlPipe implements PipeTransform {
|
|
||||||
private sanitizer = inject(DomSanitizer)
|
|
||||||
|
|
||||||
transform(url) {
|
|
||||||
if (url == null) {
|
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl('')
|
|
||||||
} else {
|
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ from datetime import time
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from time import sleep
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ from whoosh.highlight import HtmlFormatter
|
|||||||
from whoosh.idsets import BitSet
|
from whoosh.idsets import BitSet
|
||||||
from whoosh.idsets import DocIdSet
|
from whoosh.idsets import DocIdSet
|
||||||
from whoosh.index import FileIndex
|
from whoosh.index import FileIndex
|
||||||
from whoosh.index import LockError
|
|
||||||
from whoosh.index import create_in
|
from whoosh.index import create_in
|
||||||
from whoosh.index import exists_in
|
from whoosh.index import exists_in
|
||||||
from whoosh.index import open_dir
|
from whoosh.index import open_dir
|
||||||
@@ -99,33 +97,11 @@ def get_schema() -> Schema:
|
|||||||
|
|
||||||
|
|
||||||
def open_index(*, recreate=False) -> FileIndex:
|
def open_index(*, recreate=False) -> FileIndex:
|
||||||
transient_exceptions = (FileNotFoundError, LockError)
|
|
||||||
max_retries = 3
|
|
||||||
retry_delay = 0.1
|
|
||||||
|
|
||||||
for attempt in range(max_retries + 1):
|
|
||||||
try:
|
try:
|
||||||
if exists_in(settings.INDEX_DIR) and not recreate:
|
if exists_in(settings.INDEX_DIR) and not recreate:
|
||||||
return open_dir(settings.INDEX_DIR, schema=get_schema())
|
return open_dir(settings.INDEX_DIR, schema=get_schema())
|
||||||
break
|
|
||||||
except transient_exceptions as exc:
|
|
||||||
is_last_attempt = attempt == max_retries or recreate
|
|
||||||
if is_last_attempt:
|
|
||||||
logger.exception(
|
|
||||||
"Error while opening the index after retries, recreating.",
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
logger.warning(
|
|
||||||
"Transient error while opening the index (attempt %s/%s): %s. Retrying.",
|
|
||||||
attempt + 1,
|
|
||||||
max_retries + 1,
|
|
||||||
exc,
|
|
||||||
)
|
|
||||||
sleep(retry_delay)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Error while opening the index, recreating.")
|
logger.exception("Error while opening the index, recreating.")
|
||||||
break
|
|
||||||
|
|
||||||
# create_in doesn't handle corrupted indexes very well, remove the directory entirely first
|
# create_in doesn't handle corrupted indexes very well, remove the directory entirely first
|
||||||
if settings.INDEX_DIR.is_dir():
|
if settings.INDEX_DIR.is_dir():
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
@@ -252,31 +251,3 @@ class TestRewriteNaturalDateKeywords(SimpleTestCase):
|
|||||||
result = self._rewrite_with_now("added:today", fixed_now)
|
result = self._rewrite_with_now("added:today", fixed_now)
|
||||||
# Should convert to UTC properly
|
# Should convert to UTC properly
|
||||||
self.assertIn("added:[20250719", result)
|
self.assertIn("added:[20250719", result)
|
||||||
|
|
||||||
|
|
||||||
class TestIndexResilience(DirectoriesMixin, SimpleTestCase):
|
|
||||||
def test_transient_missing_segment_does_not_force_recreate(self):
|
|
||||||
file_marker = settings.INDEX_DIR / "file_marker.txt"
|
|
||||||
file_marker.write_text("keep")
|
|
||||||
expected_index = object()
|
|
||||||
|
|
||||||
with (
|
|
||||||
mock.patch("documents.index.exists_in", return_value=True),
|
|
||||||
mock.patch(
|
|
||||||
"documents.index.open_dir",
|
|
||||||
side_effect=[FileNotFoundError("missing"), expected_index],
|
|
||||||
) as mock_open_dir,
|
|
||||||
mock.patch(
|
|
||||||
"documents.index.create_in",
|
|
||||||
) as mock_create_in,
|
|
||||||
mock.patch(
|
|
||||||
"documents.index.rmtree",
|
|
||||||
) as mock_rmtree,
|
|
||||||
):
|
|
||||||
ix = index.open_index()
|
|
||||||
|
|
||||||
self.assertIs(ix, expected_index)
|
|
||||||
self.assertGreaterEqual(mock_open_dir.call_count, 2)
|
|
||||||
mock_rmtree.assert_not_called()
|
|
||||||
mock_create_in.assert_not_called()
|
|
||||||
self.assertEqual(file_marker.read_text(), "keep")
|
|
||||||
|
|||||||
Reference in New Issue
Block a user