refactor(tools): wire filesystem tools to new use cases, drop broken ones
Updates alfred/agent/tools/filesystem.py to use the five free-function use cases introduced in the previous commit: - list_folder -> list_dir_use_case(Path(path), roots) - create_directory (new) -> create_dir_use_case(Path(path), roots) - move_media -> move_file_use_case(src, dst, roots) - move_to_destination -> create_dir_use_case(dst.parent) + move_file A module-level _load_directory_roots() helper reads memory once per call and builds the DirectoryRoots VO; missing roots produce an explicit 'roots_not_configured' error. Tools whose backing code was moved to *_OLD files are removed entirely rather than left broken: manage_subtitles, set_path_for_folder, create_seed_links, and the four resolve_*_destination tools. They will come back when the matching application/domain code is rebuilt later on this branch. alfred/agent/tools/__init__.py shrinks accordingly. find_media_imdb_id (already broken before this branch — name not exported by tools.api) is dropped from the package re-exports so the package imports cleanly again.
This commit is contained in:
@@ -34,6 +34,15 @@ callers).
|
||||
and returns a frozen `<Op>Response` DTO. Roots are now injected, not
|
||||
pulled from the global memory singleton.
|
||||
|
||||
- **Agent tool wrappers partially re-wired** to the new use cases.
|
||||
`list_folder` now delegates to `list_dir_use_case`; `move_media`
|
||||
to `move_file_use_case`; `move_to_destination` chains
|
||||
`create_dir_use_case` + `move_file_use_case`; a new
|
||||
`create_directory` tool wraps `create_dir_use_case`. Roots are
|
||||
loaded once via a module-level `_load_directory_roots()` helper
|
||||
that reads the persisted memory (no more per-call singleton
|
||||
reads inside the use cases themselves).
|
||||
|
||||
### Removed
|
||||
|
||||
- `FileManager` / `MediaOrganizer` / `create_folder` / `move` from the
|
||||
@@ -46,6 +55,16 @@ callers).
|
||||
of `alfred.application.filesystem`. Same `_OLD` rename treatment.
|
||||
This intentionally breaks current tool wrappers and tests downstream
|
||||
— re-wiring is the next chunk of work on this branch.
|
||||
- **Agent tools dropped during the refactor** (to be reintroduced
|
||||
when the matching domain/application code lands):
|
||||
`manage_subtitles`, `set_path_for_folder`, `create_seed_links`,
|
||||
`resolve_season_destination`, `resolve_episode_destination`,
|
||||
`resolve_movie_destination`, `resolve_series_destination`.
|
||||
Their wrappers are removed from `alfred.agent.tools.filesystem`;
|
||||
`alfred.agent.tools.__init__` now re-exports only what still
|
||||
imports cleanly. `find_media_imdb_id` (already broken before this
|
||||
branch — name no longer exported by `tools.api`) was also dropped
|
||||
from the package re-exports.
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
"""Tools module - filesystem and API tools for the agent."""
|
||||
"""Tools module — agent-exposed wrappers.
|
||||
|
||||
Re-exports are intentionally minimal during the ``unfuck`` refactor.
|
||||
Tool wiring (registry / specs / LLM-facing surface) is the last
|
||||
chunk of work on this branch; until then, importers should reach
|
||||
into the submodules directly (``alfred.agent.tools.filesystem``, …).
|
||||
"""
|
||||
|
||||
from .api import (
|
||||
add_torrent_by_index,
|
||||
add_torrent_to_qbittorrent,
|
||||
find_media_imdb_id,
|
||||
find_torrent,
|
||||
get_torrent_by_index,
|
||||
)
|
||||
from .filesystem import list_folder, set_path_for_folder
|
||||
from .language import set_language
|
||||
|
||||
__all__ = [
|
||||
"set_path_for_folder",
|
||||
"list_folder",
|
||||
"find_media_imdb_id",
|
||||
"find_torrent",
|
||||
"get_torrent_by_index",
|
||||
"add_torrent_to_qbittorrent",
|
||||
|
||||
+125
-129
@@ -1,4 +1,20 @@
|
||||
"""Filesystem tools for folder management."""
|
||||
"""Filesystem tools for folder management.
|
||||
|
||||
Thin wrappers around the 5 atomic filesystem use cases
|
||||
(``alfred.application.filesystem``) plus a few self-contained tools
|
||||
(``analyze_release``, ``probe_media``, ``learn``, …).
|
||||
|
||||
Tools removed during the ``unfuck`` filesystem refactor — to be
|
||||
rewired in a later step:
|
||||
- ``manage_subtitles`` (depends on the rewritten subtitle services)
|
||||
- ``set_path_for_folder`` (no replacement use case yet)
|
||||
- ``create_seed_links`` (flow has changed: hard-link straight to
|
||||
library, no copy back; will be re-introduced per-file when the
|
||||
organize-release workflow lands)
|
||||
- ``resolve_season_destination`` / ``resolve_episode_destination``
|
||||
/ ``resolve_movie_destination`` / ``resolve_series_destination``
|
||||
(their use cases moved to ``_OLD`` files pending a rewrite)
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -7,25 +23,11 @@ import yaml
|
||||
|
||||
import alfred as _alfred_pkg
|
||||
from alfred.application.filesystem import (
|
||||
CreateSeedLinksUseCase,
|
||||
ListFolderUseCase,
|
||||
ManageSubtitlesUseCase,
|
||||
MoveMediaUseCase,
|
||||
SetFolderPathUseCase,
|
||||
DirectoryRoots,
|
||||
create_dir_use_case,
|
||||
list_dir_use_case,
|
||||
move_file_use_case,
|
||||
)
|
||||
from alfred.application.filesystem.resolve_destination import (
|
||||
resolve_episode_destination as _resolve_episode_destination,
|
||||
)
|
||||
from alfred.application.filesystem.resolve_destination import (
|
||||
resolve_movie_destination as _resolve_movie_destination,
|
||||
)
|
||||
from alfred.application.filesystem.resolve_destination import (
|
||||
resolve_season_destination as _resolve_season_destination,
|
||||
)
|
||||
from alfred.application.filesystem.resolve_destination import (
|
||||
resolve_series_destination as _resolve_series_destination,
|
||||
)
|
||||
from alfred.infrastructure.filesystem import FileManager, create_folder, move
|
||||
from alfred.infrastructure.knowledge.release_kb import YamlReleaseKnowledge
|
||||
from alfred.infrastructure.metadata import MetadataStore
|
||||
from alfred.infrastructure.persistence import get_memory
|
||||
@@ -40,107 +42,117 @@ _PROBER = FfprobeMediaProber()
|
||||
_LEARNED_ROOT = Path(_alfred_pkg.__file__).parent.parent / "data" / "knowledge"
|
||||
|
||||
|
||||
class _RootsNotConfigured(Exception):
|
||||
"""Raised when one of the 4 expected roots is missing from memory."""
|
||||
|
||||
def __init__(self, missing: list[str]):
|
||||
super().__init__(f"Roots not configured: {missing}")
|
||||
self.missing = missing
|
||||
|
||||
|
||||
def _load_directory_roots() -> DirectoryRoots:
|
||||
"""Build :class:`DirectoryRoots` from the persisted memory.
|
||||
|
||||
Reads:
|
||||
- ``ltm.workspace.download`` → ``downloads``
|
||||
- ``ltm.workspace.torrent`` → ``torrents``
|
||||
- ``ltm.library_paths['movies']`` → ``movies``
|
||||
- ``ltm.library_paths['tv_shows']`` → ``tv_shows``
|
||||
|
||||
Raises:
|
||||
_RootsNotConfigured: if any of the four paths is unset.
|
||||
"""
|
||||
memory = get_memory()
|
||||
downloads = memory.ltm.workspace.download
|
||||
torrents = memory.ltm.workspace.torrent
|
||||
movies = memory.ltm.library_paths.get("movies")
|
||||
tv_shows = memory.ltm.library_paths.get("tv_shows")
|
||||
|
||||
missing: list[str] = []
|
||||
if not downloads:
|
||||
missing.append("downloads")
|
||||
if not torrents:
|
||||
missing.append("torrents")
|
||||
if not movies:
|
||||
missing.append("movies")
|
||||
if not tv_shows:
|
||||
missing.append("tv_shows")
|
||||
if missing:
|
||||
raise _RootsNotConfigured(missing)
|
||||
|
||||
return DirectoryRoots(
|
||||
downloads=Path(downloads),
|
||||
torrents=Path(torrents),
|
||||
movies=Path(movies),
|
||||
tv_shows=Path(tv_shows),
|
||||
)
|
||||
|
||||
|
||||
def _roots_error(exc: _RootsNotConfigured) -> dict[str, Any]:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "roots_not_configured",
|
||||
"message": (
|
||||
f"Missing roots: {exc.missing}. "
|
||||
"Configure them via /set_path before using filesystem tools."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5 atomic filesystem tools — thin wrappers over the use cases.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def list_folder(path: str) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/list_folder.yaml."""
|
||||
try:
|
||||
roots = _load_directory_roots()
|
||||
except _RootsNotConfigured as e:
|
||||
return _roots_error(e)
|
||||
return list_dir_use_case(Path(path), roots).to_dict()
|
||||
|
||||
|
||||
def create_directory(path: str) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/create_directory.yaml."""
|
||||
try:
|
||||
roots = _load_directory_roots()
|
||||
except _RootsNotConfigured as e:
|
||||
return _roots_error(e)
|
||||
return create_dir_use_case(Path(path), roots).to_dict()
|
||||
|
||||
|
||||
def move_media(source: str, destination: str) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/move_media.yaml."""
|
||||
file_manager = FileManager()
|
||||
use_case = MoveMediaUseCase(file_manager)
|
||||
return use_case.execute(source, destination).to_dict()
|
||||
try:
|
||||
roots = _load_directory_roots()
|
||||
except _RootsNotConfigured as e:
|
||||
return _roots_error(e)
|
||||
return move_file_use_case(Path(source), Path(destination), roots).to_dict()
|
||||
|
||||
|
||||
def move_to_destination(source: str, destination: str) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/move_to_destination.yaml."""
|
||||
parent = str(Path(destination).parent)
|
||||
result = create_folder(parent)
|
||||
if result["status"] != "ok":
|
||||
return result
|
||||
return move(source, destination)
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/move_to_destination.yaml.
|
||||
|
||||
Convenience tool that creates the destination's parent directory
|
||||
if missing, then moves the file. Saves the LLM from having to
|
||||
chain ``create_directory`` + ``move_media`` explicitly.
|
||||
"""
|
||||
try:
|
||||
roots = _load_directory_roots()
|
||||
except _RootsNotConfigured as e:
|
||||
return _roots_error(e)
|
||||
|
||||
dst = Path(destination)
|
||||
mkdir_resp = create_dir_use_case(dst.parent, roots)
|
||||
if mkdir_resp.status != "ok":
|
||||
return mkdir_resp.to_dict()
|
||||
return move_file_use_case(Path(source), dst, roots).to_dict()
|
||||
|
||||
|
||||
def resolve_season_destination(
|
||||
release_name: str,
|
||||
tmdb_title: str,
|
||||
tmdb_year: int,
|
||||
confirmed_folder: str | None = None,
|
||||
source_path: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/resolve_season_destination.yaml."""
|
||||
return _resolve_season_destination(
|
||||
release_name,
|
||||
tmdb_title,
|
||||
tmdb_year,
|
||||
_KB,
|
||||
_PROBER,
|
||||
confirmed_folder,
|
||||
source_path,
|
||||
).to_dict()
|
||||
|
||||
|
||||
def resolve_episode_destination(
|
||||
release_name: str,
|
||||
source_file: str,
|
||||
tmdb_title: str,
|
||||
tmdb_year: int,
|
||||
tmdb_episode_title: str | None = None,
|
||||
confirmed_folder: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/resolve_episode_destination.yaml."""
|
||||
return _resolve_episode_destination(
|
||||
release_name,
|
||||
source_file,
|
||||
tmdb_title,
|
||||
tmdb_year,
|
||||
_KB,
|
||||
_PROBER,
|
||||
tmdb_episode_title,
|
||||
confirmed_folder,
|
||||
).to_dict()
|
||||
|
||||
|
||||
def resolve_movie_destination(
|
||||
release_name: str,
|
||||
source_file: str,
|
||||
tmdb_title: str,
|
||||
tmdb_year: int,
|
||||
) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/resolve_movie_destination.yaml."""
|
||||
return _resolve_movie_destination(
|
||||
release_name, source_file, tmdb_title, tmdb_year, _KB, _PROBER
|
||||
).to_dict()
|
||||
|
||||
|
||||
def resolve_series_destination(
|
||||
release_name: str,
|
||||
tmdb_title: str,
|
||||
tmdb_year: int,
|
||||
confirmed_folder: str | None = None,
|
||||
source_path: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/resolve_series_destination.yaml."""
|
||||
return _resolve_series_destination(
|
||||
release_name,
|
||||
tmdb_title,
|
||||
tmdb_year,
|
||||
_KB,
|
||||
_PROBER,
|
||||
confirmed_folder,
|
||||
source_path,
|
||||
).to_dict()
|
||||
|
||||
|
||||
def create_seed_links(
|
||||
library_file: str, original_download_folder: str
|
||||
) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/create_seed_links.yaml."""
|
||||
file_manager = FileManager()
|
||||
use_case = CreateSeedLinksUseCase(file_manager)
|
||||
return use_case.execute(library_file, original_download_folder).to_dict()
|
||||
|
||||
|
||||
def manage_subtitles(source_video: str, destination_video: str) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/manage_subtitles.yaml."""
|
||||
file_manager = FileManager()
|
||||
use_case = ManageSubtitlesUseCase(file_manager)
|
||||
return use_case.execute(source_video, destination_video).to_dict()
|
||||
# ---------------------------------------------------------------------------
|
||||
# Self-contained tools — not impacted by the filesystem refactor.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def learn(pack: str, category: str, key: str, values: list[str]) -> dict[str, Any]:
|
||||
@@ -200,14 +212,6 @@ def learn(pack: str, category: str, key: str, values: list[str]) -> dict[str, An
|
||||
}
|
||||
|
||||
|
||||
def set_path_for_folder(folder_name: str, path_value: str) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/set_path_for_folder.yaml."""
|
||||
file_manager = FileManager()
|
||||
use_case = SetFolderPathUseCase(file_manager)
|
||||
response = use_case.execute(folder_name, path_value)
|
||||
return response.to_dict()
|
||||
|
||||
|
||||
def analyze_release(release_name: str, source_path: str) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/analyze_release.yaml."""
|
||||
from alfred.application.release import inspect_release # noqa: PLC0415
|
||||
@@ -296,14 +300,6 @@ def probe_media(source_path: str) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def list_folder(folder_type: str, path: str = ".") -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/list_folder.yaml."""
|
||||
file_manager = FileManager()
|
||||
use_case = ListFolderUseCase(file_manager)
|
||||
response = use_case.execute(folder_type, path)
|
||||
return response.to_dict()
|
||||
|
||||
|
||||
def read_release_metadata(release_path: str) -> dict[str, Any]:
|
||||
"""Thin tool wrapper — semantics live in alfred/agent/tools/specs/read_release_metadata.yaml."""
|
||||
path = Path(release_path)
|
||||
|
||||
Reference in New Issue
Block a user