Files
Mark-LIV/actions/file_controller.py
T

543 lines
18 KiB
Python
Raw Normal View History

2026-04-19 15:22:49 +03:00
import os
2026-03-02 04:30:10 +03:00
import shutil
2026-04-19 15:22:49 +03:00
import platform
2026-03-02 04:30:10 +03:00
from pathlib import Path
from datetime import datetime
2026-04-19 15:22:49 +03:00
try:
import send2trash
_SEND2TRASH = True
except ImportError:
_SEND2TRASH = False
_OS = platform.system() # "Windows" | "Darwin" | "Linux"
_SAFE_ROOTS: list[Path] = [
Path.home(),
]
def _is_safe_path(target: Path) -> bool:
"""Verilen path _SAFE_ROOTS içinde mi? Değilse işlemi reddet."""
try:
resolved = target.resolve()
return any(
resolved == root.resolve() or resolved.is_relative_to(root.resolve())
for root in _SAFE_ROOTS
)
except Exception:
return False
2026-03-02 04:30:10 +03:00
def _get_desktop() -> Path:
2026-04-19 15:22:49 +03:00
if _OS == "Linux":
xdg = os.environ.get("XDG_DESKTOP_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
2026-03-02 04:30:10 +03:00
return Path.home() / "Desktop"
def _get_downloads() -> Path:
2026-04-19 15:22:49 +03:00
if _OS == "Linux":
xdg = os.environ.get("XDG_DOWNLOAD_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
2026-03-02 04:30:10 +03:00
return Path.home() / "Downloads"
2026-04-19 15:22:49 +03:00
def _get_documents() -> Path:
if _OS == "Linux":
xdg = os.environ.get("XDG_DOCUMENTS_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
return Path.home() / "Documents"
def _get_pictures() -> Path:
if _OS == "Linux":
xdg = os.environ.get("XDG_PICTURES_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
return Path.home() / "Pictures"
def _get_music() -> Path:
if _OS == "Linux":
xdg = os.environ.get("XDG_MUSIC_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
return Path.home() / "Music"
def _get_videos() -> Path:
if _OS == "Linux":
xdg = os.environ.get("XDG_VIDEOS_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
return Path.home() / "Videos"
2026-03-02 04:30:10 +03:00
def _resolve_path(raw: str) -> Path:
2026-04-19 15:22:49 +03:00
shortcuts: dict[str, Path] = {
"desktop": _get_desktop(),
"downloads": _get_downloads(),
"documents": _get_documents(),
"pictures": _get_pictures(),
"music": _get_music(),
"videos": _get_videos(),
2026-03-02 04:30:10 +03:00
"home": Path.home(),
}
lower = raw.strip().lower()
if lower in shortcuts:
return shortcuts[lower]
return Path(raw).expanduser()
2026-04-19 15:22:49 +03:00
def _format_size(b: int) -> str:
2026-03-02 04:30:10 +03:00
for unit in ["B", "KB", "MB", "GB", "TB"]:
2026-04-19 15:22:49 +03:00
if b < 1024:
return f"{b:.1f} {unit}"
b /= 1024
return f"{b:.1f} TB"
def _safe_trash(target: Path) -> str:
if not _SEND2TRASH:
return (
"send2trash is not installed. "
"Run: pip install send2trash — "
"Permanent deletion is disabled for safety."
)
send2trash.send2trash(str(target))
return f"Moved to Trash: {target.name}"
2026-03-02 04:30:10 +03:00
def list_files(path: str = "desktop", show_hidden: bool = False) -> str:
try:
target = _resolve_path(path)
2026-04-19 15:22:49 +03:00
if not _is_safe_path(target):
return f"Access denied: {target}"
2026-03-02 04:30:10 +03:00
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:
2026-04-19 15:22:49 +03:00
return f"Directory is empty: {target.name}/"
2026-03-02 04:30:10 +03:00
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}"
2026-04-19 15:22:49 +03:00
def create_file(path: str, name: str = "", content: str = "") -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
2026-03-02 04:30:10 +03:00
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}"
2026-04-19 15:22:49 +03:00
def create_folder(path: str, name: str = "") -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
2026-03-02 04:30:10 +03:00
target.mkdir(parents=True, exist_ok=True)
2026-04-19 15:22:49 +03:00
return f"Folder created: {target.name}"
2026-03-02 04:30:10 +03:00
except Exception as e:
return f"Could not create folder: {e}"
2026-04-19 15:22:49 +03:00
def delete_file(path: str, name: str = "") -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
2026-03-02 04:30:10 +03:00
if not target.exists():
2026-04-19 15:22:49 +03:00
return f"Not found: {target.name}"
2026-03-02 04:30:10 +03:00
2026-04-19 15:22:49 +03:00
# Güvenli dizin kontrolü — kritik kullanıcı klasörlerini koru
protected = {
_get_desktop(), _get_downloads(), _get_documents(),
_get_pictures(), _get_music(), _get_videos(), Path.home()
}
if target.resolve() in {p.resolve() for p in protected}:
return f"Protected directory, cannot delete: {target.name}"
2026-03-02 04:30:10 +03:00
2026-04-19 15:22:49 +03:00
return _safe_trash(target)
2026-03-02 04:30:10 +03:00
except PermissionError:
return f"Permission denied: {path}"
except Exception as e:
return f"Could not delete: {e}"
2026-04-19 15:22:49 +03:00
def move_file(path: str, name: str = "", destination: str = "") -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
src = (base / name) if name else base
dst = _resolve_path(destination) if destination else None
2026-03-02 04:30:10 +03:00
if not src.exists():
2026-04-19 15:22:49 +03:00
return f"Source not found: {src.name}"
if dst is None:
return "No destination specified."
if not _is_safe_path(src):
return f"Access denied (source): {src}"
if not _is_safe_path(dst):
return f"Access denied (destination): {dst}"
2026-03-02 04:30:10 +03:00
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}"
2026-04-19 15:22:49 +03:00
def copy_file(path: str, name: str = "", destination: str = "") -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
src = (base / name) if name else base
dst = _resolve_path(destination) if destination else None
2026-03-02 04:30:10 +03:00
if not src.exists():
2026-04-19 15:22:49 +03:00
return f"Source not found: {src.name}"
if dst is None:
return "No destination specified."
if not _is_safe_path(src):
return f"Access denied (source): {src}"
if not _is_safe_path(dst):
return f"Access denied (destination): {dst}"
2026-03-02 04:30:10 +03:00
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}"
2026-04-19 15:22:49 +03:00
def rename_file(path: str, name: str = "", new_name: str = "") -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
2026-03-02 04:30:10 +03:00
if not target.exists():
2026-04-19 15:22:49 +03:00
return f"Not found: {target.name}"
if not new_name:
return "No new name provided."
new_path = target.parent / new_name
2026-03-02 04:30:10 +03:00
if new_path.exists():
2026-04-19 15:22:49 +03:00
return f"A file named '{new_name}' already exists here."
2026-03-02 04:30:10 +03:00
target.rename(new_path)
return f"Renamed: {target.name} → {new_name}"
except Exception as e:
return f"Could not rename: {e}"
2026-04-19 15:22:49 +03:00
def read_file(path: str, name: str = "", max_chars: int = 4000) -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
2026-03-02 04:30:10 +03:00
if not target.exists():
2026-04-19 15:22:49 +03:00
return f"File not found: {target.name}"
2026-03-02 04:30:10 +03:00
if not target.is_file():
2026-04-19 15:22:49 +03:00
return f"Not a file: {target.name}"
2026-03-02 04:30:10 +03:00
content = target.read_text(encoding="utf-8", errors="ignore")
if len(content) > max_chars:
2026-04-19 15:22:49 +03:00
content = content[:max_chars] + f"\n\n[Truncated — {len(content)} total chars]"
2026-03-02 04:30:10 +03:00
return content
except Exception as e:
return f"Could not read file: {e}"
2026-04-19 15:22:49 +03:00
def write_file(path: str, name: str = "", content: str = "",
append: bool = False) -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
2026-03-02 04:30:10 +03:00
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}"
2026-04-19 15:22:49 +03:00
def find_files(name: str = "", extension: str = "",
path: str = "home", max_results: int = 20) -> str:
2026-03-02 04:30:10 +03:00
try:
search_path = _resolve_path(path)
2026-04-19 15:22:49 +03:00
if not _is_safe_path(search_path):
return f"Access denied: {search_path}"
2026-03-02 04:30:10 +03:00
if not search_path.exists():
return f"Search path not found: {path}"
2026-04-19 15:22:49 +03:00
results = []
dir_count = 0
max_dirs = 500 # performans + güvenlik limiti
2026-03-02 04:30:10 +03:00
2026-04-19 15:22:49 +03:00
for item in search_path.rglob("*"):
if item.is_dir():
dir_count += 1
if dir_count > max_dirs:
2026-03-02 04:30:10 +03:00
break
2026-04-19 15:22:49 +03:00
continue
if not item.is_file():
continue
if extension and item.suffix.lower() != extension.lower():
continue
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
2026-03-02 04:30:10 +03:00
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}"
2026-04-19 15:22:49 +03:00
def get_largest_files(path: str = "downloads", count: int = 10) -> str:
count = min(count, 50) # maksimum 50
2026-03-02 04:30:10 +03:00
try:
search_path = _resolve_path(path)
2026-04-19 15:22:49 +03:00
if not _is_safe_path(search_path):
return f"Access denied: {search_path}"
2026-03-02 04:30:10 +03:00
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."
2026-04-19 15:22:49 +03:00
lines = [f"Top {len(top)} largest files in {search_path.name}/:"]
2026-03-02 04:30:10 +03:00
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:
try:
target = _resolve_path(path)
usage = shutil.disk_usage(target)
pct = usage.used / usage.total * 100
return (
2026-04-19 15:22:49 +03:00
f"Disk usage ({target}):\n"
f" Total : {_format_size(usage.total)}\n"
f" Used : {_format_size(usage.used)} ({pct:.1f}%)\n"
f" Free : {_format_size(usage.free)}"
2026-03-02 04:30:10 +03:00
)
except Exception as e:
return f"Could not get disk usage: {e}"
def organize_desktop() -> str:
2026-04-19 15:22:49 +03:00
type_map = {
"Images": {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".ico", ".heic"},
"Documents": {".pdf", ".doc", ".docx", ".txt", ".xls", ".xlsx",
".ppt", ".pptx", ".csv", ".odt", ".ods", ".odp"},
"Videos": {".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v"},
"Music": {".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma", ".m4a"},
"Archives": {".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".xz"},
"Code": {".py", ".js", ".ts", ".html", ".css", ".json", ".xml",
".cpp", ".java", ".cs", ".go", ".rs", ".sh"},
}
2026-03-02 04:30:10 +03:00
2026-04-19 15:22:49 +03:00
desktop = _get_desktop()
moved, skipped = [], []
2026-03-02 04:30:10 +03:00
2026-04-19 15:22:49 +03:00
try:
2026-03-02 04:30:10 +03:00
for item in desktop.iterdir():
2026-04-19 15:22:49 +03:00
# Klasörlere, gizli dosyalara ve organize klasörlerine dokunma
2026-03-02 04:30:10 +03:00
if item.is_dir() or item.name.startswith("."):
continue
2026-04-19 15:22:49 +03:00
if item.name in {k for k in type_map}:
continue
2026-03-02 04:30:10 +03:00
ext = item.suffix.lower()
2026-04-19 15:22:49 +03:00
target_dir = desktop / "Others"
for folder, exts in type_map.items():
if ext in exts:
2026-03-02 04:30:10 +03:00
target_dir = desktop / folder
break
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}/")
2026-04-19 15:22:49 +03:00
result = f"Desktop organized: {len(moved)} files moved."
2026-03-02 04:30:10 +03:00
if moved:
2026-04-19 15:22:49 +03:00
preview = moved[:8]
result += "\n" + "\n".join(preview)
if len(moved) > 8:
result += f"\n... and {len(moved) - 8} more."
2026-03-02 04:30:10 +03:00
if skipped:
2026-04-19 15:22:49 +03:00
result += f"\n{len(skipped)} file(s) skipped (name conflict)."
2026-03-02 04:30:10 +03:00
return result
except Exception as e:
return f"Could not organize desktop: {e}"
2026-04-19 15:22:49 +03:00
def get_file_info(path: str, name: str = "") -> str:
2026-03-02 04:30:10 +03:00
try:
2026-04-19 15:22:49 +03:00
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
2026-03-02 04:30:10 +03:00
if not target.exists():
2026-04-19 15:22:49 +03:00
return f"Not found: {target.name}"
2026-03-02 04:30:10 +03:00
stat = target.stat()
info = {
2026-04-19 15:22:49 +03:00
"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 "—",
2026-03-02 04:30:10 +03:00
}
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(
2026-04-19 15:22:49 +03:00
parameters: dict = None,
2026-03-02 04:30:10 +03:00
response=None,
player=None,
2026-04-19 15:22:49 +03:00
session_memory=None,
2026-03-02 04:30:10 +03:00
) -> str:
2026-04-19 15:22:49 +03:00
params = parameters or {}
action = params.get("action", "").lower().strip()
path = params.get("path", "desktop")
name = params.get("name", "")
2026-03-02 04:30:10 +03:00
2026-04-19 15:22:49 +03:00
if player:
player.write_log(f"[file] {action} {name or path}")
2026-03-02 04:30:10 +03:00
try:
if action == "list":
2026-04-19 15:22:49 +03:00
return list_files(path)
2026-03-02 04:30:10 +03:00
elif action == "create_file":
2026-04-19 15:22:49 +03:00
return create_file(path, name=name, content=params.get("content", ""))
2026-03-02 04:30:10 +03:00
elif action == "create_folder":
2026-04-19 15:22:49 +03:00
return create_folder(path, name=name)
2026-03-02 04:30:10 +03:00
elif action == "delete":
2026-04-19 15:22:49 +03:00
return delete_file(path, name=name)
2026-03-02 04:30:10 +03:00
elif action == "move":
2026-04-19 15:22:49 +03:00
return move_file(path, name=name, destination=params.get("destination", ""))
2026-03-02 04:30:10 +03:00
elif action == "copy":
2026-04-19 15:22:49 +03:00
return copy_file(path, name=name, destination=params.get("destination", ""))
2026-03-02 04:30:10 +03:00
elif action == "rename":
2026-04-19 15:22:49 +03:00
return rename_file(path, name=name, new_name=params.get("new_name", ""))
2026-03-02 04:30:10 +03:00
elif action == "read":
2026-04-19 15:22:49 +03:00
return read_file(path, name=name)
2026-03-02 04:30:10 +03:00
elif action == "write":
2026-04-19 15:22:49 +03:00
return write_file(
path, name=name,
content=params.get("content", ""),
append=params.get("append", False)
2026-03-02 04:30:10 +03:00
)
elif action == "find":
2026-04-19 15:22:49 +03:00
return find_files(
name=name or params.get("name", ""),
extension=params.get("extension", ""),
2026-03-02 04:30:10 +03:00
path=path,
2026-04-19 15:22:49 +03:00
max_results=min(int(params.get("max_results", 20)), 50),
2026-03-02 04:30:10 +03:00
)
elif action == "largest":
2026-04-19 15:22:49 +03:00
return get_largest_files(
2026-03-02 04:30:10 +03:00
path=path,
2026-04-19 15:22:49 +03:00
count=int(params.get("count", 10)),
2026-03-02 04:30:10 +03:00
)
elif action == "disk_usage":
2026-04-19 15:22:49 +03:00
return get_disk_usage(path)
2026-03-02 04:30:10 +03:00
elif action == "organize_desktop":
2026-04-19 15:22:49 +03:00
return organize_desktop()
2026-03-02 04:30:10 +03:00
elif action == "info":
2026-04-19 15:22:49 +03:00
return get_file_info(path, name=name)
2026-03-02 04:30:10 +03:00
else:
2026-04-19 15:22:49 +03:00
return f"Unknown action: '{action}'"
2026-03-02 04:30:10 +03:00
except Exception as e:
2026-04-19 15:22:49 +03:00
return f"File controller error ({action}): {e}"