diff --git a/.env.example b/.env.example index 05fd8a2..122e7bd 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,5 @@ -PYTHON_PATH=path_to_python +PYTHON_PATH=python EMAIL_ADDRESS=example@gmail.com EMAIL_PASSWORD=ztgzegzeg EMAIL_SMTP_SERVER=smtp.gmail.com -EMAIL_SMTP_PORT=587 -LICENSE_API_URL=url -PURCHASE_URL=url -LICENSE_API_KEY=key \ No newline at end of file +EMAIL_SMTP_PORT=587 \ No newline at end of file diff --git a/app/core/license_manager.py b/app/core/license_manager.py deleted file mode 100644 index 5effd3d..0000000 --- a/app/core/license_manager.py +++ /dev/null @@ -1,220 +0,0 @@ -import hashlib -import platform -import uuid -import requests -from datetime import datetime -from typing import Dict -from PyQt6.QtCore import QObject, pyqtSignal -import os -from dotenv import load_dotenv -import app.utils.paths as paths - -# Load environment variables -load_dotenv(paths.resource_path(".env")) - -class LicenseManager(QObject): - """Gestionnaire de licences avec liaison matérielle""" - - license_activated = pyqtSignal(str) # Signal émis lors de l'activation - license_expired = pyqtSignal() - - def __init__(self, settings_manager): - super().__init__() - self.settings_manager = settings_manager - self.licensing_enabled = settings_manager.get_config("enable_licensing") - - # Si le système de licence est désactivé, initialiser en mode gratuit - if not self.licensing_enabled: - self.hardware_id = None - self.license_key = "" - self.license_data = None - return - - # Charger l'URL de l'API depuis .env - self.api_url = os.getenv("LICENSE_API_URL") - if not self.api_url: - raise ValueError("LICENSE_API_URL non définie dans le fichier .env") - - self.hardware_id = self._get_hardware_id() - - # Charger la licence sauvegardée - self.license_key = settings_manager.settings.value("license_key", "") - self.license_data = None - - if self.license_key: - self._load_license_data() - - def _get_hardware_id(self) -> str: - """Génère un identifiant unique basé sur le matériel""" - # Combine plusieurs éléments matériels pour un ID unique - mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff) - for elements in range(0, 2*6, 2)][::-1]) - system = platform.system() - machine = platform.machine() - processor = platform.processor() - - # Créer un hash unique - unique_string = f"{mac}{system}{machine}{processor}" - return hashlib.sha256(unique_string.encode()).hexdigest() - - def get_hardware_id(self) -> str: - """Retourne le hardware ID pour l'afficher à l'utilisateur""" - return self.hardware_id - - def activate_license(self, license_key: str) -> Dict: - """ - Active une licence avec le serveur - - Returns: - dict: {"success": bool, "message": str, "data": dict} - """ - try: - response = requests.post( - f"{self.api_url}/activate", - json={ - "license_key": license_key, - "hardware_id": self.hardware_id, - "app_version": self.settings_manager.get_config("app_version"), - "platform": platform.system() - }, - timeout=10 - ) - - data = response.json() - - if response.status_code == 200 and data.get("success"): - # Sauvegarder la licence - self.license_key = license_key - self.license_data = data.get("license_data", {}) - self.settings_manager.settings.setValue("license_key", license_key) - self.settings_manager.settings.setValue("license_data", self.license_data) - - self.license_activated.emit(license_key) - - return { - "success": True, - "message": "Licence activée avec succès", - "data": self.license_data - } - else: - return { - "success": False, - "message": data.get("message", "Erreur d'activation"), - "data": None - } - - except requests.exceptions.RequestException as e: - return { - "success": False, - "message": f"Erreur de connexion au serveur: {str(e)}", - "data": None - } - - def verify_license(self) -> bool: - """Vérifie la validité de la licence avec le serveur""" - if not self.license_key: - return False - - try: - response = requests.post( - f"{self.api_url}/verify", - json={ - "license_key": self.license_key, - "hardware_id": self.hardware_id - }, - timeout=10 - ) - - data = response.json() - - if response.status_code == 200 and data.get("valid"): - self.license_data = data.get("license_data", {}) - self.settings_manager.settings.setValue("license_data", self.license_data) - return True - else: - return False - - except requests.exceptions.RequestException: - # En cas d'erreur réseau, utiliser les données en cache - return self._verify_offline() - - def _verify_offline(self) -> bool: - """Vérification hors ligne basique""" - if not self.license_key or not self.license_data: - return False - - # Vérifier que le hardware_id correspond - if self.license_data.get("hardware_id") != self.hardware_id: - return False - - # Vérifier la date d'expiration si applicable - expires_at = self.license_data.get("expires_at") - if expires_at: - expiry_date = datetime.fromisoformat(expires_at) - if datetime.now() > expiry_date: - self.license_expired.emit() - return False - - return True - - def _load_license_data(self): - """Charge les données de licence depuis les settings""" - self.license_data = self.settings_manager.settings.value("license_data", {}) - - def is_activated(self) -> bool: - """Vérifie si l'application est activée""" - # Si le système de licence est désactivé, toujours retourner False - if not self.licensing_enabled: - return False - - return bool(self.license_key) and self.verify_license() - - def get_license_type(self) -> str: - """Retourne le type de licence (free, premium, etc.)""" - # Si le système de licence est désactivé, retourner "free" - if not self.licensing_enabled: - return None - - if not self.license_data: - return None - return self.license_data.get("type", None) - - def is_feature_available(self, feature_id: str) -> bool: - """ - Vérifie si une fonctionnalité est disponible - - Args: - feature_id: Identifiant de la fonctionnalité (ex: "advanced_export") - """ - # Si le système de licence est désactivé, toutes les fonctionnalités sont disponibles - if not self.licensing_enabled: - return True - - # Si pas de licence, vérifier dans la config des features gratuites - if not self.is_activated(): - free_features = self.settings_manager.get_config("features_by_license").get(None, []) - return feature_id in free_features - - # Vérifier les features autorisées par la licence - license_type = self.get_license_type() - features_by_type = self.settings_manager.get_config("features_by_license") - allowed_features = features_by_type.get(license_type, []) - - return feature_id in allowed_features - - def get_license_info(self) -> Dict: - """Retourne les informations de la licence""" - if not self.license_data: - return { - "type": None, - "status": "inactive", - "expires_at": None - } - - return { - "type": self.get_license_type(), - "status": "active" if self.is_activated() else "inactive", - "expires_at": self.license_data.get("expires_at"), - "email": self.license_data.get("email"), - "activated_at": self.license_data.get("activated_at") - } \ No newline at end of file diff --git a/app/core/main_manager.py b/app/core/main_manager.py index b6f9077..991ed0c 100644 --- a/app/core/main_manager.py +++ b/app/core/main_manager.py @@ -4,7 +4,6 @@ from app.core.theme_manager import ThemeManager from app.core.settings_manager import SettingsManager from app.core.alert_manager import AlertManager from app.core.update_manager import UpdateManager -from app.core.license_manager import LicenseManager from typing import Optional @@ -22,7 +21,6 @@ class MainManager: self.language_manager: LanguageManager = LanguageManager(self.settings_manager) self.alert_manager: AlertManager = AlertManager(self.language_manager, self.theme_manager) self.update_manager: UpdateManager = UpdateManager(self.settings_manager, self.language_manager, self.alert_manager) - self.license_manager: LicenseManager = LicenseManager(self.settings_manager) @classmethod def get_instance(cls) -> 'MainManager': if cls._instance is None: @@ -46,6 +44,3 @@ class MainManager: def get_update_manager(self) -> UpdateManager: return self.update_manager - - def get_license_manager(self) -> LicenseManager: - return self.license_manager \ No newline at end of file diff --git a/app/core/theme_manager.py b/app/core/theme_manager.py index f603464..c5383aa 100644 --- a/app/core/theme_manager.py +++ b/app/core/theme_manager.py @@ -1,6 +1,9 @@ +import json +import os +from typing import Any, Dict, List, Optional + import app.utils.paths as paths -import os, json -from typing import List, Dict, Any, Optional + class Theme: def __init__(self, name: str, colors: Dict[str, str]) -> None: @@ -10,212 +13,43 @@ class Theme: def get_color(self, element: str) -> str: return self.colors.get(element, "#FFFFFF") -class ThemeManager: +class ThemeManager: def __init__(self) -> None: theme_path: str = os.path.join(paths.get_data_dir(), "themes") + base_theme_path: str = os.path.join(theme_path, "base.qss") + + with open(base_theme_path, "r", encoding="utf-8") as theme_file: + self.base_sheet: str = theme_file.read() + self.themes: List[Theme] = [] - for theme_file in os.listdir(theme_path): - if theme_file.endswith(".json"): - with open(os.path.join(theme_path, theme_file), 'r', encoding='utf-8') as f: - theme_data: Dict[str, Any] = json.load(f) - theme: Theme = Theme(theme_data["theme_name"], theme_data["colors"]) - self.themes.append(theme) + for theme_file in sorted(os.listdir(theme_path)): + if not theme_file.endswith(".json"): + continue + + with open(os.path.join(theme_path, theme_file), "r", encoding="utf-8") as file: + theme_data: Dict[str, Any] = json.load(file) + self.themes.append(Theme(theme_data["theme_name"], theme_data["colors"])) + self.current_theme: Theme = self.themes[0] def set_theme(self, theme: str) -> None: if theme != self.current_theme.name: - found_theme: Optional[Theme] = next((t for t in self.themes if t.name == theme), None) + found_theme: Optional[Theme] = next( + (available_theme for available_theme in self.themes if available_theme.name == theme), + None, + ) if found_theme: self.current_theme = found_theme def get_theme(self) -> Theme: return self.current_theme + def get_themes(self) -> List[Theme]: return self.themes + def get_sheet(self) -> str: - return f""" - QWidget {{ - background-color: {self.current_theme.get_color("background_secondary_color")}; - color: {self.current_theme.get_color("text_color")}; - }} - QLabel {{ - background-color: transparent; - color: {self.current_theme.get_color("text_color")}; - font-size: 20px; - }} - QPushButton {{ - background-color: {self.current_theme.get_color("primary_color")}; - color: {self.current_theme.get_color("text_color")}; - border-radius: 8px; - font-size: 16px; - padding: 10px 20px; - border: none; - }} - QPushButton:hover {{ - background-color: {self.current_theme.get_color("primary_hover_color")}; - }} - QProgressBar {{ - border: 1px solid {self.current_theme.get_color("border_color")}; - border-radius: 5px; - background-color: {self.current_theme.get_color("background_secondary_color")}; - text-align: center; - color: {self.current_theme.get_color("text_color")}; - }} - QProgressBar::chunk {{ - background-color: {self.current_theme.get_color("primary_color")}; - border-radius: 3px; - }} - QTextEdit {{ - border: 2px solid {self.current_theme.get_color("border_color")}; - border-radius: 8px; - padding: 5px; - font-size: 14px; - background-color: {self.current_theme.get_color("background_tertiary_color")}; - color: {self.current_theme.get_color("text_color")}; - }} - QLineEdit {{ - border: 2px solid {self.current_theme.get_color("border_color")}; - border-radius: 8px; - padding: 5px; - font-size: 14px; - background-color: {self.current_theme.get_color("background_tertiary_color")}; - color: {self.current_theme.get_color("text_color")}; - }} - QDateEdit {{ - border: 2px solid {self.current_theme.get_color("border_color")}; - padding: 5px; - border-radius: 8px; - font-size: 14px; - min-height: 30px; - }} - QDateEdit:hover {{ - border-color: {self.current_theme.get_color("primary_hover_color")}; - }} - QComboBox {{ - border: 2px solid {self.current_theme.get_color("border_color")}; - padding: 5px; - border-radius: 8px; - font-size: 14px; - min-height: 30px; - }} - QComboBox QAbstractItemView {{ - border-radius: 8px; - padding: 0px; - outline: none; - }} - QComboBox QAbstractItemView::item {{ - padding: 12px 15px; - margin: 0px; - min-height: 20px; - border: 1px solid {self.current_theme.get_color("border_color")}; - border-radius: 8px; - }} - QComboBox QAbstractItemView::item:hover {{ - background-color: {self.current_theme.get_color("background_tertiary_color")}; - color: {self.current_theme.get_color("text_color")}; - }} - QComboBox QAbstractItemView::item:selected {{ - color: {self.current_theme.get_color("text_color")}; - }} - QComboBox::drop-down {{ - border: none; - background: transparent; - }} - QComboBox:hover {{ - border-color: {self.current_theme.get_color("primary_hover_color")}; - }} - - #table_combobox {{ - border: 1px solid {self.current_theme.get_color("border_color")}; - padding: 2px 5px; - border-radius: 4px; - font-size: 12px; - min-height: 20px; - max-height: 28px; - }} - #table_combobox::drop-down {{ - border: none; - background: transparent; - }} - #table_combobox::down-arrow {{ - image: none; - }} - #table_combobox QAbstractItemView {{ - border-radius: 4px; - padding: 0px; - outline: none; - }} - #table_combobox QAbstractItemView::item {{ - padding: 8px 10px; - margin: 0px; - min-height: 16px; - border: 1px solid {self.current_theme.get_color("border_color")}; - border-radius: 4px; - }} - #table_combobox QAbstractItemView::item:hover {{ - background-color: {self.current_theme.get_color("background_tertiary_color")}; - color: {self.current_theme.get_color("text_color")}; - }} - #table_combobox QAbstractItemView::item:selected {{ - color: {self.current_theme.get_color("text_color")}; - }} - - QSlider::groove:horizontal {{ - border: 1px solid {self.current_theme.get_color("primary_color")}; - height: 10px; - background: transparent; - border-radius: 5px; - }} - QSlider::sub-page:horizontal {{ - background: {self.current_theme.get_color("primary_color")}; - border-radius: 5px; - }} - QSlider::add-page:horizontal {{ - background: {self.current_theme.get_color("background_tertiary_color")}; - border-radius: 5px; - }} - QSlider::handle:horizontal {{ - background: white; - border: 2px solid {self.current_theme.get_color("primary_color")}; - width: 14px; - margin: -4px 0; - border-radius: 7px; - }} - QScrollBar:vertical {{ - border: none; - background: {self.current_theme.get_color("background_tertiary_color")}; - width: 8px; - margin: 0px; - }} - QScrollBar::handle:vertical {{ - background: {self.current_theme.get_color("primary_color")}; - border-radius: 4px; - min-height: 20px; - }} - QScrollBar::handle:vertical:hover {{ - background: {self.current_theme.get_color("primary_hover_color")}; - }} - QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{ - border: none; - background: none; - height: 0px; - }} - QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {{ - background: none; - }} - #tab_bar {{ - background-color: {self.current_theme.get_color("background_color")}; - }} - - #icon_label {{ - background: transparent; - border: none; - }} - - #text_label {{ - font-size: 14px; - background: transparent; - border: none; - }} - """ \ No newline at end of file + sheet: str = self.base_sheet + for color_name, color_value in self.current_theme.colors.items(): + sheet = sheet.replace("{{" + color_name + "}}", color_value) + return sheet \ No newline at end of file diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 5886cc9..c55c2b0 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -5,7 +5,6 @@ from app.core.main_manager import MainManager, NotificationType from app.ui.widgets.tabs_widget import TabsWidget, MenuDirection, ButtonPosition, BorderSide, TabSide, TextPosition from app.ui.windows.settings_window import SettingsWindow from app.ui.windows.suggestion_window import SuggestionWindow -from app.ui.windows.activation_window import ActivationWindow import app.utils.paths as paths, shutil from typing import Optional @@ -259,11 +258,6 @@ class MainWindow(QMainWindow): self.settings_window = SettingsWindow(self) self.side_menu.add_widget(self.settings_window, self.language_manager.get_text("tab_settings"), paths.get_asset_svg_path("settings"), position=ButtonPosition.CENTER, text_position=TextPosition.BOTTOM) - # Ajouter la tab d'activation uniquement si le système de licence est activé - if self.settings_manager.get_config("enable_licensing"): - self.activation_window = ActivationWindow(self) - self.side_menu.add_widget(self.activation_window, self.language_manager.get_text("tab_licensing"), paths.get_asset_svg_path("license"), position=ButtonPosition.END, text_position=TextPosition.BOTTOM) - self.setCentralWidget(self.side_menu) def get_tab_widget(self): @@ -277,5 +271,3 @@ class MainWindow(QMainWindow): # Mettre à jour les textes des onglets self.side_menu.update_button_text(0, self.language_manager.get_text("tab_suggestions")) self.side_menu.update_button_text(1, self.language_manager.get_text("tab_settings")) - if self.settings_manager.get_config("enable_licensing"): - self.side_menu.update_button_text(2, self.language_manager.get_text("tab_licensing")) \ No newline at end of file diff --git a/app/ui/windows/activation_window.py b/app/ui/windows/activation_window.py deleted file mode 100644 index 26ba045..0000000 --- a/app/ui/windows/activation_window.py +++ /dev/null @@ -1,241 +0,0 @@ -from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QLineEdit, QPushButton, QFrame, QSizePolicy) -from PyQt6.QtCore import Qt, QThread, pyqtSignal -from app.core.main_manager import MainManager, NotificationType -from app.ui.widgets.loading_bar import LoadingBar -import webbrowser -import os -from dotenv import load_dotenv -import app.utils.paths as paths - -# Load environment variables -load_dotenv(paths.resource_path(".env")) - -class ActivationThread(QThread): - """Thread pour l'activation afin de ne pas bloquer l'UI""" - finished = pyqtSignal(dict) - progress = pyqtSignal(int) - - def __init__(self, license_manager, license_key): - super().__init__() - self.license_manager = license_manager - self.license_key = license_key - - def run(self): - self.progress.emit(30) - result = self.license_manager.activate_license(self.license_key) - self.progress.emit(100) - self.finished.emit(result) - -class ActivationWindow(QWidget): - """Fenêtre d'activation de licence modernisée""" - - def __init__(self, parent=None): - super().__init__(parent) - self.main_manager = MainManager.get_instance() - self.license_manager = self.main_manager.get_license_manager() - self.language_manager = self.main_manager.get_language_manager() - self.theme_manager = self.main_manager.get_theme_manager() - self.alert_manager = self.main_manager.get_alert_manager() - self.observer_manager = self.main_manager.get_observer_manager() - self.settings_manager = self.main_manager.get_settings_manager() - - self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language) - self.observer_manager.subscribe(NotificationType.THEME, self.update_theme) - - self.activation_thread = None - - self.setup_ui() - self.update_license_status() - - def setup_ui(self): - layout = QVBoxLayout(self) - layout.setSpacing(20) - layout.setContentsMargins(20, 20, 20, 20) - layout.setAlignment(Qt.AlignmentFlag.AlignTop) - - # === Section titre === - self.title_label = QLabel(self.language_manager.get_text("activate_license")) - self.title_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) - self.title_label.setWordWrap(True) - layout.addWidget(self.title_label) - - # === Spacer flexible === - layout.addStretch(1) - - # === Section clé de licence === - key_layout = QHBoxLayout() - - self.key_title = QLabel(self.language_manager.get_text("license_key_section")) - self.key_title.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) - key_layout.addWidget(self.key_title) - - self.key_input = QLineEdit() - self.key_input.setPlaceholderText("XXXX-XXXX-XXXX-XXXX-XXXX") - self.key_input.textChanged.connect(self.format_license_key) - self.key_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - key_layout.addWidget(self.key_input) - - layout.addLayout(key_layout) - # === Barre de progression === - self.progress_bar = LoadingBar(self.language_manager.get_text("loading")) - self.progress_bar.setVisible(False) - self.progress_bar.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) - layout.addWidget(self.progress_bar) - - # === Spacer flexible === - layout.addStretch(1) - - # === Section statut === - self.status_label = QLabel() - self.status_label.setWordWrap(True) - self.status_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) - layout.addWidget(self.status_label) - - # === Spacer flexible === - layout.addStretch(1) - - # === Boutons d'action === - button_layout = QHBoxLayout() - button_layout.setSpacing(10) - - self.activate_btn = QPushButton(self.language_manager.get_text("activate")) - self.activate_btn.clicked.connect(self.activate_license) - self.activate_btn.setDefault(True) - self.activate_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - button_layout.addWidget(self.activate_btn) - - self.buy_btn = QPushButton(self.language_manager.get_text("buy_license")) - self.buy_btn.clicked.connect(self.open_purchase_page) - self.buy_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - button_layout.addWidget(self.buy_btn) - - self.compare_btn = QPushButton(self.language_manager.get_text("compare_versions")) - self.compare_btn.clicked.connect(self.show_features_comparison) - self.compare_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - button_layout.addWidget(self.compare_btn) - - layout.addLayout(button_layout) - - def show_features_comparison(self): - """Affiche la comparaison des versions dans une alerte""" - comparison_text = f"{self.language_manager.get_text('comparaisons')}\n\n" - - for version in self.settings_manager.get_config("features_by_license").keys(): - comparison_text += f"{self.language_manager.get_text(version+'_version')}:\n" - features = self.settings_manager.get_config("features_by_license").get(version, []) - for feature in features: - feature_desc = self.language_manager.get_text(feature) - comparison_text += f" • {feature_desc}\n" - comparison_text += "\n" - - - self.alert_manager.show_info(comparison_text, parent=self) - - def update_license_status(self): - """Met à jour l'affichage du statut de la licence""" - if self.license_manager.is_activated(): - license_info = self.license_manager.get_license_info() - license_type = license_info.get("type", "free").upper() - - status_text = f"✓ {self.language_manager.get_text('license_active')}\n" - status_text += f"{self.language_manager.get_text('license_type')}: {license_type}\n" - if license_info.get("email"): - status_text += f"{self.language_manager.get_text('license_email')}: {license_info['email']}\n" - if license_info.get("expires_at"): - status_text += f"{self.language_manager.get_text('license_expires')}: {license_info['expires_at']}" - - self.status_label.setText(status_text) - - self.key_input.setEnabled(False) - self.activate_btn.setEnabled(False) - else: - status_text = f"{self.language_manager.get_text('no_license')}" - self.status_label.setText(status_text) - - def format_license_key(self, text): - """Formate automatiquement la clé de licence (XXXX-XXXX-...)""" - text = text.replace("-", "").upper() - formatted = "-".join([text[i:i+4] for i in range(0, len(text), 4)]) - if len(formatted) > 24: - formatted = formatted[:24] - self.key_input.blockSignals(True) - self.key_input.setText(formatted) - self.key_input.blockSignals(False) - self.key_input.setCursorPosition(len(formatted)) - - def activate_license(self): - """Lance l'activation de la licence""" - license_key = self.key_input.text().replace("-", "") - - if len(license_key) < 16: - self.alert_manager.show_error("invalid_license_key") - return - - # Disable inputs during activation - self.activate_btn.setEnabled(False) - self.key_input.setEnabled(False) - self.buy_btn.setEnabled(False) - - # Show progress bar with initial message - self.progress_bar.set_label(self.language_manager.get_text("loading")) - self.progress_bar.set_progress(0) - self.progress_bar.setVisible(True) - - # Start activation thread - self.activation_thread = ActivationThread(self.license_manager, license_key) - self.activation_thread.finished.connect(self.on_activation_finished) - self.activation_thread.progress.connect(self.on_activation_progress) - self.activation_thread.start() - - def on_activation_progress(self, value): - """Update progress bar during activation""" - self.progress_bar.set_progress(value) - - def on_activation_finished(self, result): - """Callback quand l'activation est terminée""" - # Hide progress bar - self.progress_bar.setVisible(False) - - # Re-enable inputs - self.activate_btn.setEnabled(True) - self.key_input.setEnabled(True) - self.buy_btn.setEnabled(True) - - if result["success"]: - # Show success message using AlertManager - success_msg = result['message'] - if result.get("data"): - success_msg += f"\n\n{self.language_manager.get_text('license_type')}: {result['data'].get('type', 'N/A')}" - success_msg += f"\n{self.language_manager.get_text('license_email')}: {result['data'].get('email', 'N/A')}" - - self.alert_manager.show_info(success_msg, parent=self) - self.update_license_status() - - # Clear the input field - self.key_input.clear() - else: - # Show error message using AlertManager - self.alert_manager.show_info(f"✗ {result['message']}", parent=self) - - def open_purchase_page(self): - """Ouvre la page d'achat dans le navigateur""" - purchase_url = os.getenv("PURCHASE_URL") - if purchase_url: - webbrowser.open(purchase_url) - else: - self.alert_manager.show_error("PURCHASE_URL non définie dans .env", parent=self) - - def update_language(self): - """Met à jour tous les textes selon la nouvelle langue""" - self.title_label.setText(self.language_manager.get_text("activate_license")) - self.activate_btn.setText(self.language_manager.get_text("activate")) - self.buy_btn.setText(self.language_manager.get_text("buy_license")) - self.compare_btn.setText(self.language_manager.get_text("compare_versions")) - self.progress_bar.set_label(self.language_manager.get_text("loading")) - self.key_title.setText(self.language_manager.get_text("license_key_section")) - self.update_license_status() - - def update_theme(self): - """Met à jour le style selon le nouveau thème""" - self.update_license_status() \ No newline at end of file diff --git a/app/ui/windows/suggestion_window.py b/app/ui/windows/suggestion_window.py index 1852c0c..5b7c668 100644 --- a/app/ui/windows/suggestion_window.py +++ b/app/ui/windows/suggestion_window.py @@ -62,7 +62,6 @@ class SuggestionWindow(QWidget): self.language_manager = self.main_manager.get_language_manager() self.settings_manager = self.main_manager.get_settings_manager() self.alert_manager = self.main_manager.get_alert_manager() - self.license_manager = self.main_manager.get_license_manager() self.observer_manager = self.main_manager.get_observer_manager() self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language) @@ -114,11 +113,7 @@ class SuggestionWindow(QWidget): content = self.settings_manager.get_config('app_name').replace(' ', '_') - # Ajouter le préfixe "PRIORITAIRE" uniquement si le système de licence est activé ET que l'utilisateur a le support prioritaire - if self.settings_manager.get_config("enable_licensing") and self.license_manager.is_feature_available("priority_support"): - subject = "PRIORITAIRE - "+content - else: - subject = "Suggestion pour "+content + subject = "Suggestion pour "+content # Create and start email sender thread self.email_sender = EmailSender(subject, message) diff --git a/app/utils/licence.py b/app/utils/licence.py deleted file mode 100644 index 88623eb..0000000 --- a/app/utils/licence.py +++ /dev/null @@ -1,66 +0,0 @@ -from functools import wraps -from PyQt6.QtWidgets import QMessageBox -from app.core.main_manager import MainManager - -def require_license(feature_id: str, show_upgrade_dialog: bool = True): - """ - Décorateur pour protéger une fonctionnalité premium - - Usage: - @require_license("advanced_export") - def export_to_excel(self): - # Code de la fonctionnalité premium - """ - def decorator(func): - @wraps(func) - def wrapper(*args, **kwargs): - main_manager = MainManager.get_instance() - license_manager = main_manager.get_license_manager() - settings_manager = main_manager.get_settings_manager() - - # Si le système de licence est désactivé, autoriser toutes les fonctionnalités - if not settings_manager.get_config("enable_licensing"): - return func(*args, **kwargs) - - if not license_manager.is_feature_available(feature_id): - if show_upgrade_dialog: - show_upgrade_message(feature_id) - return None - - return func(*args, **kwargs) - return wrapper - return decorator - -def show_upgrade_message(feature_id: str): - """Affiche un message invitant à acheter la version premium""" - main_manager = MainManager.get_instance() - language_manager = main_manager.get_language_manager() - settings_manager = main_manager.get_settings_manager() - - # Si le système de licence est désactivé, ne rien afficher - if not settings_manager.get_config("enable_licensing"): - return - - feature_name = settings_manager.get_config("feature_descriptions", {}).get( - feature_id, feature_id - ) - - msg = QMessageBox() - msg.setIcon(QMessageBox.Icon.Information) - msg.setWindowTitle(language_manager.get_text("premium_feature")) - msg.setText(language_manager.get_text("premium_feature_message").format(feature=feature_name)) - msg.setInformativeText(language_manager.get_text("upgrade_prompt")) - - buy_btn = msg.addButton(language_manager.get_text("buy_now"), QMessageBox.ButtonRole.AcceptRole) - activate_btn = msg.addButton(language_manager.get_text("activate_license"), QMessageBox.ButtonRole.ActionRole) - msg.addButton(language_manager.get_text("cancel"), QMessageBox.ButtonRole.RejectRole) - - msg.exec() - - if msg.clickedButton() == buy_btn: - import webbrowser - webbrowser.open(settings_manager.get_config("purchase_url")) - elif msg.clickedButton() == activate_btn: - from app.ui.windows.activation_window import ActivationWindow - activation_window = ActivationWindow() - activation_window.exec() \ No newline at end of file diff --git a/config.json b/config.json index 95719dc..2735aaa 100644 --- a/config.json +++ b/config.json @@ -6,18 +6,5 @@ "icon_path": "data/assets/icon.ico", "splash_image": "splash", "main_script": "main.py", - "git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate", - "enable_licensing": true, - "features_by_license": { - "basic": [ - "support" - ], - "premium": [ - "priority_support" - ], - "enterprise": [ - "priority_support", - "unlimited_key_uses" - ] - } + "git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate" } \ No newline at end of file diff --git a/BUILD.spec b/config/BUILD.spec similarity index 85% rename from BUILD.spec rename to config/BUILD.spec index bf1dc03..ceac592 100644 --- a/BUILD.spec +++ b/config/BUILD.spec @@ -5,8 +5,12 @@ import os from pathlib import Path from os import getenv +# --- Resolve paths relative to this spec file --- +CONFIG_DIR = Path(SPECPATH).resolve() +PROJECT_DIR = CONFIG_DIR.parent + # --- Load config.json --- -config_path = Path("../config.json") +config_path = PROJECT_DIR / "config.json" with config_path.open("r", encoding="utf-8") as f: config = json.load(f) @@ -22,16 +26,16 @@ icon = getenv("ICON_PATH", "") # --- Data files to bundle --- datas = [ - ("data/assets/*", "data/assets/"), - ("data/", "data/"), - ("config.json", "."), - (".env", "."), + (str(PROJECT_DIR / "data/assets/*"), "data/assets/"), + (str(PROJECT_DIR / "data/"), "data/"), + (str(PROJECT_DIR / "config.json"), "."), + (str(PROJECT_DIR / ".env"), "."), ] binaries = [] # --- Analysis --- a = Analysis( - ["main.py"], + [str(PROJECT_DIR / "main.py")], pathex=[], binaries=binaries, datas=datas, diff --git a/requirements.txt b/config/requirements.txt similarity index 68% rename from requirements.txt rename to config/requirements.txt index 390d828..f9c493f 100644 --- a/requirements.txt +++ b/config/requirements.txt @@ -1,4 +1,5 @@ PyQt6 pyinstaller python-dotenv -requests \ No newline at end of file +requests +dotenv \ No newline at end of file diff --git a/data/assets/license.svg b/data/assets/license.svg deleted file mode 100644 index d0241b2..0000000 --- a/data/assets/license.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/data/lang/en.json b/data/lang/en.json index 11979a1..9dfe005 100644 --- a/data/lang/en.json +++ b/data/lang/en.json @@ -30,38 +30,10 @@ "update_details": "Update Details", "update_aborted": "Update aborted by user", "loading": "Loading...", - "verifying_license": "Verifying license...", "checking_updates": "Checking for updates...", "initializing": "Initializing interface...", "loading_complete": "Loading complete", - "activate_license": "Activate your license to unlock all features", - "hardware_id_info": "This unique identifier is tied to your hardware. Share it with support if needed.", - "hardware_id_copied": "Hardware ID copied to clipboard!", - "license_key_section": "License Key :", - "enter_license_key": "Enter your license key", - "activate": "Activate", - "buy_license": "Buy License", - "comparaisons": "Version Comparison", - "basic_version": "Basic Version", - "premium_version": "Premium Version", - "enterprise_version": "Enterprise Version", - "support": "Support", - "priority_support": "Priority support", - "unlimited_key_uses": "Unlimited key uses", - "license_active": "License active", - "license_type": "Type", - "license_email": "Email", - "license_expires": "Expires on", - "license_basic_mode": "Basic mode - Activate a license for more features", - "invalid_license_key": "Invalid license key. It must contain at least 16 characters.", - "premium_feature_message": "The feature '{feature}' requires a Premium or Enterprise license.", - "upgrade_prompt": "Would you like to upgrade your license?", - "buy_now": "Buy Now", "cancel": "Cancel", - "activation_required": "Activation is required to continue.", - "compare_versions": "Compare Versions", - "no_license": "No License", "tab_suggestions": "Suggestions", - "tab_settings": "Settings", - "tab_licensing": "Licensing" + "tab_settings": "Settings" } \ No newline at end of file diff --git a/data/lang/fr.json b/data/lang/fr.json index a985795..fb710d2 100644 --- a/data/lang/fr.json +++ b/data/lang/fr.json @@ -30,39 +30,10 @@ "update_details": "Détails de la mise à jour", "update_aborted": "Mise à jour annulée par l'utilisateur", "loading": "Chargement...", - "verifying_license": "Vérification de la licence...", "checking_updates": "Vérification des mises à jour...", "initializing": "Initialisation de l'interface...", "loading_complete": "Chargement terminé", - "activate_license": "Activez votre licence pour débloquer toutes les fonctionnalités", - "hardware_id_info": "Cet identifiant unique est lié à votre matériel. Partagez-le avec le support si nécessaire.", - "hardware_id_copied": "ID matériel copié dans le presse-papier !", - "license_key_section": "Clé de licence :", - "enter_license_key": "Entrez votre clé de licence", - "activate": "Activer", - "buy_license": "Acheter une licence", - "comparaisons": "Comparaison des versions", - "basic_version": "Version Basique", - "premium_version": "Version Premium", - "enterprise_version": "Version Enterprise", - "support": "Support", - "priority_support": "Support prioritaire", - "unlimited_key_uses": "Utilisations de clé illimitées", - "license_active": "Licence active", - "license_type": "Type", - "license_email": "Email", - "license_expires": "Expire le", - "license_basic_mode": "Mode basique - Activez une licence pour plus de fonctionnalités", - "invalid_license_key": "Clé de licence invalide. Elle doit contenir au moins 16 caractères.", - "premium_feature": "Fonctionnalité Premium", - "premium_feature_message": "La fonctionnalité '{feature}' nécessite une licence Premium ou Enterprise.", - "upgrade_prompt": "Souhaitez-vous mettre à niveau votre licence ?", - "buy_now": "Acheter maintenant", "cancel": "Annuler", - "activation_required": "L'activation est requise pour continuer.", - "compare_versions": "Comparer les versions", - "no_license": "Pas de licence", "tab_suggestions": "Suggestions", - "tab_settings": "Paramètres", - "tab_licensing": "Licence" + "tab_settings": "Paramètres" } \ No newline at end of file diff --git a/data/themes/base.qss b/data/themes/base.qss new file mode 100644 index 0000000..3f191be --- /dev/null +++ b/data/themes/base.qss @@ -0,0 +1,183 @@ +QWidget { + background-color: {{background_secondary_color}}; + color: {{text_color}}; +} +QLabel { + background-color: transparent; + color: {{text_color}}; + font-size: 20px; +} +QPushButton { + background-color: {{primary_color}}; + color: {{text_color}}; + border-radius: 8px; + font-size: 16px; + padding: 10px 20px; + border: none; +} +QPushButton:hover { + background-color: {{primary_hover_color}}; +} +QProgressBar { + border: 1px solid {{border_color}}; + border-radius: 5px; + background-color: {{background_secondary_color}}; + text-align: center; + color: {{text_color}}; +} +QProgressBar::chunk { + background-color: {{primary_color}}; + border-radius: 3px; +} +QTextEdit { + border: 2px solid {{border_color}}; + border-radius: 8px; + padding: 5px; + font-size: 14px; + background-color: {{background_tertiary_color}}; + color: {{text_color}}; +} +QLineEdit { + border: 2px solid {{border_color}}; + border-radius: 8px; + padding: 5px; + font-size: 14px; + background-color: {{background_tertiary_color}}; + color: {{text_color}}; +} +QDateEdit { + border: 2px solid {{border_color}}; + padding: 5px; + border-radius: 8px; + font-size: 14px; + min-height: 30px; +} +QDateEdit:hover { + border-color: {{primary_hover_color}}; +} +QComboBox { + border: 2px solid {{border_color}}; + padding: 5px; + border-radius: 8px; + font-size: 14px; + min-height: 30px; +} +QComboBox QAbstractItemView { + border-radius: 8px; + padding: 0px; + outline: none; +} +QComboBox QAbstractItemView::item { + padding: 12px 15px; + margin: 0px; + min-height: 20px; + border: 1px solid {{border_color}}; + border-radius: 8px; +} +QComboBox QAbstractItemView::item:hover { + background-color: {{background_tertiary_color}}; + color: {{text_color}}; +} +QComboBox QAbstractItemView::item:selected { + color: {{text_color}}; +} +QComboBox::drop-down { + border: none; + background: transparent; +} +QComboBox:hover { + border-color: {{primary_hover_color}}; +} + +#table_combobox { + border: 1px solid {{border_color}}; + padding: 2px 5px; + border-radius: 4px; + font-size: 12px; + min-height: 20px; + max-height: 28px; +} +#table_combobox::drop-down { + border: none; + background: transparent; +} +#table_combobox::down-arrow { + image: none; +} +#table_combobox QAbstractItemView { + border-radius: 4px; + padding: 0px; + outline: none; +} +#table_combobox QAbstractItemView::item { + padding: 8px 10px; + margin: 0px; + min-height: 16px; + border: 1px solid {{border_color}}; + border-radius: 4px; +} +#table_combobox QAbstractItemView::item:hover { + background-color: {{background_tertiary_color}}; + color: {{text_color}}; +} +#table_combobox QAbstractItemView::item:selected { + color: {{text_color}}; +} + +QSlider::groove:horizontal { + border: 1px solid {{primary_color}}; + height: 10px; + background: transparent; + border-radius: 5px; +} +QSlider::sub-page:horizontal { + background: {{primary_color}}; + border-radius: 5px; +} +QSlider::add-page:horizontal { + background: {{background_tertiary_color}}; + border-radius: 5px; +} +QSlider::handle:horizontal { + background: white; + border: 2px solid {{primary_color}}; + width: 14px; + margin: -4px 0; + border-radius: 7px; +} +QScrollBar:vertical { + border: none; + background: {{background_tertiary_color}}; + width: 8px; + margin: 0px; +} +QScrollBar::handle:vertical { + background: {{primary_color}}; + border-radius: 4px; + min-height: 20px; +} +QScrollBar::handle:vertical:hover { + background: {{primary_hover_color}}; +} +QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + border: none; + background: none; + height: 0px; +} +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; +} +#tab_bar { + background-color: {{background_color}}; +} + +#icon_label { + background: transparent; + border: none; +} + +#text_label { + font-size: 14px; + background: transparent; + border: none; +} diff --git a/main.py b/main.py index 7d9f402..9850de5 100644 --- a/main.py +++ b/main.py @@ -25,13 +25,6 @@ def preload_application(progress_callback, splash=None): main_manager = MainManager.get_instance() language_manager = main_manager.get_language_manager() update_manager = main_manager.get_update_manager() - license_manager = main_manager.get_license_manager() - settings_manager = main_manager.get_settings_manager() - - # Vérifier la licence uniquement si le système est activé - if settings_manager.get_config("enable_licensing"): - progress_callback(language_manager.get_text("verifying_license")) - license_manager.verify_license() progress_callback(language_manager.get_text("checking_updates")) diff --git a/tools/build.bat b/tools/build.bat index 05c661b..0687be8 100644 --- a/tools/build.bat +++ b/tools/build.bat @@ -3,15 +3,10 @@ setlocal enabledelayedexpansion REM === PATH SETUP === set PARENT_DIR=%~dp0.. +set CONFIG_DIR=%PARENT_DIR%\config set CONFIG_FILE=%PARENT_DIR%\config.json -set ICON_FILE=%PARENT_DIR%\data\assets\icon.png set ENV_FILE=%PARENT_DIR%\.env - -REM Check if .env file exists -if not exist "%ENV_FILE%" ( - echo [ERROR] .env file not found. Please copy .env.example to .env and configure it. - exit /b 1 -) +set ICON_FILE=%PARENT_DIR%\data\assets\icon.png REM === Extract values from config.json === for /f "delims=" %%i in ('powershell -NoProfile -Command ^ @@ -21,18 +16,22 @@ for /f "delims=" %%i in ('powershell -NoProfile -Command ^ for /f "delims=" %%i in ('powershell -NoProfile -Command ^ "Get-Content '%CONFIG_FILE%' | ConvertFrom-Json | Select-Object -ExpandProperty architecture"') do set ARCHITECTURE=%%i -REM === Extract python path from .env file === -for /f "usebackq tokens=2 delims==" %%i in (`findstr "PYTHON_PATH" "%ENV_FILE%"`) do set SYSTEM_PYTHON=%%i +REM === Use the Python interpreter provided by the environment === +set SYSTEM_PYTHON=python +if exist "%ENV_FILE%" ( + for /f "usebackq tokens=1,* delims==" %%A in ("%ENV_FILE%") do if /I "%%A"=="PYTHON_PATH" set "SYSTEM_PYTHON=%%B" +) -set VENV_PATH=%PARENT_DIR%\WINenv_%ARCHITECTURE% +set VENV_PATH=%CONFIG_DIR%\WINenv_%ARCHITECTURE% set EXE_NAME=%APP_NAME%.exe set PYTHON_IN_VENV=%VENV_PATH%\Scripts\python.exe set BUILD_DIR=%PARENT_DIR%\build set ZIP_FILE=%BUILD_DIR%\%APP_NAME%.zip REM === Verify Python existence === -if not exist "%SYSTEM_PYTHON%" ( - echo [ERROR] Python not found at: %SYSTEM_PYTHON% +"%SYSTEM_PYTHON%" --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python interpreter not found in PATH. exit /b 1 ) @@ -41,7 +40,7 @@ if not exist "%VENV_PATH%\Scripts\activate.bat" ( echo [INFO] Virtual environment not found. Creating... "%SYSTEM_PYTHON%" -m venv "%VENV_PATH%" "%PYTHON_IN_VENV%" -m pip install --upgrade pip - "%PYTHON_IN_VENV%" -m pip install -r "%PARENT_DIR%\requirements.txt" + "%PYTHON_IN_VENV%" -m pip install -r "%CONFIG_DIR%\requirements.txt" ) else ( echo [INFO] Virtual environment found. ) @@ -51,41 +50,9 @@ REM === Run PyInstaller === --distpath "%BUILD_DIR%" ^ --workpath "%BUILD_DIR%\dist" ^ --clean ^ - "%PARENT_DIR%\BUILD.spec" + "%CONFIG_DIR%\BUILD.spec" REM === Clean build cache === rmdir /s /q "%BUILD_DIR%\dist" -REM === Create ZIP === -echo [INFO] Creating ZIP archive... - -set TEMP_ZIP_DIR=%BUILD_DIR%\temp_zip - -REM Remove old temp dir if exists -if exist "%TEMP_ZIP_DIR%" rmdir /s /q "%TEMP_ZIP_DIR%" -mkdir "%TEMP_ZIP_DIR%" - -REM Copy compiled app - tout le contenu du build sauf les ZIP existants -move /Y "%BUILD_DIR%\%EXE_NAME%" "%TEMP_ZIP_DIR%\" - -REM Copy config.json -copy "%CONFIG_FILE%" "%TEMP_ZIP_DIR%\" /Y - -REM Copy icon.png -copy "%ICON_FILE%" "%TEMP_ZIP_DIR%\" /Y - -REM Copy data/lang -xcopy /E /I /Y "%PARENT_DIR%\data\lang" "%TEMP_ZIP_DIR%\lang" - -REM Remove old ZIP if exists -if exist "%ZIP_FILE%" del "%ZIP_FILE%" - -REM Create ZIP -powershell -NoProfile -Command "Add-Type -AssemblyName 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::CreateFromDirectory('%TEMP_ZIP_DIR%', '%ZIP_FILE%')" - -REM Remove temp folder -rmdir /s /q "%TEMP_ZIP_DIR%" - -echo [INFO] ZIP created at: %ZIP_FILE% - endlocal diff --git a/tools/build.command b/tools/build.command index 7d73185..8f76d90 100644 --- a/tools/build.command +++ b/tools/build.command @@ -4,14 +4,14 @@ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PARENT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +CONFIG_DIR="$PARENT_DIR/config" CONFIG_FILE="$PARENT_DIR/config.json" ICON_FILE="$PARENT_DIR/data/assets/icon.png" -ENV_FILE="$PARENT_DIR/.env" -# === Check .env === -if [ ! -f "$ENV_FILE" ]; then - echo "[ERROR] .env file not found. Please copy .env.example to .env and configure it." - exit 1 +if [ -f "$PARENT_DIR/.env" ]; then + set -a + . "$PARENT_DIR/.env" + set +a fi # === Extract values from config.json === @@ -19,18 +19,18 @@ ICON_PATH=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['icon APP_NAME=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['app_name'])") ARCHITECTURE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['architecture'])") -# === Extract PYTHON_PATH from .env === -SYSTEM_PYTHON=$(grep '^PYTHON_PATH=' "$ENV_FILE" | cut -d '=' -f2-) +# === Use the Python interpreter provided by the environment === +SYSTEM_PYTHON="${PYTHON_PATH:-python3}" -VENV_PATH="$PARENT_DIR/MACenv_$ARCHITECTURE" +VENV_PATH="$CONFIG_DIR/MACenv_$ARCHITECTURE" APP_FILE="$APP_NAME.app" PYTHON_IN_VENV="$VENV_PATH/bin/python" BUILD_DIR="$PARENT_DIR/build" ZIP_FILE="$BUILD_DIR/$APP_NAME.zip" # === Verify Python existence === -if [ ! -x "$SYSTEM_PYTHON" ]; then - echo "[ERROR] Python not found at: $SYSTEM_PYTHON" +if ! command -v "$SYSTEM_PYTHON" >/dev/null 2>&1; then + echo "[ERROR] Python interpreter not found in PATH." exit 1 fi @@ -39,7 +39,7 @@ if [ ! -f "$VENV_PATH/bin/activate" ]; then echo "[INFO] Virtual environment not found. Creating..." "$SYSTEM_PYTHON" -m venv "$VENV_PATH" "$PYTHON_IN_VENV" -m pip install --upgrade pip - "$PYTHON_IN_VENV" -m pip install -r "$PARENT_DIR/requirements.txt" + "$PYTHON_IN_VENV" -m pip install -r "$CONFIG_DIR/requirements.txt" else echo "[INFO] Virtual environment found." fi @@ -49,7 +49,7 @@ fi --distpath "$BUILD_DIR" \ --workpath "$BUILD_DIR/dist" \ --clean \ - "$PARENT_DIR/BUILD.spec" + "$CONFIG_DIR/BUILD.spec" # === Clean build cache === rm -rf "$BUILD_DIR/dist" diff --git a/tools/open.bat b/tools/open.bat index a6c4891..34530e6 100644 --- a/tools/open.bat +++ b/tools/open.bat @@ -3,30 +3,29 @@ setlocal enabledelayedexpansion REM Set file paths set ROOT_DIR=%~dp0.. +set CONFIG_DIR=%ROOT_DIR%\config set CONFIG_FILE=%ROOT_DIR%\config.json -set REQUIREMENTS=%ROOT_DIR%\requirements.txt +set REQUIREMENTS=%CONFIG_DIR%\requirements.txt set ENV_FILE=%ROOT_DIR%\.env -REM Check if .env file exists -if not exist "%ENV_FILE%" ( - echo [ERROR] .env file not found. Please copy .env.example to .env and configure it. - exit /b 1 -) - REM Extract config.json fields using PowerShell for /f "delims=" %%i in ('powershell -NoProfile -Command ^ "Get-Content '%CONFIG_FILE%' | ConvertFrom-Json | Select-Object -ExpandProperty architecture"') do set ARCHITECTURE=%%i -REM Extract python path from .env file -for /f "usebackq tokens=2 delims==" %%i in (`findstr "PYTHON_PATH" "%ENV_FILE%"`) do set PYTHON_EXEC=%%i +REM Use the Python interpreter provided by the environment +set PYTHON_EXEC=python +if exist "%ENV_FILE%" ( + for /f "usebackq tokens=1,* delims==" %%A in ("%ENV_FILE%") do if /I "%%A"=="PYTHON_PATH" set "PYTHON_EXEC=%%B" +) REM Construct python executable path set ENV_NAME=WINenv_%ARCHITECTURE% -set ENV_PATH=%ROOT_DIR%\%ENV_NAME% +set ENV_PATH=%CONFIG_DIR%\%ENV_NAME% -if not exist "%PYTHON_EXEC%" ( - echo [ERROR] Python introuvable à: %PYTHON_EXEC% - echo Veuillez vérifier votre .env ou le dossier d'installation. +"%PYTHON_EXEC%" --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python introuvable dans le PATH. + echo Veuillez vérifier l'environnement Python fourni par Docker. exit /b 1 )