Merge pull request #24 from tribut/feature-trash

Add temporary "delete to trash" functionality
This commit is contained in:
shamoon
2022-02-21 11:19:57 -08:00
committed by GitHub
8 changed files with 84 additions and 0 deletions

View File

@@ -225,6 +225,37 @@ def set_tags(sender,
@receiver(models.signals.post_delete, sender=Document)
def cleanup_document_deletion(sender, instance, using, **kwargs):
with FileLock(settings.MEDIA_LOCK):
if settings.TRASH_DIR:
# Find a non-conflicting filename in case a document with the same
# name was moved to trash earlier
counter = 0
old_filename = os.path.split(instance.source_path)[1]
(old_filebase, old_fileext) = os.path.splitext(old_filename)
while True:
new_file_path = os.path.join(
settings.TRASH_DIR,
old_filebase +
(f"_{counter:02}" if counter else "") +
old_fileext
)
if os.path.exists(new_file_path):
counter += 1
else:
break
logger.debug(
f"Moving {instance.source_path} to trash at {new_file_path}")
try:
os.rename(instance.source_path, new_file_path)
except OSError as e:
logger.error(
f"Failed to move {instance.source_path} to trash at "
f"{new_file_path}: {e}. Skipping cleanup!"
)
return
for filename in (instance.source_path,
instance.archive_path,
instance.thumbnail_path):

View File

@@ -2,6 +2,7 @@ import datetime
import hashlib
import os
import random
import tempfile
import uuid
from pathlib import Path
from unittest import mock
@@ -154,6 +155,40 @@ class TestFileHandling(DirectoriesMixin, TestCase):
self.assertEqual(os.path.isfile(settings.ORIGINALS_DIR + "/none/none.pdf"), False)
self.assertEqual(os.path.isdir(settings.ORIGINALS_DIR + "/none"), False)
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{correspondent}", TRASH_DIR=tempfile.mkdtemp())
def test_document_delete_trash(self):
document = Document()
document.mime_type = "application/pdf"
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
document.save()
# Ensure that filename is properly generated
document.filename = generate_filename(document)
self.assertEqual(document.filename,
"none/none.pdf")
create_source_path_directory(document.source_path)
Path(document.source_path).touch()
# Ensure file was moved to trash after delete
self.assertEqual(os.path.isfile(settings.TRASH_DIR + "/none/none.pdf"), False)
document.delete()
self.assertEqual(os.path.isfile(settings.ORIGINALS_DIR + "/none/none.pdf"), False)
self.assertEqual(os.path.isdir(settings.ORIGINALS_DIR + "/none"), False)
self.assertEqual(os.path.isfile(settings.TRASH_DIR + "/none.pdf"), True)
self.assertEqual(os.path.isfile(settings.TRASH_DIR + "/none_01.pdf"), False)
# Create an identical document and ensure it is trashed under a new name
document = Document()
document.mime_type = "application/pdf"
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
document.save()
document.filename = generate_filename(document)
create_source_path_directory(document.source_path)
Path(document.source_path).touch()
document.delete()
self.assertEqual(os.path.isfile(settings.TRASH_DIR + "/none_01.pdf"), True)
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{correspondent}")
def test_document_delete_nofile(self):
document = Document()

View File

@@ -50,6 +50,7 @@ def paths_check(app_configs, **kwargs):
"""
return path_check("PAPERLESS_DATA_DIR", settings.DATA_DIR) + \
path_check("PAPERLESS_TRASH_DIR", settings.TRASH_DIR) + \
path_check("PAPERLESS_MEDIA_ROOT", settings.MEDIA_ROOT) + \
path_check("PAPERLESS_CONSUMPTION_DIR", settings.CONSUMPTION_DIR)

View File

@@ -57,6 +57,8 @@ THUMBNAIL_DIR = os.path.join(MEDIA_ROOT, "documents", "thumbnails")
DATA_DIR = os.getenv('PAPERLESS_DATA_DIR', os.path.join(BASE_DIR, "..", "data"))
TRASH_DIR = os.getenv('PAPERLESS_TRASH_DIR')
# Lock file for synchronizing changes to the MEDIA directory across multiple
# threads.
MEDIA_LOCK = os.path.join(MEDIA_ROOT, "media.lock")