"""Clipboard-Manager für Dateioperationen.""" import os import shutil from enum import Enum from typing import Optional from PyQt6.QtCore import QObject, pyqtSignal class ClipboardMode(Enum): """Modus für Zwischenablage-Operationen.""" COPY = "copy" CUT = "cut" class FileClipboard(QObject): """Singleton-Clipboard für Dateioperationen.""" _instance: Optional['FileClipboard'] = None # Signale clipboard_changed = pyqtSignal() def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialized = False return cls._instance def __init__(self): if self._initialized: return super().__init__() self._files: list[str] = [] self._mode: ClipboardMode = ClipboardMode.COPY self._initialized = True @classmethod def instance(cls) -> 'FileClipboard': """Gibt die Singleton-Instanz zurück.""" if cls._instance is None: cls._instance = FileClipboard() return cls._instance def copy(self, paths: list[str]): """Kopiert Dateien in die Zwischenablage.""" self._files = [p for p in paths if os.path.exists(p)] self._mode = ClipboardMode.COPY self.clipboard_changed.emit() def cut(self, paths: list[str]): """Schneidet Dateien aus (zum Verschieben).""" self._files = [p for p in paths if os.path.exists(p)] self._mode = ClipboardMode.CUT self.clipboard_changed.emit() def paste(self, target_folder: str) -> tuple[int, list[str]]: """ Fügt Dateien im Zielordner ein. Returns: Tuple (Anzahl erfolgreicher Operationen, Liste der Fehler) """ if not self._files or not os.path.isdir(target_folder): return 0, ["Keine Dateien zum Einfügen oder ungültiger Zielordner"] success_count = 0 errors = [] for source_path in self._files: if not os.path.exists(source_path): errors.append(f"{os.path.basename(source_path)}: Datei nicht gefunden") continue name = os.path.basename(source_path) target_path = os.path.join(target_folder, name) # Bei Namenskonflikt umbenennen target_path = self._get_unique_path(target_path) try: if self._mode == ClipboardMode.COPY: if os.path.isdir(source_path): shutil.copytree(source_path, target_path) else: shutil.copy2(source_path, target_path) else: # CUT shutil.move(source_path, target_path) success_count += 1 except PermissionError: errors.append(f"{name}: Keine Berechtigung") except shutil.Error as e: errors.append(f"{name}: {str(e)}") except Exception as e: errors.append(f"{name}: {str(e)}") # Bei Ausschneiden die Zwischenablage leeren if self._mode == ClipboardMode.CUT: self.clear() return success_count, errors def _get_unique_path(self, path: str) -> str: """Generiert einen eindeutigen Pfad bei Namenskonflikten.""" if not os.path.exists(path): return path base, ext = os.path.splitext(path) counter = 1 while os.path.exists(path): path = f"{base} ({counter}){ext}" counter += 1 return path def clear(self): """Leert die Zwischenablage.""" self._files = [] self.clipboard_changed.emit() def has_files(self) -> bool: """Prüft ob Dateien in der Zwischenablage sind.""" return len(self._files) > 0 def get_files(self) -> list[str]: """Gibt die Dateien in der Zwischenablage zurück.""" return self._files.copy() def get_mode(self) -> ClipboardMode: """Gibt den aktuellen Modus zurück.""" return self._mode def get_file_count(self) -> int: """Gibt die Anzahl der Dateien zurück.""" return len(self._files)