- Selection multiple dans les vignettes (Ctrl+clic, Maj+clic) - Rotation/suppression sur selection multiple - Impression : os.startfile(print) avec fallback - MAJ deplacee dans Aide > Rechercher une mise a jour (plus au demarrage) - Support arguments CLI : ouvrir un PDF en argument, fusionner si plusieurs - register_windows.bat : association fichier PDF + menu contextuel Windows - Clic droit > CopyDev Impose > Ouvrir / Livret A4 / Livret A3 - Presets manager (base pour macros) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
235 lines
7.2 KiB
Python
235 lines
7.2 KiB
Python
#!/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()
|
|
|
|
# Ouvrir les fichiers passes en argument
|
|
files_to_open = [a for a in sys.argv[1:] if a.lower().endswith('.pdf') and os.path.exists(a)]
|
|
if len(files_to_open) == 1:
|
|
window._open_pdf(files_to_open[0])
|
|
elif len(files_to_open) > 1:
|
|
# Plusieurs fichiers = fusionner
|
|
from pypdf import PdfWriter
|
|
import tempfile
|
|
writer = PdfWriter()
|
|
for f in files_to_open:
|
|
writer.append(f)
|
|
fd, merged = tempfile.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")
|
|
|
|
window.show()
|
|
splash.close()
|
|
sys.exit(app.exec())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|