Files
Mark-LIV/actions/file_controller.py
T
2026-08-25 05:42:36 +03:00

555 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import shutil
import platform
from pathlib import Path
from datetime import datetime
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
def _get_desktop() -> Path:
if _OS == "Linux":
xdg = os.environ.get("XDG_DESKTOP_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
return Path.home() / "Desktop"
def _get_downloads() -> Path:
if _OS == "Linux":
xdg = os.environ.get("XDG_DOWNLOAD_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
return Path.home() / "Downloads"
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"
def _resolve_path(raw: str) -> Path:
shortcuts: dict[str, Path] = {
"desktop": _get_desktop(),
"downloads": _get_downloads(),
"documents": _get_documents(),
"pictures": _get_pictures(),
"music": _get_music(),
"videos": _get_videos(),
"home": Path.home(),
}
raw = raw.strip().strip('"').strip("'")
lower = raw.lower()
if lower in shortcuts:
return shortcuts[lower]
# "desktop/notlar/a.md" ve "desktop\notlar\a.md" — kisayol + alt yol.
# Bu dal olmadan tum dize asagidaki goreli-yol dalina dusuyor ve process'in
# CWD'sine gore cozuluyordu. Proje home disindaysa _is_safe_path reddedip
# "Access denied" veriyor; home icindeyse daha kotusu oluyor ve dosya sessizce
# projenin icindeki olmayan bir "desktop" klasorune yaziliyordu.
head, sep, rest = raw.replace("\\", "/").partition("/")
if sep and head.lower() in shortcuts:
rest = rest.strip("/")
return shortcuts[head.lower()] / rest if rest else shortcuts[head.lower()]
return Path(raw).expanduser()
def _format_size(b: int) -> str:
for unit in ["B", "KB", "MB", "GB", "TB"]:
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}"
def list_files(path: str = "desktop", show_hidden: bool = False) -> str:
try:
target = _resolve_path(path)
if not _is_safe_path(target):
return f"Access denied: {target}"
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.name}/"
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, name: str = "", content: str = "") -> str:
try:
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
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, name: str = "") -> str:
try:
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
target.mkdir(parents=True, exist_ok=True)
return f"Folder created: {target.name}"
except Exception as e:
return f"Could not create folder: {e}"
def delete_file(path: str, name: str = "") -> str:
try:
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
if not target.exists():
return f"Not found: {target.name}"
# 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}"
return _safe_trash(target)
except PermissionError:
return f"Permission denied: {path}"
except Exception as e:
return f"Could not delete: {e}"
def move_file(path: str, name: str = "", destination: str = "") -> str:
try:
base = _resolve_path(path)
src = (base / name) if name else base
dst = _resolve_path(destination) if destination else None
if not src.exists():
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}"
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(path: str, name: str = "", destination: str = "") -> str:
try:
base = _resolve_path(path)
src = (base / name) if name else base
dst = _resolve_path(destination) if destination else None
if not src.exists():
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}"
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, name: str = "", new_name: str = "") -> str:
try:
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
if not target.exists():
return f"Not found: {target.name}"
if not new_name:
return "No new name provided."
new_path = target.parent / new_name
if new_path.exists():
return f"A file named '{new_name}' already exists here."
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, name: str = "", max_chars: int = 4000) -> str:
try:
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
if not target.exists():
return f"File not found: {target.name}"
if not target.is_file():
return f"Not a file: {target.name}"
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, name: str = "", content: str = "",
append: bool = False) -> str:
try:
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
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:
try:
search_path = _resolve_path(path)
if not _is_safe_path(search_path):
return f"Access denied: {search_path}"
if not search_path.exists():
return f"Search path not found: {path}"
results = []
dir_count = 0
max_dirs = 500 # performans + güvenlik limiti
for item in search_path.rglob("*"):
if item.is_dir():
dir_count += 1
if dir_count > max_dirs:
break
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
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 = "downloads", count: int = 10) -> str:
count = min(count, 50) # maksimum 50
try:
search_path = _resolve_path(path)
if not _is_safe_path(search_path):
return f"Access denied: {search_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}/:"]
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 (
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)}"
)
except Exception as e:
return f"Could not get disk usage: {e}"
def organize_desktop() -> str:
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"},
}
desktop = _get_desktop()
moved, skipped = [], []
try:
for item in desktop.iterdir():
# Klasörlere, gizli dosyalara ve organize klasörlerine dokunma
if item.is_dir() or item.name.startswith("."):
continue
if item.name in {k for k in type_map}:
continue
ext = item.suffix.lower()
target_dir = desktop / "Others"
for folder, exts in type_map.items():
if ext in exts:
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}/")
result = f"Desktop organized: {len(moved)} files moved."
if moved:
preview = moved[:8]
result += "\n" + "\n".join(preview)
if len(moved) > 8:
result += f"\n... and {len(moved) - 8} more."
if skipped:
result += f"\n{len(skipped)} file(s) skipped (name conflict)."
return result
except Exception as e:
return f"Could not organize desktop: {e}"
def get_file_info(path: str, name: str = "") -> str:
try:
base = _resolve_path(path)
target = (base / name) if name else base
if not _is_safe_path(target):
return f"Access denied: {target}"
if not target.exists():
return f"Not found: {target.name}"
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 "—",
}
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 = None,
response=None,
player=None,
session_memory=None,
) -> str:
params = parameters or {}
action = params.get("action", "").lower().strip()
path = params.get("path", "desktop")
name = params.get("name", "")
if player:
player.write_log(f"[file] {action} {name or path}")
try:
if action == "list":
return list_files(path)
elif action == "create_file":
return create_file(path, name=name, content=params.get("content", ""))
elif action == "create_folder":
return create_folder(path, name=name)
elif action == "delete":
return delete_file(path, name=name)
elif action == "move":
return move_file(path, name=name, destination=params.get("destination", ""))
elif action == "copy":
return copy_file(path, name=name, destination=params.get("destination", ""))
elif action == "rename":
return rename_file(path, name=name, new_name=params.get("new_name", ""))
elif action == "read":
return read_file(path, name=name)
elif action == "write":
return write_file(
path, name=name,
content=params.get("content", ""),
append=params.get("append", False)
)
elif action == "find":
return find_files(
name=name or params.get("name", ""),
extension=params.get("extension", ""),
path=path,
max_results=min(int(params.get("max_results", 20)), 50),
)
elif action == "largest":
return get_largest_files(
path=path,
count=int(params.get("count", 10)),
)
elif action == "disk_usage":
return get_disk_usage(path)
elif action == "organize_desktop":
return organize_desktop()
elif action == "info":
return get_file_info(path, name=name)
else:
return f"Unknown action: '{action}'"
except Exception as e:
return f"File controller error ({action}): {e}"