From 118c7a19fb09e0aafe379ac753cbc1d5a86788a9 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 28 Oct 2015 19:34:13 -0700 Subject: [PATCH 01/14] A simple window. --- lib/gpi/update.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 lib/gpi/update.py diff --git a/lib/gpi/update.py b/lib/gpi/update.py new file mode 100755 index 00000000..088f574a --- /dev/null +++ b/lib/gpi/update.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# Copyright (C) 2014 Dignity Health +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES +# AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE +# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC +# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR +# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT +# LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR +# MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE +# SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. + +# Brief: Update utility. + +import sys +import os +from PyQt4 import QtGui, QtCore + +def update(): + + app = QtGui.QApplication(sys.argv) + + w = QtGui.QWidget() + w.resize(250, 150) + w.move(300, 300) + w.setWindowTitle('GPI Update') + w.show() + + sys.exit(app.exec_()) + +if __name__ == '__main__': + update() From 7a7593f03c307949b07ae1e304d483c211247f42 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 29 Oct 2015 09:59:39 -0700 Subject: [PATCH 02/14] Troubleshooting functools error. --- lib/gpi/update.py | 78 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 088f574a..dd5d8252 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -23,22 +23,78 @@ # MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE # SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. -# Brief: Update utility. +# Brief: Update utility, can be called directly from gpi or run as a separate +# program. -import sys import os -from PyQt4 import QtGui, QtCore +import sys +import subprocess -def update(): - - app = QtGui.QApplication(sys.argv) +# Check for Anaconda PREFIX, or assume that THIS file location is the CWD. +GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA +if GPI_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): + GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) + +GPI_LIB_DIR = GPI_PREFIX +if not GPI_PREFIX.endswith('lib'): + GPI_LIB_DIR = os.path.join(GPI_PREFIX, 'lib') +if GPI_LIB_DIR not in sys.path: + sys.path.insert(0, GPI_LIB_DIR) - w = QtGui.QWidget() - w.resize(250, 150) - w.move(300, 300) - w.setWindowTitle('GPI Update') - w.show() +from gpi import QtGui, QtCore, Signal + + +class UpdateWindow(QtGui.QWidget): + def __init__(self): + super().__init__() + + if not condaIsAvailable(): + return + + okButton = QtGui.QPushButton("OK") + cancelButton = QtGui.QPushButton("Cancel") + + hbox = QtGui.QHBoxLayout() + hbox.addStretch(1) + hbox.addWidget(okButton) + hbox.addWidget(cancelButton) + + vbox = QtGui.QVBoxLayout() + vbox.addStretch(1) + vbox.addLayout(hbox) + + self.setLayout(vbox) + + self.setGeometry(300, 300, 250, 150) + self.setWindowTitle('GPI Update') + self.show() + self.raise_() + + def condaIsAvailable(self): + try: + subprocess.check_call('conda --version') + return True + except: + print('\'conda\' failed to execute, aborting...') + return False + + def getLatestPkgVersion(self, name, channel): + try: + output = subprocess.check_output('conda search -c '+channel+' -f '+name+' -o --json', shell=True) + except subprocess.CalledProcessError as e: + print(cmd, e.output) + sys.exit(e.returncode) + + conda = json.loads(output) + print(conda) + + def getLatestGPIVersion(self): + pass + +def update(): + app = QtGui.QApplication(sys.argv) + win = UpdateWindow() sys.exit(app.exec_()) if __name__ == '__main__': From 24222ef6d15e161264f836fa6d7366957baef4c4 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 11:48:32 -0700 Subject: [PATCH 03/14] A working version of the update script cli. --- bin/gpi_update | 42 ++++++++++ lib/gpi/update.py | 195 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 206 insertions(+), 31 deletions(-) create mode 100755 bin/gpi_update diff --git a/bin/gpi_update b/bin/gpi_update new file mode 100755 index 00000000..d94fdcbf --- /dev/null +++ b/bin/gpi_update @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# Copyright (C) 2014 Dignity Health +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES +# AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE +# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC +# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR +# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT +# LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR +# MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE +# SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. + +import sys, os + +# Check for Anaconda PREFIX, or assume that THIS file location is the CWD. +GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA +if GPI_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): + GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) + +GPI_LIB_DIR = os.path.join(GPI_PREFIX, 'lib') +if GPI_LIB_DIR not in sys.path: + sys.path.insert(0, GPI_LIB_DIR) + +# gpi +from gpi import update + +if __name__ == '__main__': + update.update() diff --git a/lib/gpi/update.py b/lib/gpi/update.py index dd5d8252..0120306a 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright (C) 2014 Dignity Health # # This program is free software: you can redistribute it and/or modify @@ -28,30 +27,65 @@ import os import sys +import json import subprocess -# Check for Anaconda PREFIX, or assume that THIS file location is the CWD. -GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA -if GPI_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): - GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) - -GPI_LIB_DIR = GPI_PREFIX -if not GPI_PREFIX.endswith('lib'): - GPI_LIB_DIR = os.path.join(GPI_PREFIX, 'lib') -if GPI_LIB_DIR not in sys.path: - sys.path.insert(0, GPI_LIB_DIR) - from gpi import QtGui, QtCore, Signal +# get the anaconda path to ensure that THIS installation is being updated +ANACONDA_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA +if ANACONDA_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): + # get the path from the user env + ANACONDA_PREFIX = os.path.dirname(subprocess.check_output('which conda', shell=True).decode('latin1').strip()) + +# Load multiple json objects from string. +# Returns loaded objects in a list. +class JSONStreamLoads(object): + + def __init__(self, in_str, linefeed=True): + + if type(in_str) == str: + self._buffer = in_str + else: + raise TypeError("JSONStreamLoads(): input must be of type \'str\'.") + + if linefeed: + self._load = self.loadsByLine() + else: + self._load = self.loadsByCharacter() + + def objects(self): + return self._load + + def loadsByLine(self): + out = [] + buf = '' + for l in self._buffer.splitlines(): + buf += l.strip().strip('\0') + try: + out.append(json.loads(buf)) + buf = '' + except: + pass + return out + + def loadsByCharacter(self): + out = [] + buf = '' + for l in self._buffer: + buf += l + try: + out.append(json.loads(buf.strip().strip('\0'))) + buf = '' + except: + pass + return out class UpdateWindow(QtGui.QWidget): def __init__(self): super().__init__() - if not condaIsAvailable(): - return - okButton = QtGui.QPushButton("OK") cancelButton = QtGui.QPushButton("Cancel") @@ -71,31 +105,130 @@ def __init__(self): self.show() self.raise_() - def condaIsAvailable(self): +class CondaUpdater(object): + # use conda to update to the latest package + + def __init__(self, conda_prefix=ANACONDA_PREFIX): + self._conda_prefix = conda_prefix + self._channel = 'nckz' + self._packages = ['gpi', 'gpi-core-nodes', 'gpi-docs'] + + self.checkConda() + + # Check for the current installed versions + self._current_versions = {} + for pkg in self._packages: + self._current_versions[pkg] = self.getInstalledPkgVersion(pkg) + + # Check for the latest versions online + self._latest_versions = {} + for pkg in self._packages: + if self._current_versions[pkg] is None: + self._latest_versions[pkg] = self.updatePkg(pkg, self._channel, dry_run=True, install=True) + else: + self._latest_versions[pkg] = self.updatePkg(pkg, self._channel, dry_run=True) + + def __str__(self): + msg = '' + + # updates + if len([ pkg for pkg in self._packages \ + if (self._latest_versions[pkg] is not None) and \ + (self._current_versions[pkg] is not None) ]): + msg += 'The following packages will be updated:\n' + for pkg in self._packages: + o = self._current_versions[pkg] + n = self._latest_versions[pkg] + msg += '\t'+str(o) + ' => ' + str(n) + '\n' + + # installs + if len([ pkg for pkg in self._packages \ + if (self._latest_versions[pkg] is not None) and \ + (self._current_versions[pkg] is None) ]): + msg += 'The following packages will be installed:\n' + for pkg in self._packages: + n = self._latest_versions[pkg] + msg += '\t'+str(n) + '\n' + + if msg == '': + msg = 'GPI is totes up to date.' + + return msg + + def checkConda(self): + cmd = self._conda_prefix+'/conda --version >/dev/null 2>&1' try: - subprocess.check_call('conda --version') - return True + subprocess.check_output(cmd, shell=True) + except subprocess.CalledProcessError as e: + print('Failed to execute conda, aborting...') + print(e.cmd, e.output) + raise except: print('\'conda\' failed to execute, aborting...') - return False + print(cmd) + raise + + def getInstalledPkgVersion(self, name): + cmd = self._conda_prefix+'/conda list -f '+name+' --json' + try: + output = subprocess.check_output(cmd, shell=True).decode('utf8') + conda = JSONStreamLoads(output).objects()[-1] + return conda[-1] + except: + print('Failed to retrieve installed package information on '+name+', skipping...') + print(cmd) + + def updateAllPkgs(self): + # Install or update all the packages that have been determined. + for pkg in self._packages: + # if there is no package (due to user changes) then install it + if self._current_versions[pkg] is None: + self.updatePkg(pkg, self._channel, install=True) + # if there is a latest version then update + if self._latest_versions[pkg] is not None: + self.updatePkg(pkg, self._channel) + + def updatePkg(self, name, channel, dry_run=False, install=False): + # Updates to the latest package and returns the package string. + # -dry_run will just return the latest package string. + # -install will install the package if its not currently installed. + + conda_sub = 'update' + if install: conda_sub = 'install' + dry_cmd = '' + if dry_run: dry_cmd = '--dry-run --no-deps' + cmd = self._conda_prefix+'/conda '+conda_sub+' -c '+channel+' '+name+' -y --json '+dry_cmd - def getLatestPkgVersion(self, name, channel): try: - output = subprocess.check_output('conda search -c '+channel+' -f '+name+' -o --json', shell=True) + output = subprocess.check_output(cmd, shell=True).decode('utf8') + conda = JSONStreamLoads(output).objects() + conda = conda[-1] + + if conda['success']: + if 'message' in conda: # if we're up to date + return + for pkg in conda['actions']['LINK']: + if pkg.startswith(name): + return pkg.split()[0] + else: + raise RuntimeError('conda returned a failed fetch.') except subprocess.CalledProcessError as e: - print(cmd, e.output) - sys.exit(e.returncode) + print('Failed to update to new package, aborting...') + print(e.cmd, e.output) + raise + except: + print('Failed to retrieve package update information, aborting...') + print(cmd) + raise + +def update(): - conda = json.loads(output) - print(conda) + updater = CondaUpdater() + print(updater) + updater.updateAllPkgs() - def getLatestGPIVersion(self): - pass + return -def update(): app = QtGui.QApplication(sys.argv) win = UpdateWindow() sys.exit(app.exec_()) - -if __name__ == '__main__': - update() From 1ce961b719d2d4f3a693fefcdda7517c97c28749 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 14:25:03 -0700 Subject: [PATCH 04/14] Reworked for better error checking and added pdone. --- lib/gpi/update.py | 156 +++++++++++++++++++++++++++++++++------------- 1 file changed, 111 insertions(+), 45 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 0120306a..24336a2c 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -26,6 +26,7 @@ # program. import os +import re import sys import json import subprocess @@ -81,80 +82,97 @@ def loadsByCharacter(self): pass return out -class UpdateWindow(QtGui.QWidget): - - def __init__(self): - super().__init__() - - okButton = QtGui.QPushButton("OK") - cancelButton = QtGui.QPushButton("Cancel") - - hbox = QtGui.QHBoxLayout() - hbox.addStretch(1) - hbox.addWidget(okButton) - hbox.addWidget(cancelButton) - - vbox = QtGui.QVBoxLayout() - vbox.addStretch(1) - vbox.addLayout(hbox) - - self.setLayout(vbox) - self.setGeometry(300, 300, 250, 150) - self.setWindowTitle('GPI Update') - self.show() - self.raise_() - -class CondaUpdater(object): - # use conda to update to the latest package +# use conda to update to the latest package +class CondaUpdater(QtCore.QObject): + pdone = Signal(int) def __init__(self, conda_prefix=ANACONDA_PREFIX): + super().__init__() self._conda_prefix = conda_prefix self._channel = 'nckz' self._packages = ['gpi', 'gpi-core-nodes', 'gpi-docs'] + self._packages_for_installation = [] + self._packages_for_update = [] + + self._current_versions = {} + self._latest_versions = {} + self.checkConda() + def _status_pdone(self, pct, cr=False): + end = '' + if pct == 100: + end = '\n' + print('\tSearching for package updates: ', pct, '%\r', end=end) + self.pdone.emit(pct) + + def getStatus(self): + + # total divisions are len(self._packages)*3 + pdone = 0 + divs = len(self._packages)*3 + 1 + step = int(100/divs) + self._status_pdone(pdone) + # Check for the current installed versions - self._current_versions = {} for pkg in self._packages: self._current_versions[pkg] = self.getInstalledPkgVersion(pkg) + pdone += step + self._status_pdone(pdone) # Check for the latest versions online - self._latest_versions = {} for pkg in self._packages: if self._current_versions[pkg] is None: self._latest_versions[pkg] = self.updatePkg(pkg, self._channel, dry_run=True, install=True) else: self._latest_versions[pkg] = self.updatePkg(pkg, self._channel, dry_run=True) + pdone += step + self._status_pdone(pdone) + + # Sort targets into 'install' or 'update' + for pkg in self._packages: + # updates - if there is both an installed version and new version + if (self._latest_versions[pkg] is not None) and \ + (self._current_versions[pkg] is not None): + self._packages_for_update.append(pkg) + # installs - if there is no installed version, the latest will be + # whatever is available. + if (self._latest_versions[pkg] is not None) and \ + (self._current_versions[pkg] is None): + self._packages_for_installation.append(pkg) + pdone += step + self._status_pdone(pdone) + + self._status_pdone(100) def __str__(self): msg = '' # updates - if len([ pkg for pkg in self._packages \ - if (self._latest_versions[pkg] is not None) and \ - (self._current_versions[pkg] is not None) ]): + if len(self._packages_for_update): msg += 'The following packages will be updated:\n' - for pkg in self._packages: + for pkg in self._packages_for_update: o = self._current_versions[pkg] n = self._latest_versions[pkg] msg += '\t'+str(o) + ' => ' + str(n) + '\n' # installs - if len([ pkg for pkg in self._packages \ - if (self._latest_versions[pkg] is not None) and \ - (self._current_versions[pkg] is None) ]): + if len(self._packages_for_installation): msg += 'The following packages will be installed:\n' - for pkg in self._packages: + for pkg in self._packages_for_installation: n = self._latest_versions[pkg] msg += '\t'+str(n) + '\n' if msg == '': - msg = 'GPI is totes up to date.' + msg = 'GPI is up to date.' return msg + def statusMessage(self): + return str(self) + def checkConda(self): cmd = self._conda_prefix+'/conda --version >/dev/null 2>&1' try: @@ -169,24 +187,46 @@ def checkConda(self): raise def getInstalledPkgVersion(self, name): - cmd = self._conda_prefix+'/conda list -f '+name+' --json' + cmd = self._conda_prefix+'/conda list --json' try: output = subprocess.check_output(cmd, shell=True).decode('utf8') conda = JSONStreamLoads(output).objects()[-1] - return conda[-1] + for pkg in conda: + m = re.match('('+name+')-([0-9]+\.*[0-9]*\.*[0-9]*)-(.*)', pkg) + if m: + return pkg except: print('Failed to retrieve installed package information on '+name+', skipping...') print(cmd) + raise + + def _updateAllPkgs_pdone(self, pct, cr=False): + end = '' + if pct == 100: + end = '\n' + print('\tUpdating packages: ', pct, '%\r', end=end) + self.pdone.emit(pct) def updateAllPkgs(self): + # total divisions are the installation list plus the update list + pdone = 0 + divs = len(self._packages_for_installation) + len(self._packages_for_update) + 1 + step = int(100/divs) + self._updateAllPkgs_pdone(pdone) + # Install or update all the packages that have been determined. - for pkg in self._packages: + for pkg in self._packages_for_installation: # if there is no package (due to user changes) then install it - if self._current_versions[pkg] is None: - self.updatePkg(pkg, self._channel, install=True) + self.updatePkg(pkg, self._channel, install=True) + pdone += step + self._updateAllPkgs_pdone(pdone) + for pkg in self._packages_for_update: # if there is a latest version then update - if self._latest_versions[pkg] is not None: - self.updatePkg(pkg, self._channel) + self.updatePkg(pkg, self._channel) + pdone += step + self._updateAllPkgs_pdone(pdone) + + self._updateAllPkgs_pdone(100) def updatePkg(self, name, channel, dry_run=False, install=False): # Updates to the latest package and returns the package string. @@ -211,7 +251,7 @@ def updatePkg(self, name, channel, dry_run=False, install=False): if pkg.startswith(name): return pkg.split()[0] else: - raise RuntimeError('conda returned a failed fetch.') + raise RuntimeError('conda returned a failure status.') except subprocess.CalledProcessError as e: print('Failed to update to new package, aborting...') print(e.cmd, e.output) @@ -221,10 +261,36 @@ def updatePkg(self, name, channel, dry_run=False, install=False): print(cmd) raise +class UpdateWindow(QtGui.QWidget): + + def __init__(self): + super().__init__() + + okButton = QtGui.QPushButton("OK") + cancelButton = QtGui.QPushButton("Cancel") + + hbox = QtGui.QHBoxLayout() + hbox.addStretch(1) + hbox.addWidget(okButton) + hbox.addWidget(cancelButton) + + vbox = QtGui.QVBoxLayout() + vbox.addStretch(1) + vbox.addLayout(hbox) + + self.setLayout(vbox) + + self.setGeometry(300, 300, 250, 150) + self.setWindowTitle('GPI Update') + self.show() + self.raise_() + + def update(): updater = CondaUpdater() - print(updater) + updater.getStatus() + print(updater.statusMessage()) updater.updateAllPkgs() return From e597b8790e34c84a5e6c7da017f41785b61ad382 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 16:42:09 -0700 Subject: [PATCH 05/14] Working update GUI. --- lib/gpi/runnable.py | 39 +++++++++++++++++++++ lib/gpi/update.py | 85 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 lib/gpi/runnable.py diff --git a/lib/gpi/runnable.py b/lib/gpi/runnable.py new file mode 100644 index 00000000..b841e758 --- /dev/null +++ b/lib/gpi/runnable.py @@ -0,0 +1,39 @@ +# Copyright (C) 2014 Dignity Health +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES +# AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE +# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC +# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR +# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT +# LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR +# MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE +# SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. + +# A quick way to spawn a thread for function objects and bound methods +# Example: +# ExecRunnable(Runnable()) + +from gpi import QtCore + +def ExecRunnable(runnable): + tp = QtCore.QThreadPool.globalInstance() + tp.start(runnable) + +class Runnable(QtCore.QRunnable): + def __init__(self, func): + super().__init__() + self.run = func + self.setAutoDelete(True) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 24336a2c..20ea494b 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -32,6 +32,8 @@ import subprocess from gpi import QtGui, QtCore, Signal +from .widgets import TextBox +from .runnable import ExecRunnable, Runnable # get the anaconda path to ensure that THIS installation is being updated ANACONDA_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA @@ -86,6 +88,9 @@ def loadsByCharacter(self): # use conda to update to the latest package class CondaUpdater(QtCore.QObject): pdone = Signal(int) + message = Signal(str) + _getStatus_done = Signal() + _updateAllPkgs_done = Signal() def __init__(self, conda_prefix=ANACONDA_PREFIX): super().__init__() @@ -105,8 +110,10 @@ def _status_pdone(self, pct, cr=False): end = '' if pct == 100: end = '\n' - print('\tSearching for package updates: ', pct, '%\r', end=end) + msg = 'Searching for package updates: '+str(pct)+'%' + print('\t'+msg+'\r', end=end) self.pdone.emit(pct) + self.message.emit('Searching for package updates...') def getStatus(self): @@ -146,6 +153,7 @@ def getStatus(self): self._status_pdone(pdone) self._status_pdone(100) + self._getStatus_done.emit() def __str__(self): msg = '' @@ -204,29 +212,39 @@ def _updateAllPkgs_pdone(self, pct, cr=False): end = '' if pct == 100: end = '\n' - print('\tUpdating packages: ', pct, '%\r', end=end) + msg = 'Updating packages: '+str(pct)+'%' + print('\t'+msg+'\r', end=end) self.pdone.emit(pct) + def numberOfUpdates(self): + return len(self._packages_for_installation) + len(self._packages_for_update) + def updateAllPkgs(self): # total divisions are the installation list plus the update list pdone = 0 - divs = len(self._packages_for_installation) + len(self._packages_for_update) + 1 + divs = self.numberOfUpdates() + 1 step = int(100/divs) self._updateAllPkgs_pdone(pdone) + message_hdr = 'Updating packages...\n\t' + # Install or update all the packages that have been determined. for pkg in self._packages_for_installation: # if there is no package (due to user changes) then install it self.updatePkg(pkg, self._channel, install=True) pdone += step self._updateAllPkgs_pdone(pdone) + self.message.emit(message_hdr+pkg) for pkg in self._packages_for_update: # if there is a latest version then update self.updatePkg(pkg, self._channel) pdone += step self._updateAllPkgs_pdone(pdone) + self.message.emit(message_hdr+pkg) self._updateAllPkgs_pdone(100) + self.message.emit('Package updates complete.') + self._updateAllPkgs_done.emit() def updatePkg(self, name, channel, dry_run=False, install=False): # Updates to the latest package and returns the package string. @@ -262,19 +280,36 @@ def updatePkg(self, name, channel, dry_run=False, install=False): raise class UpdateWindow(QtGui.QWidget): - + _startGetStatus = Signal() + def __init__(self): super().__init__() - okButton = QtGui.QPushButton("OK") - cancelButton = QtGui.QPushButton("Cancel") + self._updater = CondaUpdater() + self._updater._getStatus_done.connect(self.showStatus) + self._updater._getStatus_done.connect(self._showOKorUpdateButton) + self._updater._updateAllPkgs_done.connect(self._showCloseButton) + + self._pbar = QtGui.QProgressBar(self) + self._updater.pdone.connect(self._pdone) + + self._txtbox = TextBox('') + self._updater.message.connect(self._txtbox.set_val) + self._txtbox.set_val('Checking for updates...') + + self._okButton = QtGui.QPushButton("OK") + self._okButton.setVisible(False) + self._cancelButton = QtGui.QPushButton("Cancel") + self._cancelButton.clicked.connect(self.close) hbox = QtGui.QHBoxLayout() hbox.addStretch(1) - hbox.addWidget(okButton) - hbox.addWidget(cancelButton) + hbox.addWidget(self._okButton) + hbox.addWidget(self._cancelButton) vbox = QtGui.QVBoxLayout() + vbox.addWidget(self._txtbox) + vbox.addWidget(self._pbar) vbox.addStretch(1) vbox.addLayout(hbox) @@ -284,17 +319,39 @@ def __init__(self): self.setWindowTitle('GPI Update') self.show() self.raise_() + + ExecRunnable(Runnable(self._updater.getStatus)) + def _installUpdates(self): + self._okButton.setVisible(False) + ExecRunnable(Runnable(self._updater.updateAllPkgs)) -def update(): + def _pdone(self, pct): + self._pbar.setValue(pct) + if pct < 100: + self._pbar.setVisible(True) + else: + self._pbar.setVisible(False) + self._pbar.setValue(0) - updater = CondaUpdater() - updater.getStatus() - print(updater.statusMessage()) - updater.updateAllPkgs() + def showStatus(self): + self._txtbox.set_val(self._updater.statusMessage()) - return + def _showOKorUpdateButton(self): + if self._updater.numberOfUpdates(): + self._okButton.setText('Update') + self._okButton.setVisible(True) + self._okButton.clicked.connect(self._installUpdates) + else: + self._okButton.setVisible(True) + self._okButton.clicked.connect(self.close) + def _showCloseButton(self): + self._okButton.setVisible(False) + self._cancelButton.setText('Close') + +# For running as a separate application. +def update(): app = QtGui.QApplication(sys.argv) win = UpdateWindow() sys.exit(app.exec_()) From af33491a3e73d623fb5bcecee7e1a24eec0d7f4c Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 19:28:07 -0700 Subject: [PATCH 06/14] Changed 'Cancel' to 'Close' if there are no updates. --- lib/gpi/update.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 20ea494b..fffa0522 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -343,8 +343,8 @@ def _showOKorUpdateButton(self): self._okButton.setVisible(True) self._okButton.clicked.connect(self._installUpdates) else: - self._okButton.setVisible(True) - self._okButton.clicked.connect(self.close) + self._okButton.setVisible(False) + self._cancelButton.setText('Close') def _showCloseButton(self): self._okButton.setVisible(False) From 0f3f8ddb161153547b732c5be4ebd520106ac69b Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 20:16:10 -0700 Subject: [PATCH 07/14] Allow textbox to stretch. --- lib/gpi/update.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index fffa0522..329bbea4 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -308,9 +308,8 @@ def __init__(self): hbox.addWidget(self._cancelButton) vbox = QtGui.QVBoxLayout() - vbox.addWidget(self._txtbox) + vbox.addWidget(self._txtbox, 1) vbox.addWidget(self._pbar) - vbox.addStretch(1) vbox.addLayout(hbox) self.setLayout(vbox) From 153eb0bb14d70f3cdf5c2fc98f29eb9d8da89ce6 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 20:42:07 -0700 Subject: [PATCH 08/14] Made the progress bar look more like the textbox. --- lib/gpi/update.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 329bbea4..59ec3393 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -121,7 +121,7 @@ def getStatus(self): pdone = 0 divs = len(self._packages)*3 + 1 step = int(100/divs) - self._status_pdone(pdone) + self._status_pdone(1) # Check for the current installed versions for pkg in self._packages: @@ -160,7 +160,7 @@ def __str__(self): # updates if len(self._packages_for_update): - msg += 'The following packages will be updated:\n' + msg += 'The following packages will be updated:\n\n' for pkg in self._packages_for_update: o = self._current_versions[pkg] n = self._latest_versions[pkg] @@ -168,7 +168,7 @@ def __str__(self): # installs if len(self._packages_for_installation): - msg += 'The following packages will be installed:\n' + msg += 'The following packages will be installed:\n\n' for pkg in self._packages_for_installation: n = self._latest_versions[pkg] msg += '\t'+str(n) + '\n' @@ -224,7 +224,7 @@ def updateAllPkgs(self): pdone = 0 divs = self.numberOfUpdates() + 1 step = int(100/divs) - self._updateAllPkgs_pdone(pdone) + self._updateAllPkgs_pdone(1) message_hdr = 'Updating packages...\n\t' @@ -290,7 +290,22 @@ def __init__(self): self._updater._getStatus_done.connect(self._showOKorUpdateButton) self._updater._updateAllPkgs_done.connect(self._showCloseButton) + style = ''' + QProgressBar { + background-color: rgb(226,226,226); + border: 1px solid rgb(222,222,222); + border-radius: 3px; + text-align: center; + } + + QProgressBar::chunk { + background-color: #0099FF; + height: 15px; + width: 1px; + } + ''' self._pbar = QtGui.QProgressBar(self) + self._pbar.setStyleSheet(style) self._updater.pdone.connect(self._pdone) self._txtbox = TextBox('') @@ -314,7 +329,7 @@ def __init__(self): self.setLayout(vbox) - self.setGeometry(300, 300, 250, 150) + self.setGeometry(300, 300, 400, 300) self.setWindowTitle('GPI Update') self.show() self.raise_() From c9d7fec0d4de530e7d253218f2dd4419fd4257f1 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 21:45:06 -0700 Subject: [PATCH 09/14] Trying to work with the seemingly fixed chunk border radius. -2pix on the background border radius looks more like the chunk border radius --- lib/gpi/update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 59ec3393..7fec335a 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -294,7 +294,7 @@ def __init__(self): QProgressBar { background-color: rgb(226,226,226); border: 1px solid rgb(222,222,222); - border-radius: 3px; + border-radius: 2px; text-align: center; } From 4eba1924c599061d889168b8d527faffaace43bd Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 22:14:53 -0700 Subject: [PATCH 10/14] Added updater option to main menu. --- lib/gpi/mainWindow.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/gpi/mainWindow.py b/lib/gpi/mainWindow.py index b748a9f4..9bcf5e67 100644 --- a/lib/gpi/mainWindow.py +++ b/lib/gpi/mainWindow.py @@ -40,6 +40,7 @@ from .logger import manager from .widgets import DisplayBox, TextBox, TextEdit from .sysspecs import Specs +from .update import UpdateWindow # start logger for this module log = manager.getLogger(__name__) @@ -452,12 +453,20 @@ def createMenus(self): self.helpMenu = QtGui.QMenu("&Help", self) aboutAction = self.helpMenu.addAction("&About") self.connect(aboutAction, QtCore.SIGNAL("triggered()"), self.about) + self.checkForUpdate = QtGui.QAction("Check For Updates...", self, triggered=self.openUpdater) + self.checkForUpdate.setMenuRole(QtGui.QAction.ApplicationSpecificRole) + self.helpMenu.addAction(self.checkForUpdate) self.helpMenu_openDocs = QtGui.QAction("Documentation", self, triggered=self.openWebsite) self.helpMenu.addAction(self.helpMenu_openDocs) self.helpMenu_openDocs = QtGui.QAction("Examples", self, triggered=self.openExamplesFolder) self.helpMenu.addAction(self.helpMenu_openDocs) self.menuBar().addMenu(self.helpMenu) + def openUpdater(self): + self._updateWin = UpdateWindow() + self._updateWin.show() + self._updateWin.raise_() + # TODO: move this and others like it to a common help-object that can errorcheck. def openWebsite(self): if not QtGui.QDesktopServices.openUrl(QtCore.QUrl('http://docs.gpilab.com')): From c5f23d380e118156ca2cb90b155b99a0187f7848 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 22:59:10 -0700 Subject: [PATCH 11/14] Added relaunch to updater. --- lib/gpi/mainWindow.py | 2 +- lib/gpi/update.py | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/gpi/mainWindow.py b/lib/gpi/mainWindow.py index 9bcf5e67..9df88e54 100644 --- a/lib/gpi/mainWindow.py +++ b/lib/gpi/mainWindow.py @@ -463,7 +463,7 @@ def createMenus(self): self.menuBar().addMenu(self.helpMenu) def openUpdater(self): - self._updateWin = UpdateWindow() + self._updateWin = UpdateWindow(dry_run=False) self._updateWin.show() self._updateWin.raise_() diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 7fec335a..128eae28 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -92,8 +92,9 @@ class CondaUpdater(QtCore.QObject): _getStatus_done = Signal() _updateAllPkgs_done = Signal() - def __init__(self, conda_prefix=ANACONDA_PREFIX): + def __init__(self, conda_prefix=ANACONDA_PREFIX, dry_run=False): super().__init__() + self._dry_run = dry_run self._conda_prefix = conda_prefix self._channel = 'nckz' self._packages = ['gpi', 'gpi-core-nodes', 'gpi-docs'] @@ -173,6 +174,10 @@ def __str__(self): n = self._latest_versions[pkg] msg += '\t'+str(n) + '\n' + if self.numberOfUpdates(): + msg += '\nGPI will be automatically restarted after updating.' \ + + ' Make sure your networks are saved before proceeding.' + if msg == '': msg = 'GPI is up to date.' @@ -220,6 +225,11 @@ def numberOfUpdates(self): return len(self._packages_for_installation) + len(self._packages_for_update) def updateAllPkgs(self): + if self._dry_run: + self.message.emit('Package updates complete.') + self._updateAllPkgs_done.emit() + return + # total divisions are the installation list plus the update list pdone = 0 divs = self.numberOfUpdates() + 1 @@ -282,13 +292,13 @@ def updatePkg(self, name, channel, dry_run=False, install=False): class UpdateWindow(QtGui.QWidget): _startGetStatus = Signal() - def __init__(self): + def __init__(self, dry_run=False): super().__init__() - self._updater = CondaUpdater() + self._updater = CondaUpdater(dry_run=dry_run) self._updater._getStatus_done.connect(self.showStatus) self._updater._getStatus_done.connect(self._showOKorUpdateButton) - self._updater._updateAllPkgs_done.connect(self._showCloseButton) + self._updater._updateAllPkgs_done.connect(self._relaunchGPI) style = ''' QProgressBar { @@ -309,6 +319,8 @@ def __init__(self): self._updater.pdone.connect(self._pdone) self._txtbox = TextBox('') + self._txtbox.set_wordwrap(True) + self._txtbox.set_openExternalLinks(True) self._updater.message.connect(self._txtbox.set_val) self._txtbox.set_val('Checking for updates...') @@ -353,16 +365,17 @@ def showStatus(self): def _showOKorUpdateButton(self): if self._updater.numberOfUpdates(): - self._okButton.setText('Update') + self._okButton.setText('Update && Relaunch') self._okButton.setVisible(True) self._okButton.clicked.connect(self._installUpdates) else: self._okButton.setVisible(False) self._cancelButton.setText('Close') - def _showCloseButton(self): - self._okButton.setVisible(False) - self._cancelButton.setText('Close') + def _relaunchGPI(self): + args = sys.argv[:] + args.insert(0, sys.executable) + os.execv(sys.executable, args) # For running as a separate application. def update(): From ca37967ca1d1c64f0a281006a0a13f3f670566f1 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 23:54:14 -0700 Subject: [PATCH 12/14] Cleaned up display text. -added rich text to the update display --- lib/gpi/update.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 128eae28..df1b5d4f 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -158,24 +158,27 @@ def getStatus(self): def __str__(self): msg = '' + tab = '    ' # updates if len(self._packages_for_update): - msg += 'The following packages will be updated:\n\n' + msg += 'The following packages will be updated:

