"""Workspace syncing classes""" from enum import Enum, auto from typing import Any, Callable, Dict, Generic, Optional, TypeVar from ..watchable import Watchable, WatchableSet FnsBag = Dict[str, Callable[[Any], Any]] Thunk = Callable[[], None] T = TypeVar('T') class TransportStatus(Enum): OPEN = auto() CLOSED = auto() class ConnectionStatus(Enum): CONNECTING = auto() CLOSED = auto() class TransportBase: """Base class for workspace syncers""" status: Watchable[TransportStatus] is_closed: bool methods: FnsBag device_id: str connections: WatchableSet[Connection] def __init__(self, device_id: str, methods: FnsBag): raise NotImplementedError() def on_close(self, cb: Thunk) -> Thunk: """Set a handler for when the connection closes""" raise NotImplementedError() def close(self) -> None: """Close the syncer’s connection""" raise NotImplementedError() class TransportLocal(TransportBase): def __init__(self, device_id: str, methods: BagType, description: str) -> None: self.device_id = device_id self.methods = methods self.description = description @property def is_closed(self) -> bool: return self.status == TransportStatus.CLOSED def on_close(self, func: Thunk) -> Thunk: return self.status.on_change_to(TransportStatus.CLOSED)(func) def close() -> None: if self.is_closed: return self.status.set(TransportStatus.CLOSED) for conn in self.connections: conn.close() self.connections.clear() def add_connection(self, other_trans: TransportLocal[BagType]) -> Tuple[Connection, Connection]: if self.is_closed: raise Exception('Can’t use a transport after it’s closed') this_conn: Connection[BagType] other_conn: Connection[BagType] this_conn = Connection( description=f'conn {self.device_id} to {other_trans.device_id}', transport=self, device_id=self.device_id, methods=self.methods, send_envelope=lambda conn: ConnectionBase[BagType], env: Envelope[BagType]: other_conn.handle_incoming_envelope(env), ) other_conn = Connection( description=f'conn other_trans.device_id to {this.device_id}', transport: other_trans, device_id: other_trans.device_id, methods: other_trans.methods, send_envelope: lambda conn: ConnectionBase[BagType], env: Envelope[BagType]: this_conn.handle_incoming_envelope(env), ) @this_conn.on_close def close_other(): other_conn.close() self.connections.delete(this_conn) @other_conn.on_close def close_this(): this_conn.close() self.connections.add(this_conn) other_trans.connections.add(other_conn) return this_conn, other_conn