feat: feedback verbetering 03
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from backend.app.api.errors import AppError
|
||||
from backend.app.api.schemas import TaskCreateResponse
|
||||
@@ -26,31 +27,12 @@ class DeleteTaskService:
|
||||
|
||||
def create_delete_task(self, path: str, recursive: bool = False) -> TaskCreateResponse:
|
||||
try:
|
||||
resolved_target = self._path_guard.resolve_existing_path(path)
|
||||
|
||||
if resolved_target.absolute.is_file():
|
||||
kind = "file"
|
||||
elif resolved_target.absolute.is_dir():
|
||||
kind = "directory"
|
||||
if not recursive and any(resolved_target.absolute.iterdir()):
|
||||
raise AppError(
|
||||
code="directory_not_empty",
|
||||
message="Directory is not empty",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
else:
|
||||
raise AppError(
|
||||
code="type_conflict",
|
||||
message="Unsupported path type for delete",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
item = self._build_delete_item(path=path, recursive=recursive)
|
||||
|
||||
task_id = str(uuid.uuid4())
|
||||
task = self._repository.create_task(
|
||||
operation="delete",
|
||||
source=resolved_target.relative,
|
||||
source=item["relative_path"],
|
||||
destination="",
|
||||
task_id=task_id,
|
||||
)
|
||||
@@ -58,14 +40,9 @@ class DeleteTaskService:
|
||||
entry_id=task_id,
|
||||
operation="delete",
|
||||
status="queued",
|
||||
path=resolved_target.relative,
|
||||
)
|
||||
self._runner.enqueue_delete_path(
|
||||
task_id=task["id"],
|
||||
target=str(resolved_target.absolute),
|
||||
kind=kind,
|
||||
recursive=recursive,
|
||||
path=item["relative_path"],
|
||||
)
|
||||
self._runner.enqueue_delete_path(task_id=task["id"], item=item)
|
||||
return TaskCreateResponse(task_id=task["id"], status=task["status"])
|
||||
except AppError as exc:
|
||||
self._record_history(
|
||||
@@ -94,6 +71,66 @@ class DeleteTaskService:
|
||||
)
|
||||
raise error
|
||||
|
||||
def _build_delete_item(self, path: str, recursive: bool) -> dict:
|
||||
resolved_target = self._path_guard.resolve_existing_path(path)
|
||||
|
||||
if resolved_target.absolute.is_file():
|
||||
files = [{"path": str(resolved_target.absolute), "label": resolved_target.absolute.name}]
|
||||
directories: list[str] = []
|
||||
kind = "file"
|
||||
elif resolved_target.absolute.is_dir():
|
||||
kind = "directory"
|
||||
if not recursive and any(resolved_target.absolute.iterdir()):
|
||||
raise AppError(
|
||||
code="directory_not_empty",
|
||||
message="Directory is not empty",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
if recursive:
|
||||
files, directories = self._build_recursive_delete_plan(resolved_target.absolute)
|
||||
else:
|
||||
files = []
|
||||
directories = [str(resolved_target.absolute)]
|
||||
else:
|
||||
raise AppError(
|
||||
code="type_conflict",
|
||||
message="Unsupported path type for delete",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
|
||||
return {
|
||||
"target": str(resolved_target.absolute),
|
||||
"relative_path": resolved_target.relative,
|
||||
"kind": kind,
|
||||
"recursive": recursive,
|
||||
"files": files,
|
||||
"directories": directories,
|
||||
"progress_total_items": len(files),
|
||||
"progress_label": files[0]["label"] if files else None,
|
||||
}
|
||||
|
||||
def _build_recursive_delete_plan(self, root: Path) -> tuple[list[dict[str, str]], list[str]]:
|
||||
files: list[dict[str, str]] = []
|
||||
directories: list[str] = []
|
||||
|
||||
def walk(path: Path, relative_prefix: Path) -> None:
|
||||
for entry in sorted(path.iterdir(), key=lambda child: child.name.lower()):
|
||||
relative_path = relative_prefix / entry.name
|
||||
if entry.is_symlink():
|
||||
files.append({"path": str(entry), "label": relative_path.as_posix()})
|
||||
continue
|
||||
if entry.is_dir():
|
||||
walk(entry, relative_path)
|
||||
directories.append(str(entry))
|
||||
continue
|
||||
files.append({"path": str(entry), "label": relative_path.as_posix()})
|
||||
|
||||
walk(root, Path())
|
||||
directories.append(str(root))
|
||||
return files, directories
|
||||
|
||||
def _record_history(self, **kwargs) -> None:
|
||||
if self._history_repository:
|
||||
self._history_repository.create_entry(**kwargs)
|
||||
|
||||
@@ -80,10 +80,10 @@ class TaskRunner:
|
||||
)
|
||||
thread.start()
|
||||
|
||||
def enqueue_delete_path(self, task_id: str, target: str, kind: str, recursive: bool) -> None:
|
||||
def enqueue_delete_path(self, task_id: str, item: dict[str, object]) -> None:
|
||||
thread = threading.Thread(
|
||||
target=self._run_delete_path,
|
||||
args=(task_id, target, kind, recursive),
|
||||
args=(task_id, item),
|
||||
daemon=True,
|
||||
)
|
||||
thread.start()
|
||||
@@ -429,39 +429,57 @@ class TaskRunner:
|
||||
total_items=total_items,
|
||||
)
|
||||
|
||||
def _run_delete_path(self, task_id: str, target: str, kind: str, recursive: bool) -> None:
|
||||
def _run_delete_path(self, task_id: str, item: dict[str, object]) -> None:
|
||||
target = str(item["target"])
|
||||
kind = str(item["kind"])
|
||||
recursive = bool(item["recursive"])
|
||||
files = list(item.get("files", [])) # type: ignore[arg-type]
|
||||
directories = list(item.get("directories", [])) # type: ignore[arg-type]
|
||||
total_items = int(item.get("progress_total_items", len(files)))
|
||||
current_item = str(item.get("progress_label")) if item.get("progress_label") else None
|
||||
if not self._repository.mark_running(
|
||||
task_id=task_id,
|
||||
done_items=0,
|
||||
total_items=1,
|
||||
current_item=target,
|
||||
total_items=total_items,
|
||||
current_item=current_item,
|
||||
):
|
||||
self._finalize_if_already_cancelled(task_id, done_items=0, total_items=1)
|
||||
self._finalize_if_already_cancelled(task_id, done_items=0, total_items=total_items)
|
||||
return
|
||||
|
||||
completed_items = 0
|
||||
try:
|
||||
path = Path(target)
|
||||
if kind == "file":
|
||||
self._filesystem.delete_file(path)
|
||||
file_entry = files[0]
|
||||
completed_items = self._delete_planned_file(task_id, file_entry, completed_items, total_items)
|
||||
elif recursive:
|
||||
self._filesystem.delete_directory_recursive(path)
|
||||
for file_entry in files:
|
||||
if self._is_cancel_requested(task_id):
|
||||
self._finalize_cancelled(task_id, done_items=completed_items, total_items=total_items)
|
||||
return
|
||||
completed_items = self._delete_planned_file(task_id, file_entry, completed_items, total_items)
|
||||
if self._is_cancel_requested(task_id):
|
||||
self._finalize_cancelled(task_id, done_items=completed_items, total_items=total_items)
|
||||
return
|
||||
for directory in directories:
|
||||
self._filesystem.delete_empty_directory(Path(directory))
|
||||
else:
|
||||
self._filesystem.delete_empty_directory(path)
|
||||
self._filesystem.delete_empty_directory(Path(target))
|
||||
self._complete_or_cancel_item_task(
|
||||
task_id=task_id,
|
||||
done_items=1,
|
||||
total_items=1,
|
||||
done_items=completed_items,
|
||||
total_items=total_items,
|
||||
)
|
||||
except OSError as exc:
|
||||
task = self._repository.get_task(task_id)
|
||||
self._repository.mark_failed(
|
||||
task_id=task_id,
|
||||
error_code="io_error",
|
||||
error_message=str(exc),
|
||||
failed_item=target,
|
||||
failed_item=(task.get("current_item") if task else None) or target,
|
||||
done_bytes=None,
|
||||
total_bytes=None,
|
||||
done_items=0,
|
||||
total_items=1,
|
||||
done_items=completed_items,
|
||||
total_items=total_items,
|
||||
)
|
||||
self._update_history_failed(task_id, str(exc))
|
||||
|
||||
@@ -630,6 +648,29 @@ class TaskRunner:
|
||||
)
|
||||
return completed_items
|
||||
|
||||
def _delete_planned_file(
|
||||
self,
|
||||
task_id: str,
|
||||
file_entry: dict[str, str],
|
||||
completed_items: int,
|
||||
total_items: int,
|
||||
) -> int:
|
||||
self._repository.update_progress(
|
||||
task_id=task_id,
|
||||
done_items=completed_items,
|
||||
total_items=total_items,
|
||||
current_item=file_entry["label"],
|
||||
)
|
||||
self._filesystem.delete_file(Path(file_entry["path"]))
|
||||
completed_items += 1
|
||||
self._repository.update_progress(
|
||||
task_id=task_id,
|
||||
done_items=completed_items,
|
||||
total_items=total_items,
|
||||
current_item=self._next_item_label_after_completion(completed_items, total_items, file_entry["label"]),
|
||||
)
|
||||
return completed_items
|
||||
|
||||
def _move_directory_item(
|
||||
self,
|
||||
task_id: str,
|
||||
|
||||
Reference in New Issue
Block a user