' for pkg in self._packages_for_update: o = self._current_versions[pkg] n = self._latest_versions[pkg] - msg += '\t'+str(o) + ' => ' + str(n) + '\n' + msg += tab+str(o) + '  ➪  ' + str(n) + '
' # installs if len(self._packages_for_installation): - msg += 'The following packages will be installed:\n\n' + if msg != '': + msg += '

' + msg += 'The following packages will be installed:

' for pkg in self._packages_for_installation: n = self._latest_versions[pkg] - msg += '\t'+str(n) + '\n' + msg += tab+str(n) + '
' if self.numberOfUpdates(): - msg += '\nGPI will be automatically restarted after updating.' \ + msg += '

GPI will be automatically restarted after updating.' \ + ' Make sure your networks are saved before proceeding.' if msg == '': @@ -236,7 +239,8 @@ def updateAllPkgs(self): step = int(100/divs) self._updateAllPkgs_pdone(1) - message_hdr = 'Updating packages...\n\t' + tab = '    ' + message_hdr = 'Updating packages...
'+tab # Install or update all the packages that have been determined. for pkg in self._packages_for_installation: @@ -319,6 +323,7 @@ def __init__(self, dry_run=False): self._updater.pdone.connect(self._pdone) self._txtbox = TextBox('') + self._txtbox.wdg.setTextFormat(QtCore.Qt.RichText) self._txtbox.set_wordwrap(True) self._txtbox.set_openExternalLinks(True) self._updater.message.connect(self._txtbox.set_val) From bc7b8e9f709deedb8185858075098cabbd53c37b Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 31 Oct 2015 00:16:59 -0700 Subject: [PATCH 13/14] Added final success message before relaunching. --- lib/gpi/update.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index df1b5d4f..36420c7a 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -29,6 +29,7 @@ import re import sys import json +import time import subprocess from gpi import QtGui, QtCore, Signal @@ -229,7 +230,7 @@ def numberOfUpdates(self): def updateAllPkgs(self): if self._dry_run: - self.message.emit('Package updates complete.') + self.message.emit('Package updates complete. Relaunching...') self._updateAllPkgs_done.emit() return @@ -257,7 +258,7 @@ def updateAllPkgs(self): self.message.emit(message_hdr+pkg) self._updateAllPkgs_pdone(100) - self.message.emit('Package updates complete.') + self.message.emit('Package updates complete. Relaunching...') self._updateAllPkgs_done.emit() def updatePkg(self, name, channel, dry_run=False, install=False): @@ -346,7 +347,7 @@ def __init__(self, dry_run=False): self.setLayout(vbox) - self.setGeometry(300, 300, 400, 300) + self.setGeometry(300, 300, 400, 350) self.setWindowTitle('GPI Update') self.show() self.raise_() @@ -378,6 +379,9 @@ def _showOKorUpdateButton(self): self._cancelButton.setText('Close') def _relaunchGPI(self): + self.update() + QtGui.QApplication.processEvents() # allow gui to update + time.sleep(5) args = sys.argv[:] args.insert(0, sys.executable) os.execv(sys.executable, args) From 7ddd1f27da975bcc52360ff16bd902f33b0bcf07 Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 31 Oct 2015 12:48:15 -0700 Subject: [PATCH 14/14] Added error handling to UpdateWindow() and updated public methods. --- lib/gpi/update.py | 65 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 36420c7a..570bb707 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -90,8 +90,9 @@ def loadsByCharacter(self): class CondaUpdater(QtCore.QObject): pdone = Signal(int) message = Signal(str) - _getStatus_done = Signal() - _updateAllPkgs_done = Signal() + failed = Signal(str) + getStatus_done = Signal() + updateAllPkgs_done = Signal() def __init__(self, conda_prefix=ANACONDA_PREFIX, dry_run=False): super().__init__() @@ -106,7 +107,6 @@ def __init__(self, conda_prefix=ANACONDA_PREFIX, dry_run=False): self._current_versions = {} self._latest_versions = {} - self.checkConda() def _status_pdone(self, pct, cr=False): end = '' @@ -118,6 +118,15 @@ def _status_pdone(self, pct, cr=False): self.message.emit('Searching for package updates...') def getStatus(self): + try: + self._getStatus() + self.getStatus_done.emit() + except: + self.failed.emit('Failed to fetch updates.') + + def _getStatus(self): + + self.checkConda() # total divisions are len(self._packages)*3 pdone = 0 @@ -155,7 +164,6 @@ def getStatus(self): self._status_pdone(pdone) self._status_pdone(100) - self._getStatus_done.emit() def __str__(self): msg = '' @@ -229,9 +237,15 @@ def numberOfUpdates(self): return len(self._packages_for_installation) + len(self._packages_for_update) def updateAllPkgs(self): + try: + self._updateAllPkgs() + self.updateAllPkgs_done.emit() + except: + self.failed.emit('Failed to install updates.') + + def _updateAllPkgs(self): if self._dry_run: self.message.emit('Package updates complete. Relaunching...') - self._updateAllPkgs_done.emit() return # total divisions are the installation list plus the update list @@ -259,7 +273,6 @@ def updateAllPkgs(self): self._updateAllPkgs_pdone(100) self.message.emit('Package updates complete. Relaunching...') - self._updateAllPkgs_done.emit() def updatePkg(self, name, channel, dry_run=False, install=False): # Updates to the latest package and returns the package string. @@ -301,9 +314,10 @@ def __init__(self, dry_run=False): super().__init__() self._updater = CondaUpdater(dry_run=dry_run) - self._updater._getStatus_done.connect(self.showStatus) - self._updater._getStatus_done.connect(self._showOKorUpdateButton) - self._updater._updateAllPkgs_done.connect(self._relaunchGPI) + self._updater.getStatus_done.connect(self.showStatus) + self._updater.getStatus_done.connect(self.showOKorUpdateButton) + self._updater.failed.connect(self.initFailureMode) + self._updater.updateAllPkgs_done.connect(self.relaunchProc) style = ''' QProgressBar { @@ -321,7 +335,7 @@ def __init__(self, dry_run=False): ''' self._pbar = QtGui.QProgressBar(self) self._pbar.setStyleSheet(style) - self._updater.pdone.connect(self._pdone) + self._updater.pdone.connect(self.pdone) self._txtbox = TextBox('') self._txtbox.wdg.setTextFormat(QtCore.Qt.RichText) @@ -354,11 +368,11 @@ def __init__(self, dry_run=False): ExecRunnable(Runnable(self._updater.getStatus)) - def _installUpdates(self): + def installUpdates(self): self._okButton.setVisible(False) ExecRunnable(Runnable(self._updater.updateAllPkgs)) - def _pdone(self, pct): + def pdone(self, pct): self._pbar.setValue(pct) if pct < 100: self._pbar.setVisible(True) @@ -369,22 +383,31 @@ def _pdone(self, pct): def showStatus(self): self._txtbox.set_val(self._updater.statusMessage()) - def _showOKorUpdateButton(self): + def initFailureMode(self, msg): + self._pbar.setVisible(False) + self._txtbox.set_val(msg) + self._okButton.setVisible(False) + self._cancelButton.setText('Close') + + def showOKorUpdateButton(self): if self._updater.numberOfUpdates(): self._okButton.setText('Update && Relaunch') self._okButton.setVisible(True) - self._okButton.clicked.connect(self._installUpdates) + self._okButton.clicked.connect(self.installUpdates) else: self._okButton.setVisible(False) self._cancelButton.setText('Close') - def _relaunchGPI(self): - self.update() - QtGui.QApplication.processEvents() # allow gui to update - time.sleep(5) - args = sys.argv[:] - args.insert(0, sys.executable) - os.execv(sys.executable, args) + def relaunchProc(self): + try: + self.update() + QtGui.QApplication.processEvents() # allow gui to update + time.sleep(5) + args = sys.argv[:] + args.insert(0, sys.executable) + os.execv(sys.executable, args) + except: + self.initFailureMode('Failed to relaunch GPI.') # For running as a separate application. def update():