diff --git a/app/core/alert_manager.py b/app/core/alert_manager.py index ef5dde2..81b65b3 100644 --- a/app/core/alert_manager.py +++ b/app/core/alert_manager.py @@ -18,3 +18,16 @@ class AlertManager: error_text = self.language_manager.get_text(error_key) QMessageBox.critical(parent, error_title, error_text) + + def show_choice(self, message: str, parent=None) -> bool: + box = QMessageBox(parent) + box.setWindowTitle(self.language_manager.get_text("confirmation")) + box.setText(message) + box.setIcon(QMessageBox.Icon.Question) + yes = box.addButton(QMessageBox.StandardButton.Yes) + no = box.addButton(QMessageBox.StandardButton.No) + yes.setText(self.language_manager.get_text("yes")) + no.setText(self.language_manager.get_text("no")) + box.setDefaultButton(yes) + box.exec() + return box.clickedButton() == yes diff --git a/app/core/main_manager.py b/app/core/main_manager.py index da39d9e..439b5c4 100644 --- a/app/core/main_manager.py +++ b/app/core/main_manager.py @@ -3,6 +3,7 @@ from app.core.language_manager import LanguageManager 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 typing import Optional class MainManager: @@ -18,6 +19,7 @@ class MainManager: self.settings_manager: SettingsManager = SettingsManager(self.observer_manager, self.theme_manager) 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) @classmethod def get_instance(cls) -> 'MainManager': @@ -38,4 +40,7 @@ class MainManager: return self.language_manager def get_alert_manager(self) -> AlertManager: - return self.alert_manager \ No newline at end of file + return self.alert_manager + + def get_update_manager(self) -> UpdateManager: + return self.update_manager \ No newline at end of file diff --git a/app/core/update_manager.py b/app/core/update_manager.py new file mode 100644 index 0000000..118e06e --- /dev/null +++ b/app/core/update_manager.py @@ -0,0 +1,80 @@ +import requests +from packaging import version +from PyQt6.QtWidgets import QFileDialog +from app.core.alert_manager import AlertManager +from app.core.settings_manager import SettingsManager +from app.core.language_manager import LanguageManager +import os +import sys +import subprocess + +class UpdateManager: + def __init__(self, settings_manager: SettingsManager, language_manager: LanguageManager, alert_manager: AlertManager) -> None: + self.settings_manager = settings_manager + self.language_manager = language_manager + self.alert_manager = alert_manager + self.repo_url = self.settings_manager.get_config("git_repo") + self.app_name = self.settings_manager.get_config("app_name").replace(" ","_") + self.app_os = self.settings_manager.get_config("app_os") + self.arch = self.settings_manager.get_config("architecture") + self.extension = "exe" if self.app_os.lower() == "windows" else "AppImage" # adapte selon OS + + def get_latest_release_with_asset(self) -> dict: + # Récupère la release la plus récente qui contient le bon fichier + try: + if "gitea" in self.repo_url: + owner_repo = self.repo_url.split("/")[-2:] + api_url = self.repo_url.replace(owner_repo[0], "api/v1/repos/" + owner_repo[0]) + "/releases" + resp = requests.get(api_url) + releases = resp.json() + else: + owner_repo = self.repo_url.split("/")[-2:] + api_url = f"https://api.github.com/repos/{owner_repo[0]}/{owner_repo[1]}/releases" + resp = requests.get(api_url) + releases = resp.json() + # Cherche le bon asset dans chaque release + expected_filename = f"{self.app_name}-{self.app_os}-{self.arch}.{self.extension}" + for release in releases: + for asset in release.get("assets", []): + if asset.get("name", "") == expected_filename: + return { + "tag_name": release.get("tag_name"), + "download_url": asset.get("browser_download_url") + } + except Exception: + pass + return None + + def check_for_update(self, parent=None) -> bool: + current_version = self.settings_manager.get_config("app_version") + release = self.get_latest_release_with_asset() + if release and version.parse(release["tag_name"]) > version.parse(current_version): + choice = self.alert_manager.show_choice( + self.language_manager.get_text("update_found").replace("{latest_tag}", release["tag_name"]), + parent=parent + ) + if choice: + folder = QFileDialog.getExistingDirectory(parent, self.language_manager.get_text("choose_update_folder")) + if folder: + self.download(release["download_url"], folder, parent) + return True + return False + + def download(self, download_url, folder, parent=None): + try: + filename = os.path.basename(download_url) + local_path = os.path.join(folder, filename) + resp = requests.get(download_url, stream=True) + with open(local_path, "wb") as f: + for chunk in resp.iter_content(chunk_size=8192): + f.write(chunk) + msg = self.language_manager.get_text("update_downloaded").replace("{local_path}", local_path) + self.alert_manager.show_success(msg, parent=parent) + # Ouvre le fichier téléchargé + if sys.platform.startswith("win"): + os.startfile(local_path) + else: + subprocess.Popen(["chmod", "+x", local_path]) + subprocess.Popen([local_path]) + except Exception: + self.alert_manager.show_error("update_download_error", parent=parent) diff --git a/config.json b/config.json index ca2b23b..92ba29b 100644 --- a/config.json +++ b/config.json @@ -6,5 +6,5 @@ "architecture": "x64", "icon_path": "data/assets/icon.ico", "main_script": "main.py", - "git_repo": "https://gitea.louismazin.ovh/LouisMazin/HoDA" + "git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate" } \ No newline at end of file diff --git a/data/lang/en.json b/data/lang/en.json index 63e98cb..338b7d5 100644 --- a/data/lang/en.json +++ b/data/lang/en.json @@ -13,8 +13,13 @@ "sending": "Sending...", "success": "Success", "error": "Error", + "confirmation": "Confirmation", "suggestion_sent_success": "Your message has been sent successfully!", "suggestion_send_error": "Error sending message. Try again later.", "email_credentials_error": "Email credentials not or bad configured. Please set your email and password in the .env file.", - "suggestion_too_short": "The message must be at least 15 characters long." + "suggestion_too_short": "The message must be at least 15 characters long.", + "update_found": "New version available: {latest_tag} \nDo you want to install the update?", + "choose_update_folder": "Choose destination folder", + "update_downloaded": "Update downloaded to {local_path}", + "update_download_error": "Error downloading update" } \ No newline at end of file diff --git a/data/lang/fr.json b/data/lang/fr.json index 53bc011..606f2c5 100644 --- a/data/lang/fr.json +++ b/data/lang/fr.json @@ -13,8 +13,13 @@ "sending": "Envoi...", "success": "Succès", "error": "Erreur", + "confirmation": "Confirmation", "suggestion_sent_success": "Votre message a été envoyé avec succès !", "suggestion_send_error": "Erreur lors de l'envoi du message. Essayez à nouveau plus tard.", "email_credentials_error": "Identifiants de messagerie non ou mal configurés. Veuillez définir votre email et mot de passe dans le fichier .env.", - "suggestion_too_short": "Le message doit contenir au moins 15 caractères." + "suggestion_too_short": "Le message doit contenir au moins 15 caractères.", + "update_found": "Nouvelle version disponible : {latest_tag} \nVoulez-vous installer la mise à jour ?", + "choose_update_folder": "Choisissez le dossier de destination", + "update_downloaded": "Mise à jour téléchargée dans {local_path}", + "update_download_error": "Erreur lors du téléchargement de la mise à jour" } \ No newline at end of file diff --git a/main.py b/main.py index e6605b9..065c26f 100644 --- a/main.py +++ b/main.py @@ -5,15 +5,19 @@ from PyQt6.QtGui import QIcon from app.ui.main_window import MainWindow from app.core.main_manager import MainManager def main() -> int: - main_manager: MainManager = MainManager.get_instance() theme_manager = main_manager.get_theme_manager() settings_manager = main_manager.get_settings_manager() + update_manager = main_manager.get_update_manager() # Ajout app: QApplication = QApplication(sys.argv) app.setStyleSheet(theme_manager.get_sheet()) app.setApplicationName(settings_manager.get_config("app_name")) app.setWindowIcon(QIcon(paths.get_asset_path("icon"))) + + if update_manager.check_for_update(): + return + window: MainWindow = MainWindow() window.show() return app.exec()