Initial SyncGames tree: agent, Android, deploy, docs.
Session-gated MinIO save sync with AppImage GUI, CLI edit/session flow, and Gitea release helper. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from syncgames.errors import LeaseHeldError
|
||||
from syncgames.store import Meta, ObjectStore, parse_expires, utc_now
|
||||
|
||||
|
||||
def lease_active(lease: dict[str, Any] | None) -> bool:
|
||||
if not lease:
|
||||
return False
|
||||
exp = parse_expires(lease.get("expires_at"))
|
||||
if exp is None:
|
||||
return False
|
||||
return exp > utc_now()
|
||||
|
||||
|
||||
def acquire_lease(
|
||||
store: ObjectStore,
|
||||
game_id: str,
|
||||
device_id: str,
|
||||
*,
|
||||
ttl_hours: int,
|
||||
versions_to_keep: int,
|
||||
break_other: bool = False,
|
||||
) -> Meta:
|
||||
meta = store.get_meta(game_id)
|
||||
if meta is None:
|
||||
from syncgames.store import empty_meta
|
||||
|
||||
meta = empty_meta(game_id, versions_to_keep)
|
||||
|
||||
lease = meta.lease
|
||||
if lease_active(lease) and lease and lease.get("holder") != device_id:
|
||||
if not break_other:
|
||||
raise LeaseHeldError(
|
||||
f"Lease held by {lease.get('holder')} until {lease.get('expires_at')}"
|
||||
)
|
||||
|
||||
session_id = str(uuid.uuid4())
|
||||
expires = utc_now() + timedelta(hours=ttl_hours)
|
||||
meta.raw["lease"] = {
|
||||
"holder": device_id,
|
||||
"expires_at": expires.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"session_id": session_id,
|
||||
}
|
||||
meta.raw["versions_to_keep"] = versions_to_keep
|
||||
store.put_meta(game_id, meta)
|
||||
# Re-read to reduce lost-update window (optimistic)
|
||||
again = store.get_meta(game_id)
|
||||
if again is None or not again.lease or again.lease.get("holder") != device_id:
|
||||
raise LeaseHeldError("Lost lease race — another device acquired the lease")
|
||||
return again
|
||||
|
||||
|
||||
def release_lease(store: ObjectStore, game_id: str, device_id: str, meta: Meta | None = None) -> Meta:
|
||||
meta = meta or store.get_meta(game_id)
|
||||
if meta is None:
|
||||
from syncgames.store import empty_meta
|
||||
|
||||
meta = empty_meta(game_id)
|
||||
lease = meta.lease
|
||||
if lease and lease.get("holder") not in (None, device_id):
|
||||
if lease_active(lease):
|
||||
raise LeaseHeldError(f"Cannot release lease owned by {lease.get('holder')}")
|
||||
meta.raw["lease"] = None
|
||||
store.put_meta(game_id, meta)
|
||||
return meta
|
||||
|
||||
|
||||
def break_lease(store: ObjectStore, game_id: str) -> Meta:
|
||||
from syncgames.store import empty_meta
|
||||
|
||||
meta = store.get_meta(game_id) or empty_meta(game_id)
|
||||
meta.raw["lease"] = None
|
||||
store.put_meta(game_id, meta)
|
||||
return meta
|
||||
Reference in New Issue
Block a user