diff --git a/backend_py/src/routes/recordings.py b/backend_py/src/routes/recordings.py index 9f32e023..71b188b7 100644 --- a/backend_py/src/routes/recordings.py +++ b/backend_py/src/routes/recordings.py @@ -172,7 +172,7 @@ def delete_recording(request: Request, recording_path: str) -> list[RecordingInf recordings_service: RecordingsService = request.app.state.recordings_service response = recordings_service.delete_recording(recording_path) - if not response: + if response is None: raise HTTPException( status_code=404, detail="Recording not found or could not be deleted" ) @@ -187,13 +187,7 @@ def bulk_delete_recording( ) -> list[RecordingInfo]: recordings_service: RecordingsService = request.app.state.recordings_service - response = recordings_service.bulk_delete_recordings(filenames) - if not response: - raise HTTPException( - status_code=404, detail="Recordings not found or could not be deleted" - ) - - return response + return recordings_service.bulk_delete_recordings(filenames) @recordings_router.patch("/{old_name}/{new_name}", summary="Rename a recording") diff --git a/backend_py/src/services/recordings/recordings_service.py b/backend_py/src/services/recordings/recordings_service.py index 4f70ab30..03ca3386 100644 --- a/backend_py/src/services/recordings/recordings_service.py +++ b/backend_py/src/services/recordings/recordings_service.py @@ -132,28 +132,29 @@ def get_recording(self, filename: str) -> RecordingInfo | None: return recording return None + def _remove_recording_file(self, filename: str) -> bool: + # Caller must hold recordings_lock + recording_path = os.path.join(self.recordings_path, filename) + self.durations.pop(recording_path, None) + + if not os.path.exists(recording_path): + return False + + os.remove(recording_path) + self.recordings = [rec for rec in self.recordings if rec.path != recording_path] + return True + def delete_recording(self, filename: str) -> list[RecordingInfo] | None: - if filename in self.durations: - self.durations.pop(filename, None) + with self.recordings_lock: + if not self._remove_recording_file(filename): + return None - recording_path = os.path.join(self.recordings_path, filename) - if os.path.exists(recording_path): - os.remove(recording_path) - self.recordings = [ - rec for rec in self.recordings if rec.path != recording_path - ] - return self.recordings - return None + return self.get_recordings() def bulk_delete_recordings(self, filenames: list[str]) -> list[RecordingInfo]: with self.recordings_lock: for filename in filenames: - if filename in self.durations: - self.durations.pop(filename, None) - - recording_path = os.path.join(self.recordings_path, filename) - if os.path.exists(recording_path): - os.remove(recording_path) + self._remove_recording_file(filename) return self.get_recordings() diff --git a/frontend/src/components/dwe/recordings/store/recording-store.tsx b/frontend/src/components/dwe/recordings/store/recording-store.tsx index 42fdd5d7..47612fce 100644 --- a/frontend/src/components/dwe/recordings/store/recording-store.tsx +++ b/frontend/src/components/dwe/recordings/store/recording-store.tsx @@ -305,16 +305,20 @@ export const recordingsActions = { body: targetNames, }); - if (result.data) { - recordingsState.recordings = result.data; - recordingsActions.setSelectedNames([]); - - toast.success( - deleteTargets.length > 1 - ? `Deleted ${deleteTargets.length} recordings` - : `Recording deleted: ${deleteTargets[0].name}`, - ); + if (result.error || !result.data) { + console.error(result.error); + toast.error("Failed to delete recording"); + return; } + + recordingsState.recordings = result.data; + recordingsActions.setSelectedNames([]); + + toast.success( + deleteTargets.length > 1 + ? `Deleted ${deleteTargets.length} recordings` + : `Recording deleted: ${deleteTargets[0].name}`, + ); } catch (error) { console.error(error); toast.error("Failed to delete recording"); diff --git a/run_release.py b/run_release.py index f2360f7e..a721f168 100755 --- a/run_release.py +++ b/run_release.py @@ -5,10 +5,12 @@ from contextlib import asynccontextmanager import socketio -from fastapi import FastAPI, HTTPException, Request +from fastapi import FastAPI, Request +from fastapi.exception_handlers import http_exception_handler from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import FileResponse, JSONResponse +from fastapi.responses import FileResponse, JSONResponse, Response from fastapi.staticfiles import StaticFiles +from starlette.exceptions import HTTPException as StarletteHTTPException from backend_py.src import FeatureSupport, Server @@ -95,8 +97,11 @@ async def register_service() -> JSONResponse: app.mount("/", StaticFiles(directory=FRONTEND_DIR, html=True), name="static") @app.exception_handler(404) - async def not_found(request: Request, exc: HTTPException) -> FileResponse: + async def not_found(request: Request, exc: StarletteHTTPException) -> Response: """Serve index.html for any unknown paths (Frontend Routing Support)""" + # API 404s keep their JSON error, so the frontend can tell they failed + if request.url.path.startswith("/api/"): + return await http_exception_handler(request, exc) return FileResponse(os.path.join(FRONTEND_DIR, "index.html")) async def main() -> None: