Rename config_files module → user_files + document

This commit is contained in:
miruka 2019-12-18 08:41:02 -04:00
parent 23be12fb60
commit 61cd3b2f55
2 changed files with 66 additions and 25 deletions

View File

@ -28,11 +28,11 @@ class Backend:
def __init__(self) -> None: def __init__(self) -> None:
self.appdirs = AppDirs(appname=__app_name__, roaming=True) self.appdirs = AppDirs(appname=__app_name__, roaming=True)
from . import config_files from . import user_files
self.saved_accounts = config_files.Accounts(self) self.saved_accounts = user_files.Accounts(self)
self.ui_settings = config_files.UISettings(self) self.ui_settings = user_files.UISettings(self)
self.ui_state = config_files.UIState(self) self.ui_state = user_files.UIState(self)
self.history = config_files.History(self) self.history = user_files.History(self)
self.models = ModelStore(allowed_key_types={ self.models = ModelStore(allowed_key_types={
Account, # Logged-in accounts Account, # Logged-in accounts
@ -232,7 +232,7 @@ class Backend:
async def load_settings(self) -> tuple: async def load_settings(self) -> tuple:
"""Return parsed user config files.""" """Return parsed user config files."""
from .config_files import Theme from .user_files import Theme
settings = await self.ui_settings.read() settings = await self.ui_settings.read()
ui_state = await self.ui_state.read() ui_state = await self.ui_state.read()
history = await self.history.read() history = await self.history.read()

View File

@ -1,9 +1,11 @@
"""User data and configuration files definitions."""
import asyncio import asyncio
import json import json
import logging as log import logging as log
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional from typing import Any, ClassVar, Dict, Optional
import aiofiles import aiofiles
@ -17,7 +19,11 @@ WRITE_LOCK = asyncio.Lock()
@dataclass @dataclass
class ConfigFile: class DataFile:
"""Base class representing a user data file."""
is_config: ClassVar[bool] = False
backend: Backend = field(repr=False) backend: Backend = field(repr=False)
filename: str = field() filename: str = field()
@ -30,23 +36,36 @@ class ConfigFile:
@property @property
def path(self) -> Path: def path(self) -> Path:
"""Full path of the file, even if it doesn't exist yet."""
if self.is_config:
return Path(self.backend.appdirs.user_config_dir) / self.filename return Path(self.backend.appdirs.user_config_dir) / self.filename
return Path(self.backend.appdirs.user_data_dir) / self.filename
async def default_data(self): async def default_data(self):
"""Default content if the file doesn't exist."""
return "" return ""
async def read(self): async def read(self):
"""Return the content of the existing file on disk."""
log.debug("Reading config %s at %s", type(self).__name__, self.path) log.debug("Reading config %s at %s", type(self).__name__, self.path)
return self.path.read_text() return self.path.read_text()
async def write(self, data) -> None: async def write(self, data) -> None:
"""Request for the file to be written/updated with data."""
self._to_write = data self._to_write = data
async def _write_loop(self) -> None: async def _write_loop(self) -> None:
"""Write/update file to on disk with a 1 second cooldown."""
self.path.parent.mkdir(parents=True, exist_ok=True) self.path.parent.mkdir(parents=True, exist_ok=True)
while True: while True:
@ -59,13 +78,21 @@ class ConfigFile:
await asyncio.sleep(1) await asyncio.sleep(1)
@dataclass @dataclass
class JSONConfigFile(ConfigFile): class JSONDataFile(DataFile):
"""Represent a user data file in the JSON format."""
async def default_data(self) -> JsonData: async def default_data(self) -> JsonData:
return {} return {}
async def read(self) -> JsonData: async def read(self) -> JsonData:
"""Return the content of the existing file on disk.
If the file doesn't exist on disk or it has missing keys, the missing
data will be merged and written to disk before returning.
"""
try: try:
data = json.loads(await super().read()) data = json.loads(await super().read())
except (FileNotFoundError, json.JSONDecodeError): except (FileNotFoundError, json.JSONDecodeError):
@ -86,15 +113,26 @@ class JSONConfigFile(ConfigFile):
@dataclass @dataclass
class Accounts(JSONConfigFile): class Accounts(JSONDataFile):
"""Config file for saved matrix accounts: user ID, access tokens, etc."""
is_config = True
filename: str = "accounts.json" filename: str = "accounts.json"
async def any_saved(self) -> bool: async def any_saved(self) -> bool:
"""Return whether there are any accounts saved on disk."""
return bool(await self.read()) return bool(await self.read())
async def add(self, user_id: str) -> None: async def add(self, user_id: str) -> None:
"""Add an account to the config and write it on disk.
The account's details such as its access token are retrieved from
the corresponding `MatrixClient` in `backend.clients`.
"""
client = self.backend.clients[user_id] client = self.backend.clients[user_id]
await self.write({ await self.write({
@ -108,6 +146,8 @@ class Accounts(JSONConfigFile):
async def delete(self, user_id: str) -> None: async def delete(self, user_id: str) -> None:
"""Delete an account from the config and write it on disk."""
await self.write({ await self.write({
uid: info uid: info
for uid, info in (await self.read()).items() if uid != user_id for uid, info in (await self.read()).items() if uid != user_id
@ -115,7 +155,11 @@ class Accounts(JSONConfigFile):
@dataclass @dataclass
class UISettings(JSONConfigFile): class UISettings(JSONDataFile):
"""Config file for QML interface settings and keybindings."""
is_config = True
filename: str = "settings.json" filename: str = "settings.json"
@ -174,15 +218,12 @@ class UISettings(JSONConfigFile):
@dataclass @dataclass
class UIState(JSONConfigFile): class UIState(JSONDataFile):
"""File to save and restore the state of the QML interface."""
filename: str = "state.json" filename: str = "state.json"
@property
def path(self) -> Path:
return Path(self.backend.appdirs.user_data_dir) / self.filename
async def default_data(self) -> JsonData: async def default_data(self) -> JsonData:
return { return {
"collapseAccounts": {}, "collapseAccounts": {},
@ -192,21 +233,21 @@ class UIState(JSONConfigFile):
@dataclass @dataclass
class History(JSONConfigFile): class History(JSONDataFile):
"""File to save and restore lines typed by the user in QML components."""
filename: str = "history.json" filename: str = "history.json"
@property
def path(self) -> Path:
return Path(self.backend.appdirs.user_data_dir) / self.filename
async def default_data(self) -> JsonData: async def default_data(self) -> JsonData:
return {"console": []} return {"console": []}
@dataclass @dataclass
class Theme(ConfigFile): class Theme(DataFile):
"""A theme file defining the look of QML components."""
@property @property
def path(self) -> Path: def path(self) -> Path:
data_dir = Path(self.backend.appdirs.user_data_dir) data_dir = Path(self.backend.appdirs.user_data_dir)