Fall back to disk when in-memory store is empty on client connect

storage_helpers: load_today_records() scans logs/ for all files
matching today's UTC date and VIN, merges and sorts their records.

network: _accept_loop receives an optional load_today callable;
if store_ref["current"] has no records it calls load_today() to
replay the day's history from disk before adding the client.

collect: passes a load_today lambda (None when -o is used).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 21:21:17 +02:00
co-authored by Claude Sonnet 4.6
parent b38f8d052e
commit 9d2d742368
3 changed files with 51 additions and 19 deletions
+26 -15
View File
@@ -2,6 +2,7 @@ import json
import logging
import socket
import threading
from typing import Callable
log = logging.getLogger(__name__)
@@ -10,30 +11,36 @@ def _accept_loop(
server_sock: socket.socket,
clients: list,
lock: threading.Lock,
last_snapshot: dict,
store_ref: dict,
load_today: Callable | None,
) -> None:
while True:
try:
conn, addr = server_sock.accept()
log.info("Push client connected from %s", addr)
with lock:
if last_snapshot["data"] is not None:
try:
line = (json.dumps(last_snapshot["data"], default=str) + "\n").encode()
conn.sendall(line)
except OSError:
log.debug("Failed to send initial snapshot to %s", addr)
conn.close()
continue
store = store_ref["current"]
records = store["records"] if store else []
if not records and load_today is not None:
log.info("In-memory store empty — loading today's records from disk")
records = load_today()
try:
for record in records:
conn.sendall((json.dumps(record, default=str) + "\n").encode())
if records:
log.info("Sent %d record(s) to %s", len(records), addr)
except OSError:
log.debug("Failed to send history to %s", addr)
conn.close()
continue
clients.append(conn)
except OSError:
break
def broadcast(snapshot: dict, clients: list, lock: threading.Lock, last_snapshot: dict) -> None:
def broadcast(snapshot: dict, clients: list, lock: threading.Lock) -> None:
line = (json.dumps(snapshot, default=str) + "\n").encode()
with lock:
last_snapshot["data"] = snapshot
dead = []
for conn in clients:
try:
@@ -46,19 +53,23 @@ def broadcast(snapshot: dict, clients: list, lock: threading.Lock, last_snapshot
log.info("Push client disconnected")
def start_push_server(host: str, port: int) -> tuple:
def start_push_server(
host: str,
port: int,
load_today: Callable | None = None,
) -> tuple:
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind((host, port))
server_sock.listen()
clients: list = []
lock = threading.Lock()
last_snapshot: dict = {"data": None}
store_ref: dict = {"current": None}
t = threading.Thread(
target=_accept_loop,
args=(server_sock, clients, lock, last_snapshot),
args=(server_sock, clients, lock, store_ref, load_today),
daemon=True,
)
t.start()
log.info("Push server listening on %s:%d", host, port)
return clients, lock, last_snapshot
return clients, lock, store_ref