feat: delete non empty folders
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -31,7 +31,7 @@ async def delete(
|
||||
request: DeleteRequest,
|
||||
service: FileOpsService = Depends(get_file_ops_service),
|
||||
) -> DeleteResponse:
|
||||
return service.delete(path=request.path)
|
||||
return service.delete(path=request.path, recursive=request.recursive)
|
||||
|
||||
|
||||
@router.post("/upload", response_model=UploadResponse)
|
||||
|
||||
@@ -52,6 +52,7 @@ class RenameResponse(BaseModel):
|
||||
|
||||
class DeleteRequest(BaseModel):
|
||||
path: str
|
||||
recursive: bool = False
|
||||
|
||||
|
||||
class DeleteResponse(BaseModel):
|
||||
|
||||
Binary file not shown.
@@ -104,6 +104,9 @@ class FilesystemAdapter:
|
||||
def delete_empty_directory(self, path: Path) -> None:
|
||||
path.rmdir()
|
||||
|
||||
def delete_directory_recursive(self, path: Path) -> None:
|
||||
shutil.rmtree(path)
|
||||
|
||||
def copy_file(self, source: str, destination: str, on_progress: callable | None = None) -> None:
|
||||
src = Path(source)
|
||||
dst = Path(destination)
|
||||
|
||||
Binary file not shown.
@@ -158,7 +158,7 @@ class FileOpsService:
|
||||
self._record_history_error(operation="rename", source=path, destination=new_name, path=path, error=error)
|
||||
raise error
|
||||
|
||||
def delete(self, path: str) -> DeleteResponse:
|
||||
def delete(self, path: str, recursive: bool = False) -> DeleteResponse:
|
||||
try:
|
||||
resolved_target = self._path_guard.resolve_existing_path(path)
|
||||
|
||||
@@ -166,13 +166,16 @@ class FileOpsService:
|
||||
self._filesystem.delete_file(resolved_target.absolute)
|
||||
elif resolved_target.absolute.is_dir():
|
||||
if not self._filesystem.is_directory_empty(resolved_target.absolute):
|
||||
raise AppError(
|
||||
code="directory_not_empty",
|
||||
message="Directory is not empty",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
self._filesystem.delete_empty_directory(resolved_target.absolute)
|
||||
if not recursive:
|
||||
raise AppError(
|
||||
code="directory_not_empty",
|
||||
message="Directory is not empty",
|
||||
status_code=409,
|
||||
details={"path": resolved_target.relative},
|
||||
)
|
||||
self._filesystem.delete_directory_recursive(resolved_target.absolute)
|
||||
else:
|
||||
self._filesystem.delete_empty_directory(resolved_target.absolute)
|
||||
else:
|
||||
raise AppError(
|
||||
code="type_conflict",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -300,6 +300,22 @@ class FileOpsApiGoldenTest(unittest.TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_delete_non_empty_directory_recursive_success(self) -> None:
|
||||
target = self.scope / "non_empty_recursive"
|
||||
target.mkdir()
|
||||
nested = target / "nested"
|
||||
nested.mkdir()
|
||||
(nested / "a.txt").write_text("a", encoding="utf-8")
|
||||
|
||||
response = self._post(
|
||||
"/api/files/delete",
|
||||
{"path": "storage1/scope/non_empty_recursive", "recursive": True},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"path": "storage1/scope/non_empty_recursive"})
|
||||
self.assertFalse(target.exists())
|
||||
|
||||
def test_delete_invalid_path(self) -> None:
|
||||
response = self._post(
|
||||
"/api/files/delete",
|
||||
|
||||
@@ -65,6 +65,9 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
||||
self.assertIn('id="upload-modal-count"', body)
|
||||
self.assertIn('id="upload-modal-status"', body)
|
||||
self.assertIn('id="upload-modal-cancel-btn"', body)
|
||||
self.assertIn('id="feedback-modal"', body)
|
||||
self.assertIn('id="feedback-message"', body)
|
||||
self.assertIn('id="feedback-close-btn"', body)
|
||||
self.assertIn('id="settings-btn"', body)
|
||||
self.assertIn('id="rename-btn"', body)
|
||||
self.assertIn('id="view-btn"', body)
|
||||
@@ -132,6 +135,10 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
||||
self.assertIn(">Target path</label>", body)
|
||||
self.assertIn('id="batch-move-popup"', body)
|
||||
self.assertIn('id="batch-move-apply-btn"', body)
|
||||
self.assertIn('id="delete-confirm-modal"', body)
|
||||
self.assertIn('id="delete-confirm-apply-btn"', body)
|
||||
self.assertIn('id="delete-confirm-cancel-btn"', body)
|
||||
self.assertIn("Delete folder and contents?", body)
|
||||
self.assertIn('id="mkdir-btn"', body)
|
||||
self.assertIn('id="copy-btn"', body)
|
||||
self.assertIn('id="move-btn"', body)
|
||||
@@ -189,12 +196,18 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
||||
self.assertIn("document.documentElement.dataset.theme", app_js)
|
||||
self.assertIn('document.getElementById("theme-toggle").onclick = toggleTheme;', app_js)
|
||||
self.assertIn('document.getElementById("upload-btn").onclick = openUploadPicker;', app_js)
|
||||
self.assertIn('function feedbackElements()', app_js)
|
||||
self.assertIn('function openFeedbackModal(message)', app_js)
|
||||
self.assertIn('function closeFeedbackModal()', app_js)
|
||||
self.assertIn('document.getElementById("upload-menu-toggle").onclick = (event) => {', app_js)
|
||||
self.assertIn('document.getElementById("upload-folder-btn").onclick = openFolderPicker;', app_js)
|
||||
self.assertIn('throw createApiError(response, data);', app_js)
|
||||
self.assertIn('function closeUploadMenu()', app_js)
|
||||
self.assertIn('function toggleUploadMenu()', app_js)
|
||||
self.assertNotIn('if (event.altKey) {', app_js)
|
||||
self.assertIn('document.getElementById("settings-btn").onclick = () => openSettings("general");', app_js)
|
||||
self.assertIn('err.code === "directory_not_empty"', app_js)
|
||||
self.assertIn('openDeleteConfirmModal(item.path);', app_js)
|
||||
self.assertIn('async function loadSettings()', app_js)
|
||||
self.assertIn('await loadSettings();', app_js)
|
||||
self.assertIn('settings.showThumbnailsInput.onchange = handleShowThumbnailsChange;', app_js)
|
||||
@@ -225,6 +238,11 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
||||
self.assertIn('async function ensureFolderDirectoryExists(path)', app_js)
|
||||
self.assertIn('async function executeFolderUploadPlan(plan)', app_js)
|
||||
self.assertIn('async function handleFolderSelection(event)', app_js)
|
||||
self.assertIn('function deleteConfirmElements()', app_js)
|
||||
self.assertIn('function openDeleteConfirmModal(path)', app_js)
|
||||
self.assertIn('async function submitDeleteConfirmModal()', app_js)
|
||||
self.assertIn('recursive: true', app_js)
|
||||
self.assertIn('err.code === "directory_not_empty"', app_js)
|
||||
self.assertIn('input.setAttribute("webkitdirectory", "")', app_js)
|
||||
self.assertIn('await apiRequest("POST", "/api/files/mkdir", {', app_js)
|
||||
self.assertIn('await uploadFileRequest(targetPath, entry.file, overwrite);', app_js)
|
||||
|
||||
Reference in New Issue
Block a user