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 sys
|
||||
|
||||
# logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# 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
|
||||
# of litering the source folders with .qmlc files.
|
||||
@ -13,5 +11,11 @@ os.environ["QML_DISABLE_DISK_CACHE"] = "1"
|
||||
|
||||
|
||||
def run() -> None:
|
||||
from . import app
|
||||
_ = app.Application(sys.argv)
|
||||
from .app import Application
|
||||
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):
|
||||
def __init__(self, args: Optional[List[str]] = None) -> None:
|
||||
try:
|
||||
args.index("--debug") # type: ignore
|
||||
debug = True
|
||||
except (AttributeError, ValueError):
|
||||
debug = False
|
||||
self.debug = False
|
||||
|
||||
if args and "--debug" in args:
|
||||
del args[args.index("--debug")]
|
||||
self.debug = True
|
||||
|
||||
super().__init__(args or [])
|
||||
|
||||
self.setApplicationName(__about__.__pkg_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 .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):
|
||||
roomInvited = pyqtSignal([str, dict], [str])
|
||||
@ -36,9 +31,9 @@ class Client(QObject):
|
||||
|
||||
def __init__(self,
|
||||
manager,
|
||||
hostname: str,
|
||||
username: str,
|
||||
device_id: str = "") -> None:
|
||||
hostname: str,
|
||||
username: str,
|
||||
device_id: str = "") -> None:
|
||||
super().__init__(manager)
|
||||
self.manager = manager
|
||||
|
||||
@ -46,7 +41,7 @@ class Client(QObject):
|
||||
self.host: str = host
|
||||
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 = \
|
||||
nio.client.HttpClient(self.host, username, device_id)
|
||||
@ -109,9 +104,12 @@ class Client(QObject):
|
||||
@futurize(pyqt=False)
|
||||
def startSyncing(self) -> None:
|
||||
while True:
|
||||
self._on_sync(self.net_sync.talk(
|
||||
self.nio_sync.sync, timeout=8000
|
||||
))
|
||||
try:
|
||||
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():
|
||||
self._stop_sync.clear()
|
||||
|
@ -85,6 +85,13 @@ class ClientManager(QObject):
|
||||
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)
|
||||
def defaultDeviceName(self) -> str: # pylint: disable=no-self-use
|
||||
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)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
logging.error("Exiting thread due to exception.")
|
||||
logging.error("Exiting thread/process due to exception.")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
del _RUNNING[_RUNNING.index((self.pool, func, args, kws))]
|
||||
|
@ -4,6 +4,7 @@ import "Banners"
|
||||
import "RoomEventList"
|
||||
|
||||
HColumnLayout {
|
||||
Component.onCompleted: Backend.pdb()
|
||||
property string userId: ""
|
||||
property string roomId: ""
|
||||
|
||||
|
@ -8,6 +8,8 @@ ApplicationWindow {
|
||||
width: Math.min(Screen.width, 1152)
|
||||
height: Math.min(Screen.height, 768)
|
||||
|
||||
onClosing: Backend.clientManager.deleteAll()
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
source: "UI.qml"
|
||||
|
@ -1,57 +1,54 @@
|
||||
# Copyright 2019 miruka
|
||||
# This file is part of harmonyqml, licensed under GPLv3.
|
||||
|
||||
import logging
|
||||
import signal
|
||||
import sys
|
||||
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 .app import Application
|
||||
from .backend.backend import Backend
|
||||
|
||||
# logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
class Engine(QQmlApplicationEngine):
|
||||
def __init__(self,
|
||||
app: Application,
|
||||
debug: bool = False) -> None:
|
||||
super().__init__(app)
|
||||
self.app = app
|
||||
self.backend = Backend(self)
|
||||
self.app_dir = Path(sys.argv[0]).resolve().parent
|
||||
def __init__(self, debug: bool = False) -> None:
|
||||
# Connect UNXI signals to properly exit program
|
||||
self._original_signal_handlers: Dict[int, Any] = {}
|
||||
|
||||
# Set QML properties
|
||||
self.rootContext().setContextProperty("Engine", self)
|
||||
self.rootContext().setContextProperty("Backend", self.backend)
|
||||
|
||||
# Connect Qt signals
|
||||
self.quit.connect(self.app.quit)
|
||||
for signame in ("INT" , "HUP", "QUIT", "TERM"):
|
||||
sig = signal.Signals[f"SIG{signame}"] # pylint: disable=no-member
|
||||
self._original_signal_handlers[sig] = signal.getsignal(sig)
|
||||
signal.signal(sig, self.onExitSignal)
|
||||
|
||||
# Make SIGINT (ctrl-c) work
|
||||
self._sigint_timer = QTimer()
|
||||
self._sigint_timer.timeout.connect(lambda: None)
|
||||
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
|
||||
if debug:
|
||||
from PyQt5.QtCore import 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))
|
||||
|
||||
for _dir in list(self._recursive_dirs_in(self.app_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.rootObjects()[0].show()
|
||||
sys.exit(self.app.exec())
|
||||
self._original_signal_handlers.clear()
|
||||
self.closeWindow()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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")
|
||||
|
||||
source = loader.property("source")
|
||||
loader.setProperty("source", None)
|
||||
self.clearComponentCache()
|
||||
|
||||
loader.setProperty("source", source)
|
||||
logging.info("Reloaded: %s", source)
|
||||
|
Loading…
Reference in New Issue
Block a user