mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-09 18:47:33 +02:00
138 lines
4.2 KiB
Python
138 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from dataclasses import asdict, dataclass
|
|
|
|
from services.mesh.mesh_peer_store import PeerRecord
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SyncWorkerState:
|
|
last_sync_started_at: int = 0
|
|
last_sync_finished_at: int = 0
|
|
last_sync_ok_at: int = 0
|
|
next_sync_due_at: int = 0
|
|
last_peer_url: str = ""
|
|
last_error: str = ""
|
|
last_outcome: str = "idle"
|
|
current_head: str = ""
|
|
fork_detected: bool = False
|
|
consecutive_failures: int = 0
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return asdict(self)
|
|
|
|
|
|
def eligible_sync_peers(records: list[PeerRecord], *, now: float | None = None) -> list[PeerRecord]:
|
|
current_time = int(now if now is not None else time.time())
|
|
candidates = [
|
|
record
|
|
for record in records
|
|
if record.bucket == "sync" and record.enabled and int(record.cooldown_until or 0) <= current_time
|
|
]
|
|
return sorted(
|
|
candidates,
|
|
key=lambda record: (
|
|
-int(record.last_sync_ok_at or 0),
|
|
int(record.failure_count or 0),
|
|
int(record.added_at or 0),
|
|
record.peer_url,
|
|
),
|
|
)
|
|
|
|
|
|
def begin_sync(
|
|
state: SyncWorkerState,
|
|
*,
|
|
peer_url: str = "",
|
|
current_head: str = "",
|
|
now: float | None = None,
|
|
) -> SyncWorkerState:
|
|
timestamp = int(now if now is not None else time.time())
|
|
return SyncWorkerState(
|
|
last_sync_started_at=timestamp,
|
|
last_sync_finished_at=state.last_sync_finished_at,
|
|
last_sync_ok_at=state.last_sync_ok_at,
|
|
next_sync_due_at=state.next_sync_due_at,
|
|
last_peer_url=peer_url or state.last_peer_url,
|
|
last_error="",
|
|
last_outcome="running",
|
|
current_head=current_head or state.current_head,
|
|
fork_detected=False,
|
|
consecutive_failures=state.consecutive_failures,
|
|
)
|
|
|
|
|
|
def finish_sync(
|
|
state: SyncWorkerState,
|
|
*,
|
|
ok: bool,
|
|
peer_url: str = "",
|
|
current_head: str = "",
|
|
error: str = "",
|
|
fork_detected: bool = False,
|
|
now: float | None = None,
|
|
interval_s: int = 300,
|
|
failure_backoff_s: int = 60,
|
|
) -> SyncWorkerState:
|
|
timestamp = int(now if now is not None else time.time())
|
|
if ok:
|
|
return SyncWorkerState(
|
|
last_sync_started_at=state.last_sync_started_at,
|
|
last_sync_finished_at=timestamp,
|
|
last_sync_ok_at=timestamp,
|
|
next_sync_due_at=timestamp + max(0, int(interval_s or 0)),
|
|
last_peer_url=peer_url or state.last_peer_url,
|
|
last_error="",
|
|
last_outcome="ok",
|
|
current_head=current_head or state.current_head,
|
|
fork_detected=bool(fork_detected),
|
|
consecutive_failures=0,
|
|
)
|
|
|
|
return SyncWorkerState(
|
|
last_sync_started_at=state.last_sync_started_at,
|
|
last_sync_finished_at=timestamp,
|
|
last_sync_ok_at=state.last_sync_ok_at,
|
|
next_sync_due_at=timestamp + max(0, int(failure_backoff_s or 0)),
|
|
last_peer_url=peer_url or state.last_peer_url,
|
|
last_error=str(error or "").strip(),
|
|
last_outcome="fork" if fork_detected else "error",
|
|
current_head=current_head or state.current_head,
|
|
fork_detected=bool(fork_detected),
|
|
consecutive_failures=state.consecutive_failures + 1,
|
|
)
|
|
|
|
|
|
def finish_solo_sync(
|
|
state: SyncWorkerState,
|
|
*,
|
|
current_head: str = "",
|
|
now: float | None = None,
|
|
interval_s: int = 300,
|
|
) -> SyncWorkerState:
|
|
timestamp = int(now if now is not None else time.time())
|
|
return SyncWorkerState(
|
|
last_sync_started_at=state.last_sync_started_at,
|
|
last_sync_finished_at=timestamp,
|
|
last_sync_ok_at=state.last_sync_ok_at,
|
|
next_sync_due_at=timestamp + max(0, int(interval_s or 0)),
|
|
last_peer_url="",
|
|
last_error="",
|
|
last_outcome="solo",
|
|
current_head=current_head or state.current_head,
|
|
fork_detected=False,
|
|
consecutive_failures=0,
|
|
)
|
|
|
|
|
|
def should_run_sync(
|
|
state: SyncWorkerState,
|
|
*,
|
|
now: float | None = None,
|
|
) -> bool:
|
|
current_time = int(now if now is not None else time.time())
|
|
if state.last_outcome == "running":
|
|
return False
|
|
return int(state.next_sync_due_at or 0) <= current_time
|