URI: 
       t[Qt] Add optional update notifications - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 34c99c3b366ade7adaa919bf1f75d39fe9fcf250
   DIR parent 5613f9b903c6da799ec461d3a06b25ea0d0ee577
  HTML Author: Johann Bauer <bauerj@bauerj.eu>
       Date:   Tue,  1 Jan 2019 19:38:33 +0100
       
       t[Qt] Add optional update notifications
       
       Diffstat:
         M electrum/gui/qt/main_window.py      |      43 +++++++++++++++++++++++++++++--
         M electrum/gui/qt/util.py             |      97 ++++++++++++++++++++++++++++++-
         M icons.qrc                           |       1 +
         A icons/update.png                    |       0 
       
       4 files changed, 138 insertions(+), 3 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -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()
       t@@ -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)
       t@@ -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:<br/>"),
       t@@ -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)
       t@@ -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)
       t@@ -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)
       t@@ -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')),
                ]
   DIR diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py
       t@@ -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
        
       t@@ -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:
       t@@ -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('<h2>' + _("Update check failed") + '</h2>')
       +        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('<h2>' + _("There is a new update available") + '</h2>')
       +                url = "<a href='{u}'>{u}</a>".format(u=UpdateCheck.download_url)
       +                self.detail_label.setText(_("You can download the new version from {}.").format(url))
       +            else:
       +                self.heading_label.setText('<h2>' + _("Already up to date") + '</h2>')
       +                self.detail_label.setText(_("You are already on the latest version of Electrum."))
       +        else:
       +            self.heading_label.setText('<h2>' + _("Checking for updates...") + '</h2>')
       +            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"))
   DIR diff --git a/icons.qrc b/icons.qrc
       t@@ -63,6 +63,7 @@
            <file>icons/unconfirmed.png</file>
            <file>icons/unpaid.png</file>
            <file>icons/unlock.png</file>
       +    <file>icons/update.png</file>
            <file>icons/warning.png</file>
            <file>icons/zoom.png</file>
          </qresource>
   DIR diff --git a/icons/update.png b/icons/update.png
       Binary files differ.