Files
Projektarbeit-MYP/backend/utils/file_utils.py

320 lines
11 KiB
Python

#!/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")