From 67d080b34a244f6e13c43052a0f4ba3d7c6eb346 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 18:45:42 +0100 Subject: [PATCH] mv qt update checker to its own file --- electrum/gui/qt/main_window.py | 1 + electrum/gui/qt/update_checker.py | 141 ++++++++++++++++++++++++++++++ electrum/gui/qt/util.py | 134 +--------------------------- 3 files changed, 145 insertions(+), 131 deletions(-) create mode 100644 electrum/gui/qt/update_checker.py diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 340411e26..93926e950 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -75,6 +75,7 @@ from .fee_slider import FeeSlider from .util import * from .installwizard import WIF_HELP_TEXT from .history_list import HistoryList, HistoryModel +from .update_checker import UpdateCheck, UpdateCheckThread class StatusBarButton(QPushButton): diff --git a/electrum/gui/qt/update_checker.py b/electrum/gui/qt/update_checker.py new file mode 100644 index 000000000..4106ccf9b --- /dev/null +++ b/electrum/gui/qt/update_checker.py @@ -0,0 +1,141 @@ +# Copyright (C) 2019 The Electrum developers +# Distributed under the MIT software license, see the accompanying +# file LICENCE or http://www.opensource.org/licenses/mit-license.php + +import asyncio +import base64 +from distutils.version import StrictVersion + +from PyQt5.QtCore import Qt, QThread, pyqtSignal +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar, + QHBoxLayout, QPushButton) + +from electrum import version +from electrum import constants +from electrum import ecc +from electrum.i18n import _ +from electrum.util import PrintError, make_aiohttp_session + + +class UpdateCheck(QWidget, PrintError): + url = "https://electrum.org/version" + download_url = "https://electrum.org/#download" + + VERSION_ANNOUNCEMENT_SIGNING_KEYS = ( + "13xjmVAB1EATPP8RshTE8S8sNwwSUM9p1P", + ) + + def __init__(self, main_window, latest_version=None): + self.main_window = main_window + QWidget.__init__(self) + self.setWindowTitle('Electrum - ' + _('Update Check')) + self.content = QVBoxLayout() + self.content.setContentsMargins(*[10]*4) + + self.heading_label = QLabel() + self.content.addWidget(self.heading_label) + + self.detail_label = QLabel() + self.detail_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse) + self.detail_label.setOpenExternalLinks(True) + self.content.addWidget(self.detail_label) + + self.pb = QProgressBar() + self.pb.setMaximum(0) + self.pb.setMinimum(0) + self.content.addWidget(self.pb) + + versions = QHBoxLayout() + versions.addWidget(QLabel(_("Current version: {}".format(version.ELECTRUM_VERSION)))) + self.latest_version_label = QLabel(_("Latest version: {}".format(" "))) + versions.addWidget(self.latest_version_label) + self.content.addLayout(versions) + + self.update_view(latest_version) + + self.update_check_thread = UpdateCheckThread(self.main_window) + self.update_check_thread.checked.connect(self.on_version_retrieved) + self.update_check_thread.failed.connect(self.on_retrieval_failed) + self.update_check_thread.start() + + close_button = QPushButton(_("Close")) + close_button.clicked.connect(self.close) + self.content.addWidget(close_button) + self.setLayout(self.content) + self.show() + + def on_version_retrieved(self, version): + self.update_view(version) + + def on_retrieval_failed(self): + self.heading_label.setText('

' + _("Update check failed") + '

') + self.detail_label.setText(_("Sorry, but we were unable to check for updates. Please try again later.")) + self.pb.hide() + + @staticmethod + def is_newer(latest_version): + return latest_version > StrictVersion(version.ELECTRUM_VERSION) + + def update_view(self, latest_version=None): + if latest_version: + self.pb.hide() + self.latest_version_label.setText(_("Latest version: {}".format(latest_version))) + if self.is_newer(latest_version): + self.heading_label.setText('

' + _("There is a new update available") + '

') + url = "{u}".format(u=UpdateCheck.download_url) + self.detail_label.setText(_("You can download the new version from {}.").format(url)) + else: + self.heading_label.setText('

' + _("Already up to date") + '

') + self.detail_label.setText(_("You are already on the latest version of Electrum.")) + else: + self.heading_label.setText('

' + _("Checking for updates...") + '

