Repository: paperless-ngx/paperless-ngx

Issue:
[BUG] Delete option of document_exporter not working
### Description

I'm using paperless-ngx in a docker container on a synology (last version).

I've set a cron task to export my paperless data daily, and specified the option -d to delete the existing files in the target directory.
Unfortunately, the existing files never gets removed.
I've tried with -d, --delete, from the docker image itself as well as from outside the docker, the export always work, but the existings files remains.
The rights are correctly configured, i've even tried with 777. 
Nothing appears in the log.

If anyone have an idea, 

### Steps to reproduce

1. Have existing files in the export folder (for exemple, old_export.zip)
2. Execute the command `docker exec paperless-webserver document_exporter ../export -d -na -nt  -z`
3. Check the files in the export folder. The previously existing files still remains. I now have the new export-XX-XX.zip as well as old_export.zip

### Webserver logs

```bash
(nothing there)
```


### Browser logs

_No response_

### Paperless-ngx version

2.2.1

### Host OS

Synology DSM 7.2.1-69057 Update 3

### Installation method

Docker - official image

### Browser

_No response_

### Configuration changes

_No response_

### Other

_No response_

### Please confirm the following

- [X] I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
- [X] I have already searched for relevant existing issues and discussions before opening this report.
- [X] I have updated the title field above with a concise description.


Discussion:
If you're using the zip export, delete is not going to do anything. It's just outputting a zip of everything to the given directory.
This explains it. Thanks for the info :)
After thinking about it some more, it is inconsistent.  It's a little odd I think to request `--delete` with `--zip`, but we can still clear everything out of the destination folder.  So it will be a use with caution option
Thanks a lot for giving a thought to this.

The idea to combine `--delete` with `--zip` is: I make a daily backup of paperless to a private dropbox. The objective is to keep this archive in case my synology crash completly. But i don't want 1 zip  for each day (because 340Mo x 30 days for each month, i'll reach my dropbox quota really quickly). That's why I just want the last one, the more up-to-date :)

Resolving patch:
diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py
--- a/src/documents/management/commands/document_exporter.py
+++ b/src/documents/management/commands/document_exporter.py
@@ -5,6 +5,7 @@
 import tempfile
 import time
 from pathlib import Path
+from typing import Optional
 
 import tqdm
 from django.conf import settings
@@ -172,14 +173,14 @@ def handle(self, *args, **options):
         self.delete: bool = options["delete"]
         self.no_archive: bool = options["no_archive"]
         self.no_thumbnail: bool = options["no_thumbnail"]
-        zip_export: bool = options["zip"]
+        self.zip_export: bool = options["zip"]
 
         # If zipping, save the original target for later and
         # get a temporary directory for the target instead
         temp_dir = None
-        original_target = None
-        if zip_export:
-            original_target = self.target
+        self.original_target: Optional[Path] = None
+        if self.zip_export:
+            self.original_target = self.target
 
             os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
             temp_dir = tempfile.TemporaryDirectory(
@@ -203,10 +204,10 @@ def handle(self, *args, **options):
 
                 # We've written everything to the temporary directory in this case,
                 # now make an archive in the original target, with all files stored
-                if zip_export:
+                if self.zip_export:
                     shutil.make_archive(
                         os.path.join(
-                            original_target,
+                            self.original_target,
                             options["zip_name"],
                         ),
                         format="zip",
@@ -215,7 +216,7 @@ def handle(self, *args, **options):
 
         finally:
             # Always cleanup the temporary directory, if one was created
-            if zip_export and temp_dir is not None:
+            if self.zip_export and temp_dir is not None:
                 temp_dir.cleanup()
 
     def dump(self, progress_bar_disable=False):
@@ -466,14 +467,21 @@ def dump(self, progress_bar_disable=False):
 
         if self.delete:
             # 5. Remove files which we did not explicitly export in this run
+            if not self.zip_export:
+                for f in self.files_in_export_dir:
+                    f.unlink()
 
-            for f in self.files_in_export_dir:
-                f.unlink()
-
-                delete_empty_directories(
-                    f.parent,
-                    self.target,
-                )
+                    delete_empty_directories(
+                        f.parent,
+                        self.target,
+                    )
+            else:
+                # 5. Remove anything in the original location (before moving the zip)
+                for item in self.original_target.glob("*"):
+                    if item.is_dir():
+                        shutil.rmtree(item)
+                    else:
+                        item.unlink()
 
     def check_and_copy(self, source, source_checksum, target: Path):
         if target in self.files_in_export_dir:


Test patch:
diff --git a/src/documents/tests/test_management_exporter.py b/src/documents/tests/test_management_exporter.py
--- a/src/documents/tests/test_management_exporter.py
+++ b/src/documents/tests/test_management_exporter.py
@@ -42,7 +42,7 @@
 
 class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
     def setUp(self) -> None:
-        self.target = tempfile.mkdtemp()
+        self.target = Path(tempfile.mkdtemp())
         self.addCleanup(shutil.rmtree, self.target)
 
         self.user = User.objects.create(username="temp_admin")
@@ -496,6 +496,54 @@ def test_export_zipped_format(self):
             self.assertIn("manifest.json", zip.namelist())
             self.assertIn("version.json", zip.namelist())
 
+    @override_settings(PASSPHRASE="test")
+    def test_export_zipped_with_delete(self):
+        """
+        GIVEN:
+            - Request to export documents to zipfile
+            - There is one existing file in the target
+            - There is one existing directory in the target
+        WHEN:
+            - Documents are exported
+            - deletion of existing files is requested
+        THEN:
+            - Zipfile is created
+            - Zipfile contains exported files
+            - The existing file and directory in target are removed
+        """
+        shutil.rmtree(os.path.join(self.dirs.media_dir, "documents"))
+        shutil.copytree(
+            os.path.join(os.path.dirname(__file__), "samples", "documents"),
+            os.path.join(self.dirs.media_dir, "documents"),
+        )
+
+        # Create stuff in target directory
+        existing_file = self.target / "test.txt"
+        existing_file.touch()
+        existing_dir = self.target / "somedir"
+        existing_dir.mkdir(parents=True)
+
+        self.assertIsFile(existing_file)
+        self.assertIsDir(existing_dir)
+
+        args = ["document_exporter", self.target, "--zip", "--delete"]
+
+        call_command(*args)
+
+        expected_file = os.path.join(
+            self.target,
+            f"export-{timezone.localdate().isoformat()}.zip",
+        )
+
+        self.assertIsFile(expected_file)
+        self.assertIsNotFile(existing_file)
+        self.assertIsNotDir(existing_dir)
+
+        with ZipFile(expected_file) as zip:
+            self.assertEqual(len(zip.namelist()), 11)
+            self.assertIn("manifest.json", zip.namelist())
+            self.assertIn("version.json", zip.namelist())
+
     def test_export_target_not_exists(self):
         """
         GIVEN: