mirror of
https://github.com/rommapp/romm.git
synced 2026-02-19 07:50:57 +01:00
Implement device registration and save sync tracking to enable multi-device save management with conflict detection. - Device CRUD endpoints (POST/GET/PUT/DELETE /api/devices) - Save sync state tracking per device - Conflict detection on upload (409 when device has stale sync) - Download sync tracking (optimistic and confirmed modes) - Track/untrack saves per device - DEVICES_READ/WRITE scopes for authorization
118 lines
3.5 KiB
Python
118 lines
3.5 KiB
Python
from collections.abc import Sequence
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import select, update
|
|
from sqlalchemy.orm import Session
|
|
|
|
from decorators.database import begin_session
|
|
from models.device_save_sync import DeviceSaveSync
|
|
|
|
from .base_handler import DBBaseHandler
|
|
|
|
|
|
class DBDeviceSaveSyncHandler(DBBaseHandler):
|
|
@begin_session
|
|
def get_sync(
|
|
self,
|
|
device_id: str,
|
|
save_id: int,
|
|
session: Session = None, # type: ignore
|
|
) -> DeviceSaveSync | None:
|
|
return session.scalar(
|
|
select(DeviceSaveSync)
|
|
.filter_by(device_id=device_id, save_id=save_id)
|
|
.limit(1)
|
|
)
|
|
|
|
@begin_session
|
|
def get_syncs_for_device_and_saves(
|
|
self,
|
|
device_id: str,
|
|
save_ids: list[int],
|
|
session: Session = None, # type: ignore
|
|
) -> Sequence[DeviceSaveSync]:
|
|
if not save_ids:
|
|
return []
|
|
return session.scalars(
|
|
select(DeviceSaveSync).filter(
|
|
DeviceSaveSync.device_id == device_id,
|
|
DeviceSaveSync.save_id.in_(save_ids),
|
|
)
|
|
).all()
|
|
|
|
@begin_session
|
|
def upsert_sync(
|
|
self,
|
|
device_id: str,
|
|
save_id: int,
|
|
synced_at: datetime | None = None,
|
|
session: Session = None, # type: ignore
|
|
) -> DeviceSaveSync:
|
|
now = synced_at or datetime.now(timezone.utc)
|
|
existing = session.scalar(
|
|
select(DeviceSaveSync)
|
|
.filter_by(device_id=device_id, save_id=save_id)
|
|
.limit(1)
|
|
)
|
|
if existing:
|
|
session.execute(
|
|
update(DeviceSaveSync)
|
|
.where(
|
|
DeviceSaveSync.device_id == device_id,
|
|
DeviceSaveSync.save_id == save_id,
|
|
)
|
|
.values(last_synced_at=now, is_untracked=False)
|
|
.execution_options(synchronize_session="evaluate")
|
|
)
|
|
existing.last_synced_at = now
|
|
existing.is_untracked = False
|
|
return existing
|
|
else:
|
|
sync = DeviceSaveSync(
|
|
device_id=device_id,
|
|
save_id=save_id,
|
|
last_synced_at=now,
|
|
is_untracked=False,
|
|
)
|
|
session.add(sync)
|
|
session.flush()
|
|
return sync
|
|
|
|
@begin_session
|
|
def set_untracked(
|
|
self,
|
|
device_id: str,
|
|
save_id: int,
|
|
untracked: bool,
|
|
session: Session = None, # type: ignore
|
|
) -> DeviceSaveSync | None:
|
|
existing = session.scalar(
|
|
select(DeviceSaveSync)
|
|
.filter_by(device_id=device_id, save_id=save_id)
|
|
.limit(1)
|
|
)
|
|
if existing:
|
|
session.execute(
|
|
update(DeviceSaveSync)
|
|
.where(
|
|
DeviceSaveSync.device_id == device_id,
|
|
DeviceSaveSync.save_id == save_id,
|
|
)
|
|
.values(is_untracked=untracked)
|
|
.execution_options(synchronize_session="evaluate")
|
|
)
|
|
existing.is_untracked = untracked
|
|
return existing
|
|
elif untracked:
|
|
now = datetime.now(timezone.utc)
|
|
sync = DeviceSaveSync(
|
|
device_id=device_id,
|
|
save_id=save_id,
|
|
last_synced_at=now,
|
|
is_untracked=True,
|
|
)
|
|
session.add(sync)
|
|
session.flush()
|
|
return sync
|
|
return None
|