Initial commit
Dieser Commit ist enthalten in:
424
updates/update_checker.py
Normale Datei
424
updates/update_checker.py
Normale Datei
@ -0,0 +1,424 @@
|
||||
"""
|
||||
Update-Checking-Funktionalität für den Social Media Account Generator.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Any, Optional, Tuple, Union
|
||||
from licensing.api_client import LicenseAPIClient
|
||||
|
||||
logger = logging.getLogger("update_checker")
|
||||
|
||||
class UpdateChecker:
|
||||
"""Klasse zum Überprüfen und Herunterladen von Updates."""
|
||||
|
||||
CONFIG_FILE = os.path.join("config", "app_version.json")
|
||||
|
||||
def __init__(self, api_client: Optional[LicenseAPIClient] = None):
|
||||
"""
|
||||
Initialisiert den UpdateChecker und lädt die Konfiguration.
|
||||
|
||||
Args:
|
||||
api_client: Optional vorkonfigurierter API Client
|
||||
"""
|
||||
self.api_client = api_client or LicenseAPIClient()
|
||||
self.version_info = self.load_version_info()
|
||||
|
||||
# Stelle sicher, dass das Konfigurationsverzeichnis existiert
|
||||
os.makedirs(os.path.dirname(self.CONFIG_FILE), exist_ok=True)
|
||||
|
||||
# Updates-Verzeichnis für Downloads
|
||||
os.makedirs("updates", exist_ok=True)
|
||||
|
||||
def load_version_info(self) -> Dict[str, Any]:
|
||||
"""Lädt die Versionsinformationen aus der Konfigurationsdatei."""
|
||||
if not os.path.exists(self.CONFIG_FILE):
|
||||
default_info = {
|
||||
"current_version": "1.0.0",
|
||||
"last_check": "",
|
||||
"channel": "stable",
|
||||
"auto_check": True,
|
||||
"auto_download": False
|
||||
}
|
||||
|
||||
# Standardwerte speichern
|
||||
try:
|
||||
with open(self.CONFIG_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(default_info, f, indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Speichern der Standardversionsinformationen: {e}")
|
||||
|
||||
return default_info
|
||||
|
||||
try:
|
||||
with open(self.CONFIG_FILE, "r", encoding="utf-8") as f:
|
||||
version_info = json.load(f)
|
||||
|
||||
logger.info(f"Versionsinformationen geladen: {version_info.get('current_version', 'unbekannt')}")
|
||||
|
||||
return version_info
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden der Versionsinformationen: {e}")
|
||||
return {
|
||||
"current_version": "1.0.0",
|
||||
"last_check": "",
|
||||
"channel": "stable",
|
||||
"auto_check": True,
|
||||
"auto_download": False
|
||||
}
|
||||
|
||||
def save_version_info(self) -> bool:
|
||||
"""Speichert die Versionsinformationen in die Konfigurationsdatei."""
|
||||
try:
|
||||
with open(self.CONFIG_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(self.version_info, f, indent=2)
|
||||
|
||||
logger.info("Versionsinformationen gespeichert")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Speichern der Versionsinformationen: {e}")
|
||||
return False
|
||||
|
||||
def compare_versions(self, version1: str, version2: str) -> int:
|
||||
"""
|
||||
Vergleicht zwei Versionsstrings (semver).
|
||||
|
||||
Args:
|
||||
version1: Erste Version
|
||||
version2: Zweite Version
|
||||
|
||||
Returns:
|
||||
-1, wenn version1 < version2
|
||||
0, wenn version1 == version2
|
||||
1, wenn version1 > version2
|
||||
"""
|
||||
v1_parts = [int(part) for part in version1.split(".")]
|
||||
v2_parts = [int(part) for part in version2.split(".")]
|
||||
|
||||
# Fülle fehlende Teile mit Nullen auf
|
||||
while len(v1_parts) < 3:
|
||||
v1_parts.append(0)
|
||||
while len(v2_parts) < 3:
|
||||
v2_parts.append(0)
|
||||
|
||||
# Vergleiche die Teile
|
||||
for i in range(3):
|
||||
if v1_parts[i] < v2_parts[i]:
|
||||
return -1
|
||||
elif v1_parts[i] > v2_parts[i]:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
def check_for_updates(self, license_key: Optional[str] = None, force: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Überprüft, ob Updates verfügbar sind.
|
||||
|
||||
Args:
|
||||
license_key: Lizenzschlüssel für die Update-Prüfung
|
||||
force: Erzwingt eine Überprüfung, auch wenn erst kürzlich geprüft wurde
|
||||
|
||||
Returns:
|
||||
Dictionary mit Update-Informationen
|
||||
"""
|
||||
result = {
|
||||
"has_update": False,
|
||||
"current_version": self.version_info["current_version"],
|
||||
"latest_version": self.version_info["current_version"],
|
||||
"release_date": "",
|
||||
"release_notes": "",
|
||||
"download_url": "",
|
||||
"error": ""
|
||||
}
|
||||
|
||||
# Prüfe, ob seit der letzten Überprüfung genügend Zeit vergangen ist (24 Stunden)
|
||||
if not force and self.version_info.get("last_check"):
|
||||
try:
|
||||
last_check = datetime.fromisoformat(self.version_info["last_check"])
|
||||
now = datetime.now()
|
||||
|
||||
# Wenn weniger als 24 Stunden seit der letzten Überprüfung vergangen sind
|
||||
if (now - last_check).total_seconds() < 86400:
|
||||
logger.info("Update-Überprüfung übersprungen (letzte Überprüfung vor weniger als 24 Stunden)")
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Parsen des letzten Überprüfungsdatums: {e}")
|
||||
|
||||
try:
|
||||
logger.info("Prüfe auf Updates...")
|
||||
|
||||
# Nutze die API für Update-Check
|
||||
if license_key:
|
||||
api_result = self.api_client.check_version(
|
||||
current_version=self.version_info["current_version"],
|
||||
license_key=license_key
|
||||
)
|
||||
else:
|
||||
# Ohne Lizenz nur Latest Version Info
|
||||
api_result = self.api_client.get_latest_version()
|
||||
|
||||
if api_result.get("success"):
|
||||
data = api_result.get("data", {})
|
||||
|
||||
# Für check_version endpoint
|
||||
if "update_available" in data:
|
||||
result["has_update"] = data.get("update_available", False)
|
||||
result["latest_version"] = data.get("latest_version", self.version_info["current_version"])
|
||||
result["download_url"] = data.get("download_url", "")
|
||||
result["release_notes"] = data.get("release_notes", "")
|
||||
# Für get_latest_version endpoint
|
||||
else:
|
||||
latest_version = data.get("version", self.version_info["current_version"])
|
||||
result["latest_version"] = latest_version
|
||||
result["has_update"] = self.compare_versions(self.version_info["current_version"], latest_version) < 0
|
||||
result["release_date"] = data.get("release_date", "")
|
||||
result["release_notes"] = data.get("release_notes", "")
|
||||
result["download_url"] = data.get("download_url", "")
|
||||
|
||||
# Update der letzten Überprüfung
|
||||
self.version_info["last_check"] = datetime.now().isoformat()
|
||||
self.save_version_info()
|
||||
|
||||
if result["has_update"]:
|
||||
logger.info(f"Update verfügbar: {result['latest_version']}")
|
||||
else:
|
||||
logger.info("Keine Updates verfügbar")
|
||||
|
||||
else:
|
||||
error_msg = api_result.get("error", "Fehler bei der Update-Prüfung")
|
||||
logger.error(f"API-Fehler bei Update-Check: {error_msg}")
|
||||
result["error"] = error_msg
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Unerwarteter Fehler bei der Update-Überprüfung: {e}"
|
||||
logger.error(error_msg)
|
||||
result["error"] = error_msg
|
||||
return result
|
||||
|
||||
def download_update(self, download_url: str, version: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Lädt ein Update herunter.
|
||||
|
||||
Args:
|
||||
download_url: URL zum Herunterladen des Updates
|
||||
version: Version des Updates
|
||||
|
||||
Returns:
|
||||
Dictionary mit Download-Informationen
|
||||
"""
|
||||
result = {
|
||||
"success": False,
|
||||
"file_path": "",
|
||||
"version": version,
|
||||
"error": ""
|
||||
}
|
||||
|
||||
try:
|
||||
# Zieldateiname erstellen
|
||||
file_name = f"update_v{version}.zip"
|
||||
file_path = os.path.join("updates", file_name)
|
||||
|
||||
# Simuliere einen Download für Entwicklungszwecke
|
||||
# In der Produktion sollte ein echter Download implementiert werden
|
||||
|
||||
# response = requests.get(download_url, stream=True, timeout=60)
|
||||
# if response.status_code == 200:
|
||||
# with open(file_path, "wb") as f:
|
||||
# shutil.copyfileobj(response.raw, f)
|
||||
|
||||
# Simulierter Download (erstelle eine leere Datei)
|
||||
with open(file_path, "w") as f:
|
||||
f.write(f"Placeholder for version {version} update")
|
||||
|
||||
result["success"] = True
|
||||
result["file_path"] = file_path
|
||||
|
||||
logger.info(f"Update v{version} heruntergeladen: {file_path}")
|
||||
|
||||
return result
|
||||
|
||||
except requests.RequestException as e:
|
||||
error_msg = f"Netzwerkfehler beim Herunterladen des Updates: {e}"
|
||||
logger.error(error_msg)
|
||||
result["error"] = error_msg
|
||||
return result
|
||||
except Exception as e:
|
||||
error_msg = f"Unerwarteter Fehler beim Herunterladen des Updates: {e}"
|
||||
logger.error(error_msg)
|
||||
result["error"] = error_msg
|
||||
return result
|
||||
|
||||
def is_update_available(self) -> bool:
|
||||
"""
|
||||
Überprüft, ob ein Update verfügbar ist.
|
||||
|
||||
Returns:
|
||||
True, wenn ein Update verfügbar ist, sonst False
|
||||
"""
|
||||
update_info = self.check_for_updates()
|
||||
return update_info["has_update"]
|
||||
|
||||
def get_current_version(self) -> str:
|
||||
"""
|
||||
Gibt die aktuelle Version zurück.
|
||||
|
||||
Returns:
|
||||
Aktuelle Version
|
||||
"""
|
||||
return self.version_info["current_version"]
|
||||
|
||||
def set_current_version(self, version: str) -> bool:
|
||||
"""
|
||||
Setzt die aktuelle Version.
|
||||
|
||||
Args:
|
||||
version: Neue Version
|
||||
|
||||
Returns:
|
||||
True bei Erfolg, False im Fehlerfall
|
||||
"""
|
||||
try:
|
||||
self.version_info["current_version"] = version
|
||||
return self.save_version_info()
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Setzen der aktuellen Version: {e}")
|
||||
return False
|
||||
|
||||
def set_update_channel(self, channel: str) -> bool:
|
||||
"""
|
||||
Setzt den Update-Kanal (stable, beta, dev).
|
||||
|
||||
Args:
|
||||
channel: Update-Kanal
|
||||
|
||||
Returns:
|
||||
True bei Erfolg, False im Fehlerfall
|
||||
"""
|
||||
if channel not in ["stable", "beta", "dev"]:
|
||||
logger.warning(f"Ungültiger Update-Kanal: {channel}")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.version_info["channel"] = channel
|
||||
return self.save_version_info()
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Setzen des Update-Kanals: {e}")
|
||||
return False
|
||||
|
||||
def get_update_channel(self) -> str:
|
||||
"""
|
||||
Gibt den aktuellen Update-Kanal zurück.
|
||||
|
||||
Returns:
|
||||
Update-Kanal (stable, beta, dev)
|
||||
"""
|
||||
return self.version_info.get("channel", "stable")
|
||||
|
||||
def set_auto_check(self, auto_check: bool) -> bool:
|
||||
"""
|
||||
Aktiviert oder deaktiviert die automatische Update-Überprüfung.
|
||||
|
||||
Args:
|
||||
auto_check: True, um automatische Updates zu aktivieren, False zum Deaktivieren
|
||||
|
||||
Returns:
|
||||
True bei Erfolg, False im Fehlerfall
|
||||
"""
|
||||
try:
|
||||
self.version_info["auto_check"] = bool(auto_check)
|
||||
return self.save_version_info()
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Setzen der automatischen Update-Überprüfung: {e}")
|
||||
return False
|
||||
|
||||
def is_auto_check_enabled(self) -> bool:
|
||||
"""
|
||||
Überprüft, ob die automatische Update-Überprüfung aktiviert ist.
|
||||
|
||||
Returns:
|
||||
True, wenn die automatische Update-Überprüfung aktiviert ist, sonst False
|
||||
"""
|
||||
return self.version_info.get("auto_check", True)
|
||||
|
||||
def set_auto_download(self, auto_download: bool) -> bool:
|
||||
"""
|
||||
Aktiviert oder deaktiviert den automatischen Download von Updates.
|
||||
|
||||
Args:
|
||||
auto_download: True, um automatische Downloads zu aktivieren, False zum Deaktivieren
|
||||
|
||||
Returns:
|
||||
True bei Erfolg, False im Fehlerfall
|
||||
"""
|
||||
try:
|
||||
self.version_info["auto_download"] = bool(auto_download)
|
||||
return self.save_version_info()
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Setzen des automatischen Downloads: {e}")
|
||||
return False
|
||||
|
||||
def is_auto_download_enabled(self) -> bool:
|
||||
"""
|
||||
Überprüft, ob der automatische Download von Updates aktiviert ist.
|
||||
|
||||
Returns:
|
||||
True, wenn der automatische Download aktiviert ist, sonst False
|
||||
"""
|
||||
return self.version_info.get("auto_download", False)
|
||||
|
||||
def apply_update(self, update_file: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Wendet ein heruntergeladenes Update an.
|
||||
|
||||
Args:
|
||||
update_file: Pfad zur Update-Datei
|
||||
|
||||
Returns:
|
||||
Dictionary mit Informationen über die Anwendung des Updates
|
||||
"""
|
||||
result = {
|
||||
"success": False,
|
||||
"version": "",
|
||||
"error": ""
|
||||
}
|
||||
|
||||
if not os.path.exists(update_file):
|
||||
result["error"] = f"Update-Datei nicht gefunden: {update_file}"
|
||||
logger.error(result["error"])
|
||||
return result
|
||||
|
||||
try:
|
||||
# In der Produktion sollte hier die tatsächliche Update-Logik implementiert werden
|
||||
# 1. Extrahieren des Updates
|
||||
# 2. Sichern der aktuellen Version
|
||||
# 3. Anwenden der Änderungen
|
||||
# 4. Aktualisieren der Versionsinformationen
|
||||
|
||||
# Simuliere ein erfolgreiches Update
|
||||
logger.info(f"Update aus {update_file} erfolgreich angewendet (simuliert)")
|
||||
|
||||
# Extrahiere Version aus dem Dateinamen
|
||||
import re
|
||||
file_name = os.path.basename(update_file)
|
||||
version_match = re.search(r"v([0-9.]+)", file_name)
|
||||
|
||||
if version_match:
|
||||
new_version = version_match.group(1)
|
||||
self.set_current_version(new_version)
|
||||
result["version"] = new_version
|
||||
|
||||
result["success"] = True
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler beim Anwenden des Updates: {e}"
|
||||
logger.error(error_msg)
|
||||
result["error"] = error_msg
|
||||
return result
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren