| 524 | |
| 525 | |
| 526 | class LogManager: |
| 527 | def __init__( |
| 528 | self, |
| 529 | root: Optional[Path], |
| 530 | socketio: SocketIO, |
| 531 | shutdown_event: threading.Event, |
| 532 | initial_path: Optional[Path] = None, |
| 533 | ): |
| 534 | self.root = root |
| 535 | self.socketio = socketio |
| 536 | self.shutdown_event = shutdown_event |
| 537 | self.initial_path = initial_path |
| 538 | self.sessions: Dict[str, LogSession] = {} |
| 539 | self.sid_to_path: Dict[str, str] = {} |
| 540 | self.lock = threading.Lock() |
| 541 | |
| 542 | def list_logs(self) -> list[dict]: |
| 543 | return _collect_logs(self.root) |
| 544 | |
| 545 | def _default_path(self) -> Optional[str]: |
| 546 | if self.initial_path: |
| 547 | return str(self.initial_path) |
| 548 | logs = self.list_logs() |
| 549 | if not logs: |
| 550 | return None |
| 551 | return logs[0].get("path") |
| 552 | |
| 553 | def _get_or_create(self, path: Path) -> LogSession: |
| 554 | key = str(path) |
| 555 | session = self.sessions.get(key) |
| 556 | if session: |
| 557 | return session |
| 558 | session = LogSession(path, self.socketio, self.shutdown_event) |
| 559 | session.load_initial() |
| 560 | self.sessions[key] = session |
| 561 | return session |
| 562 | |
| 563 | def subscribe(self, sid: str, path: Optional[str]) -> Optional[LogSession]: |
| 564 | with self.lock: |
| 565 | if not path: |
| 566 | path = self._default_path() |
| 567 | if not path: |
| 568 | return None |
| 569 | resolved = _safe_resolve(path) |
| 570 | if not resolved: |
| 571 | return None |
| 572 | resolved_str = str(resolved) |
| 573 | |
| 574 | current = self.sid_to_path.get(sid) |
| 575 | if current and current != resolved_str: |
| 576 | self._leave_room(sid, current) |
| 577 | |
| 578 | session = self._get_or_create(resolved) |
| 579 | self.sid_to_path[sid] = resolved_str |
| 580 | session.subscribers.add(sid) |
| 581 | join_room(session.room, sid=sid) |
| 582 | session.start() |
| 583 | return session |