#!/usr/bin/env python3 """ File Utilities für MYP Platform Ersetzt Send2Trash mit nativen Lösungen für alle Plattformen """ import os import shutil import platform import subprocess import logging from pathlib import Path from typing import Union, List, Optional # Logger logger = logging.getLogger(__name__) class SafeFileHandler: """ Sichere Datei-Operationen ohne externe Abhängigkeiten Ersetzt Send2Trash mit nativen Lösungen """ def __init__(self): self.platform = platform.system().lower() self.is_windows = self.platform == 'windows' self.is_linux = self.platform == 'linux' self.is_macos = self.platform == 'darwin' def move_to_trash(self, file_path: Union[str, Path]) -> bool: """ Verschiebt eine Datei in den Papierkorb/Trash Args: file_path: Pfad zur Datei oder zum Ordner Returns: bool: True wenn erfolgreich, False bei Fehler """ file_path = Path(file_path) if not file_path.exists(): logger.warning(f"Datei nicht gefunden: {file_path}") return False try: if self.is_windows: return self._move_to_trash_windows(file_path) elif self.is_linux: return self._move_to_trash_linux(file_path) elif self.is_macos: return self._move_to_trash_macos(file_path) else: # Fallback: Direkte Löschung logger.warning(f"Unbekanntes System: {self.platform} - verwende direkte Löschung") return self._delete_permanently(file_path) except Exception as e: logger.error(f"Fehler beim Verschieben in Papierkorb: {e}") return False def _move_to_trash_windows(self, file_path: Path) -> bool: """Windows-spezifische Papierkorb-Implementation""" try: # Verwende PowerShell für Windows-Papierkorb cmd = [ 'powershell', '-Command', f'Add-Type -AssemblyName Microsoft.VisualBasic; ' f'[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile("{file_path}", ' f'"OnlyErrorDialogs", "SendToRecycleBin")' ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: logger.info(f"Datei erfolgreich in Papierkorb verschoben: {file_path}") return True else: logger.error(f"PowerShell-Fehler: {result.stderr}") return False except Exception as e: logger.error(f"Windows Papierkorb-Fehler: {e}") return False def _move_to_trash_linux(self, file_path: Path) -> bool: """Linux-spezifische Papierkorb-Implementation""" try: # Prüfe verfügbare Tools tools = ['gio', 'gvfs-trash', 'kioclient5', 'trash-put'] for tool in tools: if shutil.which(tool): if tool == 'gio': cmd = ['gio', 'trash', str(file_path)] elif tool == 'gvfs-trash': cmd = ['gvfs-trash', str(file_path)] elif tool == 'kioclient5': cmd = ['kioclient5', 'move', str(file_path), 'trash:/'] elif tool == 'trash-put': cmd = ['trash-put', str(file_path)] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: logger.info(f"Datei erfolgreich in Papierkorb verschoben ({tool}): {file_path}") return True else: logger.warning(f"{tool} fehlgeschlagen: {result.stderr}") continue # Fallback: Manueller Trash-Ordner return self._move_to_trash_manual_linux(file_path) except Exception as e: logger.error(f"Linux Papierkorb-Fehler: {e}") return False def _move_to_trash_manual_linux(self, file_path: Path) -> bool: """Manueller Linux-Papierkorb nach XDG-Standard""" try: # XDG Trash-Verzeichnis ermitteln xdg_data_home = os.getenv('XDG_DATA_HOME') if not xdg_data_home: home = Path.home() xdg_data_home = home / '.local' / 'share' else: xdg_data_home = Path(xdg_data_home) trash_dir = xdg_data_home / 'Trash' files_dir = trash_dir / 'files' info_dir = trash_dir / 'info' # Erstelle Trash-Verzeichnisse files_dir.mkdir(parents=True, exist_ok=True) info_dir.mkdir(parents=True, exist_ok=True) # Eindeutigen Namen generieren import time timestamp = int(time.time()) trash_name = f"{file_path.name}_{timestamp}" # Verschiebe Datei trash_file = files_dir / trash_name shutil.move(str(file_path), str(trash_file)) # Erstelle .trashinfo Datei info_file = info_dir / f"{trash_name}.trashinfo" info_content = f"""[Trash Info] Path={file_path.absolute()} DeletionDate={time.strftime('%Y-%m-%dT%H:%M:%S')} """ info_file.write_text(info_content) logger.info(f"Datei manuell in Linux-Papierkorb verschoben: {file_path}") return True except Exception as e: logger.error(f"Manueller Linux-Papierkorb-Fehler: {e}") return False def _move_to_trash_macos(self, file_path: Path) -> bool: """macOS-spezifische Papierkorb-Implementation""" try: # Verwende osascript für macOS-Papierkorb cmd = [ 'osascript', '-e', f'tell application "Finder" to delete POSIX file "{file_path.absolute()}"' ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: logger.info(f"Datei erfolgreich in Papierkorb verschoben: {file_path}") return True else: logger.error(f"osascript-Fehler: {result.stderr}") return False except Exception as e: logger.error(f"macOS Papierkorb-Fehler: {e}") return False def _delete_permanently(self, file_path: Path) -> bool: """Permanente Löschung als Fallback""" try: if file_path.is_file(): file_path.unlink() elif file_path.is_dir(): shutil.rmtree(file_path) else: logger.warning(f"Unbekannter Dateityp: {file_path}") return False logger.info(f"Datei permanent gelöscht: {file_path}") return True except Exception as e: logger.error(f"Permanente Löschung fehlgeschlagen: {e}") return False def safe_delete(self, file_path: Union[str, Path], use_trash: bool = True) -> bool: """ Sichere Datei-Löschung mit konfigurierbarer Papierkorb-Nutzung Args: file_path: Pfad zur Datei use_trash: True für Papierkorb, False für permanente Löschung Returns: bool: True wenn erfolgreich """ file_path = Path(file_path) if not file_path.exists(): logger.warning(f"Datei existiert nicht: {file_path}") return True # Bereits gelöscht = Erfolg if use_trash: # Versuche Papierkorb zuerst if self.move_to_trash(file_path): return True else: logger.warning("Papierkorb fehlgeschlagen - verwende permanente Löschung") return self._delete_permanently(file_path) else: # Direkte permanente Löschung return self._delete_permanently(file_path) def clean_temp_files(self, temp_dir: Union[str, Path], max_age_hours: int = 24) -> int: """ Bereinigt temporäre Dateien älter als max_age_hours Args: temp_dir: Temporäres Verzeichnis max_age_hours: Maximales Alter in Stunden Returns: int: Anzahl gelöschter Dateien """ temp_dir = Path(temp_dir) if not temp_dir.exists(): return 0 import time current_time = time.time() max_age_seconds = max_age_hours * 3600 deleted_count = 0 try: for item in temp_dir.rglob('*'): if item.is_file(): file_age = current_time - item.stat().st_mtime if file_age > max_age_seconds: if self.safe_delete(item, use_trash=False): deleted_count += 1 logger.debug(f"Temporäre Datei gelöscht: {item}") except Exception as e: logger.error(f"Fehler beim Bereinigen temporärer Dateien: {e}") if deleted_count > 0: logger.info(f"{deleted_count} temporäre Dateien bereinigt") return deleted_count # Globale Instanz für einfache Nutzung file_handler = SafeFileHandler() # Convenience-Funktionen def move_to_trash(file_path: Union[str, Path]) -> bool: """Verschiebt Datei in Papierkorb""" return file_handler.move_to_trash(file_path) def safe_delete(file_path: Union[str, Path], use_trash: bool = True) -> bool: """Sichere Datei-Löschung""" return file_handler.safe_delete(file_path, use_trash) def clean_temp_files(temp_dir: Union[str, Path], max_age_hours: int = 24) -> int: """Bereinigt temporäre Dateien""" return file_handler.clean_temp_files(temp_dir, max_age_hours) # Rückwärtskompatibilität mit Send2Trash API def send2trash(path: Union[str, Path]) -> None: """ Kompatibilitätsfunktion für Send2Trash Args: path: Pfad zur Datei/zum Ordner Raises: OSError: Bei Fehlern beim Löschen """ if not move_to_trash(path): raise OSError(f"Konnte Datei nicht in Papierkorb verschieben: {path}") # Beispiel-Usage: if __name__ == "__main__": # Test der Funktionalität import tempfile # Erstelle Testdatei with tempfile.NamedTemporaryFile(delete=False) as tmp: tmp.write(b"Test-Inhalt") test_file = tmp.name print(f"Teste Papierkorb-Funktionalität mit: {test_file}") # Teste Papierkorb if move_to_trash(test_file): print("✅ Datei erfolgreich in Papierkorb verschoben") else: print("❌ Papierkorb-Verschiebung fehlgeschlagen") # Aufräumen falls Papierkorb nicht funktioniert if os.path.exists(test_file): os.unlink(test_file) print("🗑️ Datei direkt gelöscht")