export_summary.txt wird nicht mehr erstellt

Dieser Commit ist enthalten in:
Claude Project Manager
2025-12-15 22:59:27 +01:00
Ursprung 8e4a052b6c
Commit 4e82d5ef8f
10 geänderte Dateien mit 236 neuen und 760 gelöschten Zeilen

Datei anzeigen

@ -6,7 +6,10 @@
"Bash(git -C /mnt/a/GiTea/AccountForger log --oneline)",
"Bash(cat:*)",
"Bash(sqlite3:*)",
"Bash(find:*)"
"Bash(find:*)",
"Bash(pkill:*)",
"Bash(lsof:*)",
"Bash(ls:*)"
],
"deny": [],
"ask": [],

Datei anzeigen

@ -72,6 +72,9 @@ class PlaywrightManager:
self.context = None
self.page = None
# Feature 5: Flag für Disconnect-Tracking (verhindert doppelte Counter-Dekrementierung)
self._browser_was_disconnected = False
# Zähler für Wiederhholungsversuche
self.retry_counter = {}
@ -141,12 +144,29 @@ class PlaywrightManager:
if self.page is not None:
return self.page
# Feature 5: Reset Disconnect-Flag bei neuem Browser-Start
self._browser_was_disconnected = False
# Feature 5: Browser-Instanz Schutz - Nur eine Instanz gleichzeitig
if PlaywrightManager._active_count >= 1:
raise RuntimeError(
"Browser bereits aktiv. Nur eine Browser-Instanz gleichzeitig erlaubt. "
"Beenden Sie den aktuellen Prozess."
)
# Safety-Check: Prüfe ob Counter "hängt" (Absturz-Schutz)
# Wenn ProcessGuard NICHT locked ist, aber Counter > 0, dann ist Counter "tot"
from utils.process_guard import get_guard
guard = get_guard()
if not guard.is_locked():
# Counter hängt! Process Guard ist frei, aber Counter sagt Browser läuft
logger.warning(
f"⚠️ SAFETY-RESET: _active_count war {PlaywrightManager._active_count}, "
f"aber ProcessGuard ist nicht locked. Counter wird zurückgesetzt."
)
PlaywrightManager._active_count = 0
else:
# Guard ist locked UND Counter ist > 0 → echte parallele Instanz
raise RuntimeError(
"Browser bereits aktiv. Nur eine Browser-Instanz gleichzeitig erlaubt. "
"Beenden Sie den aktuellen Prozess."
)
try:
self.playwright = sync_playwright().start()
@ -257,6 +277,10 @@ class PlaywrightManager:
# Event-Listener für Konsolen-Logs
self.page.on("console", lambda msg: logger.debug(f"BROWSER CONSOLE: {msg.text}"))
# Feature 5: Browser-Disconnect-Handler registrieren (für manuelles Schließen)
self.browser.on("disconnected", self._on_browser_disconnected)
logger.debug("Browser-Disconnect-Handler registriert")
# Feature 5: Browser-Instanz Counter erhöhen
PlaywrightManager._active_count += 1
logger.info(f"Browser gestartet (aktive Instanzen: {PlaywrightManager._active_count})")
@ -967,6 +991,32 @@ class PlaywrightManager:
self.protection_style = None
logger.info("Browser-Schutz entfernt")
def _on_browser_disconnected(self):
"""
Event-Handler: Wird aufgerufen wenn Browser-Verbindung getrennt wird.
Dekrementiert den aktiven Browser-Counter, auch wenn der Browser
manuell geschlossen wurde (z.B. durch User-Klick auf X-Button).
Dies stellt sicher dass der Missbrauchs-Schutz korrekt funktioniert,
selbst wenn close() nicht explizit aufgerufen wird.
"""
# Verhindere doppelte Dekrementierung
if self._browser_was_disconnected:
logger.debug("Browser bereits als disconnected markiert, überspringe Counter-Dekrementierung")
return
self._browser_was_disconnected = True
if PlaywrightManager._active_count > 0:
PlaywrightManager._active_count -= 1
logger.info(
f"🔌 Browser disconnected - Counter dekrementiert "
f"(aktive Instanzen: {PlaywrightManager._active_count})"
)
else:
logger.warning("Browser disconnected aber Counter war bereits 0")
def close(self):
"""Schließt den Browser und gibt Ressourcen frei."""
try:
@ -1012,9 +1062,15 @@ class PlaywrightManager:
self.playwright = None
# Feature 5: Browser-Instanz Counter dekrementieren
if PlaywrightManager._active_count > 0:
PlaywrightManager._active_count -= 1
logger.info(f"Browser geschlossen (aktive Instanzen: {PlaywrightManager._active_count})")
# (nur wenn nicht bereits durch disconnected-Event dekrementiert)
if not self._browser_was_disconnected:
if PlaywrightManager._active_count > 0:
PlaywrightManager._active_count -= 1
logger.info(f"Browser geschlossen (aktive Instanzen: {PlaywrightManager._active_count})")
else:
logger.warning("Browser-Counter war bereits 0 beim Schließen")
else:
logger.debug("Counter wurde bereits durch disconnected-Event dekrementiert")
logger.info("Browser-Sitzung erfolgreich geschlossen")

