2df7843d8b
Replaces the monolithic FileManager class + scattered helpers in
alfred/infrastructure/filesystem with five free functions, each
single-responsibility and pathlib-native:
list_dir / create_dir / link_file / move_file / move_dir
The infra layer now raises typed exceptions (FilesystemError base
+ SourceNotFound / DestinationExists / NotADirectory / NotAFile /
PermissionDenied / CrossDevice / FilesystemOSError) instead of
returning {status: ok|error} dicts. No more get_memory() reads
from infra.
Application layer mirrors the same split: five free use cases
(<op>_use_case) wrap each infra op, guard inputs against escaping
the new DirectoryRoots VO (downloads / torrents / movies /
tv_shows), catch infra exceptions, and return frozen DTOs. Roots
are injected — no global state.
Legacy files kept on disk with _OLD suffix for reference during
the follow-up rewiring (FileManager, MediaOrganizer,
create_folder/move helpers; CreateSeedLinks/ListFolder/MoveMedia/
ManageSubtitles use cases, resolve_destination). They are no
longer exported from __init__, which intentionally breaks current
agent tool wrappers and downstream tests — re-wiring is the next
chunk of work on the unfuck branch.
41 lines
1.3 KiB
Python
41 lines
1.3 KiB
Python
"""link_file use case — hard-link a file from one root to another."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from alfred.infrastructure.filesystem import FilesystemError, link_file
|
|
|
|
from ._errors import PATH_NOT_ALLOWED, code_for
|
|
from .directory_roots import DirectoryRoots
|
|
from .dto import LinkFileResponse
|
|
|
|
|
|
def link_file_use_case(
|
|
src: Path, dst: Path, roots: DirectoryRoots
|
|
) -> LinkFileResponse:
|
|
"""Hard-link ``src`` to ``dst``. Both must be under configured roots.
|
|
|
|
The destination parent must already exist — the caller is expected
|
|
to have created it via ``create_dir_use_case`` if needed.
|
|
"""
|
|
if not roots.contains(src):
|
|
return LinkFileResponse(
|
|
status="error",
|
|
error=PATH_NOT_ALLOWED,
|
|
message=f"Source is outside configured roots: {src}",
|
|
)
|
|
if not roots.contains(dst):
|
|
return LinkFileResponse(
|
|
status="error",
|
|
error=PATH_NOT_ALLOWED,
|
|
message=f"Destination is outside configured roots: {dst}",
|
|
)
|
|
|
|
try:
|
|
link_file(src, dst)
|
|
except FilesystemError as e:
|
|
return LinkFileResponse(status="error", error=code_for(e), message=str(e))
|
|
|
|
return LinkFileResponse(status="ok", source=src, destination=dst)
|