85 lines
2.4 KiB
Python
85 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
from enum import StrEnum
|
|
|
|
|
|
class SyncState(StrEnum):
|
|
PREPARED = "prepared"
|
|
SYNCING = "syncing"
|
|
ERROR = "error"
|
|
RECONNECTING = "reconnecting"
|
|
STOPPED = "stopped"
|
|
|
|
|
|
_SYNC_STATE_TRANSITIONS: dict[SyncState, set[SyncState]] = {
|
|
SyncState.PREPARED: {SyncState.SYNCING, SyncState.ERROR, SyncState.STOPPED},
|
|
SyncState.SYNCING: {SyncState.ERROR, SyncState.RECONNECTING, SyncState.STOPPED},
|
|
SyncState.ERROR: {SyncState.RECONNECTING, SyncState.STOPPED},
|
|
SyncState.RECONNECTING: {SyncState.SYNCING, SyncState.ERROR, SyncState.STOPPED},
|
|
SyncState.STOPPED: {SyncState.PREPARED},
|
|
}
|
|
|
|
|
|
class SyncStateMachine:
|
|
def __init__(self):
|
|
self._state = SyncState.PREPARED
|
|
self._last_error: str = ""
|
|
self._retry_count: int = 0
|
|
self._max_retries: int = 10
|
|
|
|
@property
|
|
def state(self) -> SyncState:
|
|
return self._state
|
|
|
|
@property
|
|
def last_error(self) -> str:
|
|
return self._last_error
|
|
|
|
@property
|
|
def retry_count(self) -> int:
|
|
return self._retry_count
|
|
|
|
def can_transition(self, to_state: SyncState) -> bool:
|
|
allowed = _SYNC_STATE_TRANSITIONS.get(self._state, set())
|
|
return to_state in allowed
|
|
|
|
def transition(self, to_state: SyncState, error: str = "") -> bool:
|
|
if not self.can_transition(to_state):
|
|
return False
|
|
self._state = to_state
|
|
self._last_error = error
|
|
|
|
if to_state == SyncState.ERROR:
|
|
self._retry_count += 1
|
|
elif to_state == SyncState.SYNCING:
|
|
self._retry_count = 0
|
|
|
|
return True
|
|
|
|
def to_healthy(self) -> bool:
|
|
return self.transition(SyncState.SYNCING)
|
|
|
|
def to_error(self, error: str = "") -> bool:
|
|
return self.transition(SyncState.ERROR, error)
|
|
|
|
def to_reconnecting(self, error: str = "") -> bool:
|
|
return self.transition(SyncState.RECONNECTING, error)
|
|
|
|
def to_stopped(self) -> bool:
|
|
return self.transition(SyncState.STOPPED)
|
|
|
|
def to_prepared(self) -> bool:
|
|
return self.transition(SyncState.PREPARED)
|
|
|
|
@property
|
|
def is_healthy(self) -> bool:
|
|
return self._state in (SyncState.PREPARED, SyncState.SYNCING)
|
|
|
|
@property
|
|
def is_active(self) -> bool:
|
|
return self._state in (SyncState.PREPARED, SyncState.SYNCING, SyncState.RECONNECTING)
|
|
|
|
@property
|
|
def should_stop_retry(self) -> bool:
|
|
return self._retry_count >= self._max_retries
|