diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 479f7ec..0962bf8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,13 +1,18 @@ { "permissions": { "allow": [ - "Bash(python3:*)" + "Bash(python3:*)", + "Bash(tree:*)", + "Bash(git -C /mnt/a/GiTea/AccountForger log --oneline)", + "Bash(cat:*)", + "Bash(sqlite3:*)", + "Bash(find:*)" ], "deny": [], + "ask": [], "defaultMode": "acceptEdits", "additionalDirectories": [ "/mnt/a/GiTea/Styleguide" - ], - "ask": [] + ] } } diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index d665929..8e0ef3e 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -5,9 +5,9 @@ ## Project Overview - **Path**: `A:\GiTea\AccountForger` -- **Files**: 1393 files +- **Files**: 1394 files - **Size**: 257.7 MB -- **Last Modified**: 2025-11-16 22:31 +- **Last Modified**: 2025-11-27 19:34 ## Technology Stack @@ -21,13 +21,13 @@ ``` check_rotation_system.py +CLAUDE.md CLAUDE_PROJECT_README.md debug_video_issue.py init install_requirements.py main.py package.json -README.md application/ │ ├── __init__.py │ ├── services/ @@ -436,3 +436,6 @@ This project is managed with Claude Project Manager. To work with this project: - README updated on 2025-11-09 21:00:06 - README updated on 2025-11-16 22:31:39 - README updated on 2025-11-16 22:32:35 +- README updated on 2025-11-27 19:33:36 +- README updated on 2025-11-27 19:34:18 +- README updated on 2025-11-27 19:41:04 diff --git a/config/email_config.json b/config/email_config.json index e08dda9..575fc91 100644 --- a/config/email_config.json +++ b/config/email_config.json @@ -1,6 +1,6 @@ { - "imap_server": "imap.ionos.de", - "imap_port": 993, - "imap_user": "info@z5m7q9dk3ah2v1plx6ju.com", - "imap_pass": "GZsg9:66@a@M%etP" - } \ No newline at end of file + "imap_server": "imap.ionos.de", + "imap_port": 993, + "imap_user": "info@z5m7q9dk3ah2v1plx6ju.com", + "imap_pass": "GZsg9:66@a@M%etP" +} \ No newline at end of file diff --git a/controllers/platform_controllers/base_controller.py b/controllers/platform_controllers/base_controller.py index 54b9c09..d60fdf2 100644 --- a/controllers/platform_controllers/base_controller.py +++ b/controllers/platform_controllers/base_controller.py @@ -277,6 +277,37 @@ class BasePlatformController(QObject): """ generator_tab = self.get_generator_tab() generator_tab.set_running(False) - + # Standard-Implementierung - kann von Unterklassen erweitert werden - self.logger.info(f"Account erfolgreich erstellt für {self.platform_name}") \ No newline at end of file + self.logger.info(f"Account erfolgreich erstellt für {self.platform_name}") + + def stop_account_creation(self): + """ + Stoppt die Account-Erstellung (Default-Implementierung). + + Diese Methode kann von Unterklassen überschrieben werden für spezielle Anforderungen. + Sie stellt sicher dass: + 1. Der Process Guard freigegeben wird + 2. Der Worker-Thread gestoppt wird + 3. Die UI zurückgesetzt wird + 4. Dialoge geschlossen werden + """ + # Guard-Freigabe (wichtig: VOR Worker-Stop) + from utils.process_guard import get_guard + guard = get_guard() + if guard.is_locked(): + guard.end(success=False) + self.logger.info("Guard freigegeben bei Stop (BaseController)") + + # Worker stoppen falls vorhanden + if self.worker_thread and self.worker_thread.isRunning(): + self.worker_thread.stop() + generator_tab = self.get_generator_tab() + generator_tab.add_log(f"{self.platform_name}-Account-Erstellung wurde abgebrochen") + generator_tab.set_running(False) + generator_tab.set_progress(0) + + # Forge-Dialog schließen falls vorhanden + if hasattr(self, 'forge_dialog') and self.forge_dialog: + self.forge_dialog.close() + self.forge_dialog = None \ No newline at end of file diff --git a/controllers/platform_controllers/base_worker_thread.py b/controllers/platform_controllers/base_worker_thread.py index f18dc12..8d2575b 100644 --- a/controllers/platform_controllers/base_worker_thread.py +++ b/controllers/platform_controllers/base_worker_thread.py @@ -283,6 +283,24 @@ class BaseAccountCreationWorkerThread(QThread): return save_result def stop(self): - """Stoppt den Thread""" + """ + Stoppt den Thread sauber mit Guard-Freigabe. + + WICHTIG: Guard wird SOFORT freigegeben, da terminate() den finally-Block überspringt. + """ + import logging + logger = logging.getLogger(__name__) + self.running = False + + # Guard SOFORT freigeben bevor terminate() + # Grund: terminate() überspringt den finally-Block in run() + from utils.process_guard import get_guard + guard = get_guard() + if guard.is_locked(): + guard.end(success=False) + logger.info("Guard freigegeben bei Worker-Stop (vor terminate)") + + # Jetzt Thread beenden self.terminate() + self.wait(2000) # Warte max 2 Sekunden auf sauberes Ende diff --git a/controllers/platform_controllers/facebook_controller.py b/controllers/platform_controllers/facebook_controller.py index 755ac94..889ccb6 100644 --- a/controllers/platform_controllers/facebook_controller.py +++ b/controllers/platform_controllers/facebook_controller.py @@ -287,7 +287,15 @@ class FacebookController(BasePlatformController): self.forge_dialog = None def stop_account_creation(self): - """Stoppt die Facebook-Account-Erstellung.""" + """Stoppt die Facebook-Account-Erstellung mit Guard-Freigabe.""" + # Guard-Freigabe (wichtig: VOR Worker-Stop) + from utils.process_guard import get_guard + guard = get_guard() + if guard.is_locked(): + guard.end(success=False) + self.logger.info("Guard freigegeben bei Facebook Stop") + + # Worker stoppen if self.worker_thread and self.worker_thread.isRunning(): self.worker_thread.stop() generator_tab = self.get_generator_tab() diff --git a/controllers/platform_controllers/gmail_controller.py b/controllers/platform_controllers/gmail_controller.py index 56fa3d2..a7d888e 100644 --- a/controllers/platform_controllers/gmail_controller.py +++ b/controllers/platform_controllers/gmail_controller.py @@ -198,16 +198,25 @@ class GmailController(BasePlatformController): logger.info(f"[GMAIL] start_account_creation abgeschlossen") def stop_account_creation(self): - """Stoppt die laufende Account-Erstellung""" + """Stoppt die laufende Account-Erstellung mit Guard-Freigabe""" logger.info("[GMAIL] Stoppe Account-Erstellung") - + + # Guard-Freigabe (wichtig: VOR Worker-Stop) + from utils.process_guard import get_guard + guard = get_guard() + if guard.is_locked(): + guard.end(success=False) + logger.info("Guard freigegeben bei Gmail Stop") + + # Worker stoppen if self.worker_thread and self.worker_thread.isRunning(): self.worker_thread.stop() self.worker_thread.wait() - + + # Dialog schließen if hasattr(self, 'forge_dialog') and self.forge_dialog: self.forge_dialog.close() - + # UI zurücksetzen generator_tab = self.get_generator_tab() generator_tab.set_running(False) diff --git a/controllers/platform_controllers/instagram_controller.py b/controllers/platform_controllers/instagram_controller.py index e15bec8..30b76bc 100644 --- a/controllers/platform_controllers/instagram_controller.py +++ b/controllers/platform_controllers/instagram_controller.py @@ -269,14 +269,22 @@ class InstagramController(BasePlatformController): self.forge_dialog.show() def stop_account_creation(self): - """Stoppt die Instagram-Account-Erstellung.""" + """Stoppt die Instagram-Account-Erstellung mit Guard-Freigabe.""" + # Guard-Freigabe (wichtig: VOR Worker-Stop) + from utils.process_guard import get_guard + guard = get_guard() + if guard.is_locked(): + guard.end(success=False) + self.logger.info("Guard freigegeben bei Instagram Stop") + + # Worker stoppen if self.worker_thread and self.worker_thread.isRunning(): self.worker_thread.stop() generator_tab = self.get_generator_tab() generator_tab.add_log("Account-Erstellung wurde abgebrochen") generator_tab.set_running(False) generator_tab.set_progress(0) - + # Forge-Dialog schließen falls vorhanden if hasattr(self, 'forge_dialog') and self.forge_dialog: self.forge_dialog.close() diff --git a/controllers/platform_controllers/ok_ru_controller.py b/controllers/platform_controllers/ok_ru_controller.py index c4362cb..a574246 100644 --- a/controllers/platform_controllers/ok_ru_controller.py +++ b/controllers/platform_controllers/ok_ru_controller.py @@ -131,14 +131,22 @@ class OkRuController(BasePlatformController): self.forge_dialog.show() def stop_account_creation(self): - """Stoppt die OK.ru-Account-Erstellung.""" + """Stoppt die OK.ru-Account-Erstellung mit Guard-Freigabe.""" + # Guard-Freigabe (wichtig: VOR Worker-Stop) + from utils.process_guard import get_guard + guard = get_guard() + if guard.is_locked(): + guard.end(success=False) + self.logger.info("Guard freigegeben bei OK.ru Stop") + + # Worker stoppen if self.worker_thread and self.worker_thread.isRunning(): self.worker_thread.stop() generator_tab = self.get_generator_tab() generator_tab.add_log("Account-Erstellung wurde abgebrochen") generator_tab.set_running(False) generator_tab.set_progress(0) - + # Forge-Dialog schließen falls vorhanden if hasattr(self, 'forge_dialog') and self.forge_dialog: self.forge_dialog.close() diff --git a/controllers/platform_controllers/tiktok_controller.py b/controllers/platform_controllers/tiktok_controller.py index 8cef3df..d4fa361 100644 --- a/controllers/platform_controllers/tiktok_controller.py +++ b/controllers/platform_controllers/tiktok_controller.py @@ -273,14 +273,22 @@ class TikTokController(BasePlatformController): self.forge_dialog.show() def stop_account_creation(self): - """Stoppt die TikTok-Account-Erstellung.""" + """Stoppt die TikTok-Account-Erstellung mit Guard-Freigabe.""" + # Guard-Freigabe (wichtig: VOR Worker-Stop) + from utils.process_guard import get_guard + guard = get_guard() + if guard.is_locked(): + guard.end(success=False) + self.logger.info("Guard freigegeben bei TikTok Stop") + + # Worker stoppen if self.worker_thread and self.worker_thread.isRunning(): self.worker_thread.stop() generator_tab = self.get_generator_tab() generator_tab.add_log("Account-Erstellung wurde abgebrochen") generator_tab.set_running(False) generator_tab.set_progress(0) - + # Forge-Dialog schließen falls vorhanden if hasattr(self, 'forge_dialog') and self.forge_dialog: self.forge_dialog.close() diff --git a/controllers/platform_controllers/x_controller.py b/controllers/platform_controllers/x_controller.py index d417d1a..f5a24ea 100644 --- a/controllers/platform_controllers/x_controller.py +++ b/controllers/platform_controllers/x_controller.py @@ -271,14 +271,22 @@ class XController(BasePlatformController): self.forge_dialog.show() def stop_account_creation(self): - """Stoppt die X-Account-Erstellung.""" + """Stoppt die X-Account-Erstellung mit Guard-Freigabe.""" + # Guard-Freigabe (wichtig: VOR Worker-Stop) + from utils.process_guard import get_guard + guard = get_guard() + if guard.is_locked(): + guard.end(success=False) + self.logger.info("Guard freigegeben bei X Stop") + + # Worker stoppen if self.worker_thread and self.worker_thread.isRunning(): self.worker_thread.stop() generator_tab = self.get_generator_tab() generator_tab.add_log("Account-Erstellung wurde abgebrochen") generator_tab.set_running(False) generator_tab.set_progress(0) - + # Forge-Dialog schließen falls vorhanden if hasattr(self, 'forge_dialog') and self.forge_dialog: self.forge_dialog.close() diff --git a/controllers/session_controller.py b/controllers/session_controller.py index de7c095..3c7b7e4 100644 --- a/controllers/session_controller.py +++ b/controllers/session_controller.py @@ -129,18 +129,18 @@ class SessionController(QObject): self.login_failed.emit(account_id, str(e)) def _cancel_login(self, account_id: str): - """Bricht den Login-Prozess ab""" + """Bricht den Login-Prozess ab mit Guard-Freigabe""" logger.info(f"Login für Account {account_id} wurde abgebrochen") - # Feature 5: Guard freigeben bei Cancel - # HINWEIS: Worker Thread gibt Guard in seinem finally-Block frei - # Nur freigeben wenn Worker noch nicht gestartet (Guard locked aber kein Worker) + # Guard IMMER freigeben bei Cancel (einfacher + robuster) + # Doppelte Freigabe ist kein Problem (wird von ProcessGuard ignoriert) from utils.process_guard import get_guard guard = get_guard() - if guard.is_locked() and not (hasattr(self, 'login_worker') and self.login_worker and self.login_worker.isRunning()): - logger.warning("Guard war locked aber Worker nicht aktiv - gebe Guard frei") + if guard.is_locked(): guard.end(success=False) + logger.info("Guard freigegeben bei Login-Cancel") + # Dialog schließen if hasattr(self, 'login_dialog') and self.login_dialog: self.login_dialog.close() self.login_dialog = None diff --git a/database/accounts.db b/database/accounts.db index aeb978d..078b327 100644 Binary files a/database/accounts.db and b/database/accounts.db differ diff --git a/utils/email_handler.py b/utils/email_handler.py index 9a8fd6f..f11ed55 100644 --- a/utils/email_handler.py +++ b/utils/email_handler.py @@ -15,6 +15,7 @@ from email.header import decode_header from datetime import datetime, timedelta from utils.text_similarity import TextSimilarity +from config.paths import PathConfig logger = logging.getLogger("email_handler") @@ -22,8 +23,8 @@ class EmailHandler: """ Handler für den Zugriff auf E-Mail-Dienste und den Abruf von Bestätigungscodes. """ - - CONFIG_FILE = os.path.join("config", "email_config.json") + + CONFIG_FILE = os.path.join(PathConfig.CONFIG_DIR, "email_config.json") def __init__(self): """Initialisiert den EmailHandler und lädt die Konfiguration.""" diff --git a/utils/process_guard.py b/utils/process_guard.py index 15835e6..8d62686 100644 --- a/utils/process_guard.py +++ b/utils/process_guard.py @@ -31,6 +31,7 @@ class ProcessGuard: # Konfiguration MAX_FAILURES = 3 PAUSE_DURATION_HOURS = 1 + LOCK_TIMEOUT_MINUTES = 10 # Auto-Unlock nach 10 Minuten bei hängenden Prozessen def __init__(self): """Initialisiert den Process Guard.""" @@ -38,6 +39,7 @@ class ProcessGuard: self._is_locked = False self._current_process = None self._current_platform = None + self._lock_started_at = None # Timestamp für Auto-Timeout # Error Tracking self._failure_count = 0 @@ -95,6 +97,7 @@ class ProcessGuard: self._is_locked = True self._current_process = process_type self._current_platform = platform + self._lock_started_at = datetime.now() # Timestamp für Auto-Timeout logger.info(f"Process locked: {process_type} ({platform})") def end(self, success: bool): @@ -109,6 +112,7 @@ class ProcessGuard: self._is_locked = False self._current_process = None self._current_platform = None + self._lock_started_at = None # Timestamp zurücksetzen # Fehler-Tracking if success: @@ -133,6 +137,7 @@ class ProcessGuard: self._is_locked = False self._current_process = None self._current_platform = None + self._lock_started_at = None # Timestamp zurücksetzen self._load_pause_state() if self._is_paused(): @@ -143,12 +148,31 @@ class ProcessGuard: def is_locked(self) -> bool: """ - Gibt zurück ob aktuell ein Prozess läuft. + Gibt zurück ob aktuell ein Prozess läuft (mit Auto-Timeout-Check). Returns: True wenn ein Prozess aktiv ist """ - return self._is_locked + if not self._is_locked: + return False + + # Auto-Timeout-Check: Unlock bei hängenden Prozessen + if self._lock_started_at: + elapsed_minutes = (datetime.now() - self._lock_started_at).total_seconds() / 60 + + if elapsed_minutes > self.LOCK_TIMEOUT_MINUTES: + logger.warning( + f"⏰ AUTO-TIMEOUT: Lock nach {int(elapsed_minutes)} Minuten freigegeben. " + f"Prozess: {self._current_process} ({self._current_platform})" + ) + # Lock automatisch freigeben + self._is_locked = False + self._current_process = None + self._current_platform = None + self._lock_started_at = None + return False + + return True def is_paused(self) -> bool: """ diff --git a/views/main_window.py b/views/main_window.py index 0d289b8..3e6a1e9 100644 --- a/views/main_window.py +++ b/views/main_window.py @@ -11,7 +11,7 @@ from PyQt5.QtWidgets import ( QLabel, QPushButton, QStackedWidget, QTabWidget, QAction, QMessageBox ) -from PyQt5.QtCore import Qt, pyqtSignal, QSize, QFile +from PyQt5.QtCore import Qt, pyqtSignal, QSize, QFile, QTimer from PyQt5.QtGui import QIcon, QFont, QPixmap from localization.language_manager import LanguageManager @@ -114,6 +114,14 @@ class MainWindow(QMainWindow): # Verbinde Signale self.connect_signals() + # Timer für periodische Guard-Status-Prüfung des Zurück-Buttons + self.guard_check_timer = QTimer() + self.guard_check_timer.timeout.connect(self._update_back_button_state) + self.guard_check_timer.start(1000) # Alle 1 Sekunde prüfen + + # Initiale Button-Status-Prüfung + self._update_back_button_state() + def connect_signals(self): """Verbindet die internen Signale.""" # Platform-Selector-Signal verbinden @@ -267,6 +275,37 @@ class MainWindow(QMainWindow): """Setzt eine Nachricht in der Statusleiste.""" self.statusBar().showMessage(message) + def _update_back_button_state(self): + """ + Aktualisiert den Status des Zurück-Buttons basierend auf ProcessGuard. + + Deaktiviert den Zurück-Button wenn ein Prozess läuft oder Pause aktiv ist, + um zu verhindern dass User während Account-Erstellung/Login navigieren. + """ + from utils.process_guard import get_guard + guard = get_guard() + + # Prüfe ob blockiert + is_locked = guard.is_locked() + is_paused = guard.is_paused() + is_blocked = is_locked or is_paused + + # Button entsprechend setzen (nur wenn auf Platform-View) + if self.stacked_widget.currentWidget() == self.platform_container: + self.back_button.setEnabled(not is_blocked) + + # Tooltip setzen + if is_blocked: + status_msg = guard.get_status_message() + self.back_button.setToolTip(f"⚠ {status_msg}") + else: + # Reset Tooltip + if self.language_manager: + tooltip = self.language_manager.get_text("buttons.back", "↩ Zurück") + else: + tooltip = "↩ Zurück" + self.back_button.setToolTip(tooltip) + def add_log_widget(self, text_widget): """Fügt einen GUI-Handler zum Logger hinzu.""" add_gui_handler(logger, text_widget)