#!/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, QMessageBox 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=5) 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_background(key): """Verification en arriere-plan (thread).""" import threading def _bg(): _check_license_online(key) t = threading.Thread(target=_bg, daemon=True) t.start() def _check_license(): """Verifier la licence au lancement.""" cache = _load_license_cache() # Si on a un cache valide, lancer direct et verifier en arriere-plan if cache and cache.get("key"): key = cache["key"] 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: # Cache encore valide — lancer l'app et verifier en background _check_license_background(key) return True # Cache expire — on doit verifier en ligne result, msg = _check_license_online(key) if result is True: return True if result is False: from app.license_dialog import LicenseMessageDialog LicenseMessageDialog("Licence invalide", f"Votre licence a ete refusee :\n{msg}\n\n" "Contactez CopyDev : contact@copydev.fr", "error").exec() return False # Hors-ligne et cache expire from app.license_dialog import LicenseMessageDialog LicenseMessageDialog("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", "error").exec() return False # Pas de cache — demander la cle de licence from app.license_dialog import LicenseDialog, LicenseMessageDialog dlg = LicenseDialog() if dlg.exec() != LicenseDialog.DialogCode.Accepted: return False key = dlg.get_key() if not key: return False result, msg = _check_license_online(key) if result is True: LicenseMessageDialog("Licence activee", f"Bienvenue, {msg} !\nVotre licence est activee.", "success").exec() return True if result is False: LicenseMessageDialog("Licence invalide", f"{msg}\n\nContactez CopyDev : contact@copydev.fr", "error").exec() return False # Hors-ligne a la premiere activation LicenseMessageDialog("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", "error").exec() 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...") QApplication.processEvents() splash.hide() if not _check_license(): sys.exit(1) splash.show() QApplication.processEvents() # Plus de verification de MAJ au demarrage — c'est dans Aide > Rechercher une MAJ splash.set_status("Chargement de l'interface...") from app.main_window import MainWindow window = MainWindow() # Gestion des arguments args = sys.argv[1:] action = None files_to_open = [] i = 0 while i < len(args): if args[i] == "--action" and i + 1 < len(args): action = args[i + 1] i += 2 elif args[i].lower().endswith('.pdf') and os.path.exists(args[i]): files_to_open.append(args[i]) i += 1 else: i += 1 if len(files_to_open) == 1: window._open_pdf(files_to_open[0]) elif len(files_to_open) > 1: from pypdf import PdfWriter import tempfile as tf writer = PdfWriter() for f in files_to_open: writer.append(f) fd, merged = tf.mkstemp(suffix=".pdf") os.close(fd) with open(merged, "wb") as out: writer.write(out) window._open_pdf(merged) window._status_label.setText(f"{len(files_to_open)} fichiers fusionnes") # Actions en ligne de commande if action and files_to_open: from PyQt6.QtCore import QTimer def run_cli_action(): from app.engine.booklet import create_booklet from app.engine.nup import nup_pages from app.engine.unbooklet import unbooklet import tempfile as tf fd, output = tf.mkstemp(suffix=".pdf") os.close(fd) src = files_to_open[0] try: if action == "booklet_a4": create_booklet(src, output, binding="saddle_stitch", sheet_size=(841.89, 595.28)) elif action == "booklet_a3": create_booklet(src, output, binding="saddle_stitch", sheet_size=(1190.55, 841.89)) elif action == "nup_2x2": nup_pages(src, output, cols=2, rows=2, sheet_size=(841.89, 595.28)) elif action == "unbooklet": unbooklet(src, output) else: return window._show_result(output) window._status_label.setText(f"Action '{action}' executee") except Exception as e: QMessageBox.critical(window, "Erreur", str(e)) QTimer.singleShot(500, run_cli_action) window.show() splash.close() sys.exit(app.exec()) if __name__ == "__main__": main()