some basic statistics and invite tree, plus watch new data

This commit is contained in:
Davide Scaini
2026-04-10 13:21:31 +02:00
parent 6b2d31a44a
commit 053da10ab9
9 changed files with 655 additions and 5 deletions
+60
View File
@@ -86,6 +86,62 @@ def _start_serve(data: Path, api_port: int, site: Path) -> None:
server.run()
def _watch_data(data: Path) -> None:
"""Watch the data directory for sidecar/activity changes and re-merge.
Monitors every user's edits/ and activities/ subdirectories. When any file
changes (new activity extracted, sidecar saved), re-runs merge_all for that
user so the _merged/ symlink tree stays current. Astro dev picks up the
result automatically because public/data is a symlink into the live data dir.
Uses watchfiles (bundled with uvicorn[standard]) for efficient OS-level
file watching — no polling.
"""
from watchfiles import watch, Change
watch_paths = []
for user_dir in _user_dirs(data):
for sub in ("edits", "activities"):
p = user_dir / sub
p.mkdir(exist_ok=True)
watch_paths.append(p)
if not watch_paths:
return
console.print(f" [dim]Watching {len(watch_paths)} director{'y' if len(watch_paths) == 1 else 'ies'} for changes…[/dim]")
# Build a map from path prefix → user dir for targeted merge
prefix_to_user: dict[str, Path] = {}
for user_dir in _user_dirs(data):
for sub in ("edits", "activities"):
prefix_to_user[str(user_dir / sub)] = user_dir
for changes in watch(*watch_paths, yield_on_timeout=False):
# Find which users were affected
affected: set[Path] = set()
for change_type, path in changes:
# Skip timeseries / geojson / index churn written by merge itself
if any(path.endswith(s) for s in (".timeseries.json", ".geojson", "index.json")):
continue
for prefix, user_dir in prefix_to_user.items():
if path.startswith(prefix):
affected.add(user_dir)
break
if not affected:
continue
for user_dir in affected:
handle = user_dir.name
try:
from bincio.render.merge import merge_all
merge_all(user_dir)
console.print(f" [dim]↺ {handle}: merged[/dim]")
except Exception as exc:
console.print(f" [yellow]⚠ {handle}: merge failed — {exc}[/yellow]")
@click.command("dev")
@click.option("--data-dir", default=None, help="BAS data directory (must contain instance.db)")
@click.option("--site-dir", default=None, help="Astro project directory (default: ./site)")
@@ -144,6 +200,10 @@ def dev(
t = threading.Thread(target=_start_serve, args=(data, api_port, site), daemon=True)
t.start()
# Watch data dir for sidecar/activity changes → auto-merge
watcher = threading.Thread(target=_watch_data, args=(data,), daemon=True)
watcher.start()
# Build env for astro dev
env = {
**os.environ,