Files
dl-organizer/src/vlm/commands/plan.py
T

110 lines
4.2 KiB
Python
Raw Normal View History

"""Plan command implementation."""
import json
from pathlib import Path
from typing import Optional
import click
from vlm.context import CLIContext
from vlm.io import identities_to_plan_input, load_identities_json, load_analysis_json
from vlm.planner import generate_plan, save_plan
def plan_cmd(ctx: CLIContext, input: Path, output: Path, analysis: Optional[Path] = None) -> None:
"""Generate execution plan from identities; optionally use analysis for duplicate handling."""
config = ctx.config
logger = ctx.logger
click.echo(f"Generating execution plan from: {input}")
if analysis:
click.echo(f"Using analysis: {analysis}")
click.echo()
identities_data = load_identities_json(input)
movies_data = identities_data.get("movies", [])
series_data = identities_data.get("series", [])
anime_data = identities_data.get("anime", [])
other_data = identities_data.get("other", [])
click.echo(
f"Loaded {len(movies_data)} movies, {len(series_data)} series, "
f"{len(anime_data)} anime, {len(other_data)} other"
)
click.echo()
identities_list = identities_to_plan_input(identities_data)
analysis_data = None
if analysis:
if not analysis.exists():
click.echo(f"Error: Analysis file not found: {analysis}", err=True)
click.echo("Run 'vlm analyze' first to generate analysis.json.", err=True)
raise FileNotFoundError(analysis)
try:
analysis_data = load_analysis_json(analysis)
except (json.JSONDecodeError, KeyError) as e:
click.echo(f"Error: Invalid or incomplete analysis file: {e}", err=True)
click.echo("Run 'vlm analyze' to regenerate analysis.json.", err=True)
raise
click.echo("Generating execution plan...")
2026-02-13 13:36:39 +08:00
# Load state to filter ignored files
from vlm.state import StateManager
state_path = Path.home() / ".vlm" / "state.json"
state_manager = StateManager(state_path)
ignored_paths = {str(s.file_path) for s in state_manager.query_by_status("ignored")}
if ignored_paths:
click.echo(f" Filtering {len(ignored_paths)} files marked as 'ignored' in state.")
execution_plan = generate_plan(
identities_list,
config,
analysis_data=analysis_data,
ignored_paths=ignored_paths
)
click.echo()
click.echo("Plan generation complete!")
click.echo()
click.echo("Operation summary:")
click.echo(f" Total operations: {execution_plan.summary['total']}")
click.echo(f" Move operations: {execution_plan.summary['move']}")
click.echo(f" Rename operations: {execution_plan.summary['rename']}")
click.echo(f" Quarantine operations: {execution_plan.summary['quarantine']}")
click.echo(f" No-op (skipped): {execution_plan.summary['no-op']}")
conflicts = sum(1 for op in execution_plan.operations if op.has_conflict)
if conflicts > 0:
click.echo()
click.echo(f" ⚠️ Conflicts detected: {conflicts}")
click.echo(" Review the plan file for details on conflicting operations.")
# Check for directory warnings
directory_warning = execution_plan.metadata.get("directory_warning", False)
if directory_warning:
emptied_dirs = execution_plan.metadata.get("emptied_directories", [])
click.echo()
click.echo(f" ⚠️ Directory preservation warning: {len(emptied_dirs)} directories will be emptied")
click.echo(" These directories will be preserved but may be empty after execution.")
click.echo(" Review the plan file for details on preserved directories.")
click.echo()
click.echo(f"Saving execution plan to: {output}")
output.parent.mkdir(parents=True, exist_ok=True)
save_plan(execution_plan, output)
click.echo("Execution plan saved successfully!")
click.echo()
click.echo("Next steps:")
click.echo(f" 1. Review the plan: {output}")
click.echo(" 2. Edit the plan if needed (it's JSON)")
click.echo(f" 3. Dry-run: vlm execute --plan {output}")
click.echo(f" 4. Execute: vlm execute --plan {output} --confirm")
logger.info(
f"Plan generated: {execution_plan.summary['total']} operations, "
f"{conflicts} conflicts, saved to {output}"
)