Skip to content

Commit

Permalink
feat: added automatic update check during startup
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmueller committed Jan 6, 2022
1 parent b93f73e commit 28d9558
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG
@@ -1,5 +1,6 @@
0.12.0
- feat: support loading nanite model files as extensions
- feat: added automatic update check during startup
- ref: add preferences dialog
- setup: bump nanite from 3.3.1 to 3.4.0
0.11.2
Expand Down
57 changes: 56 additions & 1 deletion pyjibe/head/main.py
Expand Up @@ -25,8 +25,9 @@
from . import custom_widgets
from .dlg_tool_convert import ConvertDialog
from . import preferences
from ..extensions import ExtensionManager
from . import update

from ..extensions import ExtensionManager
from .. import registry
from .._version import version as __version__

Expand Down Expand Up @@ -60,6 +61,10 @@ def __init__(self, *args, **kwargs):
self.settings = QtCore.QSettings()
self.settings.setIniCodec("utf-8")

# update check
self._update_thread = None
self._update_worker = None

# load ui files
path_ui = pkg_resources.resource_filename("pyjibe.head", "main.ui")
uic.loadUi(path_ui, self)
Expand Down Expand Up @@ -105,6 +110,11 @@ def __init__(self, *args, **kwargs):
QtWidgets.QApplication.processEvents(
QtCore.QEventLoop.AllEvents, 300)
sys.exit(0)

# check for updates
do_update = int(self.settings.value("check for updates", 1))
self.on_action_check_update(do_update)

self.show()
self.raise_()
self.activateWindow()
Expand Down Expand Up @@ -179,6 +189,51 @@ def on_about(self):
"PyJibe {}".format(__version__),
about_text)

@QtCore.pyqtSlot(bool)
def on_action_check_update(self, b):
self.settings.setValue("check for updates", int(b))
if b and self._update_thread is None:
self._update_thread = QtCore.QThread()
self._update_worker = update.UpdateWorker()
self._update_worker.moveToThread(self._update_thread)
self._update_worker.finished.connect(self._update_thread.quit)
self._update_worker.data_ready.connect(
self.on_action_check_update_finished)
self._update_thread.start()

version = __version__
ghrepo = "AFM-analysis/PyJibe"

QtCore.QMetaObject.invokeMethod(self._update_worker,
'processUpdate',
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, version),
QtCore.Q_ARG(str, ghrepo),
)

@QtCore.pyqtSlot(dict)
def on_action_check_update_finished(self, mdict):
# cleanup
self._update_thread.quit()
self._update_thread.wait()
self._update_worker = None
self._update_thread = None
# display message box
ver = mdict["version"]
web = mdict["releases url"]
dlb = mdict["binary url"]
msg = QtWidgets.QMessageBox()
msg.setWindowTitle("PyJibe {} available!".format(ver))
msg.setTextFormat(QtCore.Qt.RichText)
text = "You can install PyJibe {} ".format(ver)
if dlb is not None:
text += 'from a <a href="{}">direct download</a>. '.format(dlb)
else:
text += 'by running `pip install --upgrade pyjibe`. '
text += 'Visit the <a href="{}">official release page</a>!'.format(web)
msg.setText(text)
msg.exec_()

@QtCore.pyqtSlot()
def on_documentation(self):
webbrowser.open("https://pyjibe.readthedocs.io")
Expand Down
94 changes: 94 additions & 0 deletions pyjibe/head/update.py
@@ -0,0 +1,94 @@
from distutils.version import LooseVersion, StrictVersion
import json
import os
import struct
import sys
import traceback
import urllib.request

from PyQt5 import QtCore


class UpdateWorker(QtCore.QObject):
finished = QtCore.pyqtSignal()
data_ready = QtCore.pyqtSignal(dict)

@QtCore.pyqtSlot(str, str)
def processUpdate(self, version, ghrepo):
mdict = check_release(ghrepo, version)
if mdict["update available"]:
self.data_ready.emit(mdict)
self.finished.emit()


def check_for_update(version, ghrepo):
thread = QtCore.QThread()
obj = UpdateWorker()
obj.moveToThread(thread)
obj.finished.connect(thread.quit)
thread.start()

QtCore.QMetaObject.invokeMethod(obj, 'processUpdate',
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, version),
QtCore.Q_ARG(str, ghrepo),
)


def check_release(ghrepo="user/repo", version=None, timeout=20):
"""Check GitHub repository for latest release"""
url = "https://api.github.com/repos/{}/releases/latest".format(ghrepo)
if "GITHUB_TOKEN" in os.environ:
hdr = {'authorization': os.environ["GITHUB_TOKEN"]}
else:
hdr = {}
web = "https://github.com/{}/releases".format(ghrepo)
errors = None # error messages (str)
update = False # whether an update is available
binary = None # download link to binary file
new_version = None # string identifying new version
try:
req = urllib.request.Request(url, headers=hdr)
data = urllib.request.urlopen(req, timeout=timeout).read()
except BaseException:
errors = traceback.format_exc()
else:
j = json.loads(data)

newversion = j["tag_name"]

if version is not None:
try:
new = StrictVersion(newversion)
old = StrictVersion(version)
except ValueError:
new = LooseVersion(newversion)
old = LooseVersion(version)
if new > old:
update = True
new_version = newversion
if hasattr(sys, "frozen"):
# determine which binary URL we need
if sys.platform == "win32":
nbit = 8 * struct.calcsize("P")
if nbit == 32:
dlid = "win_32bit_setup.exe"
else:
dlid = "win_64bit_setup.exe"
elif sys.platform == "darwin":
dlid = ".pkg"
else:
dlid = False
# search for binary download file
if dlid:
for a in j["assets"]:
if a["browser_download_url"].count(dlid):
binary = a["browser_download_url"]
break
mdict = {"releases url": web,
"binary url": binary,
"version": new_version,
"update available": update,
"errors": errors,
}
return mdict
30 changes: 30 additions & 0 deletions tests/test_head_update.py
@@ -0,0 +1,30 @@
import os
import socket

import pytest
from pyjibe.head import update


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.connect(("www.python.org", 80))
NET_AVAILABLE = True
except socket.gaierror:
# no internet
NET_AVAILABLE = False


@pytest.mark.xfail(os.getenv("APPVEYOR") in ["true", "True"],
reason="does not always run on Appveyor")
@pytest.mark.xfail(os.getenv("TRAVIS") == "true",
reason="does not always run on travisCI")
@pytest.mark.skipif(not NET_AVAILABLE, reason="No network connection!")
def test_update_basic():
mdict = update.check_release(ghrepo="AFM-analysis/PyJibe",
version="0.11.0")
assert mdict["errors"] is None
assert mdict["update available"]
mdict = update.check_release(ghrepo="AFM-analysis/PyJibe",
version="8472.0.0")
assert mdict["errors"] is None
assert not mdict["update available"]

0 comments on commit 28d9558

Please sign in to comment.