text: # 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 LooseVersion
  
  from PyQt5.QtCore import Qt, QThread, pyqtSignal
  from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar,
                               QHBoxLayout, QPushButton, QDialog)
  
  from electrum_spero import version
  from electrum_spero import constants
  from electrum_spero import ecc
  from electrum_spero.i18n import _
  from electrum_spero.util import make_aiohttp_session
  from electrum_spero.logging import Logger
  from electrum_spero.network import Network
  
  
  class UpdateCheck(QDialog, Logger):
      url = "https://sperocoin.org/version"
      download_url = "https://sperocoin.org/#download"
  
      VERSION_ANNOUNCEMENT_SIGNING_KEYS = (
          "LWZzbv5SbiRRjBDL6dUYRdBX9Dp89RDZgG",
      )
  
      def __init__(self, *, latest_version=None):
          QDialog.__init__(self)
          self.setWindowTitle('SperoCoin 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.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 > LooseVersion(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, Logger):
      checked = pyqtSignal(object)
      failed = pyqtSignal()
  
      def __init__(self):
          QThread.__init__(self)
          Logger.__init__(self)
          self.network = Network.get_instance()
  
      async def get_update_info(self):
          # note: Use long timeout here as it is not critical that we get a response fast,
          #       and it's bad not to get an update notification just because we did not wait enough.
          async with make_aiohttp_session(proxy=self.network.proxy, timeout=120) 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.logger.info(f"valid sig for version announcement '{version_num}' from address '{address}'")
                          break
                  else:
                      raise Exception('no valid signature for version announcement')
                  return LooseVersion(version_num.strip())
  
      def run(self):
          if not self.network:
              self.failed.emit()
              return
          try:
              update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), self.network.asyncio_loop).result()
          except Exception as e:
              self.logger.info(f"got exception: '{repr(e)}'")
              self.failed.emit()
          else:
              self.checked.emit(update_info)
  