import time import datetime from pathlib import Path from typing import List, Dict, Any, Optional class ToolError(Exception): """Custom exception for tool-related errors to be caught by the MCP server.""" pass def format_relative_time(timestamp: float) -> str: """Converts a timestamp to a human-readable relative format.""" now = time.time() diff = now - timestamp seconds = int(diff) if seconds < 0: return "In the future" if seconds < 60: return "Just now" minutes = seconds // 60 if minutes < 60: return f"{minutes}m ago" hours = minutes // 60 if hours < 24: return f"{hours}h ago" days = hours // 24 if days == 1: return "Yesterday" if days < 30: return f"{days}d ago" months = days // 30 if months < 12: return f"{months}mo ago" dt = datetime.datetime.fromtimestamp(timestamp) return dt.strftime('%Y-%m-%d') # Cross-platform font candidates # ImageFont.truetype can often find these by name on Windows, # or we can provide paths for Linux. COMMON_FONTS = [ # Linux paths "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", "/usr/share/fonts/truetype/freefont/FreeSansBold.ttf", # Windows filenames (Pillow often finds these in the system path) "arialbd.ttf", "calibrib.ttf", "verdanab.ttf", "tahomabd.ttf", ] def get_file_info_list(path: Path, extensions: Optional[tuple] = None) -> List[Dict[str, Any]]: """ Gathers basic information about files in a directory. Optional extensions filter (e.g. ('.png', '.jpg')). """ items = [] try: for entry in path.iterdir(): if extensions and not entry.name.lower().endswith(extensions): continue stats = entry.stat() items.append({ "name": entry.name, "path": entry, "mtime": stats.st_mtime, "size": stats.st_size, "is_dir": entry.is_dir() }) except Exception as e: raise ToolError(f"Error accessing directory {path}: {e}") return items def sort_file_list(items: List[Dict[str, Any]], sort_by: str) -> List[Dict[str, Any]]: """Sorts items based on the provided key. Directories always come first.""" if sort_by == "mtime": # Newest first items.sort(key=lambda x: (not x["is_dir"], -x["mtime"])) elif sort_by == "size": # Largest first items.sort(key=lambda x: (not x["is_dir"], -x["size"])) else: # Alphabetical items.sort(key=lambda x: (not x["is_dir"], x["name"].lower())) return items def get_paginated_list(items: List[Any], page: int, page_size: int) -> List[Any]: """Returns a slice of the list based on page and page_size.""" start_idx = (page - 1) * page_size end_idx = start_idx + page_size return items[start_idx:end_idx]