nmreval/src/gui_qt/lib/utils.py

217 lines
6.9 KiB
Python

import sys
from os import getenv, stat
import hashlib
import subprocess
from datetime import datetime
from contextlib import contextmanager
import requests
from numpy import linspace
from scipy.interpolate import interp1d
from gui_qt.Qt import QtGui, QtWidgets, QtCore
@contextmanager
def busy_cursor():
try:
cursor = QtGui.QCursor(QtCore.Qt.ForbiddenCursor)
QtWidgets.QApplication.setOverrideCursor(cursor)
yield
finally:
QtWidgets.QApplication.restoreOverrideCursor()
class RdBuCMap:
# taken from Excel sheet from colorbrewer.org
_rdbu = [
(103, 0, 31),
(178, 24, 43),
(214, 96, 77),
(244, 165, 130),
(253, 219, 199),
(247, 247, 247),
(209, 229, 240),
(146, 197, 222),
(67, 147, 195),
(33, 102, 172),
(5, 48, 97)
]
def __init__(self, vmin=-1., vmax=1.):
self.min = vmin
self.max = vmax
self.spline = [interp1d(linspace(self.max, self.min, num=11), [rgb[i] for rgb in RdBuCMap._rdbu])
for i in range(3)]
def color(self, val: float):
if val > self.max:
col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[0])
elif val < self.min:
col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[-1])
else:
col = QtGui.QColor.fromRgb(*(float(self.spline[i](val)) for i in range(3)))
return col
class UpdateDialog(QtWidgets.QDialog):
host = 'mirror.infra.pkm'
bucket = 'nmreval'
version = 'NMReval-latest-x86_64'
def __init__(self, filename: str = None, parent=None):
super().__init__(parent=parent)
self._init_ui()
if filename is None:
filename = getenv('APPIMAGE')
self._appfile = filename
self._url_zsync = f'http://{self.host}/{self.bucket}/{self.version}.AppImage.zsync'
self.process = QtCore.QProcess(self)
self.look_for_updates()
def _init_ui(self):
self.setWindowTitle('Updates')
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel()
layout.addWidget(self.label)
layout.addSpacing(10)
self.dialog_button = QtWidgets.QDialogButtonBox()
self.dialog_button.accepted.connect(self.accept)
self.dialog_button.rejected.connect(self.reject)
layout.addWidget(self.dialog_button)
self.setLayout(layout)
def look_for_updates(self):
# Download zsync file of latest Appimage, look for SHA-1 hash and compare with hash of AppImage
m_time_zsync, checksum_zsync = self.get_zsync()
m_time_file, checksum_file = self.get_appimage_info()
if m_time_zsync is None:
label_text = '<p>Retrieval of version information failed.</p>' \
'<p>Please try later (or complain to people that it does not work).</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Close
else:
label_text = f'<p>Found most recent update: {m_time_zsync.strftime("%d %B %Y %H:%M")}</p>'
if m_time_file is None:
label_text += 'No AppImage file found, press Ok to downlaod latest version.'
dialog_bttns = QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Close
else:
label_text += f'<p>Date of used AppImage: {m_time_file.strftime("%d %B %Y %H:%M")}</p>'
if not ((checksum_file is not None) and (checksum_zsync is not None)):
label_text += 'Could not determine if this version is newer, please update manually (if necessary).'
dialog_bttns = QtWidgets.QDialogButtonBox.Close
elif checksum_file != checksum_zsync:
label_text += f'<p>Newer version available. <b>Update?</b></p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel
else:
label_text += f'<p>Version is already the newest</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Close
self.label.setText(label_text)
self.dialog_button.setStandardButtons(dialog_bttns)
def get_zsync(self):
m_time_zsync = None
checksum_zsync = None
zsync_file = None
try:
response = requests.get(self._url_zsync)
if response.status_code == requests.codes['\o/']:
zsync_file = response.content
except Exception as e:
pass
if zsync_file is not None:
for line in zsync_file.split(b'\n'):
try:
kw, val = line.split(b': ')
if kw == b'MTime':
m_time_zsync = datetime.strptime(str(val, encoding='utf-8'), '%a, %d %b %Y %H:%M:%S %z').astimezone(None)
elif kw == b'SHA-1':
checksum_zsync = str(val, encoding='utf-8')
except ValueError:
# stop when empty line is reached
break
return m_time_zsync, checksum_zsync
def get_appimage_info(self):
if self._appfile is None:
return None, None
stat_mtime = stat(self._appfile).st_mtime
m_time_file = datetime.fromtimestamp(stat_mtime).replace(microsecond=0)
with open(self._appfile, 'rb') as f:
checksum_file = hashlib.sha1(f.read()).hexdigest()
return m_time_file, checksum_file
def update_appimage(self):
if self._appfile is None:
args = [self._url_zsync]
else:
args = ['-i', self._appfile, self._url_zsync]
self.process.readyReadStandardOutput.connect(self.onReadyReadStandardOutput)
self.process.readyReadStandardError.connect(self.onReadyReadStandardOutput)
self.process.start('zsync', args)
if not self.process.waitForFinished():
return False
return True
def onReadyReadStandardOutput(self):
result = self.process.readAllStandardOutput().data().decode()
self.label.setText(result)
def accept(self):
if self.update_appimage():
_ = QtWidgets.QMessageBox.information(self, 'Updates', 'Download finished. Execute AppImage for new version.')
super().accept()
def open_bug_report():
form_entries = {
'description': 'Please state the nature of the medical emergency.',
'title': 'Everything is awesome?',
'assign[0]': 'dominik',
'subscribers[0]': 'dominik',
'tag': 'nmreval',
'priority': 'normal',
'status': 'open',
}
full_url = 'https://chaos3.fkp.physik.tu-darmstadt.de/maniphest/task/edit/?'
for k, v in form_entries.items():
full_url += f'{k}={v}&'
full_url.replace(' ', '+')
import webbrowser
webbrowser.open(full_url)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
w = UpdateDialog()
w.show()
sys.exit(app.exec())