Python backend "App" becomes QmlBridge
Is now strictly about setting up asyncio/uvloop and providing the functions for QML to interact with it and call backend coroutines. The appdirs attribute is moved to Backend.
This commit is contained in:
parent
5d7d66f99b
commit
9e372d01d5
|
@ -1 +1 @@
|
||||||
from .app import APP # noqa
|
from .qml_bridge import BRIDGE # noqa
|
||||||
|
|
|
@ -1,22 +1,28 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging as log
|
import logging as log
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, DefaultDict, Dict, List, Optional, Tuple
|
from typing import Any, Callable, DefaultDict, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import hsluv
|
import hsluv
|
||||||
|
from appdirs import AppDirs
|
||||||
|
|
||||||
import nio
|
import nio
|
||||||
|
|
||||||
from .app import App
|
from . import __about__
|
||||||
from .errors import MatrixError
|
from .errors import MatrixError
|
||||||
from .matrix_client import MatrixClient
|
from .matrix_client import MatrixClient
|
||||||
from .models.items import Account, Device, Event, Member, Room, Upload
|
from .models.items import Account, Device, Event, Member, Room, Upload
|
||||||
from .models.model_store import ModelStore
|
from .models.model_store import ModelStore
|
||||||
|
|
||||||
|
log.getLogger().setLevel(log.INFO)
|
||||||
|
nio.logger_group.level = nio.log.logbook.ERROR
|
||||||
|
nio.log.logbook.StreamHandler(sys.stderr).push_application()
|
||||||
|
|
||||||
|
|
||||||
class Backend:
|
class Backend:
|
||||||
def __init__(self, app: App) -> None:
|
def __init__(self) -> None:
|
||||||
self.app = app
|
self.appdirs = AppDirs(appname=__about__.__pkg_name__, roaming=True)
|
||||||
|
|
||||||
from . import config_files
|
from . import config_files
|
||||||
self.saved_accounts = config_files.Accounts(self)
|
self.saved_accounts = config_files.Accounts(self)
|
||||||
|
@ -43,7 +49,7 @@ class Backend:
|
||||||
DefaultDict(asyncio.Lock) # {room_id: lock}
|
DefaultDict(asyncio.Lock) # {room_id: lock}
|
||||||
|
|
||||||
from .media_cache import MediaCache
|
from .media_cache import MediaCache
|
||||||
cache_dir = Path(self.app.appdirs.user_cache_dir)
|
cache_dir = Path(self.appdirs.user_cache_dir)
|
||||||
self.media_cache = MediaCache(self, cache_dir)
|
self.media_cache = MediaCache(self, cache_dir)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ class ConfigFile:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
return Path(self.backend.app.appdirs.user_config_dir) / self.filename
|
return Path(self.backend.appdirs.user_config_dir) / self.filename
|
||||||
|
|
||||||
|
|
||||||
async def default_data(self):
|
async def default_data(self):
|
||||||
|
@ -180,7 +180,7 @@ class UIState(JSONConfigFile):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
return Path(self.backend.app.appdirs.user_data_dir) / self.filename
|
return Path(self.backend.appdirs.user_data_dir) / self.filename
|
||||||
|
|
||||||
|
|
||||||
async def default_data(self) -> JsonData:
|
async def default_data(self) -> JsonData:
|
||||||
|
@ -198,7 +198,7 @@ class History(JSONConfigFile):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
return Path(self.backend.app.appdirs.user_data_dir) / self.filename
|
return Path(self.backend.appdirs.user_data_dir) / self.filename
|
||||||
|
|
||||||
|
|
||||||
async def default_data(self) -> JsonData:
|
async def default_data(self) -> JsonData:
|
||||||
|
@ -209,7 +209,7 @@ class History(JSONConfigFile):
|
||||||
class Theme(ConfigFile):
|
class Theme(ConfigFile):
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
data_dir = Path(self.backend.app.appdirs.user_data_dir)
|
data_dir = Path(self.backend.appdirs.user_data_dir)
|
||||||
return data_dir / "themes" / self.filename
|
return data_dir / "themes" / self.filename
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ class MatrixClient(nio.AsyncClient):
|
||||||
f"homeserver is missing scheme (e.g. https://): {homeserver}",
|
f"homeserver is missing scheme (e.g. https://): {homeserver}",
|
||||||
)
|
)
|
||||||
|
|
||||||
store = Path(backend.app.appdirs.user_data_dir) / "encryption"
|
store = Path(backend.appdirs.user_data_dir) / "encryption"
|
||||||
store.mkdir(parents=True, exist_ok=True)
|
store.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
|
@ -1,56 +1,37 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging as log
|
import logging as log
|
||||||
import signal
|
import signal
|
||||||
import sys
|
|
||||||
import traceback
|
import traceback
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Coroutine, Sequence
|
from typing import Coroutine, Sequence
|
||||||
|
|
||||||
import nio
|
from .backend import Backend
|
||||||
from appdirs import AppDirs
|
|
||||||
|
|
||||||
from . import __about__
|
|
||||||
from .pyotherside_events import CoroutineDone
|
from .pyotherside_events import CoroutineDone
|
||||||
|
|
||||||
log.getLogger().setLevel(log.INFO)
|
|
||||||
nio.logger_group.level = nio.log.logbook.ERROR
|
|
||||||
nio.log.logbook.StreamHandler(sys.stderr).push_application()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uvloop
|
import uvloop
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
UVLOOP = False
|
log.warning("uvloop module not found, using slower default asyncio loop")
|
||||||
log.info("uvloop not available, using default asyncio loop.")
|
|
||||||
else:
|
else:
|
||||||
UVLOOP = True
|
uvloop.install()
|
||||||
log.info("uvloop is available.")
|
|
||||||
|
|
||||||
|
|
||||||
class App:
|
class QmlBridge:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.appdirs = AppDirs(appname=__about__.__pkg_name__, roaming=True)
|
self.backend = Backend()
|
||||||
|
|
||||||
from .backend import Backend
|
|
||||||
self.backend = Backend(app=self)
|
|
||||||
self.debug = False
|
|
||||||
|
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
self.loop_thread = Thread(target=self._loop_starter)
|
Thread(target=self._start_loop_in_thread).start()
|
||||||
self.loop_thread.start()
|
|
||||||
|
|
||||||
|
|
||||||
def _loop_starter(self) -> None:
|
def _start_loop_in_thread(self) -> None:
|
||||||
asyncio.set_event_loop(self.loop)
|
asyncio.set_event_loop(self.loop)
|
||||||
|
|
||||||
if UVLOOP:
|
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
|
||||||
|
|
||||||
self.loop.run_forever()
|
self.loop.run_forever()
|
||||||
|
|
||||||
|
|
||||||
def run_in_loop(self, coro: Coroutine) -> Future:
|
def _run_coro_in_loop(self, coro: Coroutine) -> Future:
|
||||||
return asyncio.run_coroutine_threadsafe(coro, self.loop)
|
return asyncio.run_coroutine_threadsafe(coro, self.loop)
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,37 +47,40 @@ class App:
|
||||||
|
|
||||||
CoroutineDone(uuid, result, exception, trace)
|
CoroutineDone(uuid, result, exception, trace)
|
||||||
|
|
||||||
future = self.run_in_loop(coro)
|
future = self._run_coro_in_loop(coro)
|
||||||
future.add_done_callback(on_done)
|
future.add_done_callback(on_done)
|
||||||
return future
|
return future
|
||||||
|
|
||||||
|
|
||||||
def call_backend_coro(self, name: str, uuid: str, args: Sequence[str] = (),
|
def call_backend_coro(
|
||||||
|
self, name: str, uuid: str, args: Sequence[str] = (),
|
||||||
) -> Future:
|
) -> Future:
|
||||||
return self._call_coro(attrgetter(name)(self.backend)(*args), uuid)
|
return self._call_coro(attrgetter(name)(self.backend)(*args), uuid)
|
||||||
|
|
||||||
|
|
||||||
def call_client_coro(self,
|
def call_client_coro(
|
||||||
account_id: str,
|
self, user_id: str, name: str, uuid: str, args: Sequence[str] = (),
|
||||||
name: str,
|
) -> Future:
|
||||||
uuid: str,
|
|
||||||
args: Sequence[str] = ()) -> Future:
|
client = self.backend.clients[user_id]
|
||||||
client = self.backend.clients[account_id]
|
|
||||||
return self._call_coro(attrgetter(name)(client)(*args), uuid)
|
return self._call_coro(attrgetter(name)(client)(*args), uuid)
|
||||||
|
|
||||||
|
|
||||||
def pdb(self, additional_data: Sequence = ()) -> None:
|
def pdb(self, additional_data: Sequence = ()) -> None:
|
||||||
ad = additional_data # noqa
|
ad = additional_data # noqa
|
||||||
rl = self.run_in_loop # noqa
|
rc = self._run_coro_in_loop # noqa
|
||||||
ba = self.backend # noqa
|
ba = self.backend # noqa
|
||||||
mo = self.backend.models # noqa
|
mo = self.backend.models # noqa
|
||||||
cl = self.backend.clients
|
cl = self.backend.clients
|
||||||
tcl = lambda user: cl[f"@{user}:matrix.org"] # noqa
|
gcl = lambda user: cl[f"@{user}:matrix.org"] # noqa
|
||||||
|
|
||||||
from .models.items import Account, Room, Member, Event, Device # noqa
|
from .models.items import Account, Room, Member, Event, Device # noqa
|
||||||
|
|
||||||
p = print # pdb's `p` doesn't print a class's __str__ # noqa
|
p = print # pdb's `p` doesn't print a class's __str__ # noqa
|
||||||
|
try:
|
||||||
from pprintpp import pprint as pp # noqa
|
from pprintpp import pprint as pp # noqa
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
log.info("\n=> Run `socat readline tcp:127.0.0.1:4444` in a terminal "
|
log.info("\n=> Run `socat readline tcp:127.0.0.1:4444` in a terminal "
|
||||||
"to connect to pdb.")
|
"to connect to pdb.")
|
||||||
|
@ -107,4 +91,4 @@ class App:
|
||||||
# Make CTRL-C work again
|
# Make CTRL-C work again
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
APP = App()
|
BRIDGE = QmlBridge()
|
|
@ -24,7 +24,7 @@ Item {
|
||||||
HShortcut {
|
HShortcut {
|
||||||
enabled: debugMode
|
enabled: debugMode
|
||||||
sequences: settings.keys.startPythonDebugger
|
sequences: settings.keys.startPythonDebugger
|
||||||
onActivated: py.call("APP.pdb")
|
onActivated: py.call("BRIDGE.pdb")
|
||||||
}
|
}
|
||||||
|
|
||||||
HShortcut {
|
HShortcut {
|
||||||
|
|
|
@ -12,7 +12,7 @@ Python {
|
||||||
addImportPath("src")
|
addImportPath("src")
|
||||||
addImportPath("qrc:/src")
|
addImportPath("qrc:/src")
|
||||||
|
|
||||||
importNames("backend", ["APP"], () => {
|
importNames("backend", ["BRIDGE"], () => {
|
||||||
loadSettings(() => {
|
loadSettings(() => {
|
||||||
callCoro("saved_accounts.any_saved", [], any => {
|
callCoro("saved_accounts.any_saved", [], any => {
|
||||||
if (any) { py.callCoro("load_saved_accounts", []) }
|
if (any) { py.callCoro("load_saved_accounts", []) }
|
||||||
|
@ -45,7 +45,7 @@ Python {
|
||||||
|
|
||||||
|
|
||||||
function callSync(name, args=[]) {
|
function callSync(name, args=[]) {
|
||||||
return call_sync("APP.backend." + name, args)
|
return call_sync("BRIDGE.backend." + name, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ Python {
|
||||||
|
|
||||||
let future = privates.makeFuture()
|
let future = privates.makeFuture()
|
||||||
|
|
||||||
call("APP.call_backend_coro", [name, uuid, args], pyFuture => {
|
call("BRIDGE.call_backend_coro", [name, uuid, args], pyFuture => {
|
||||||
future.privates.pythonFuture = pyFuture
|
future.privates.pythonFuture = pyFuture
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ Python {
|
||||||
|
|
||||||
let call_args = [accountId, name, uuid, args]
|
let call_args = [accountId, name, uuid, args]
|
||||||
|
|
||||||
call("APP.call_client_coro", call_args, pyFuture => {
|
call("BRIDGE.call_client_coro", call_args, pyFuture => {
|
||||||
future.privates.pythonFuture = pyFuture
|
future.privates.pythonFuture = pyFuture
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user