Initial public release
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"""Saved device profiles for the BT Keyboard streamer.
|
||||
|
||||
A profile is a named list of devices, each ``{"address", "label"}``. Saving
|
||||
records the MAC + friendly name of every device currently in the streamer;
|
||||
loading re-adds and reconnects them in one click, naming each automatically.
|
||||
|
||||
All profiles live in a single JSON file in the app config dir so they're in
|
||||
one place and trivially portable:
|
||||
|
||||
{ "profiles": { "<name>": [ {"address": "AA:..", "label": "Left PC"}, ... ] } }
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
|
||||
from utils.constants import APPDATA_DIR
|
||||
|
||||
_FILE = os.path.join(APPDATA_DIR, "bt_kbd_profiles.json")
|
||||
_lock = threading.Lock()
|
||||
|
||||
|
||||
def _load_all() -> dict:
|
||||
try:
|
||||
with open(_FILE, "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {}
|
||||
profs = d.get("profiles") if isinstance(d, dict) else None
|
||||
return profs if isinstance(profs, dict) else {}
|
||||
|
||||
|
||||
def _save_all(profs: dict) -> None:
|
||||
os.makedirs(APPDATA_DIR, exist_ok=True)
|
||||
tmp = _FILE + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump({"profiles": profs}, f, indent=2)
|
||||
os.replace(tmp, _FILE)
|
||||
|
||||
|
||||
def list_profiles() -> list:
|
||||
"""Names of all saved profiles, sorted."""
|
||||
with _lock:
|
||||
return sorted(_load_all().keys())
|
||||
|
||||
|
||||
def load_profile(name: str) -> list:
|
||||
"""Return a profile's devices as a list of {"address","label"} dicts.
|
||||
Tolerates a malformed/missing file by returning whatever is valid."""
|
||||
with _lock:
|
||||
devs = _load_all().get(name, [])
|
||||
out = []
|
||||
if isinstance(devs, list):
|
||||
for d in devs:
|
||||
if isinstance(d, dict) and d.get("address"):
|
||||
out.append({"address": str(d["address"]),
|
||||
"label": str(d.get("label") or "")})
|
||||
return out
|
||||
|
||||
|
||||
def save_profile(name: str, devices, transport: str = "mesh") -> None:
|
||||
"""Save ``devices`` (iterable of (address, label) tuples or dicts) under
|
||||
``name``, replacing any existing profile of that name.
|
||||
|
||||
``transport`` is recorded per device ("mesh" for ESP-NOW STA MACs,
|
||||
"ble" for legacy Bleak BT MACs) so the loader can migrate old
|
||||
profiles. Extra keys are ignored by load_profile for back-compat."""
|
||||
norm = []
|
||||
for d in devices:
|
||||
if isinstance(d, dict):
|
||||
addr, lbl = d.get("address"), d.get("label") or ""
|
||||
else:
|
||||
addr, lbl = d
|
||||
if addr:
|
||||
norm.append({"address": str(addr), "label": str(lbl or ""),
|
||||
"transport": transport})
|
||||
with _lock:
|
||||
profs = _load_all()
|
||||
profs[name] = norm
|
||||
_save_all(profs)
|
||||
|
||||
|
||||
def delete_profile(name: str) -> bool:
|
||||
with _lock:
|
||||
profs = _load_all()
|
||||
if name in profs:
|
||||
del profs[name]
|
||||
_save_all(profs)
|
||||
return True
|
||||
return False
|
||||
Reference in New Issue
Block a user