From 34c99c3b366ade7adaa919bf1f75d39fe9fcf250 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Tue, 1 Jan 2019 19:38:33 +0100 Subject: [PATCH] [Qt] Add optional update notifications --- electrum/gui/qt/main_window.py | 43 ++++++++++++++- electrum/gui/qt/util.py | 97 ++++++++++++++++++++++++++++++++- icons.qrc | 1 + icons/update.png | Bin 0 -> 1580 bytes 4 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 icons/update.png diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 1a6d94d2b..26d7d728d 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -226,6 +226,28 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): gui_object.timer.timeout.connect(self.timer_actions) self.fetch_alias() + # If the option hasn't been set yet + if config.get('check_updates') is None: + choice = QMessageBox.question(self, + "Electrum - " + _("Enable update check"), + _("For security reasons we advise that you always use the latest version of Electrum.") + " " + + _("Would you like to be notified when there is a newer version of Electrum available?"), + QMessageBox.Yes, + QMessageBox.No) + config.set_key('check_updates', choice == QMessageBox.Yes, save=True) + + if config.get('check_updates', False): + # The references to both the thread and the window need to be stored somewhere + # to prevent GC from getting in our way. + def on_version_received(v): + if UpdateCheck.is_newer(v): + self.update_check_button.setText(_("Update to Electrum {} is available").format(v)) + self.update_check_button.clicked.connect(lambda: self.show_update_check(v)) + self.update_check_button.show() + self._update_check_thread = UpdateCheckThread(self) + self._update_check_thread.checked.connect(on_version_received) + self._update_check_thread.start() + def on_history(self, b): self.wallet.clear_coin_price_cache() self.new_fx_history_signal.emit() @@ -577,6 +599,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): help_menu = menubar.addMenu(_("&Help")) help_menu.addAction(_("&About"), self.show_about) + help_menu.addAction(_("&Check for updates"), self.show_update_check) help_menu.addAction(_("&Official website"), lambda: webbrowser.open("https://electrum.org")) help_menu.addSeparator() help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://docs.electrum.org/")).setShortcut(QKeySequence.HelpContents) @@ -604,6 +627,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): "servers that handle the most complicated parts of the Bitcoin system.") + "\n\n" + _("Uses icons from the Icons8 icon pack (icons8.com)."))) + def show_update_check(self, version=None): + self._update_check = UpdateCheck(self, version) + def show_report_bug(self): msg = ' '.join([ _("Please report any bugs as issues on github:
"), @@ -1998,7 +2024,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): sb = QStatusBar() sb.setFixedHeight(35) - qtVersion = qVersion() self.balance_label = QLabel("Loading wallet...") self.balance_label.setTextInteractionFlags(Qt.TextSelectableByMouse) @@ -2010,6 +2035,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.search_box.hide() sb.addPermanentWidget(self.search_box) + self.update_check_button = QPushButton("") + self.update_check_button.setFlat(True) + self.update_check_button.setCursor(QCursor(Qt.PointingHandCursor)) + self.update_check_button.setIcon(QIcon(":icons/update.png")) + self.update_check_button.hide() + sb.addPermanentWidget(self.update_check_button) + self.lock_icon = QIcon() self.password_button = StatusBarButton(self.lock_icon, _("Password"), self.change_password_dialog ) sb.addPermanentWidget(self.password_button) @@ -2905,6 +2937,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): colortheme_combo.currentIndexChanged.connect(on_colortheme) gui_widgets.append((colortheme_label, colortheme_combo)) + updatecheck_cb = QCheckBox(_("Automatically check for software updates")) + updatecheck_cb.setChecked(self.config.get('check_updates', False)) + def on_set_updatecheck(v): + self.config.set_key('check_updates', v == Qt.Checked, save=True) + updatecheck_cb.stateChanged.connect(on_set_updatecheck) + gui_widgets.append((updatecheck_cb, None)) + usechange_cb = QCheckBox(_('Use change addresses')) usechange_cb.setChecked(self.wallet.use_change) if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False) @@ -3078,7 +3117,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): tabs_info = [ (fee_widgets, _('Fees')), (tx_widgets, _('Transactions')), - (gui_widgets, _('Appearance')), + (gui_widgets, _('General')), (fiat_widgets, _('Fiat')), (id_widgets, _('Identity')), ] diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index a8358ba57..dcc11cdf5 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -1,8 +1,11 @@ +import asyncio import os.path import time import sys import platform import queue +import traceback +from distutils.version import StrictVersion from functools import partial from typing import NamedTuple, Callable, Optional, TYPE_CHECKING @@ -10,8 +13,9 @@ from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * +from electrum import version from electrum.i18n import _, languages -from electrum.util import FileImportFailed, FileExportFailed +from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, PrintError from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED if TYPE_CHECKING: @@ -819,6 +823,97 @@ class FromList(QTreeWidget): self.header().setSectionResizeMode(0, sm) self.header().setSectionResizeMode(1, sm) + +class UpdateCheck(QWidget, PrintError): + url = "https://electrum.org/version" + download_url = "https://electrum.org/#download" + + 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.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: + return StrictVersion((await result.text()).strip()) + + def run(self): + try: + self.checked.emit(asyncio.run_coroutine_threadsafe(self.get_update_info(), self.main_window.network.asyncio_loop).result()) + except Exception: + self.print_error(traceback.format_exc()) + self.failed.emit() + + if __name__ == "__main__": app = QApplication([]) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done")) diff --git a/icons.qrc b/icons.qrc index cd77e912d..ab4318dc1 100644 --- a/icons.qrc +++ b/icons.qrc @@ -63,6 +63,7 @@ icons/unconfirmed.png icons/unpaid.png icons/unlock.png + icons/update.png icons/warning.png icons/zoom.png diff --git a/icons/update.png b/icons/update.png new file mode 100644 index 0000000000000000000000000000000000000000..e774ee10e4b39fd98887f72a2cd4ef103253debf GIT binary patch literal 1580 zcmV+{2GjY8P)O&kYYQ{>=>31;jM_TJ;jVwUMcui zSGe(p1Qll)gB~W}Apr9Mw42UMSq;RGx`lARj$+0d67)N_6Vo;ZR+`FaB6VhL^h!Z* z(n8v=A#~j!cqf02*f%t)CEPP*8Dq5@DUrx_QEkUG7-0fx0o)$a<}xkg(#u zJeivTB%f8ZeM0n<1W+E zkl-434FR|6sMR6{sa-90?yCMAccGSsSd7PX)M*X0f4DZ~UT10bY(uM)#`DLxDsZ6C@aR~y1FtZ=_q41d*KI+KR5B9%q1CWF+b zdfW!T)=orr3}Z<)V?-ehiHHcj&Iy0@-1|<|Oy*?rFDq|!%@|QgL#|Bgj1h%2WPI9Z zj3}fb|If&b5k=yL*cP2q%wNP=Ge&ACX|5@4Ja!t4?yluSKW6=2gb+dqA%qY@2qA=M zW|jBV|4YA3d zs!)&n@R`JqbxM6tJ*>Fv0on_oGwtj=NVOqB+1tuMFJxnET&etGxDRbXySoG6r_#vF zGvf{6forFRDtwQC)uyvrfzgIk4|UE8RdpXgOHJmq5@QVs2Al;-(E$JpOl7npBNz3H zGX{N$95-V$qi3;bZ^%D0#$f>0>#m(9+pe$3B3J9btgzy)XW-2kb?^4e?kt_}K?}k{ zFkXng&O+i-{n{FGamL^m5#x4@MZ85DE~$f$C#{jFAu?n9OW=CJ z?@5a^+z^>DE=M2HB8()h(ojQW#`rH%cwbAMxC_F8uA5m|pCad9Yqeq;a7%tcN2%Cz z$X^$x{DCIj!^ntIXy7aII2zh9lA~i4;qM4uQAM^u;Oh& zi_H>oFtF1t1Wyxzb=6F~ryf+CO#p2N(5%nlHDQz)n{+&d^m1AbIfRG2HOk_537D#* zUX%Nrf#0v-2R6NBnhm*G{s+JxW=}vd9-g2qA