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.
112 lines
3.3 KiB
Python
112 lines
3.3 KiB
Python
"""DTOs for the 5 atomic filesystem use cases.
|
|
|
|
Each use case returns a small frozen dataclass tagged with a ``status``
|
|
field. On error, ``error`` (machine-readable code) and ``message``
|
|
(human-readable) are populated; on success, the relevant payload
|
|
fields are.
|
|
|
|
Error codes mirror the infrastructure exception types (lowercased,
|
|
snake-cased) — e.g. ``SourceNotFound`` → ``"source_not_found"`` — plus
|
|
the application-layer ``"path_not_allowed"`` for guard violations.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ListDirResponse:
|
|
"""Response from ``list_dir_use_case``."""
|
|
|
|
status: str # "ok" | "error"
|
|
path: Path | None = None
|
|
entries: tuple[Path, ...] = ()
|
|
error: str | None = None
|
|
message: str | None = None
|
|
|
|
def to_dict(self) -> dict:
|
|
if self.error:
|
|
return {"status": self.status, "error": self.error, "message": self.message}
|
|
return {
|
|
"status": self.status,
|
|
"path": str(self.path) if self.path else None,
|
|
"entries": [str(p) for p in self.entries],
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CreateDirResponse:
|
|
"""Response from ``create_dir_use_case``."""
|
|
|
|
status: str
|
|
path: Path | None = None
|
|
error: str | None = None
|
|
message: str | None = None
|
|
|
|
def to_dict(self) -> dict:
|
|
if self.error:
|
|
return {"status": self.status, "error": self.error, "message": self.message}
|
|
return {"status": self.status, "path": str(self.path) if self.path else None}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LinkFileResponse:
|
|
"""Response from ``link_file_use_case``."""
|
|
|
|
status: str
|
|
source: Path | None = None
|
|
destination: Path | None = None
|
|
error: str | None = None
|
|
message: str | None = None
|
|
|
|
def to_dict(self) -> dict:
|
|
if self.error:
|
|
return {"status": self.status, "error": self.error, "message": self.message}
|
|
return {
|
|
"status": self.status,
|
|
"source": str(self.source) if self.source else None,
|
|
"destination": str(self.destination) if self.destination else None,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MoveFileResponse:
|
|
"""Response from ``move_file_use_case``."""
|
|
|
|
status: str
|
|
source: Path | None = None
|
|
destination: Path | None = None
|
|
error: str | None = None
|
|
message: str | None = None
|
|
|
|
def to_dict(self) -> dict:
|
|
if self.error:
|
|
return {"status": self.status, "error": self.error, "message": self.message}
|
|
return {
|
|
"status": self.status,
|
|
"source": str(self.source) if self.source else None,
|
|
"destination": str(self.destination) if self.destination else None,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MoveDirResponse:
|
|
"""Response from ``move_dir_use_case``."""
|
|
|
|
status: str
|
|
source: Path | None = None
|
|
destination: Path | None = None
|
|
error: str | None = None
|
|
message: str | None = None
|
|
|
|
def to_dict(self) -> dict:
|
|
if self.error:
|
|
return {"status": self.status, "error": self.error, "message": self.message}
|
|
return {
|
|
"status": self.status,
|
|
"source": str(self.source) if self.source else None,
|
|
"destination": str(self.destination) if self.destination else None,
|
|
}
|