Refactor Backend and config file operations
This commit is contained in:
parent
31184071db
commit
d597e1dda8
|
@ -26,7 +26,7 @@ After this, verify the permissions of the installed plugin files.
|
||||||
Dependencies on Pypi:
|
Dependencies on Pypi:
|
||||||
|
|
||||||
pip3 install --user --upgrade \
|
pip3 install --user --upgrade \
|
||||||
Pillow atomicfile dataclasses filetype lxml mistune uvloop
|
Pillow aiofiles dataclasses filetype lxml mistune uvloop
|
||||||
|
|
||||||
Dependencies on Github (most recent version needed):
|
Dependencies on Github (most recent version needed):
|
||||||
|
|
||||||
|
|
1
TODO.md
1
TODO.md
|
@ -1,3 +1,4 @@
|
||||||
|
- aiofiles Thumbnails
|
||||||
- Devices and client settings in edit account page
|
- Devices and client settings in edit account page
|
||||||
- Multiaccount aliases
|
- Multiaccount aliases
|
||||||
- If avatar is set, name color from average color?
|
- If avatar is set, name color from average color?
|
||||||
|
|
|
@ -2,26 +2,23 @@
|
||||||
# This file is part of harmonyqml, licensed under LGPLv3.
|
# This file is part of harmonyqml, licensed under LGPLv3.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
|
||||||
import random
|
import random
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Optional, Set, Tuple
|
from typing import Dict, Optional, Set, Tuple
|
||||||
|
|
||||||
from atomicfile import AtomicFile
|
|
||||||
|
|
||||||
from .app import App
|
from .app import App
|
||||||
from .events import users
|
from .events import users
|
||||||
from .html_filter import HTML_FILTER
|
from .html_filter import HTML_FILTER
|
||||||
from .matrix_client import MatrixClient
|
from .matrix_client import MatrixClient
|
||||||
|
|
||||||
SavedAccounts = Dict[str, Dict[str, str]]
|
|
||||||
CONFIG_LOCK = asyncio.Lock()
|
|
||||||
|
|
||||||
|
|
||||||
class Backend:
|
class Backend:
|
||||||
def __init__(self, app: App) -> None:
|
def __init__(self, app: App) -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
|
from . import config_files
|
||||||
|
self.saved_accounts = config_files.Accounts(self)
|
||||||
|
self.ui_settings = config_files.UISettings(self)
|
||||||
|
|
||||||
self.clients: Dict[str, MatrixClient] = {}
|
self.clients: Dict[str, MatrixClient] = {}
|
||||||
|
|
||||||
self.past_tokens: Dict[str, str] = {} # {room_id: token}
|
self.past_tokens: Dict[str, str] = {} # {room_id: token}
|
||||||
|
@ -64,6 +61,22 @@ class Backend:
|
||||||
users.AccountUpdated(client.user_id)
|
users.AccountUpdated(client.user_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def load_saved_accounts(self) -> Tuple[str, ...]:
|
||||||
|
async def resume(user_id: str, info: Dict[str, str]) -> str:
|
||||||
|
await self.resume_client(
|
||||||
|
user_id = user_id,
|
||||||
|
token = info["token"],
|
||||||
|
device_id = info["device_id"],
|
||||||
|
homeserver = info["homeserver"],
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
return await asyncio.gather(*(
|
||||||
|
resume(uid, info)
|
||||||
|
for uid, info in (await self.saved_accounts.read()).items()
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
async def logout_client(self, user_id: str) -> None:
|
async def logout_client(self, user_id: str) -> None:
|
||||||
client = self.clients.pop(user_id, None)
|
client = self.clients.pop(user_id, None)
|
||||||
if client:
|
if client:
|
||||||
|
@ -77,75 +90,13 @@ class Backend:
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
# Saved account operations - TODO: Use aiofiles?
|
|
||||||
|
|
||||||
@property
|
|
||||||
def saved_accounts_path(self) -> Path:
|
|
||||||
return Path(self.app.appdirs.user_config_dir) / "accounts.json"
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def saved_accounts(self) -> SavedAccounts:
|
|
||||||
try:
|
|
||||||
return json.loads(self.saved_accounts_path.read_text())
|
|
||||||
except (json.JSONDecodeError, FileNotFoundError):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
async def has_saved_accounts(self) -> bool:
|
|
||||||
return bool(self.saved_accounts)
|
|
||||||
|
|
||||||
|
|
||||||
async def load_saved_accounts(self) -> Tuple[str, ...]:
|
|
||||||
async def resume(user_id: str, info: Dict[str, str]) -> str:
|
|
||||||
await self.resume_client(
|
|
||||||
user_id = user_id,
|
|
||||||
token = info["token"],
|
|
||||||
device_id = info["device_id"],
|
|
||||||
homeserver = info["homeserver"],
|
|
||||||
)
|
|
||||||
return user_id
|
|
||||||
|
|
||||||
return await asyncio.gather(*(
|
|
||||||
resume(uid, info) for uid, info in self.saved_accounts.items()
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
async def save_account(self, user_id: str) -> None:
|
|
||||||
client = self.clients[user_id]
|
|
||||||
|
|
||||||
await self._write_config({
|
|
||||||
**self.saved_accounts,
|
|
||||||
client.user_id: {
|
|
||||||
"homeserver": client.homeserver,
|
|
||||||
"token": client.access_token,
|
|
||||||
"device_id": client.device_id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
async def forget_account(self, user_id: str) -> None:
|
|
||||||
await self._write_config({
|
|
||||||
uid: info
|
|
||||||
for uid, info in self.saved_accounts.items() if uid != user_id
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
async def _write_config(self, accounts: SavedAccounts) -> None:
|
|
||||||
js = json.dumps(accounts, indent=4, ensure_ascii=False, sort_keys=True)
|
|
||||||
|
|
||||||
async with CONFIG_LOCK:
|
|
||||||
self.saved_accounts_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
with AtomicFile(self.saved_accounts_path, "w") as new:
|
|
||||||
new.write(js)
|
|
||||||
|
|
||||||
|
|
||||||
# General functions
|
# General functions
|
||||||
|
|
||||||
async def request_user_update_event(self, user_id: str) -> None:
|
async def request_user_update_event(self, user_id: str) -> None:
|
||||||
client = self.clients.get(user_id,
|
client = self.clients.get(
|
||||||
random.choice(tuple(self.clients.values())))
|
user_id,
|
||||||
|
random.choice(tuple(self.clients.values()))
|
||||||
|
)
|
||||||
await client.request_user_update_event(user_id)
|
await client.request_user_update_event(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
78
src/python/config_files.py
Normal file
78
src/python/config_files.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# Copyright 2019 miruka
|
||||||
|
# This file is part of harmonyqml, licensed under LGPLv3.
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from .backend import Backend
|
||||||
|
|
||||||
|
WRITE_LOCK = asyncio.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ConfigFile:
|
||||||
|
backend: Backend = field()
|
||||||
|
filename: str = field()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self) -> Path:
|
||||||
|
# pylint: disable=no-member
|
||||||
|
return Path(self.backend.app.appdirs.user_config_dir) / self.filename
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class JSONConfigFile(ConfigFile):
|
||||||
|
async def read(self) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
return json.loads(self.path.read_text())
|
||||||
|
except (json.JSONDecodeError, FileNotFoundError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
async def write(self, data: Dict[str, Any]) -> None:
|
||||||
|
js = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)
|
||||||
|
|
||||||
|
async with WRITE_LOCK:
|
||||||
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
async with aiofiles.open(self.path, "w") as new:
|
||||||
|
await new.write(js)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Accounts(JSONConfigFile):
|
||||||
|
filename: str = "accounts.json"
|
||||||
|
|
||||||
|
async def any_saved(self) -> bool:
|
||||||
|
return bool(await self.read())
|
||||||
|
|
||||||
|
|
||||||
|
async def add(self, user_id: str) -> None:
|
||||||
|
# pylint: disable=no-member
|
||||||
|
client = self.backend.clients[user_id]
|
||||||
|
|
||||||
|
await self.write({
|
||||||
|
**await self.read(),
|
||||||
|
client.user_id: {
|
||||||
|
"homeserver": client.homeserver,
|
||||||
|
"token": client.access_token,
|
||||||
|
"device_id": client.device_id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
async def delete(self, user_id: str) -> None:
|
||||||
|
await self.write({
|
||||||
|
uid: info
|
||||||
|
for uid, info in (await self.read()).items() if uid != user_id
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UISettings(JSONConfigFile):
|
||||||
|
filename: str = "ui-settings.json"
|
|
@ -9,12 +9,6 @@ from dataclasses import dataclass
|
||||||
import pyotherside
|
import pyotherside
|
||||||
|
|
||||||
|
|
||||||
class AutoStrEnum(Enum):
|
|
||||||
@staticmethod
|
|
||||||
def _generate_next_value_(name, *_):
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Event:
|
class Event:
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
# This file is part of harmonyqml, licensed under LGPLv3.
|
# This file is part of harmonyqml, licensed under LGPLv3.
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import auto
|
|
||||||
from typing import Any, Dict, List, Sequence, Type, Union
|
from typing import Any, Dict, List, Sequence, Type, Union
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
@ -10,7 +9,8 @@ from dataclasses import dataclass, field
|
||||||
import nio
|
import nio
|
||||||
from nio.rooms import MatrixRoom
|
from nio.rooms import MatrixRoom
|
||||||
|
|
||||||
from .event import AutoStrEnum, Event
|
from ..utils import AutoStrEnum, auto
|
||||||
|
from .event import Event
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
13
src/python/utils.py
Normal file
13
src/python/utils.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2019 miruka
|
||||||
|
# This file is part of harmonyqml, licensed under LGPLv3.
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from enum import auto as autostr
|
||||||
|
|
||||||
|
auto = autostr # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
|
||||||
|
class AutoStrEnum(Enum):
|
||||||
|
@staticmethod
|
||||||
|
def _generate_next_value_(name, *_):
|
||||||
|
return name
|
|
@ -23,11 +23,11 @@ Item {
|
||||||
|
|
||||||
buttonCallbacks: ({
|
buttonCallbacks: ({
|
||||||
yes: button => {
|
yes: button => {
|
||||||
py.callCoro("save_account", [userId])
|
py.callCoro("saved_accounts.add", [userId])
|
||||||
pageStack.showPage("Default")
|
pageStack.showPage("Default")
|
||||||
},
|
},
|
||||||
no: button => {
|
no: button => {
|
||||||
py.callCoro("forget_account", [userId])
|
py.callCoro("saved_accounts.delete", [userId])
|
||||||
pageStack.showPage("Default")
|
pageStack.showPage("Default")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -46,11 +46,11 @@ Python {
|
||||||
call("APP.is_debug_on", [Qt.application.arguments], on => {
|
call("APP.is_debug_on", [Qt.application.arguments], on => {
|
||||||
window.debug = on
|
window.debug = on
|
||||||
|
|
||||||
callCoro("has_saved_accounts", [], has => {
|
callCoro("saved_accounts.any_saved", [], any => {
|
||||||
py.ready = true
|
py.ready = true
|
||||||
willLoadAccounts(has)
|
willLoadAccounts(any)
|
||||||
|
|
||||||
if (has) {
|
if (any) {
|
||||||
py.loadingAccounts = true
|
py.loadingAccounts = true
|
||||||
py.callCoro("load_saved_accounts", [], () => {
|
py.callCoro("load_saved_accounts", [], () => {
|
||||||
py.loadingAccounts = false
|
py.loadingAccounts = false
|
||||||
|
|
Loading…
Reference in New Issue
Block a user