Move favorites to local library and keep confirmed task heads in cloud

This commit is contained in:
Pigway
2026-10-06 09:31:09 +08:00
parent c005a9444a
commit 180b6ae100
16 changed files with 1187 additions and 56 deletions
+41 -1
View File
@@ -5,6 +5,7 @@ import time
from datetime import UTC, datetime, timedelta
from uuid import uuid4
from pigway_frame_flow.display.cloud_library import CloudLibrary
from pigway_frame_flow.display.retention import Retention
from pigway_frame_flow.drivers.base import DriverRegistry
from pigway_frame_flow.errors import ErrorCode as E
@@ -38,6 +39,7 @@ class DisplayService:
)
self.lock = threading.RLock()
self.retention = Retention(self)
self.cloud_library = CloudLibrary(self)
self.expiration_checked: dict[str, float] = {}
# A killed write cannot be replayed safely; pre-write work can resume.
db.execute(
@@ -383,6 +385,7 @@ class DisplayService:
asset = self.content_asset(
TypeAdapter(Content).validate_python(payload), device_id, id
)
self.assets.record_origin(asset["asset_id"], op)
if action == "local-upload":
asset = self.assets.promote(asset["asset_id"])
self.update(
@@ -402,6 +405,26 @@ class DisplayService:
state = self.state(device_id)
if action == "delete":
image_id = payload["image_id"]
if payload.get("cloud_cleanup"):
if (
not self.cloud_library.eligible(device_id, image_id)
or previous == image_id
):
self.db.execute(
"UPDATE managed_cloud_uploads SET cleanup_operation=NULL WHERE device_id=? AND image_id=?",
(device_id, image_id),
)
self.update(
id, "COMPLETED", {"image_id": image_id, "deferred": True}
)
return
# The confirmed replacement supersedes this task-owned restore
# reference; unrelated artwork and active overrides stay protected.
self.db.execute(
"UPDATE device_state SET previous_image_id=NULL WHERE device_id=? AND previous_image_id=?",
(device_id, image_id),
)
state = self.state(device_id)
if payload.get("retention") and (
not self.retention.eligible(device_id, image_id) or image_id == previous
):
@@ -418,12 +441,26 @@ class DisplayService:
"SELECT image_id FROM overrides WHERE device_id=?", (device_id,)
)
}
if image_id in protected:
if image_id in protected and not payload.get("purge"):
raise FlowError(E.CONFLICT, "Image is needed for restoration", 409)
if image_id == previous and not payload["confirm_current"]:
raise FlowError(E.CONFLICT, "Current image needs confirmation", 409)
self._start_write(id)
driver.delete(image_id)
self.db.execute(
"DELETE FROM managed_cloud_uploads WHERE device_id=? AND image_id=?",
(device_id, image_id),
)
if payload.get("purge"):
self.db.execute(
"DELETE FROM overrides WHERE device_id=? AND image_id=?",
(device_id, image_id),
)
for field in ("previous_image_id", "base_image_id", "managed_image_id"):
self.db.execute(
f"UPDATE device_state SET {field}=NULL WHERE device_id=? AND {field}=?",
(device_id, image_id),
)
for suffix in [".original", "-thumb.jpg"]:
(
self.settings.data_dir / "cache" / f"{device_id}-{image_id}{suffix}"
@@ -476,6 +513,7 @@ class DisplayService:
image_id = matches[0].image_id
else:
asset = self.content_asset(content, device_id, id)
self.assets.record_origin(asset["asset_id"], op)
image_id = None
self.db.execute(
"UPDATE device_state SET previous_image_id=? WHERE device_id=?",
@@ -494,6 +532,7 @@ class DisplayService:
if asset:
self.update(id, "UPLOADING")
image_id = driver.upload(self.assets.path(asset["asset_id"]), asset["name"])
self.cloud_library.register(device_id, image_id, asset["asset_id"], id)
if (
request.options.cleanup_unstarred
and isinstance(content, PhotoSourceContent)
@@ -561,6 +600,7 @@ class DisplayService:
and device.current_content.synced
):
self.update(op["id"], "COMPLETED")
self.cloud_library.confirm(op["id"])
self.db.history(op, "COMPLETED")
self.db.event("operation.completed", {"operation_id": op["id"]})
elif (