62 lines
2.8 KiB
Python
62 lines
2.8 KiB
Python
"""Permanent deletion shared by the trash API and scheduled retention cleanup."""
|
||
from django.db import transaction
|
||
from .models import DriveItem, UploadSession
|
||
from .services import descendant_files
|
||
from .storage_layout import disk_path, file_location
|
||
|
||
|
||
def has_active_uploads_in_tree(item):
|
||
"""Do not purge directories while a resumable upload targets a descendant."""
|
||
if not item.is_folder:
|
||
return False
|
||
pending = [item.pk]
|
||
while pending:
|
||
batch, pending = pending[:250], pending[250:]
|
||
if UploadSession.objects.filter(parent_id__in=batch).exists():
|
||
return True
|
||
pending.extend(DriveItem.objects.filter(parent_id__in=batch, is_folder=True).values_list('pk', flat=True))
|
||
return False
|
||
|
||
|
||
def delete_trashed_item(item):
|
||
"""Delete the SQL tree, then its tracked files after commit.
|
||
|
||
Untracked files and directories are not erased. Failures resolving a stored
|
||
path abort before the SQL delete; missing physical files are tolerated.
|
||
"""
|
||
if item.trashed_at is None:
|
||
raise ValueError('L’élément doit être dans la corbeille.')
|
||
if has_active_uploads_in_tree(item):
|
||
raise ValueError('Un transfert est encore en cours dans ce dossier.')
|
||
files = [disk_path(key) for key in descendant_files(item)]
|
||
# A child may have been trashed separately, into another isolated directory.
|
||
folders = []
|
||
queue = [item.pk] if item.is_folder else []
|
||
if item.is_folder:
|
||
folders.append(disk_path(item.storage_location or file_location(item.space, item.parent, item.name)))
|
||
while queue:
|
||
batch, queue = queue[:300], queue[300:]
|
||
children = list(DriveItem.objects.filter(parent_id__in=batch, is_folder=True).select_related('parent','space'))
|
||
queue.extend(child.pk for child in children)
|
||
for child in children:
|
||
folders.append(disk_path(child.storage_location or file_location(child.space, child.parent, child.name)))
|
||
with transaction.atomic():
|
||
item.delete()
|
||
def remove_files():
|
||
for path in files:
|
||
path.unlink(missing_ok=True)
|
||
for folder in sorted(set(folders), key=lambda p:len(p.parts), reverse=True):
|
||
if folder.is_dir() and not folder.is_symlink():
|
||
# Never recursively erase untracked files.
|
||
try: folder.rmdir()
|
||
except OSError: pass
|
||
# Remove empty UUID trash containers (never the shared .trash root).
|
||
trash_root = disk_path('.gooddrive-trash')
|
||
for path in [*files, *folders]:
|
||
parent = path.parent
|
||
while parent != trash_root and parent.is_relative_to(trash_root):
|
||
try: parent.rmdir()
|
||
except OSError: break
|
||
parent = parent.parent
|
||
transaction.on_commit(remove_files)
|