Datei anzeigen

@ -105,80 +105,100 @@ class FacebookController(BasePlatformController):
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
# Guard freigeben da Worker nicht gestartet wird
from utils.process_guard import get_guard
get_guard().end(success=False)
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
if hasattr(generator_tab, 'clear_log'):
generator_tab.clear_log()
if hasattr(generator_tab, 'set_progress'):
generator_tab.set_progress(0)
# Schmiedeanimation-Dialog erstellen und anzeigen
parent_widget = generator_tab.window() # Hauptfenster als Parent
self.forge_dialog = ForgeAnimationDialog(parent_widget, "Facebook")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Fingerprint VOR Account-Erstellung generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
if hasattr(generator_tab, 'clear_log'):
generator_tab.clear_log()
if hasattr(generator_tab, 'set_progress'):
generator_tab.set_progress(0)
fingerprint_service = FingerprintGeneratorService()
# Schmiedeanimation-Dialog erstellen und anzeigen
parent_widget = generator_tab.window() # Hauptfenster als Parent
self.forge_dialog = ForgeAnimationDialog(parent_widget, "Facebook")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Generiere einen neuen Fingerprint für diesen Account
fingerprint_data = fingerprint_service.generate_fingerprint()
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Erstelle BrowserFingerprint Entity mit allen notwendigen Daten
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
# Fingerprint VOR Account-Erstellung generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
# Konvertiere zu Dictionary für Übertragung
params["fingerprint"] = fingerprint.to_dict()
fingerprint_service = FingerprintGeneratorService()
# Generiere einen neuen Fingerprint für diesen Account
fingerprint_data = fingerprint_service.generate_fingerprint()
# Erstelle BrowserFingerprint Entity mit allen notwendigen Daten
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
# Konvertiere zu Dictionary für Übertragung
params["fingerprint"] = fingerprint.to_dict()
logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Fortfahren ohne Fingerprint - wird später generiert
# Worker-Thread starten mit optionalen Parametern
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
self.worker_thread = FacebookWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Updates an Forge-Dialog weiterleiten
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab für Backup
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
if hasattr(generator_tab, 'set_progress'):
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen und Animation starten
self.forge_dialog.start_animation()
self.forge_dialog.show()
logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Fortfahren ohne Fingerprint - wird später generiert
# Kritischer Fehler VOR Worker-Start → Guard freigeben!
logger.error(f"Fehler beim Start der Account-Erstellung: {e}", exc_info=True)
# Worker-Thread starten mit optionalen Parametern
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
from utils.process_guard import get_guard
get_guard().end(success=False)
self.worker_thread = FacebookWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Dialog schließen falls vorhanden
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
# Updates an Forge-Dialog weiterleiten
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab für Backup
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
if hasattr(generator_tab, 'set_progress'):
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen und Animation starten
self.forge_dialog.start_animation()
self.forge_dialog.show()
# UI zurücksetzen
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
generator_tab.show_error(f"Fehler beim Start: {str(e)}")
def handle_account_created(self, result: Dict[str, Any]) -> bool:
"""

Datei anzeigen

@ -197,76 +197,96 @@ class InstagramController(BasePlatformController):
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
# Guard freigeben da Worker nicht gestartet wird
from utils.process_guard import get_guard
get_guard().end(success=False)
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Schmiedeanimation-Dialog erstellen und anzeigen
parent_widget = generator_tab.window() # Hauptfenster als Parent
self.forge_dialog = ForgeAnimationDialog(parent_widget, "Instagram")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Fingerprint VOR Account-Erstellung generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
fingerprint_service = FingerprintGeneratorService()
# Schmiedeanimation-Dialog erstellen und anzeigen
parent_widget = generator_tab.window() # Hauptfenster als Parent
self.forge_dialog = ForgeAnimationDialog(parent_widget, "Instagram")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Generiere einen neuen Fingerprint für diesen Account
fingerprint_data = fingerprint_service.generate_fingerprint()
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Erstelle BrowserFingerprint Entity mit allen notwendigen Daten
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
# Fingerprint VOR Account-Erstellung generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
# Konvertiere zu Dictionary für Übertragung
params["fingerprint"] = fingerprint.to_dict()
fingerprint_service = FingerprintGeneratorService()
# Generiere einen neuen Fingerprint für diesen Account
fingerprint_data = fingerprint_service.generate_fingerprint()
# Erstelle BrowserFingerprint Entity mit allen notwendigen Daten
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
# Konvertiere zu Dictionary für Übertragung
params["fingerprint"] = fingerprint.to_dict()
logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Fortfahren ohne Fingerprint - wird später generiert
# Worker-Thread starten mit optionalen Parametern
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
self.worker_thread = InstagramWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Updates an Forge-Dialog weiterleiten
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab für Backup
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen und Animation starten
self.forge_dialog.start_animation()
self.forge_dialog.show()
logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Fortfahren ohne Fingerprint - wird später generiert
# Kritischer Fehler VOR Worker-Start → Guard freigeben!
logger.error(f"Fehler beim Start der Account-Erstellung: {e}", exc_info=True)
# Worker-Thread starten mit optionalen Parametern
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
from utils.process_guard import get_guard
get_guard().end(success=False)
self.worker_thread = InstagramWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Updates an Forge-Dialog weiterleiten
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Dialog schließen falls vorhanden
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
# Auch an Generator-Tab für Backup
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen und Animation starten
self.forge_dialog.start_animation()
self.forge_dialog.show()
# UI zurücksetzen
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
generator_tab.show_error(f"Fehler beim Start: {str(e)}")
def stop_account_creation(self):
"""Stoppt die Instagram-Account-Erstellung mit Guard-Freigabe."""

Datei anzeigen

@ -274,40 +274,7 @@ class ProfileExportController:
logger.error(f"Fehler beim Export von {username}: {e}")
failed_accounts.append(username)
# 5. Summary-Datei erstellen
summary_path = os.path.join(save_directory, "export_summary.txt")
with open(summary_path, 'w', encoding='utf-8') as f:
f.write(f"AccountForge Batch-Export\n")
f.write(f"="*50 + "\n\n")
f.write(f"Exportiert am: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n")
f.write(f"Anzahl Accounts: {len(accounts_data)}\n")
f.write(f"Erfolgreich: {len(accounts_data) - len(failed_accounts)}\n")
if failed_accounts:
f.write(f"Fehlgeschlagen: {len(failed_accounts)}\n")
f.write(f"\nFormate: {', '.join(formats).upper()}\n")
f.write(f"\n" + "="*50 + "\n\n")
# Gruppiere nach Plattform
platforms = {}
for account_data in accounts_data:
platform = account_data.get("platform", "unknown").lower()
if platform not in platforms:
platforms[platform] = []
platforms[platform].append(account_data.get("username", ""))
for platform, usernames in sorted(platforms.items()):
f.write(f"{platform.capitalize()}:\n")
for username in usernames:
if username in failed_accounts:
f.write(f"{username} (FEHLER)\n")
else:
f.write(f"{username}\n")
f.write(f"\n")
exported_files.append("export_summary.txt")
logger.info("Summary-Datei erstellt")
# 6. Erfolgs-Dialog anzeigen
# 5. Erfolgs-Dialog anzeigen
show_export_success(
parent_widget,
exported_files,

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -1,158 +0,0 @@
#!/usr/bin/env python3
"""
Run method rotation system database migration.
This script applies the rotation system database schema to the existing database.
"""
import sys
import os
import sqlite3
import logging
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def run_migration():
"""Run the method rotation system migration"""
try:
# Database path
db_path = project_root / "database" / "accounts.db"
migration_path = project_root / "database" / "migrations" / "add_method_rotation_system.sql"
if not db_path.exists():
logger.error(f"Database not found at {db_path}")
return False
if not migration_path.exists():
logger.error(f"Migration file not found at {migration_path}")
return False
# Read migration SQL
with open(migration_path, 'r', encoding='utf-8') as f:
migration_sql = f.read()
# Connect to database
logger.info(f"Connecting to database at {db_path}")
conn = sqlite3.connect(str(db_path))
try:
# Check if migration has already been run
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='method_strategies'")
if cursor.fetchone():
logger.info("Method rotation tables already exist, skipping migration")
return True
# Run migration
logger.info("Running method rotation system migration...")
conn.executescript(migration_sql)
conn.commit()
# Verify migration
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%method%' OR name LIKE '%rotation%'")
tables = cursor.fetchall()
expected_tables = [
'method_strategies',
'rotation_sessions',
'rotation_events',
'method_performance_analytics',
'method_cooldowns',
'platform_method_states'
]
created_tables = [table[0] for table in tables]
missing_tables = [t for t in expected_tables if t not in created_tables]
if missing_tables:
logger.error(f"Migration incomplete, missing tables: {missing_tables}")
return False
logger.info(f"Migration successful! Created tables: {created_tables}")
# Verify default data was inserted
cursor.execute("SELECT COUNT(*) FROM method_strategies")
strategy_count = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM platform_method_states")
state_count = cursor.fetchone()[0]
logger.info(f"Default data inserted: {strategy_count} strategies, {state_count} platform states")
return True
finally:
conn.close()
except Exception as e:
logger.error(f"Migration failed: {e}")
return False
def check_migration_status():
"""Check if migration has been applied"""
try:
db_path = project_root / "database" / "accounts.db"
if not db_path.exists():
logger.warning(f"Database not found at {db_path}")
return False
conn = sqlite3.connect(str(db_path))
cursor = conn.cursor()
# Check for rotation tables
cursor.execute("""
SELECT name FROM sqlite_master
WHERE type='table' AND (
name LIKE '%method%' OR
name LIKE '%rotation%'
)
ORDER BY name
""")
tables = [row[0] for row in cursor.fetchall()]
conn.close()
if tables:
logger.info(f"Method rotation tables found: {tables}")
return True
else:
logger.info("No method rotation tables found")
return False
except Exception as e:
logger.error(f"Failed to check migration status: {e}")
return False
def main():
"""Main function"""
logger.info("Method Rotation System Database Migration")
logger.info("=" * 50)
# Check current status
logger.info("Checking current migration status...")
if check_migration_status():
logger.info("Migration already applied")
return
# Run migration
logger.info("Applying migration...")
if run_migration():
logger.info("Migration completed successfully!")
logger.info("Method rotation system is now ready to use")
else:
logger.error("Migration failed!")
sys.exit(1)
if __name__ == "__main__":
main()

Datei anzeigen

@ -1,65 +0,0 @@
#!/usr/bin/env python3
"""
Debug script to check what's happening with logo switching
"""
import os
import sys
# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from themes.theme_config import ThemeConfig
def check_logo_files():
"""Check the actual logo files and their paths."""
print("=" * 60)
print("LOGO FILE ANALYSIS")
print("=" * 60)
base_dir = os.path.dirname(os.path.abspath(__file__))
# Check what's in the config
light_theme = ThemeConfig.get_theme('light')
dark_theme = ThemeConfig.get_theme('dark')
print(f"\nTheme Config:")
print(f" Light theme logo_path: {light_theme.get('logo_path', 'NOT SET')}")
print(f" Dark theme logo_path: {dark_theme.get('logo_path', 'NOT SET')}")
# Check actual files
icons_dir = os.path.join(base_dir, "resources", "icons")
print(f"\nIcon files in {icons_dir}:")
for file in os.listdir(icons_dir):
if "intelsight" in file.lower():
file_path = os.path.join(icons_dir, file)
file_size = os.path.getsize(file_path)
print(f" - {file} ({file_size} bytes)")
# Test the path resolution
print("\n" + "=" * 60)
print("PATH RESOLUTION TEST")
print("=" * 60)
# Simulate what happens in get_icon_path
for theme_name in ['light', 'dark']:
theme = ThemeConfig.get_theme(theme_name)
logo_name = theme.get('logo_path', 'intelsight-logo.svg').replace('.svg', '')
full_path = os.path.join(base_dir, "resources", "icons", f"{logo_name}.svg")
print(f"\n{theme_name.upper()} theme:")
print(f" logo_name from config: {logo_name}")
print(f" full_path: {full_path}")
print(f" file exists: {os.path.exists(full_path)}")
if os.path.exists(full_path):
# Check if it's actually an SVG
with open(full_path, 'r') as f:
first_line = f.readline().strip()
is_svg = '<svg' in first_line.lower() or '<?xml' in first_line.lower()
print(f" is valid SVG: {is_svg}")
print(f" first line: {first_line[:50]}...")
if __name__ == "__main__":
check_logo_files()

Datei anzeigen

@ -1,88 +0,0 @@
#!/usr/bin/env python3
"""
Test script to verify logo switching logic without PyQt5
"""
import os
import sys
# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from themes.theme_config import ThemeConfig
def test_logo_paths():
"""Test that logo paths are correctly configured."""
print("=" * 60)
print("LOGO SWITCHING TEST")
print("=" * 60)
# Test light theme logo
light_theme = ThemeConfig.get_theme('light')
light_logo = light_theme.get('logo_path', 'NOT_FOUND')
print(f"\n✓ Light theme logo: {light_logo}")
# Test dark theme logo
dark_theme = ThemeConfig.get_theme('dark')
dark_logo = dark_theme.get('logo_path', 'NOT_FOUND')
print(f"✓ Dark theme logo: {dark_logo}")
# Check if files exist
base_dir = os.path.dirname(os.path.abspath(__file__))
light_path = os.path.join(base_dir, "resources", "icons", light_logo)
dark_path = os.path.join(base_dir, "resources", "icons", dark_logo)
print(f"\n✓ Light logo exists: {os.path.exists(light_path)} ({light_path})")
print(f"✓ Dark logo exists: {os.path.exists(dark_path)} ({dark_path})")
# Simulate theme manager logic
print("\n" + "=" * 60)
print("SIMULATING THEME MANAGER LOGIC")
print("=" * 60)
class MockThemeManager:
def __init__(self):
self.base_dir = base_dir
self.current_theme = 'light'
def get_icon_path(self, icon_name):
if icon_name == "intelsight-logo":
theme = ThemeConfig.get_theme(self.current_theme)
logo_name = theme.get('logo_path', 'intelsight-logo.svg').replace('.svg', '')
return os.path.join(self.base_dir, "resources", "icons", f"{logo_name}.svg")
return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg")
tm = MockThemeManager()
# Test light theme
tm.current_theme = 'light'
light_result = tm.get_icon_path("intelsight-logo")
print(f"\nLight theme path: {light_result}")
print(f"File exists: {os.path.exists(light_result)}")
# Test dark theme
tm.current_theme = 'dark'
dark_result = tm.get_icon_path("intelsight-logo")
print(f"\nDark theme path: {dark_result}")
print(f"File exists: {os.path.exists(dark_result)}")
# Check if paths are different
if light_result != dark_result:
print("\n✅ SUCCESS: Different logos for different themes!")
else:
print("\n❌ ERROR: Same logo path for both themes!")
return False
# Check actual file names
if "intelsight-logo" in light_result and "intelsight-dark" in dark_result:
print("✅ SUCCESS: Correct logo files selected!")
else:
print("❌ ERROR: Wrong logo files!")
return False
return True
if __name__ == "__main__":
success = test_logo_paths()
sys.exit(0 if success else 1)

Datei anzeigen

@ -1,279 +0,0 @@
#!/usr/bin/env python3
"""
Comprehensive test script for the theme system refactoring.
Tests all components without requiring PyQt5.
"""
import sys
import os
import json
from pathlib import Path
# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def test_theme_config():
"""Test theme configuration module."""
print("\n=== Testing Theme Configuration ===")
try:
from themes.theme_config import ThemeConfig
# Test light theme
light = ThemeConfig.get_theme('light')
print(f"✓ Light theme loaded: {len(light)} color definitions")
# Test dark theme
dark = ThemeConfig.get_theme('dark')
print(f"✓ Dark theme loaded: {len(dark)} color definitions")
# Check critical keys
critical_keys = [
'bg_primary', 'bg_secondary', 'bg_tertiary',
'text_primary', 'text_secondary', 'text_tertiary',
'accent', 'accent_hover', 'accent_pressed',
'error', 'error_dark', 'success', 'warning',
'border_default', 'border_subtle',
'logo_path'
]
for key in critical_keys:
if key not in light:
print(f"✗ Missing in light theme: {key}")
return False
if key not in dark:
print(f"✗ Missing in dark theme: {key}")
return False
print(f"✓ All {len(critical_keys)} critical keys present in both themes")
# Test sizes, fonts, etc
sizes = ThemeConfig.FONT_SIZES
fonts = ThemeConfig.FONTS
weights = ThemeConfig.FONT_WEIGHTS
spacing = ThemeConfig.SPACING
radius = ThemeConfig.RADIUS
print(f"✓ Font sizes defined: {list(sizes.keys())}")
print(f"✓ Font families defined: {list(fonts.keys())}")
print(f"✓ Font weights defined: {list(weights.keys())}")
print(f"✓ Spacing values defined: {list(spacing.keys())}")
print(f"✓ Border radius defined: {list(radius.keys())}")
return True
except Exception as e:
print(f"✗ Error testing theme config: {e}")
return False
def test_qss_generator():
"""Test QSS generation."""
print("\n=== Testing QSS Generator ===")
try:
from themes.qss_generator import QSSGenerator
# Generate light theme QSS
light_qss = QSSGenerator.generate('light')
print(f"✓ Light theme QSS generated: {len(light_qss)} characters")
# Generate dark theme QSS
dark_qss = QSSGenerator.generate('dark')
print(f"✓ Dark theme QSS generated: {len(dark_qss)} characters")
# Check for key selectors
key_selectors = [
'QMainWindow', 'QPushButton', 'QLabel', 'QLineEdit',
'QTextEdit', 'QScrollArea', 'QFrame', 'QWidget',
'QMenuBar', 'QTabBar', 'QDialog'
]
missing_light = []
missing_dark = []
for selector in key_selectors:
if selector not in light_qss:
missing_light.append(selector)
if selector not in dark_qss:
missing_dark.append(selector)
if missing_light:
print(f"✗ Missing selectors in light QSS: {missing_light}")
if missing_dark:
print(f"✗ Missing selectors in dark QSS: {missing_dark}")
if not missing_light and not missing_dark:
print(f"✓ All {len(key_selectors)} key selectors present in both themes")
# Check for object name selectors (our custom ones)
custom_selectors = [
'#platform_button', '#filter_button', '#account_username',
'#account_login_btn', '#account_export_btn', '#account_delete_btn',
'#logo_button', '#dark_mode_toggle'
]
found_custom = []
for selector in custom_selectors:
if selector in light_qss:
found_custom.append(selector)
print(f"✓ Custom selectors found: {len(found_custom)}/{len(custom_selectors)}")
return True
except Exception as e:
print(f"✗ Error testing QSS generator: {e}")
return False
def test_file_structure():
"""Test that all required files exist."""
print("\n=== Testing File Structure ===")
required_files = [
'themes/__init__.py',
'themes/theme_config.py',
'themes/qss_generator.py',
'views/base/__init__.py',
'views/base/theme_aware_widget.py',
'views/widgets/dark_mode_toggle.py',
'utils/theme_manager.py',
'resources/icons/intelsight-logo.svg',
'resources/icons/intelsight-dark.svg'
]
missing = []
for file in required_files:
path = Path(file)
if path.exists():
print(f"{file}")
else:
print(f"✗ Missing: {file}")
missing.append(file)
if missing:
print(f"\n✗ Missing {len(missing)} required files")
return False
else:
print(f"\n✓ All {len(required_files)} required files present")
return True
def test_no_hardcoded_colors():
"""Check for hardcoded colors in view files."""
print("\n=== Checking for Hardcoded Colors ===")
import re
# Pattern to match hex colors
hex_pattern = re.compile(r'#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3}')
rgb_pattern = re.compile(r'rgb\s*\([^)]+\)|rgba\s*\([^)]+\)')
view_files = [
'views/main_window.py',
'views/platform_selector.py',
'views/components/tab_navigation.py',
'views/components/platform_grid_view.py',
'views/components/accounts_overview_view.py',
'views/widgets/platform_button.py',
'views/widgets/account_card.py'
]
files_with_colors = []
for file in view_files:
if not Path(file).exists():
continue
with open(file, 'r') as f:
content = f.read()
# Skip import statements and comments
lines = content.split('\n')
for i, line in enumerate(lines):
# Skip comments and imports
if line.strip().startswith('#') or line.strip().startswith('from') or line.strip().startswith('import'):
continue
# Check for hex colors
if 'setStyleSheet' in line and (hex_pattern.search(line) or rgb_pattern.search(line)):
files_with_colors.append((file, i+1, line.strip()))
if files_with_colors:
print(f"✗ Found {len(files_with_colors)} lines with hardcoded colors:")
for file, line_no, line in files_with_colors[:5]: # Show first 5
print(f" {file}:{line_no}: {line[:60]}...")
return False
else:
print(f"✓ No hardcoded colors found in {len(view_files)} view files")
return True
def test_imports():
"""Test that all imports work correctly."""
print("\n=== Testing Imports ===")
test_imports = [
('themes.theme_config', 'ThemeConfig'),
('themes.qss_generator', 'QSSGenerator'),
('views.base.theme_aware_widget', 'ThemeAwareWidget'),
]
failed = []
for module_name, class_name in test_imports:
try:
module = __import__(module_name, fromlist=[class_name])
if hasattr(module, class_name):
print(f"{module_name}.{class_name}")
else:
print(f"{module_name} missing {class_name}")
failed.append(f"{module_name}.{class_name}")
except ImportError as e:
# Check if it's just PyQt5 missing
if 'PyQt5' in str(e):
print(f"{module_name}.{class_name} (requires PyQt5)")
else:
print(f"{module_name}.{class_name}: {e}")
failed.append(f"{module_name}.{class_name}")
if failed:
print(f"\n{len(failed)} imports failed")
return False
else:
print(f"\n✓ All critical imports successful (PyQt5-dependent modules skipped)")
return True
def main():
"""Run all tests."""
print("=" * 60)
print("THEME SYSTEM COMPREHENSIVE TEST")
print("=" * 60)
results = []
# Run tests
results.append(("File Structure", test_file_structure()))
results.append(("Theme Config", test_theme_config()))
results.append(("QSS Generator", test_qss_generator()))
results.append(("Imports", test_imports()))
results.append(("No Hardcoded Colors", test_no_hardcoded_colors()))
# Summary
print("\n" + "=" * 60)
print("TEST SUMMARY")
print("=" * 60)
passed = sum(1 for _, result in results if result)
total = len(results)
for name, result in results:
status = "✓ PASS" if result else "✗ FAIL"
print(f"{status}: {name}")
print(f"\nTotal: {passed}/{total} tests passed")
if passed == total:
print("\n✅ ALL TESTS PASSED! Theme system is properly configured.")
else:
print(f"\n⚠️ {total - passed} tests failed. Review the output above.")
return 0 if passed == total else 1
if __name__ == "__main__":
sys.exit(main())