') + self.detail_label.setText(_("Please wait while Electrum checks for available updates.")) + + +class UpdateCheckThread(QThread, PrintError): + checked = pyqtSignal(object) + failed = pyqtSignal() + + def __init__(self, main_window): + super().__init__() + self.main_window = main_window + + async def get_update_info(self): + async with make_aiohttp_session(proxy=self.main_window.network.proxy) as session: + async with session.get(UpdateCheck.url) as result: + signed_version_dict = await result.json(content_type=None) + # example signed_version_dict: + # { + # "version": "3.9.9", + # "signatures": { + # "1Lqm1HphuhxKZQEawzPse8gJtgjm9kUKT4": "IA+2QG3xPRn4HAIFdpu9eeaCYC7S5wS/sDxn54LJx6BdUTBpse3ibtfq8C43M7M1VfpGkD5tsdwl5C6IfpZD/gQ=" + # } + # } + version_num = signed_version_dict['version'] + sigs = signed_version_dict['signatures'] + for address, sig in sigs.items(): + if address not in UpdateCheck.VERSION_ANNOUNCEMENT_SIGNING_KEYS: + continue + sig = base64.b64decode(sig) + msg = version_num.encode('utf-8') + if ecc.verify_message_with_address(address=address, sig65=sig, message=msg, + net=constants.BitcoinMainnet): + self.print_error(f"valid sig for version announcement '{version_num}' from address '{address}'") + break + else: + raise Exception('no valid signature for version announcement') + return StrictVersion(version_num.strip()) + + def run(self): + network = self.main_window.network + if not network: + self.failed.emit() + return + try: + update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), network.asyncio_loop).result() + except Exception as e: + #self.print_error(traceback.format_exc()) + self.print_error(f"got exception: '{repr(e)}'") + self.failed.emit() + else: + self.checked.emit(update_info) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 09c2bb1a1..c46c6dfb9 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -5,21 +5,17 @@ import sys import platform import queue import traceback -from distutils.version import StrictVersion + from functools import partial, lru_cache from typing import NamedTuple, Callable, Optional, TYPE_CHECKING -import base64 from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * -from electrum import version -from electrum import ecc -from electrum import constants from electrum.i18n import _, languages -from electrum.util import (FileImportFailed, FileExportFailed, make_aiohttp_session, - PrintError, resource_path) +from electrum.util import (FileImportFailed, FileExportFailed, + resource_path) from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED if TYPE_CHECKING: @@ -826,130 +822,6 @@ class FromList(QTreeWidget): self.header().setSectionResizeMode(1, sm) -class UpdateCheck(QWidget, PrintError): - url = "https://electrum.org/version" - download_url = "https://electrum.org/#download" - - VERSION_ANNOUNCEMENT_SIGNING_KEYS = ( - "13xjmVAB1EATPP8RshTE8S8sNwwSUM9p1P", - ) - - def __init__(self, main_window, latest_version=None): - self.main_window = main_window - QWidget.__init__(self) - self.setWindowTitle('Electrum - ' + _('Update Check')) - self.content = QVBoxLayout() - self.content.setContentsMargins(*[10]*4) - - self.heading_label = QLabel() - self.content.addWidget(self.heading_label) - - self.detail_label = QLabel() - self.detail_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse) - self.detail_label.setOpenExternalLinks(True) - self.content.addWidget(self.detail_label) - - self.pb = QProgressBar() - self.pb.setMaximum(0) - self.pb.setMinimum(0) - self.content.addWidget(self.pb) - - versions = QHBoxLayout() - versions.addWidget(QLabel(_("Current version: {}".format(version.ELECTRUM_VERSION)))) - self.latest_version_label = QLabel(_("Latest version: {}".format(" "))) - versions.addWidget(self.latest_version_label) - self.content.addLayout(versions) - - self.update_view(latest_version) - - self.update_check_thread = UpdateCheckThread(self.main_window) - self.update_check_thread.checked.connect(self.on_version_retrieved) - self.update_check_thread.failed.connect(self.on_retrieval_failed) - self.update_check_thread.start() - - close_button = QPushButton(_("Close")) - close_button.clicked.connect(self.close) - self.content.addWidget(close_button) - self.setLayout(self.content) - self.show() - - def on_version_retrieved(self, version): - self.update_view(version) - - def on_retrieval_failed(self): - self.heading_label.setText('

' + _("Update check failed") + '

') - self.detail_label.setText(_("Sorry, but we were unable to check for updates. Please try again later.")) - self.pb.hide() - - @staticmethod - def is_newer(latest_version): - return latest_version > StrictVersion(version.ELECTRUM_VERSION) - - def update_view(self, latest_version=None): - if latest_version: - self.pb.hide() - self.latest_version_label.setText(_("Latest version: {}".format(latest_version))) - if self.is_newer(latest_version): - self.heading_label.setText('

' + _("There is a new update available") + '

') - url = "{u}".format(u=UpdateCheck.download_url) - self.detail_label.setText(_("You can download the new version from {}.").format(url)) - else: - self.heading_label.setText('

' + _("Already up to date") + '

') - self.detail_label.setText(_("You are already on the latest version of Electrum.")) - else: - self.heading_label.setText('

' + _("Checking for updates...") + '

') - self.detail_label.setText(_("Please wait while Electrum checks for available updates.")) - - -class UpdateCheckThread(QThread, PrintError): - checked = pyqtSignal(object) - failed = pyqtSignal() - - def __init__(self, main_window): - super().__init__() - self.main_window = main_window - - async def get_update_info(self): - async with make_aiohttp_session(proxy=self.main_window.network.proxy) as session: - async with session.get(UpdateCheck.url) as result: - signed_version_dict = await result.json(content_type=None) - # example signed_version_dict: - # { - # "version": "3.9.9", - # "signatures": { - # "1Lqm1HphuhxKZQEawzPse8gJtgjm9kUKT4": "IA+2QG3xPRn4HAIFdpu9eeaCYC7S5wS/sDxn54LJx6BdUTBpse3ibtfq8C43M7M1VfpGkD5tsdwl5C6IfpZD/gQ=" - # } - # } - version_num = signed_version_dict['version'] - sigs = signed_version_dict['signatures'] - for address, sig in sigs.items(): - if address not in UpdateCheck.VERSION_ANNOUNCEMENT_SIGNING_KEYS: - continue - sig = base64.b64decode(sig) - msg = version_num.encode('utf-8') - if ecc.verify_message_with_address(address=address, sig65=sig, message=msg, - net=constants.BitcoinMainnet): - self.print_error(f"valid sig for version announcement '{version_num}' from address '{address}'") - break - else: - raise Exception('no valid signature for version announcement') - return StrictVersion(version_num.strip()) - - def run(self): - network = self.main_window.network - if not network: - self.failed.emit() - return - try: - update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), network.asyncio_loop).result() - except Exception as e: - #self.print_error(traceback.format_exc()) - self.print_error(f"got exception: '{repr(e)}'") - self.failed.emit() - else: - self.checked.emit(update_info) - - if __name__ == "__main__": app = QApplication([]) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done"))