Files
backup_box/backup_box/config.py
2025-11-06 11:38:24 +08:00

113 lines
3.1 KiB
Python

from . import _env
from os import path as _pth
from tomllib import load as toml_load
from tomli_w import dump as toml_dump
from typing import TypedDict, Literal
EntryItem = TypedDict("EntryItem", {
"name": str,
"storage": str,
"sources": list[str],
"included": list[str],
"ignored": list[str],
})
LocalStorageItem = TypedDict("LocalStorageItem", {
"type": Literal["LocalStorageItem"],
"path": str,
})
RemoteStorageItem = TypedDict("RemoteStorageItem", {
"type": Literal["RemoteStorageItem"],
"host": str,
"port": str,
})
StorageItem = LocalStorageItem | RemoteStorageItem
ConfigDict = TypedDict("ConfigDict", {
"storage": dict[str, StorageItem],
"entry": dict[str, EntryItem],
})
CONFIG_FILE_NAME = "backup_box.toml"
def _new_config() -> ConfigDict:
return {
"storage": {},
"entry": {},
}
_cfg: ConfigDict = _new_config()
_config: list[str] = []
def _override_dict(target: ConfigDict, top_dict: ConfigDict):
for k, v in top_dict.items():
if isinstance(v, dict) and isinstance(target.get(k, None), dict):
_override_dict(target[k], v) # type: ignore
else:
target[k] = v
def _path_related_to(base: str, *sub: str):
return _pth.abspath(_pth.join(base, *sub))
def load_single_config(cfg_path: str) -> ConfigDict:
try:
with open(cfg_path, "rb") as f:
config: ConfigDict = toml_load(f) # type: ignore
except:
config: ConfigDict = {} # type: ignore
# ensure default value
cfg_dir = _pth.dirname(cfg_path)
config.setdefault("storage", {})
for item in config["storage"].values():
if item["type"] == "LocalStorageItem":
item.setdefault("path", "")
# path related to the config file.
if item["path"]:
item["path"] = _path_related_to(cfg_dir, item["path"])
config.setdefault("entry", {})
for item in config["entry"].values():
item.setdefault("name", "")
item.setdefault("storage", "")
# path related to the config file.
item.setdefault("sources", [])
for i, s in enumerate(item["sources"]):
item["sources"][i] = _path_related_to(cfg_dir, s)
item.setdefault("included", [])
item.setdefault("ignored", [])
return config
def save_single_config(cfg_path: str, config: ConfigDict):
with open(cfg_path, "wb") as fp:
toml_dump(config, fp, indent=2)
def apply_user_config(dir_or_path: str):
if _pth.isdir(dir_or_path):
dir_or_path = _pth.abspath(_pth.join(dir_or_path, CONFIG_FILE_NAME))
if dir_or_path in _config:
return # skip if already added
# ensure default value
config = load_single_config(dir_or_path)
# update config
_override_dict(_cfg, config)
# append config file list
_config.append(dir_or_path)
def init_default_config():
global _cfg
_cfg = _new_config()
apply_user_config(_env.DEFAULT_USER_CONFIG_DIR)
apply_user_config(_env.PWD)
def reload_config():
global _cfg
cfg_list = _config
_cfg = _new_config()
_config.clear()
for cfg in cfg_list:
apply_user_config(cfg)
def get_config():
return _cfg