Starting PyPromise
This commit is contained in:
parent
b33e1329db
commit
f734e40ad9
5 changed files with 157 additions and 44 deletions
|
@ -45,17 +45,17 @@ Popup {
|
|||
property bool changelogNeedsFetching: true
|
||||
|
||||
onAboutToShow: if(changelogNeedsFetching) {
|
||||
Helper.fetchChangelog()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Helper
|
||||
function onChangelogFetched(chl) {
|
||||
changelogNeedsFetching = false;
|
||||
changelog.text = chl
|
||||
Helper.fetchChangelog().then((fetchedText) => {
|
||||
changelogNeedsFetching = false
|
||||
changelog.text = fetchedText
|
||||
changelogView.contentItem.implicitHeight = changelog.height
|
||||
// console.log(changelog.height, changelogView.contentItem.implicitHeight)
|
||||
}
|
||||
}, (error) => {
|
||||
const e = qsTranslate("changelog", "Could not fetch update: {}.").replace('{}', error)
|
||||
console.error(e)
|
||||
changelogNeedsFetching = false
|
||||
changelog.text = e
|
||||
changelogView.contentItem.implicitHeight = changelog.height
|
||||
})
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
|
|
|
@ -29,13 +29,16 @@ from urllib.error import HTTPError, URLError
|
|||
|
||||
from LogarithmPlotter import __VERSION__
|
||||
from LogarithmPlotter.util import config
|
||||
from LogarithmPlotter.util.promise import PyPromise
|
||||
|
||||
SHOW_GUI_MESSAGES = "--test-build" not in argv
|
||||
CHANGELOG_VERSION = __VERSION__
|
||||
CHANGELOG_CACHE_PATH = path.join(path.dirname(path.realpath(__file__)), "CHANGELOG.md")
|
||||
|
||||
|
||||
class InvalidFileException(Exception): pass
|
||||
|
||||
|
||||
def show_message(msg: str) -> None:
|
||||
"""
|
||||
Shows a GUI message if GUI messages are enabled
|
||||
|
@ -46,31 +49,30 @@ def show_message(msg: str) -> None:
|
|||
raise InvalidFileException(msg)
|
||||
|
||||
|
||||
def fetch_changelog():
|
||||
msg_text = "Unknown changelog error."
|
||||
try:
|
||||
# Fetching version
|
||||
r = urlopen("https://api.ad5001.eu/changelog/logarithmplotter/?version=" + CHANGELOG_VERSION)
|
||||
lines = r.readlines()
|
||||
r.close()
|
||||
msg_text = "".join(map(lambda x: x.decode('utf-8'), lines)).strip()
|
||||
except HTTPError as e:
|
||||
msg_text = QCoreApplication.translate("changelog", "Could not fetch changelog: Server error {}.").format(
|
||||
str(e.code))
|
||||
except URLError as e:
|
||||
msg_text = QCoreApplication.translate("changelog", "Could not fetch update: {}.").format(str(e.reason))
|
||||
return msg_text
|
||||
|
||||
class ChangelogFetcher(QRunnable):
|
||||
def __init__(self, helper):
|
||||
QRunnable.__init__(self)
|
||||
self.helper = helper
|
||||
|
||||
def run(self):
|
||||
msg_text = "Unknown changelog error."
|
||||
try:
|
||||
# Fetching version
|
||||
r = urlopen("https://api.ad5001.eu/changelog/logarithmplotter/?version=" + CHANGELOG_VERSION)
|
||||
lines = r.readlines()
|
||||
r.close()
|
||||
msg_text = "".join(map(lambda x: x.decode('utf-8'), lines)).strip()
|
||||
except HTTPError as e:
|
||||
msg_text = QCoreApplication.translate("changelog", "Could not fetch changelog: Server error {}.").format(
|
||||
str(e.code))
|
||||
except URLError as e:
|
||||
msg_text = QCoreApplication.translate("changelog", "Could not fetch update: {}.").format(str(e.reason))
|
||||
self.helper.changelogFetched.emit(msg_text)
|
||||
def read_changelog():
|
||||
f = open(CHANGELOG_CACHE_PATH, 'r', -1)
|
||||
data = f.read().strip()
|
||||
f.close()
|
||||
return data
|
||||
|
||||
|
||||
class Helper(QObject):
|
||||
changelogFetched = Signal(str)
|
||||
|
||||
def __init__(self, cwd: str, tmpfile: str):
|
||||
QObject.__init__(self)
|
||||
self.cwd = cwd
|
||||
|
@ -150,15 +152,14 @@ class Helper(QObject):
|
|||
msg = QCoreApplication.translate('main', "Built with PySide6 (Qt) v{} and python v{}")
|
||||
return msg.format(PySide6_version, sys_version.split("\n")[0])
|
||||
|
||||
@Slot()
|
||||
@Slot(result=PyPromise)
|
||||
def fetchChangelog(self):
|
||||
changelog_cache_path = path.join(path.dirname(path.realpath(__file__)), "CHANGELOG.md")
|
||||
if path.exists(changelog_cache_path):
|
||||
"""
|
||||
Fetches the changelog and returns a Promise.
|
||||
"""
|
||||
if path.exists(CHANGELOG_CACHE_PATH):
|
||||
# We have a cached version of the changelog, for env that don't have access to the internet.
|
||||
f = open(changelog_cache_path);
|
||||
self.changelogFetched.emit("".join(f.readlines()).strip())
|
||||
f.close()
|
||||
return PyPromise(read_changelog)
|
||||
else:
|
||||
# Fetch it from the internet.
|
||||
runnable = ChangelogFetcher(self)
|
||||
QThreadPool.globalInstance().start(runnable)
|
||||
return PyPromise(fetch_changelog)
|
||||
|
|
|
@ -16,13 +16,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from re import Pattern
|
||||
from typing import Callable
|
||||
from PySide6.QtCore import QMetaObject, QObject, QDateTime
|
||||
from PySide6.QtQml import QJSValue
|
||||
|
||||
class InvalidAttributeValueException(Exception): pass
|
||||
class NotAPrimitiveException(Exception): pass
|
||||
|
||||
class Function: pass
|
||||
class URL: pass
|
||||
|
||||
class PyJSValue:
|
||||
|
@ -78,7 +78,7 @@ class PyJSValue:
|
|||
matcher = [
|
||||
(lambda: self.qjs_value.isArray(), list),
|
||||
(lambda: self.qjs_value.isBool(), bool),
|
||||
(lambda: self.qjs_value.isCallable(), Function),
|
||||
(lambda: self.qjs_value.isCallable(), Callable),
|
||||
(lambda: self.qjs_value.isDate(), QDateTime),
|
||||
(lambda: self.qjs_value.isError(), Exception),
|
||||
(lambda: self.qjs_value.isNull(), None),
|
||||
|
@ -104,3 +104,5 @@ class PyJSValue:
|
|||
if self.type() not in [bool, float, str, None]:
|
||||
raise NotAPrimitiveException()
|
||||
return self.qjs_value.toPrimitive().toVariant()
|
||||
|
||||
|
||||
|
|
|
@ -16,18 +16,17 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from PySide6.QtCore import QObject, Slot, Property, QCoreApplication
|
||||
from PySide6.QtCore import QObject, Slot, Property, QCoreApplication, Signal
|
||||
from PySide6.QtGui import QImage, QColor
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from os import path, remove, environ, makedirs
|
||||
from os import path, remove, makedirs
|
||||
from string import Template
|
||||
from subprocess import Popen, TimeoutExpired, PIPE
|
||||
from hashlib import sha512
|
||||
from shutil import which
|
||||
from sys import argv
|
||||
|
||||
|
||||
"""
|
||||
Searches for a valid Latex and DVIPNG (http://savannah.nongnu.org/projects/dvipng/)
|
||||
installation and collects the binary path in the DVIPNG_PATH variable.
|
||||
|
@ -76,6 +75,7 @@ class Latex(QObject):
|
|||
dvipng to be installed on the system.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, cache_path):
|
||||
QObject.__init__(self)
|
||||
self.tempdir = path.join(cache_path, "latex")
|
||||
|
|
110
runtime-pyside6/LogarithmPlotter/util/promise.py
Normal file
110
runtime-pyside6/LogarithmPlotter/util/promise.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
"""
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from typing import Callable
|
||||
|
||||
from PySide6.QtCore import QRunnable, Signal, QObject, Slot, QThreadPool
|
||||
from PySide6.QtQml import QJSValue
|
||||
|
||||
from LogarithmPlotter.util.js import PyJSValue
|
||||
|
||||
|
||||
class InvalidReturnValue(Exception): pass
|
||||
|
||||
|
||||
class PyPromiseRunner(QRunnable):
|
||||
"""
|
||||
QRunnable for running Promises in different threads.
|
||||
"""
|
||||
def __init__(self, runner, promise):
|
||||
QRunnable.__init__(self)
|
||||
self.runner = runner
|
||||
self.promise = promise
|
||||
print("Initialized", self.runner)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
data = self.runner()
|
||||
if isinstance(data, QObject):
|
||||
data = data
|
||||
elif type(data) in [int, str, float, bool, bytes]:
|
||||
data = QJSValue(data)
|
||||
elif data is None:
|
||||
data = QJSValue.SpecialValue.UndefinedValue
|
||||
elif isinstance(data, QJSValue):
|
||||
data = data
|
||||
elif isinstance(data, PyJSValue):
|
||||
data = data.qjs_value
|
||||
else:
|
||||
raise InvalidReturnValue("Must return either a primitive, a valid QObject, JS Value, or None.")
|
||||
self.promise.finished.emit(data)
|
||||
except Exception as e:
|
||||
self.promise.errored.emit(repr(e))
|
||||
|
||||
|
||||
class PyPromise(QObject):
|
||||
"""
|
||||
Asynchronous Promise-like object meant to interface between Python and Javascript easily.
|
||||
Runs to_run in another thread, and calls fulfilled (populated by then) with its return value.
|
||||
"""
|
||||
finished = Signal((QJSValue,), (QObject,))
|
||||
errored = Signal(Exception)
|
||||
|
||||
def __init__(self, to_run: Callable):
|
||||
QObject.__init__(self)
|
||||
self._fulfills = []
|
||||
self._rejects = []
|
||||
self.finished.connect(self._fulfill)
|
||||
self.errored.connect(self._reject)
|
||||
self._runner = PyPromiseRunner(to_run, self)
|
||||
QThreadPool.globalInstance().start(self._runner)
|
||||
|
||||
|
||||
@Slot(QJSValue, result=QObject)
|
||||
@Slot(QJSValue, QJSValue, result=QObject)
|
||||
def then(self, on_fulfill: QJSValue | Callable, on_reject: QJSValue | Callable = None):
|
||||
"""
|
||||
Adds listeners for both fulfilment and catching errors of the Promise.
|
||||
"""
|
||||
if isinstance(on_fulfill, QJSValue):
|
||||
self._fulfills.append(PyJSValue(on_fulfill))
|
||||
elif isinstance(on_fulfill, Callable):
|
||||
self._fulfills.append(on_fulfill)
|
||||
if isinstance(on_reject, QJSValue):
|
||||
self._rejects.append(PyJSValue(on_reject))
|
||||
elif isinstance(on_reject, Callable):
|
||||
self._rejects.append(on_reject)
|
||||
|
||||
@Slot(QJSValue)
|
||||
@Slot(QObject)
|
||||
def _fulfill(self, data):
|
||||
no_return = [None, QJSValue.SpecialValue.UndefinedValue]
|
||||
for on_fulfill in self._fulfills:
|
||||
try:
|
||||
result = on_fulfill(data)
|
||||
data = result if result not in no_return else data # Forward data.
|
||||
except Exception as e:
|
||||
self._reject(repr(e))
|
||||
break
|
||||
|
||||
@Slot(QJSValue)
|
||||
@Slot(str)
|
||||
def _reject(self, error):
|
||||
no_return = [None, QJSValue.SpecialValue.UndefinedValue]
|
||||
for on_reject in self._rejects:
|
||||
result = on_reject(error)
|
||||
error = result if result not in no_return else error # Forward data.
|
Loading…
Reference in a new issue