Files
Mark-XXXIX-OR/actions/file_controller.py
T
2026-03-29 12:58:03 +03:00

482 lines
15 KiB
Python

# actions/file_controller.py
# File management — create, delete, move, rename, list, find, organize
import shutil
from pathlib import Path
from datetime import datetime
import send2trash
def _get_desktop() -> Path:
"""Returns desktop path — works on Windows, Mac, Linux."""
return Path.home() / "Desktop"
def _get_downloads() -> Path:
return Path.home() / "Downloads"
def _resolve_path(raw: str) -> Path:
"""
Resolves a path from user input.
Supports shortcuts: 'desktop', 'downloads', 'documents', 'home'
"""
shortcuts = {
"desktop": Path.home() / "Desktop",
"downloads": Path.home() / "Downloads",
"documents": Path.home() / "Documents",
"pictures": Path.home() / "Pictures",
"music": Path.home() / "Music",
"videos": Path.home() / "Videos",
"home": Path.home(),
}
lower = raw.strip().lower()
if lower in shortcuts:
return shortcuts[lower]
return Path(raw).expanduser()
def _format_size(bytes_size: int) -> str:
"""Converts bytes to human readable format."""
for unit in ["B", "KB", "MB", "GB", "TB"]:
if bytes_size < 1024:
return f"{bytes_size:.1f} {unit}"
bytes_size /= 1024
return f"{bytes_size:.1f} TB"
def list_files(path: str = "desktop", show_hidden: bool = False) -> str:
"""Lists files and folders in a directory."""
try:
target = _resolve_path(path)
if not target.exists():
return f"Path not found: {target}"
if not target.is_dir():
return f"Not a directory: {target}"
items = []
for item in sorted(target.iterdir()):
if not show_hidden and item.name.startswith("."):
continue
if item.is_dir():
items.append(f"📁 {item.name}/")
else:
size = _format_size(item.stat().st_size)
items.append(f"📄 {item.name} ({size})")
if not items:
return f"Directory is empty: {target}"
return f"Contents of {target.name}/ ({len(items)} items):\n" + "\n".join(items)
except PermissionError:
return f"Permission denied: {path}"
except Exception as e:
return f"Error listing files: {e}"
def create_file(path: str, content: str = "") -> str:
"""Creates a new file with optional content."""
try:
target = Path(path).expanduser()
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8")
return f"File created: {target.name}"
except Exception as e:
return f"Could not create file: {e}"
def create_folder(path: str) -> str:
"""Creates a new folder (and parent folders if needed)."""
try:
target = Path(path).expanduser()
target.mkdir(parents=True, exist_ok=True)
return f"Folder created: {target}"
except Exception as e:
return f"Could not create folder: {e}"
def delete_file(path: str, confirm: bool = True) -> str:
"""
Deletes a file or folder.
Moves to Recycle Bin on Windows if possible, otherwise permanent delete.
"""
try:
target = Path(path).expanduser()
if not target.exists():
return f"Not found: {path}"
try:
send2trash.send2trash(str(target))
return f"Moved to Recycle Bin: {target.name}"
except ImportError:
pass
# Fallback: permanent delete
if target.is_dir():
shutil.rmtree(target)
return f"Folder deleted permanently: {target.name}"
else:
target.unlink()
return f"File deleted permanently: {target.name}"
except PermissionError:
return f"Permission denied: {path}"
except Exception as e:
return f"Could not delete: {e}"
def move_file(source: str, destination: str) -> str:
"""Moves a file or folder to a new location."""
try:
src = Path(source).expanduser()
dst = _resolve_path(destination)
if not src.exists():
return f"Source not found: {source}"
if dst.is_dir():
dst = dst / src.name
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(src), str(dst))
return f"Moved: {src.name} → {dst.parent.name}/"
except Exception as e:
return f"Could not move: {e}"
def copy_file(source: str, destination: str) -> str:
"""Copies a file or folder."""
try:
src = Path(source).expanduser()
dst = _resolve_path(destination)
if not src.exists():
return f"Source not found: {source}"
if dst.is_dir():
dst = dst / src.name
dst.parent.mkdir(parents=True, exist_ok=True)
if src.is_dir():
shutil.copytree(str(src), str(dst))
else:
shutil.copy2(str(src), str(dst))
return f"Copied: {src.name} → {dst.parent.name}/"
except Exception as e:
return f"Could not copy: {e}"
def rename_file(path: str, new_name: str) -> str:
"""Renames a file or folder."""
try:
target = Path(path).expanduser()
new_path = target.parent / new_name
if not target.exists():
return f"Not found: {path}"
if new_path.exists():
return f"A file named '{new_name}' already exists."
target.rename(new_path)
return f"Renamed: {target.name} → {new_name}"
except Exception as e:
return f"Could not rename: {e}"
def read_file(path: str, max_chars: int = 3000) -> str:
"""Reads and returns the content of a text file."""
try:
target = Path(path).expanduser()
if not target.exists():
return f"File not found: {path}"
if not target.is_file():
return f"Not a file: {path}"
content = target.read_text(encoding="utf-8", errors="ignore")
if len(content) > max_chars:
content = content[:max_chars] + f"\n\n... (truncated, {len(content)} total chars)"
return content
except Exception as e:
return f"Could not read file: {e}"
def write_file(path: str, content: str, append: bool = False) -> str:
"""Writes or appends content to a file."""
try:
target = Path(path).expanduser()
target.parent.mkdir(parents=True, exist_ok=True)
mode = "a" if append else "w"
with open(target, mode, encoding="utf-8") as f:
f.write(content)
action = "Appended to" if append else "Written to"
return f"{action}: {target.name}"
except Exception as e:
return f"Could not write file: {e}"
def find_files(name: str = "", extension: str = "", path: str = "home",
max_results: int = 20) -> str:
"""
Searches for files by name or extension.
Example: find_files(extension=".pdf", path="documents")
"""
try:
search_path = _resolve_path(path)
if not search_path.exists():
return f"Search path not found: {path}"
results = []
pattern = f"*{extension}" if extension else "*"
for item in search_path.rglob(pattern):
if item.is_file():
if name and name.lower() not in item.name.lower():
continue
size = _format_size(item.stat().st_size)
results.append(f"📄 {item.name} ({size}) — {item.parent}")
if len(results) >= max_results:
break
if not results:
query = name or extension or "files"
return f"No {query} found in {search_path.name}/"
return f"Found {len(results)} file(s):\n" + "\n".join(results)
except Exception as e:
return f"Search error: {e}"
def get_largest_files(path: str = "home", count: int = 10) -> str:
"""Returns the largest files in a directory."""
try:
search_path = _resolve_path(path)
if not search_path.exists():
return f"Path not found: {path}"
files = []
for item in search_path.rglob("*"):
if item.is_file():
try:
files.append((item.stat().st_size, item))
except Exception:
continue
files.sort(reverse=True)
top = files[:count]
if not top:
return "No files found."
lines = [f"Top {len(top)} largest files in {search_path.name}/:\n"]
for size, f in top:
lines.append(f" {_format_size(size):>10} {f.name} ({f.parent})")
return "\n".join(lines)
except Exception as e:
return f"Error: {e}"
def get_disk_usage(path: str = "home") -> str:
"""Returns disk usage information."""
try:
target = _resolve_path(path)
usage = shutil.disk_usage(target)
total = _format_size(usage.total)
used = _format_size(usage.used)
free = _format_size(usage.free)
pct = usage.used / usage.total * 100
return (
f"Disk usage for {target}:\n"
f" Total : {total}\n"
f" Used : {used} ({pct:.1f}%)\n"
f" Free : {free}"
)
except Exception as e:
return f"Could not get disk usage: {e}"
def organize_desktop() -> str:
"""
Organizes the desktop by grouping files into folders by type.
Creates folders: Images, Documents, Videos, Music, Archives, Others
"""
try:
desktop = _get_desktop()
type_map = {
"Images": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".ico"],
"Documents": [".pdf", ".doc", ".docx", ".txt", ".xls", ".xlsx", ".ppt", ".pptx", ".csv"],
"Videos": [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm"],
"Music": [".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma"],
"Archives": [".zip", ".rar", ".7z", ".tar", ".gz"],
"Code": [".py", ".js", ".html", ".css", ".json", ".xml", ".ts", ".cpp", ".java"],
}
moved = []
skipped = []
for item in desktop.iterdir():
if item.is_dir() or item.name.startswith("."):
continue
ext = item.suffix.lower()
target_dir = None
for folder, extensions in type_map.items():
if ext in extensions:
target_dir = desktop / folder
break
if target_dir is None:
target_dir = desktop / "Others"
target_dir.mkdir(exist_ok=True)
new_path = target_dir / item.name
if new_path.exists():
skipped.append(item.name)
continue
shutil.move(str(item), str(new_path))
moved.append(f"{item.name} → {target_dir.name}/")
result = f"Desktop organized. {len(moved)} files moved."
if moved:
result += "\n" + "\n".join(moved[:10])
if len(moved) > 10:
result += f"\n... and {len(moved)-10} more."
if skipped:
result += f"\n{len(skipped)} files skipped (already exist)."
return result
except Exception as e:
return f"Could not organize desktop: {e}"
def get_file_info(path: str) -> str:
"""Returns detailed information about a file."""
try:
target = Path(path).expanduser()
if not target.exists():
return f"Not found: {path}"
stat = target.stat()
info = {
"Name": target.name,
"Type": "Folder" if target.is_dir() else "File",
"Size": _format_size(stat.st_size),
"Location": str(target.parent),
"Created": datetime.fromtimestamp(stat.st_ctime).strftime("%Y-%m-%d %H:%M"),
"Modified": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M"),
"Extension": target.suffix or "None",
}
return "\n".join(f" {k}: {v}" for k, v in info.items())
except Exception as e:
return f"Could not get file info: {e}"
def file_controller(
parameters: dict,
response=None,
player=None,
session_memory=None
) -> str:
action = (parameters or {}).get("action", "").lower().strip()
path = (parameters or {}).get("path", "desktop")
name = (parameters or {}).get("name", "")
content = (parameters or {}).get("content", "")
def _full_path(p: str, n: str) -> str:
base = _resolve_path(p)
if n:
return str(base / n)
return str(base)
result = "Unknown action."
try:
if action == "list":
result = list_files(path)
elif action == "create_file":
full = _full_path(path, name)
result = create_file(full, content=content)
elif action == "create_folder":
full = _full_path(path, name)
result = create_folder(full)
elif action == "delete":
full = _full_path(path, name)
result = delete_file(full)
elif action == "move":
full = _full_path(path, name)
result = move_file(full, parameters.get("destination", ""))
elif action == "copy":
full = _full_path(path, name)
result = copy_file(full, parameters.get("destination", ""))
elif action == "rename":
full = _full_path(path, name)
result = rename_file(full, parameters.get("new_name", ""))
elif action == "read":
full = _full_path(path, name)
result = read_file(full)
elif action == "write":
full = _full_path(path, name)
result = write_file(
full,
content=content,
append=parameters.get("append", False)
)
elif action == "find":
result = find_files(
name=name or parameters.get("name", ""),
extension=parameters.get("extension", ""),
path=path,
max_results=parameters.get("max_results", 20)
)
elif action == "largest":
result = get_largest_files(
path=path,
count=parameters.get("count", 10)
)
elif action == "disk_usage":
result = get_disk_usage(path)
elif action == "organize_desktop":
result = organize_desktop()
elif action == "info":
full = _full_path(path, name)
result = get_file_info(full)
else:
result = f"Unknown action: '{action}'"
except Exception as e:
result = f"File controller error: {e}"
if player:
player.write_log(f"[file] {result[:60]}")
return result