# Copyright (C) 2024–present Loren Eteval & contributors # # This file is part of Furious. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Provide Qt support for updates manager.""" from __future__ import annotations from Furious.Frozenlib import * from Furious.Models import * from Furious.Qt.QtWidgets import * from Furious.Qt.DynamicTranslate import gettext as _ from Furious.Qt.WebGETManager import * from PySide6 import QtCore from PySide6.QtGui import * from typing import Callable import logging import functools __all__ = ['UpdateManager'] logger = logging.getLogger(__name__) class MBoxQuestionUpdate(AppQMessageBox): """Represent m box question update.""" def __init__(self, *args, **kwargs): """Initialize the MBoxQuestionUpdate.""" super().__init__(*args, **kwargs) self.version = '0.0.0' self.setWindowTitle(_(APPLICATION_NAME)) self.setStandardButtons( AppQMessageBox.StandardButton.Yes | AppQMessageBox.StandardButton.No ) def customText(self): """Return the user-facing message text for the m box question update.""" return _('New version available') + f': {self.version}' def retranslate(self): """Refresh translated text for the m box question update.""" self.setText(self.customText()) self.setWindowTitle(_(self.windowTitle())) self.setInformativeText(_(self.informativeText())) # Ignore button text self.moveToCenter() class UpdateManager(WebGETManager): """Coordinate updates operations.""" API_URL = ( f'https://api.github.com/repos/' f'{APPLICATION_REPO_OWNER_NAME}/{APPLICATION_REPO_NAME}/releases/latest' ) def __init__(self, parent=None, **kwargs): """Initialize the update manager.""" actionMessage = kwargs.pop('actionMessage', 'check for updates') super().__init__(parent, actionMessage=actionMessage) @staticmethod def showErrorMessageBox(parent=None): """Show error message box.""" mbox = AppQMessageBox(parent=parent, icon=AppQMessageBox.Icon.Critical) mbox.setWindowTitle(_(APPLICATION_NAME)) mbox.setText(_('Check for updates failed')) # Show the MessageBox asynchronously mbox.open() def successCallback(self, networkReply, **kwargs): """Handle a successful network operation.""" parent = kwargs.pop('parent', None) showMessageBox = kwargs.pop('showMessageBox', True) hasNewVersionCallback = kwargs.pop('hasNewVersionCallback', None) data = networkReply.readAll().data() try: info = UJSONEncoder.decode(data) except Exception as ex: # Any non-exit exceptions logger.error(f'bad network reply while checking for updates. {ex}') if showMessageBox: self.showErrorMessageBox(parent) else: newVersion = info['tag_name'] if versionToValue(newVersion) > versionToValue(APPLICATION_VERSION): def handleResultCode(code): """Handle result code.""" if code == PySide6Legacy.enumValueWrapper( AppQMessageBox.StandardButton.Yes ): if QDesktopServices.openUrl(QtCore.QUrl(info['html_url'])): logger.info('open download page success') else: logger.error('open download page failed') else: # Do nothing pass if callable(hasNewVersionCallback): hasNewVersionCallback(newVersion) if showMessageBox: mbox = MBoxQuestionUpdate( parent=parent, icon=AppQMessageBox.Icon.Information, ) mbox.version = newVersion mbox.setText(mbox.customText()) mbox.setInformativeText(_('Go to download page?')) mbox.finished.connect(functools.partial(handleResultCode)) # Show the MessageBox asynchronously mbox.open() else: if showMessageBox: mbox = AppQMessageBox( parent=parent, icon=AppQMessageBox.Icon.Information, ) mbox.setWindowTitle(_(APPLICATION_NAME)) mbox.setText(_(f'{APPLICATION_NAME} is already the latest version')) # Show the MessageBox asynchronously mbox.open() def failureCallback(self, networkReply, **kwargs): """Handle a failed network operation.""" showMessageBox = kwargs.pop('showMessageBox', True) if showMessageBox: self.showErrorMessageBox() def checkForUpdates( self, showMessageBox=True, hasNewVersionCallback: Callable[[str], None] = None, **kwargs, ): """Check for updates.""" self.webGET( self.API_URL, showMessageBox=showMessageBox, hasNewVersionCallback=hasNewVersionCallback, **kwargs, )