refactor: 统一临时文件清理逻辑
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import re
|
import re
|
||||||
from contextlib import suppress
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@@ -12,17 +11,13 @@ from starlette.background import BackgroundTask
|
|||||||
from app import state
|
from app import state
|
||||||
from app.config import CACHE_DIR
|
from app.config import CACHE_DIR
|
||||||
from app.database import DatabaseManager
|
from app.database import DatabaseManager
|
||||||
|
from app.utils.files import remove_file_safely
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(tags=["database"])
|
router = APIRouter(tags=["database"])
|
||||||
INVALID_SHEET_NAME_CHARS = re.compile(r"[:\\/?*\[\]]")
|
INVALID_SHEET_NAME_CHARS = re.compile(r"[:\\/?*\[\]]")
|
||||||
|
|
||||||
|
|
||||||
def _remove_file(path: Path) -> None:
|
|
||||||
with suppress(OSError):
|
|
||||||
path.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_requested_tables(
|
def _resolve_requested_tables(
|
||||||
table_name: Optional[str],
|
table_name: Optional[str],
|
||||||
table_names: Optional[list[str]],
|
table_names: Optional[list[str]],
|
||||||
@@ -227,12 +222,12 @@ async def download_table(
|
|||||||
df.to_excel(writer, sheet_name=_make_sheet_name(name, used_sheet_names), index=False)
|
df.to_excel(writer, sheet_name=_make_sheet_name(name, used_sheet_names), index=False)
|
||||||
media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
except Exception:
|
except Exception:
|
||||||
_remove_file(filepath)
|
remove_file_safely(filepath)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return FileResponse(
|
return FileResponse(
|
||||||
path=str(filepath),
|
path=str(filepath),
|
||||||
filename=filename,
|
filename=filename,
|
||||||
media_type=media_type,
|
media_type=media_type,
|
||||||
background=BackgroundTask(_remove_file, filepath),
|
background=BackgroundTask(remove_file_safely, filepath),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import zipfile
|
import zipfile
|
||||||
from contextlib import suppress
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -10,17 +9,12 @@ from starlette.background import BackgroundTask
|
|||||||
|
|
||||||
from app import state
|
from app import state
|
||||||
from app.config import CACHE_DIR
|
from app.config import CACHE_DIR
|
||||||
from app.utils.files import format_size, get_dir_size
|
from app.utils.files import format_size, get_dir_size, remove_file_safely
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(tags=["history"])
|
router = APIRouter(tags=["history"])
|
||||||
|
|
||||||
|
|
||||||
def _remove_file(path: Path) -> None:
|
|
||||||
with suppress(OSError):
|
|
||||||
path.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
def _safe_filename_part(value: str) -> str:
|
def _safe_filename_part(value: str) -> str:
|
||||||
safe = "".join(char if char.isalnum() or char in {"-", "_"} else "_" for char in value)
|
safe = "".join(char if char.isalnum() or char in {"-", "_"} else "_" for char in value)
|
||||||
return safe.strip("_") or "history"
|
return safe.strip("_") or "history"
|
||||||
@@ -202,14 +196,14 @@ async def download_history(record_id: str = Body(..., embed=True)):
|
|||||||
try:
|
try:
|
||||||
_zip_directory(work_dir, archive_path)
|
_zip_directory(work_dir, archive_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
_remove_file(archive_path)
|
remove_file_safely(archive_path)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return FileResponse(
|
return FileResponse(
|
||||||
path=str(archive_path),
|
path=str(archive_path),
|
||||||
filename=filename,
|
filename=filename,
|
||||||
media_type="application/zip",
|
media_type="application/zip",
|
||||||
background=BackgroundTask(_remove_file, archive_path),
|
background=BackgroundTask(remove_file_safely, archive_path),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -238,12 +232,12 @@ async def download_history_file(
|
|||||||
try:
|
try:
|
||||||
_zip_history_item(target_path, archive_path)
|
_zip_history_item(target_path, archive_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
_remove_file(archive_path)
|
remove_file_safely(archive_path)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return FileResponse(
|
return FileResponse(
|
||||||
path=str(archive_path),
|
path=str(archive_path),
|
||||||
filename=filename,
|
filename=filename,
|
||||||
media_type="application/zip",
|
media_type="application/zip",
|
||||||
background=BackgroundTask(_remove_file, archive_path),
|
background=BackgroundTask(remove_file_safely, archive_path),
|
||||||
)
|
)
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,13 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def remove_file_safely(path: Path) -> None:
|
||||||
|
try:
|
||||||
|
path.unlink()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_dir_size(path: Path) -> int:
|
def get_dir_size(path: Path) -> int:
|
||||||
total_size = 0
|
total_size = 0
|
||||||
try:
|
try:
|
||||||
@@ -26,4 +33,3 @@ def format_size(size_bytes: int) -> str:
|
|||||||
return f"{value:.2f} {unit}"
|
return f"{value:.2f} {unit}"
|
||||||
value /= 1024.0
|
value /= 1024.0
|
||||||
return f"{value:.2f} PB"
|
return f"{value:.2f} PB"
|
||||||
|
|
||||||
|
|||||||
@@ -632,3 +632,9 @@
|
|||||||
- `app/api/routers/task_runtime.py` centralizes shared task-stage updates, processing license log output, and safe history-retention cleanup for manual processing and remote processing routes.
|
- `app/api/routers/task_runtime.py` centralizes shared task-stage updates, processing license log output, and safe history-retention cleanup for manual processing and remote processing routes.
|
||||||
- `app/api/routers/tasks.py` and `app/api/routers/remote.py` now reuse the shared helpers instead of carrying duplicate `_set_task_stage` and `_log_license_check` implementations.
|
- `app/api/routers/tasks.py` and `app/api/routers/remote.py` now reuse the shared helpers instead of carrying duplicate `_set_task_stage` and `_log_license_check` implementations.
|
||||||
- Verification performed: `.venv\Scripts\python.exe -m compileall app`, `npm run build`, and `cargo check --manifest-path src-tauri\Cargo.toml` with a temporary sidecar placeholder all passed. Generated build output, Python caches, and temporary Tauri sidecar files were removed after verification.
|
- Verification performed: `.venv\Scripts\python.exe -m compileall app`, `npm run build`, and `cargo check --manifest-path src-tauri\Cargo.toml` with a temporary sidecar placeholder all passed. Generated build output, Python caches, and temporary Tauri sidecar files were removed after verification.
|
||||||
|
|
||||||
|
## 2026-06-01: Download cleanup helper
|
||||||
|
|
||||||
|
- `app/utils/files.py` now provides `remove_file_safely()` for best-effort temporary file cleanup.
|
||||||
|
- Database table export and history archive download routes now reuse this helper instead of carrying duplicate private `_remove_file()` functions.
|
||||||
|
- Verification performed: `.venv\Scripts\python.exe -m compileall app`, `npm run build`, and `cargo check --manifest-path src-tauri\Cargo.toml` with a temporary sidecar placeholder all passed. Generated build output, Python caches, and temporary Tauri sidecar files were removed after verification.
|
||||||
|
|||||||
Reference in New Issue
Block a user