Update
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
"""Saves files the server queues via its deliver_files tool (ai/desk_api.py
|
||||
in the main tmn-api repo) to a local downloads folder.
|
||||
|
||||
The server side of this is already generic — any desk client can list
|
||||
GET /desk/files and fetch GET /desk/files/<id> (see server_client.
|
||||
list_outbox_files / download_outbox_file) — so this module is just the
|
||||
filesystem half: turn a server-supplied display name into a safe path and
|
||||
write the bytes.
|
||||
|
||||
Pure filename/path logic lives here so it's testable without touching a real
|
||||
mic/network; the only I/O is the final write in save().
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
_FALLBACK_NAME = "delivered_file"
|
||||
|
||||
|
||||
def sanitize_filename(name: str) -> str:
|
||||
"""Reduce a server-supplied name to a bare filename. Defends against a
|
||||
delivered name that's actually a path (../../etc, an absolute path, ...)
|
||||
— Path(...).name strips every directory component, and anything that
|
||||
collapses to nothing (or "." / "..") falls back to a generic name."""
|
||||
candidate = Path(str(name or "").strip()).name
|
||||
if candidate in ("", ".", ".."):
|
||||
return _FALLBACK_NAME
|
||||
return candidate
|
||||
|
||||
|
||||
def unique_path(directory: Path, name: str) -> Path:
|
||||
"""*name* under *directory*, suffixed " (1)", " (2)", ... if that name is
|
||||
already taken — a delivered file never overwrites an earlier download."""
|
||||
directory = Path(directory)
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
candidate = directory / name
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
stem, suffix = candidate.stem, candidate.suffix
|
||||
n = 1
|
||||
while True:
|
||||
candidate = directory / f"{stem} ({n}){suffix}"
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
n += 1
|
||||
|
||||
|
||||
def save(directory: Path, name: str, data: bytes) -> Path:
|
||||
"""Write *data* under *directory* as *name* (sanitized + uniquified),
|
||||
returning the path written."""
|
||||
path = unique_path(directory, sanitize_filename(name))
|
||||
path.write_bytes(data)
|
||||
return path
|
||||
Reference in New Issue
Block a user