export_summary.txt wird nicht mehr erstellt
Dieser Commit ist enthalten in:
@ -6,7 +6,10 @@
|
|||||||
"Bash(git -C /mnt/a/GiTea/AccountForger log --oneline)",
|
"Bash(git -C /mnt/a/GiTea/AccountForger log --oneline)",
|
||||||
"Bash(cat:*)",
|
"Bash(cat:*)",
|
||||||
"Bash(sqlite3:*)",
|
"Bash(sqlite3:*)",
|
||||||
"Bash(find:*)"
|
"Bash(find:*)",
|
||||||
|
"Bash(pkill:*)",
|
||||||
|
"Bash(lsof:*)",
|
||||||
|
"Bash(ls:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": [],
|
"ask": [],
|
||||||
|
|||||||
@ -72,6 +72,9 @@ class PlaywrightManager:
|
|||||||
self.context = None
|
self.context = None
|
||||||
self.page = 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
|
# Zähler für Wiederhholungsversuche
|
||||||
self.retry_counter = {}
|
self.retry_counter = {}
|
||||||
|
|
||||||
@ -141,8 +144,25 @@ class PlaywrightManager:
|
|||||||
if self.page is not None:
|
if self.page is not None:
|
||||||
return self.page
|
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
|
# Feature 5: Browser-Instanz Schutz - Nur eine Instanz gleichzeitig
|
||||||
if PlaywrightManager._active_count >= 1:
|
if PlaywrightManager._active_count >= 1:
|
||||||
|
# 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(
|
raise RuntimeError(
|
||||||
"Browser bereits aktiv. Nur eine Browser-Instanz gleichzeitig erlaubt. "
|
"Browser bereits aktiv. Nur eine Browser-Instanz gleichzeitig erlaubt. "
|
||||||
"Beenden Sie den aktuellen Prozess."
|
"Beenden Sie den aktuellen Prozess."
|
||||||
@ -257,6 +277,10 @@ class PlaywrightManager:
|
|||||||
# Event-Listener für Konsolen-Logs
|
# Event-Listener für Konsolen-Logs
|
||||||
self.page.on("console", lambda msg: logger.debug(f"BROWSER CONSOLE: {msg.text}"))
|
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
|
# Feature 5: Browser-Instanz Counter erhöhen
|
||||||
PlaywrightManager._active_count += 1
|
PlaywrightManager._active_count += 1
|
||||||
logger.info(f"Browser gestartet (aktive Instanzen: {PlaywrightManager._active_count})")
|
logger.info(f"Browser gestartet (aktive Instanzen: {PlaywrightManager._active_count})")
|
||||||
@ -967,6 +991,32 @@ class PlaywrightManager:
|
|||||||
self.protection_style = None
|
self.protection_style = None
|
||||||
logger.info("Browser-Schutz entfernt")
|
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):
|
def close(self):
|
||||||
"""Schließt den Browser und gibt Ressourcen frei."""
|
"""Schließt den Browser und gibt Ressourcen frei."""
|
||||||
try:
|
try:
|
||||||
@ -1012,9 +1062,15 @@ class PlaywrightManager:
|
|||||||
self.playwright = None
|
self.playwright = None
|
||||||
|
|
||||||
# Feature 5: Browser-Instanz Counter dekrementieren
|
# Feature 5: Browser-Instanz Counter dekrementieren
|
||||||
|
# (nur wenn nicht bereits durch disconnected-Event dekrementiert)
|
||||||
|
if not self._browser_was_disconnected:
|
||||||
if PlaywrightManager._active_count > 0:
|
if PlaywrightManager._active_count > 0:
|
||||||
PlaywrightManager._active_count -= 1
|
PlaywrightManager._active_count -= 1
|
||||||
logger.info(f"Browser geschlossen (aktive Instanzen: {PlaywrightManager._active_count})")
|
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")
|
logger.info("Browser-Sitzung erfolgreich geschlossen")
|
||||||
|
|
||||||
|
|||||||
@ -105,9 +105,13 @@ class FacebookController(BasePlatformController):
|
|||||||
# Validiere Eingaben
|
# Validiere Eingaben
|
||||||
is_valid, error_msg = self.validate_inputs(params)
|
is_valid, error_msg = self.validate_inputs(params)
|
||||||
if not is_valid:
|
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)
|
self.get_generator_tab().show_error(error_msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
# UI aktualisieren
|
# UI aktualisieren
|
||||||
generator_tab = self.get_generator_tab()
|
generator_tab = self.get_generator_tab()
|
||||||
generator_tab.set_running(True)
|
generator_tab.set_running(True)
|
||||||
@ -180,6 +184,22 @@ class FacebookController(BasePlatformController):
|
|||||||
self.forge_dialog.start_animation()
|
self.forge_dialog.start_animation()
|
||||||
self.forge_dialog.show()
|
self.forge_dialog.show()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Kritischer Fehler VOR Worker-Start → Guard freigeben!
|
||||||
|
logger.error(f"Fehler beim Start der Account-Erstellung: {e}", exc_info=True)
|
||||||
|
|
||||||
|
from utils.process_guard import get_guard
|
||||||
|
get_guard().end(success=False)
|
||||||
|
|
||||||
|
# Dialog schließen falls vorhanden
|
||||||
|
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)
|
||||||
|
generator_tab.show_error(f"Fehler beim Start: {str(e)}")
|
||||||
|
|
||||||
def handle_account_created(self, result: Dict[str, Any]) -> bool:
|
def handle_account_created(self, result: Dict[str, Any]) -> bool:
|
||||||
"""
|
"""
|
||||||
Verarbeitet erfolgreich erstellte Accounts mit Clean Architecture.
|
Verarbeitet erfolgreich erstellte Accounts mit Clean Architecture.
|
||||||
|
|||||||
@ -197,9 +197,13 @@ class InstagramController(BasePlatformController):
|
|||||||
# Validiere Eingaben
|
# Validiere Eingaben
|
||||||
is_valid, error_msg = self.validate_inputs(params)
|
is_valid, error_msg = self.validate_inputs(params)
|
||||||
if not is_valid:
|
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)
|
self.get_generator_tab().show_error(error_msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
# UI aktualisieren
|
# UI aktualisieren
|
||||||
generator_tab = self.get_generator_tab()
|
generator_tab = self.get_generator_tab()
|
||||||
generator_tab.set_running(True)
|
generator_tab.set_running(True)
|
||||||
@ -268,6 +272,22 @@ class InstagramController(BasePlatformController):
|
|||||||
self.forge_dialog.start_animation()
|
self.forge_dialog.start_animation()
|
||||||
self.forge_dialog.show()
|
self.forge_dialog.show()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Kritischer Fehler VOR Worker-Start → Guard freigeben!
|
||||||
|
logger.error(f"Fehler beim Start der Account-Erstellung: {e}", exc_info=True)
|
||||||
|
|
||||||
|
from utils.process_guard import get_guard
|
||||||
|
get_guard().end(success=False)
|
||||||
|
|
||||||
|
# Dialog schließen falls vorhanden
|
||||||
|
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)
|
||||||
|
generator_tab.show_error(f"Fehler beim Start: {str(e)}")
|
||||||
|
|
||||||
def stop_account_creation(self):
|
def stop_account_creation(self):
|
||||||
"""Stoppt die Instagram-Account-Erstellung mit Guard-Freigabe."""
|
"""Stoppt die Instagram-Account-Erstellung mit Guard-Freigabe."""
|
||||||
# Guard-Freigabe (wichtig: VOR Worker-Stop)
|
# Guard-Freigabe (wichtig: VOR Worker-Stop)
|
||||||
|
|||||||
@ -274,40 +274,7 @@ class ProfileExportController:
|
|||||||
logger.error(f"Fehler beim Export von {username}: {e}")
|
logger.error(f"Fehler beim Export von {username}: {e}")
|
||||||
failed_accounts.append(username)
|
failed_accounts.append(username)
|
||||||
|
|
||||||
# 5. Summary-Datei erstellen
|
# 5. Erfolgs-Dialog anzeigen
|
||||||
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
|
|
||||||
show_export_success(
|
show_export_success(
|
||||||
parent_widget,
|
parent_widget,
|
||||||
exported_files,
|
exported_files,
|
||||||
|
|||||||
Binäre Datei nicht angezeigt.
158
run_migration.py
158
run_migration.py
@ -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()
|
|
||||||
@ -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()
|
|
||||||
@ -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)
|
|
||||||
@ -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())
|
|
||||||
In neuem Issue referenzieren
Einen Benutzer sperren