diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/roster.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/roster.py | 53 |
1 files changed, 37 insertions, 16 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/roster.py b/packages/meshbay-node/src/meshbay_node/roster.py index e2f749f..5f38acd 100644 --- a/packages/meshbay-node/src/meshbay_node/roster.py +++ b/packages/meshbay-node/src/meshbay_node/roster.py @@ -31,6 +31,8 @@ from pathlib import Path import aiosqlite +from meshbay_common.paths import fold + log = logging.getLogger(__name__) # Crockford base32 without I, L, O and U: no character pair a human can confuse @@ -543,10 +545,40 @@ class Roster: # ── Group settings ────────────────────────────────────────────────────── - # Whether members who are not the operator may upload. Default is yes: a - # group that nobody may add to is the unusual case, and an existing node - # must not change behaviour because a table was added under it. - SETTING_MEMBER_UPLOAD = "member_upload" + # Whether a root is ejected. Runtime state, one key per root, keyed by the + # *folded* name so it agrees with the case-insensitive comparison the rest + # of the root code makes. It lives here rather than in node.toml because it + # is not configuration — an operator's hand-written config file should not + # be rewritten because a USB drive was unplugged — and it has to survive a + # restart, or the rescan that follows reads an empty mount point as an + # erased library, which is the whole thing eject exists to prevent. + SETTING_ROOT_EJECTED_PREFIX = "root_ejected:" + + @classmethod + def root_ejected_key(cls, root_name: str) -> str: + return cls.SETTING_ROOT_EJECTED_PREFIX + fold(root_name) + + async def set_root_ejected(self, group_id: str, root_name: str, + ejected: bool, set_by: str = "") -> None: + await self.set_setting(group_id, self.root_ejected_key(root_name), + "1" if ejected else "0", set_by) + + async def ejected_roots(self, group_id: str) -> set[str]: + """ + The folded names of this group's ejected roots. + + Matched in Python rather than with `LIKE 'root_ejected:%'`: `_` is a + single-character wildcard there, so that pattern also matches keys this + does not own. A group has a handful of settings rows, so reading them + all costs nothing and the prefix test is then exact. + """ + prefix = self.SETTING_ROOT_EJECTED_PREFIX + async with self._db.execute( + "SELECT key, value FROM group_settings WHERE group_id = ?", + (group_id,)) as cur: + rows = await cur.fetchall() + return {r["key"][len(prefix):] for r in rows + if r["key"].startswith(prefix) and r["value"] == "1"} async def get_setting(self, group_id: str, key: str, default: str | None = None) -> str | None: @@ -567,17 +599,6 @@ class Roster: (group_id, key, value, set_by, _now())) await self._db.commit() - async def member_upload_allowed(self, group_id: str) -> bool: - """Whether an ordinary member may upload to this group.""" - value = await self.get_setting(group_id, self.SETTING_MEMBER_UPLOAD, "1") - return value != "0" - - async def set_member_upload(self, group_id: str, allowed: bool, - set_by: str = "") -> bool: - await self.set_setting(group_id, self.SETTING_MEMBER_UPLOAD, - "1" if allowed else "0", set_by) - return allowed - # Which group "applications" (Chat, Files, and whatever registers later in # apps.js) are shown to members. Unset means every app that exists — an # existing group's tabs must not disappear because a node was upgraded. @@ -609,7 +630,7 @@ class Roster: # user_id)` authorizing the operator node-wide (desktop-client-v1.md # §6.3). Unset means "the shipped default token, TMDB's own default # language" — the same "absent means the old behaviour" discipline - # member_upload/enabled_apps already follow. + # enabled_apps already follows. # # Whether TMDB is used *at all*, though, is per-group (moved off the # node-wide sentinel below, 2026-08-24): an operator running a real media |