Rework startup and Application-Engine relation
- Application and Engine will be started by __init__.run() independently - Exiting app will disconnect clients - Signals like SIGINT (Ctrl-C) are now handled for proper exit
This commit is contained in:
parent
5ad13aed7d
commit
12ce4cdb30
@ -4,8 +4,6 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
# The disk cache is responsible for multiple display bugs when running
|
# The disk cache is responsible for multiple display bugs when running
|
||||||
# the app for the first time/when cache needs to be recompiled, on top
|
# the app for the first time/when cache needs to be recompiled, on top
|
||||||
# of litering the source folders with .qmlc files.
|
# of litering the source folders with .qmlc files.
|
||||||
@ -13,5 +11,11 @@ os.environ["QML_DISABLE_DISK_CACHE"] = "1"
|
|||||||
|
|
||||||
|
|
||||||
def run() -> None:
|
def run() -> None:
|
||||||
from . import app
|
from .app import Application
|
||||||
_ = app.Application(sys.argv)
|
app = Application(sys.argv)
|
||||||
|
|
||||||
|
from .engine import Engine
|
||||||
|
engine = Engine(debug=app.debug)
|
||||||
|
engine.showWindow()
|
||||||
|
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
@ -10,17 +10,13 @@ from . import __about__
|
|||||||
|
|
||||||
class Application(QGuiApplication):
|
class Application(QGuiApplication):
|
||||||
def __init__(self, args: Optional[List[str]] = None) -> None:
|
def __init__(self, args: Optional[List[str]] = None) -> None:
|
||||||
try:
|
self.debug = False
|
||||||
args.index("--debug") # type: ignore
|
|
||||||
debug = True
|
if args and "--debug" in args:
|
||||||
except (AttributeError, ValueError):
|
del args[args.index("--debug")]
|
||||||
debug = False
|
self.debug = True
|
||||||
|
|
||||||
super().__init__(args or [])
|
super().__init__(args or [])
|
||||||
|
|
||||||
self.setApplicationName(__about__.__pkg_name__)
|
self.setApplicationName(__about__.__pkg_name__)
|
||||||
self.setApplicationDisplayName(__about__.__pretty_name__)
|
self.setApplicationDisplayName(__about__.__pretty_name__)
|
||||||
|
|
||||||
from .engine import Engine
|
|
||||||
engine = Engine(self, debug=debug)
|
|
||||||
engine.show_window()
|
|
||||||
|
@ -13,11 +13,6 @@ import nio
|
|||||||
from .network_manager import NetworkManager
|
from .network_manager import NetworkManager
|
||||||
from .pyqt_future import futurize
|
from .pyqt_future import futurize
|
||||||
|
|
||||||
# One pool per hostname/remote server;
|
|
||||||
# multiple Client for different accounts on the same server can exist.
|
|
||||||
_POOLS: DefaultDict[str, ThreadPoolExecutor] = \
|
|
||||||
DefaultDict(lambda: ThreadPoolExecutor(max_workers=6))
|
|
||||||
|
|
||||||
|
|
||||||
class Client(QObject):
|
class Client(QObject):
|
||||||
roomInvited = pyqtSignal([str, dict], [str])
|
roomInvited = pyqtSignal([str, dict], [str])
|
||||||
@ -46,7 +41,7 @@ class Client(QObject):
|
|||||||
self.host: str = host
|
self.host: str = host
|
||||||
self.port: int = int(port[0]) if port else 443
|
self.port: int = int(port[0]) if port else 443
|
||||||
|
|
||||||
self.pool: ThreadPoolExecutor = _POOLS[self.host]
|
self.pool: ThreadPoolExecutor = ThreadPoolExecutor(6)
|
||||||
|
|
||||||
self.nio: nio.client.HttpClient = \
|
self.nio: nio.client.HttpClient = \
|
||||||
nio.client.HttpClient(self.host, username, device_id)
|
nio.client.HttpClient(self.host, username, device_id)
|
||||||
@ -109,9 +104,12 @@ class Client(QObject):
|
|||||||
@futurize(pyqt=False)
|
@futurize(pyqt=False)
|
||||||
def startSyncing(self) -> None:
|
def startSyncing(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
self._on_sync(self.net_sync.talk(
|
try:
|
||||||
self.nio_sync.sync, timeout=8000
|
response = self.net_sync.talk(self.nio_sync.sync, timeout=8000)
|
||||||
))
|
except nio.LocalProtocolError: # logout occured
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self._on_sync(response)
|
||||||
|
|
||||||
if self._stop_sync.is_set():
|
if self._stop_sync.is_set():
|
||||||
self._stop_sync.clear()
|
self._stop_sync.clear()
|
||||||
|
@ -85,6 +85,13 @@ class ClientManager(QObject):
|
|||||||
client.logout()
|
client.logout()
|
||||||
|
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def deleteAll(self) -> None:
|
||||||
|
for user_id in self.clients.copy():
|
||||||
|
self.delete(user_id)
|
||||||
|
print("deleted", user_id, self.clients)
|
||||||
|
|
||||||
|
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant=True)
|
||||||
def defaultDeviceName(self) -> str: # pylint: disable=no-self-use
|
def defaultDeviceName(self) -> str: # pylint: disable=no-self-use
|
||||||
os_ = f" on {platform.system()}".rstrip()
|
os_ = f" on {platform.system()}".rstrip()
|
||||||
|
@ -80,7 +80,7 @@ def futurize(max_instances: Optional[int] = None, pyqt: bool = True
|
|||||||
return func(self, *args, **kws)
|
return func(self, *args, **kws)
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
logging.error("Exiting thread due to exception.")
|
logging.error("Exiting thread/process due to exception.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
finally:
|
finally:
|
||||||
del _RUNNING[_RUNNING.index((self.pool, func, args, kws))]
|
del _RUNNING[_RUNNING.index((self.pool, func, args, kws))]
|
||||||
|
@ -4,6 +4,7 @@ import "Banners"
|
|||||||
import "RoomEventList"
|
import "RoomEventList"
|
||||||
|
|
||||||
HColumnLayout {
|
HColumnLayout {
|
||||||
|
Component.onCompleted: Backend.pdb()
|
||||||
property string userId: ""
|
property string userId: ""
|
||||||
property string roomId: ""
|
property string roomId: ""
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ ApplicationWindow {
|
|||||||
width: Math.min(Screen.width, 1152)
|
width: Math.min(Screen.width, 1152)
|
||||||
height: Math.min(Screen.height, 768)
|
height: Math.min(Screen.height, 768)
|
||||||
|
|
||||||
|
onClosing: Backend.clientManager.deleteAll()
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: "UI.qml"
|
source: "UI.qml"
|
||||||
|
@ -1,57 +1,54 @@
|
|||||||
# Copyright 2019 miruka
|
# Copyright 2019 miruka
|
||||||
# This file is part of harmonyqml, licensed under GPLv3.
|
# This file is part of harmonyqml, licensed under GPLv3.
|
||||||
|
|
||||||
import logging
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Generator
|
from typing import Any, Dict, Generator
|
||||||
|
|
||||||
from PyQt5.QtCore import QFileSystemWatcher, QObject, QTimer
|
from PyQt5.QtCore import QObject, QTimer
|
||||||
from PyQt5.QtQml import QQmlApplicationEngine
|
from PyQt5.QtQml import QQmlApplicationEngine
|
||||||
|
|
||||||
from .app import Application
|
|
||||||
from .backend.backend import Backend
|
|
||||||
|
|
||||||
# logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
class Engine(QQmlApplicationEngine):
|
class Engine(QQmlApplicationEngine):
|
||||||
def __init__(self,
|
def __init__(self, debug: bool = False) -> None:
|
||||||
app: Application,
|
# Connect UNXI signals to properly exit program
|
||||||
debug: bool = False) -> None:
|
self._original_signal_handlers: Dict[int, Any] = {}
|
||||||
super().__init__(app)
|
|
||||||
self.app = app
|
|
||||||
self.backend = Backend(self)
|
|
||||||
self.app_dir = Path(sys.argv[0]).resolve().parent
|
|
||||||
|
|
||||||
# Set QML properties
|
for signame in ("INT" , "HUP", "QUIT", "TERM"):
|
||||||
self.rootContext().setContextProperty("Engine", self)
|
sig = signal.Signals[f"SIG{signame}"] # pylint: disable=no-member
|
||||||
self.rootContext().setContextProperty("Backend", self.backend)
|
self._original_signal_handlers[sig] = signal.getsignal(sig)
|
||||||
|
signal.signal(sig, self.onExitSignal)
|
||||||
# Connect Qt signals
|
|
||||||
self.quit.connect(self.app.quit)
|
|
||||||
|
|
||||||
# Make SIGINT (ctrl-c) work
|
# Make SIGINT (ctrl-c) work
|
||||||
self._sigint_timer = QTimer()
|
self._sigint_timer = QTimer()
|
||||||
self._sigint_timer.timeout.connect(lambda: None)
|
self._sigint_timer.timeout.connect(lambda: None)
|
||||||
self._sigint_timer.start(100)
|
self._sigint_timer.start(100)
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
self.app_dir = Path(sys.argv[0]).resolve().parent
|
||||||
|
|
||||||
|
from .backend.backend import Backend
|
||||||
|
self.backend = Backend(self)
|
||||||
|
self.rootContext().setContextProperty("Backend", self.backend)
|
||||||
|
|
||||||
# Setup UI live-reloading when a file is edited
|
# Setup UI live-reloading when a file is edited
|
||||||
if debug:
|
if debug:
|
||||||
|
from PyQt5.QtCore import QFileSystemWatcher
|
||||||
self._watcher = QFileSystemWatcher()
|
self._watcher = QFileSystemWatcher()
|
||||||
self._watcher.directoryChanged.connect(lambda _: self.reload_qml())
|
self._watcher.directoryChanged.connect(lambda _: self.reloadQml())
|
||||||
self._watcher.addPath(str(self.app_dir))
|
self._watcher.addPath(str(self.app_dir))
|
||||||
|
|
||||||
for _dir in list(self._recursive_dirs_in(self.app_dir)):
|
for _dir in list(self._recursive_dirs_in(self.app_dir)):
|
||||||
self._watcher.addPath(str(_dir))
|
self._watcher.addPath(str(_dir))
|
||||||
|
|
||||||
# Load QML page and show window
|
|
||||||
self.load(str(self.app_dir / "components" / "Window.qml"))
|
|
||||||
|
|
||||||
|
def onExitSignal(self, *_) -> None:
|
||||||
|
for sig, handler in self._original_signal_handlers.items():
|
||||||
|
signal.signal(sig, handler)
|
||||||
|
|
||||||
def show_window(self) -> None:
|
self._original_signal_handlers.clear()
|
||||||
self.rootObjects()[0].show()
|
self.closeWindow()
|
||||||
sys.exit(self.app.exec())
|
|
||||||
|
|
||||||
|
|
||||||
def _recursive_dirs_in(self, path: Path) -> Generator[Path, None, None]:
|
def _recursive_dirs_in(self, path: Path) -> Generator[Path, None, None]:
|
||||||
@ -61,10 +58,22 @@ class Engine(QQmlApplicationEngine):
|
|||||||
yield from self._recursive_dirs_in(item)
|
yield from self._recursive_dirs_in(item)
|
||||||
|
|
||||||
|
|
||||||
def reload_qml(self) -> None:
|
def showWindow(self) -> None:
|
||||||
|
self.load(str(self.app_dir / "components" / "Window.qml"))
|
||||||
|
|
||||||
|
|
||||||
|
def closeWindow(self) -> None:
|
||||||
|
try:
|
||||||
|
self.rootObjects()[0].close()
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def reloadQml(self) -> None:
|
||||||
loader = self.rootObjects()[0].findChild(QObject, "UILoader")
|
loader = self.rootObjects()[0].findChild(QObject, "UILoader")
|
||||||
|
|
||||||
source = loader.property("source")
|
source = loader.property("source")
|
||||||
loader.setProperty("source", None)
|
loader.setProperty("source", None)
|
||||||
self.clearComponentCache()
|
self.clearComponentCache()
|
||||||
|
|
||||||
loader.setProperty("source", source)
|
loader.setProperty("source", source)
|
||||||
logging.info("Reloaded: %s", source)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user