#!/usr/bin/env python3 """Copydev Imposing Tool — Point d'entree.""" import sys import os import json import time import urllib.request import urllib.error import urllib.parse # Support PyInstaller (chemin des resources) if getattr(sys, 'frozen', False): BASE_DIR = os.path.dirname(sys.executable) BUNDLE_DIR = sys._MEIPASS else: BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BUNDLE_DIR = BASE_DIR sys.path.insert(0, BUNDLE_DIR) from PyQt6.QtWidgets import QApplication, QInputDialog, QMessageBox, QLineEdit import hashlib import platform import socket import uuid LICENSE_API = "https://vision.copydev.fr/api/license.php" LICENSE_FILE = os.path.join(BASE_DIR, ".license") def _get_machine_id(): """Generer un identifiant unique pour cette machine.""" raw = "" try: raw += platform.node() raw += str(uuid.getnode()) # MAC address except Exception: raw += "unknown" return hashlib.sha256(raw.encode()).hexdigest()[:32] def _get_hostname(): """Recuperer le nom NetBIOS / hostname.""" try: return platform.node() or socket.gethostname() except Exception: return "inconnu" def _load_license_cache(): """Charger le cache de licence local.""" if not os.path.exists(LICENSE_FILE): return None try: with open(LICENSE_FILE, "r") as f: return json.load(f) except Exception: return None def _save_license_cache(data): """Sauvegarder le cache de licence local.""" try: with open(LICENSE_FILE, "w") as f: json.dump(data, f) except Exception: pass def _check_license_online(key): """Verifier la licence aupres du serveur Vision.""" try: mid = _get_machine_id() hname = _get_hostname() url = f"{LICENSE_API}?key={urllib.parse.quote(key)}&machine_id={urllib.parse.quote(mid)}&hostname={urllib.parse.quote(hname)}" req = urllib.request.Request(url, headers={"User-Agent": "CopydevImposingTool/1.0"}) with urllib.request.urlopen(req, timeout=10) as resp: data = json.loads(resp.read().decode()) if data.get("valid"): cache = { "key": key, "client": data.get("client", ""), "product": data.get("product", ""), "offline_timeout_hours": data.get("offline_timeout_hours", 72), "last_validated": time.time(), } _save_license_cache(cache) return True, data.get("client", "") return False, "Licence invalide" except urllib.error.HTTPError as e: try: body = json.loads(e.read().decode()) return False, body.get("error", "Erreur serveur") except Exception: return False, f"Erreur HTTP {e.code}" except Exception as e: return None, str(e) # None = hors-ligne def _check_license(): """Verifier la licence au lancement.""" cache = _load_license_cache() # Si on a un cache, essayer de valider en ligne if cache and cache.get("key"): key = cache["key"] result, msg = _check_license_online(key) if result is True: # Licence valide en ligne return True if result is False: # Licence refusee par le serveur QMessageBox.critical( None, "Licence invalide", f"Votre licence a ete refusee :\n{msg}\n\n" "Contactez CopyDev : contact@copydev.fr" ) return False # result is None = hors-ligne, verifier le timeout timeout_hours = cache.get("offline_timeout_hours", 72) last_validated = cache.get("last_validated", 0) elapsed_hours = (time.time() - last_validated) / 3600 if elapsed_hours < timeout_hours: remaining = timeout_hours - elapsed_hours return True # Timeout depasse QMessageBox.critical( None, "Licence expiree hors-ligne", f"Impossible de verifier votre licence depuis {int(elapsed_hours)}h.\n" f"Le delai hors-ligne autorise est de {timeout_hours}h.\n\n" "Connectez-vous a internet ou contactez CopyDev :\n" "contact@copydev.fr" ) return False # Pas de cache — demander la cle de licence key, ok = QInputDialog.getText( None, "Copydev Imposing Tool — Activation", "Entrez votre cle de licence :", QLineEdit.EchoMode.Normal, ) if not ok or not key.strip(): return False key = key.strip() result, msg = _check_license_online(key) if result is True: QMessageBox.information( None, "Licence activee", f"Bienvenue, {msg} !\nVotre licence est activee." ) return True if result is False: QMessageBox.critical( None, "Licence invalide", f"{msg}\n\nContactez CopyDev : contact@copydev.fr" ) return False # Hors-ligne a la premiere activation QMessageBox.critical( None, "Connexion requise", "Impossible de contacter le serveur de licences.\n" "Une connexion internet est necessaire pour la premiere activation.\n\n" "Contactez CopyDev : contact@copydev.fr" ) return False def main(): app = QApplication(sys.argv) app.setApplicationName("Copydev Imposing Tool") app.setOrganizationName("Copydev") # Icone application from PyQt6.QtGui import QIcon icon_path = os.path.join(BUNDLE_DIR, "resources", "logo.png") if os.path.exists(icon_path): app.setWindowIcon(QIcon(icon_path)) # Splash screen from app.splash import SplashScreen splash = SplashScreen() splash.show() QApplication.processEvents() splash.set_status("Verification de la licence...") if not _check_license(): sys.exit(1) splash.set_status("Recherche de mises a jour...") # Verification de mise a jour silencieuse from app.updater import check_for_update, VERSION update_available, remote_version = check_for_update() if update_available: reply = QMessageBox.question( None, "Mise a jour disponible", f"Une nouvelle version est disponible : {remote_version}\n" f"(version actuelle : {VERSION})\n\n" "Voulez-vous mettre a jour maintenant ?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: from app.updater import do_update success, msg = do_update() if success: QMessageBox.information(None, "Mise a jour", f"{msg}\n\nL'application va redemarrer.") os.execv(sys.executable, [sys.executable] + sys.argv) else: QMessageBox.warning(None, "Mise a jour", f"Echec de la mise a jour :\n{msg}") splash.set_status("Chargement de l'interface...") from app.main_window import MainWindow window = MainWindow() window.show() splash.close() sys.exit(app.exec()) if __name__ == "__main__": main()