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:
|
||||
|
||||
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):
|
||||
|
||||
|
|
1
TODO.md
1
TODO.md
|
@ -1,3 +1,4 @@
|
|||
- aiofiles Thumbnails
|
||||
- Devices and client settings in edit account page
|
||||
- Multiaccount aliases
|
||||
- If avatar is set, name color from average color?
|
||||
|
|
|
@ -2,26 +2,23 @@
|
|||
# This file is part of harmonyqml, licensed under LGPLv3.
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import random
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Set, Tuple
|
||||
|
||||
from atomicfile import AtomicFile
|
||||
|
||||
from .app import App
|
||||
from .events import users
|
||||
from .html_filter import HTML_FILTER
|
||||
from .matrix_client import MatrixClient
|
||||
|
||||
SavedAccounts = Dict[str, Dict[str, str]]
|
||||
CONFIG_LOCK = asyncio.Lock()
|
||||
|
||||
|
||||
class Backend:
|
||||
def __init__(self, app: App) -> None:
|
||||
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.past_tokens: Dict[str, str] = {} # {room_id: token}
|
||||
|
@ -64,6 +61,22 @@ class Backend:
|
|||
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:
|
||||
client = self.clients.pop(user_id, None)
|
||||
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
|
||||
|
||||
async def request_user_update_event(self, user_id: str) -> None:
|
||||
client = self.clients.get(user_id,
|
||||
random.choice(tuple(self.clients.values())))
|
||||
client = self.clients.get(
|
||||
user_id,
|
||||
random.choice(tuple(self.clients.values()))
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
class AutoStrEnum(Enum):
|
||||
@staticmethod
|
||||
def _generate_next_value_(name, *_):
|
||||
return name
|
||||
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
def __post_init__(self) -> None:
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# This file is part of harmonyqml, licensed under LGPLv3.
|
||||
|
||||
from datetime import datetime
|
||||
from enum import auto
|
||||
from typing import Any, Dict, List, Sequence, Type, Union
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
@ -10,7 +9,8 @@ from dataclasses import dataclass, field
|
|||
import nio
|
||||
from nio.rooms import MatrixRoom
|
||||
|
||||
from .event import AutoStrEnum, Event
|
||||
from ..utils import AutoStrEnum, auto
|
||||
from .event import Event
|
||||
|
||||
|
||||
@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: ({
|
||||
yes: button => {
|
||||
py.callCoro("save_account", [userId])
|
||||
py.callCoro("saved_accounts.add", [userId])
|
||||
pageStack.showPage("Default")
|
||||
},
|
||||
no: button => {
|
||||
py.callCoro("forget_account", [userId])
|
||||
py.callCoro("saved_accounts.delete", [userId])
|
||||
pageStack.showPage("Default")
|
||||
},
|
||||
})
|
||||
|
|
|
@ -46,11 +46,11 @@ Python {
|
|||
call("APP.is_debug_on", [Qt.application.arguments], on => {
|
||||
window.debug = on
|
||||
|
||||
callCoro("has_saved_accounts", [], has => {
|
||||
callCoro("saved_accounts.any_saved", [], any => {
|
||||
py.ready = true
|
||||
willLoadAccounts(has)
|
||||
willLoadAccounts(any)
|
||||
|
||||
if (has) {
|
||||
if (any) {
|
||||
py.loadingAccounts = true
|
||||
py.callCoro("load_saved_accounts", [], () => {
|
||||
py.loadingAccounts = false
|
||||
|
|
Loading…
Reference in New Issue
Block a user