import re import time from threading import Thread from backend.a_backend import ABackend from msg.container import MsgContainer _FILENAME_RE = re.compile(r'\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}_(.+)\.log$') _CHUNK = 64 def source_id_from_path(path: str) -> str | None: m = _FILENAME_RE.search(path) return m.group(1) if m else None class FileBackend(ABackend): def __init__(self, files: list[str], delay: float = 1.0): self._delay = max(0.0, min(10.0, delay)) self._entries = [{'path': p, 'source_id': source_id_from_path(p), 'xcvr': None, 'file': None, 'thread': None} for p in files] self._running = False @property def delay(self) -> float: return self._delay @delay.setter def delay(self, value: float): self._delay = max(0.0, min(10.0, value)) def register_xcvr(self, xcvr): for e in self._entries: if e['source_id'] == xcvr.name.strip(): e['xcvr'] = xcvr xcvr.on_register(self) return raise Exception(f"No log file found for source \"{xcvr.name}\"") def connect(self): for e in self._entries: e['file'] = open(e['path'], 'rb') def start(self): self._running = True for e in self._entries: e['thread'] = Thread(target=self._run, args=(e,), daemon=True) e['thread'].start() def stop(self): self._running = False for e in self._entries: if e['thread'] and e['thread'].is_alive(): e['thread'].join(timeout=self._delay + 1.0) def join(self): for e in self._entries: if e['thread']: e['thread'].join() def disconnect(self): for e in self._entries: if e['file']: e['file'].close() e['file'] = None def _run(self, entry: dict): xcvr = entry['xcvr'] f = entry['file'] while self._running: data = f.read(_CHUNK) if not data: break if xcvr: xcvr.on_recv(MsgContainer(data, xcvr)) if self._delay > 0: time.sleep(self._delay)