Files
pigway-frame-flow/pigway_frame_flow/display/cloud_library.py
T

133 lines
5.0 KiB
Python

"""Keep confirmed task heads and locally starred uploads; never retry deletion."""
import json
import time
from datetime import UTC, datetime
class CloudLibrary:
def __init__(self, service):
self.service = service
self.db = service.db
self.checked = 0.0
def register(self, device_id, image_id, asset_id, operation_id):
meta = self.service.assets.info(asset_id)
self.db.execute(
"INSERT INTO managed_cloud_uploads VALUES(?,?,?,?,?,?,NULL,NULL)",
(
device_id,
image_id,
asset_id,
meta.get("job_id"),
operation_id,
datetime.now(UTC).isoformat(),
),
)
def confirm(self, operation_id):
self.db.execute(
"UPDATE managed_cloud_uploads SET confirmed_at=? WHERE operation_id=?",
(datetime.now(UTC).isoformat(), operation_id),
)
self.checked = 0.0
def retained(self, row):
meta = self.db.one(
"SELECT starred FROM asset_metadata WHERE asset_id=?", (row["asset_id"],)
)
if meta and meta["starred"]:
head = self.db.one(
"SELECT image_id FROM managed_cloud_uploads WHERE device_id=? AND asset_id=? AND confirmed_at IS NOT NULL ORDER BY uploaded_at DESC,image_id DESC LIMIT 1",
(row["device_id"], row["asset_id"]),
)
if not head or head["image_id"] == row["image_id"]:
return True
if row["job_id"]:
head = self.db.one(
"SELECT image_id FROM managed_cloud_uploads WHERE device_id=? AND job_id=? AND confirmed_at IS NOT NULL ORDER BY uploaded_at DESC,image_id DESC LIMIT 1",
(row["device_id"], row["job_id"]),
)
if head and head["image_id"] == row["image_id"]:
return True
return False
def eligible(self, device_id, image_id):
row = self.db.one(
"SELECT * FROM managed_cloud_uploads WHERE device_id=? AND image_id=?",
(device_id, image_id),
)
if not row or self.retained(row):
return False
# A confirmed replacement is mandatory; failed/unknown uploads cannot
# evict the last known usable cloud image.
newer = self.db.one(
"SELECT image_id FROM managed_cloud_uploads WHERE device_id=? AND confirmed_at IS NOT NULL AND uploaded_at>? LIMIT 1",
(device_id, row["uploaded_at"]),
)
if not newer:
return False
state = self.service.state(device_id)
if state["base_image_id"] == image_id:
return False
if self.db.one(
"SELECT id FROM overrides WHERE device_id=? AND image_id=?",
(device_id, image_id),
):
return False
for r in self.db.all(
"SELECT payload FROM playlists UNION ALL SELECT payload FROM jobs"
):
data = json.loads(r["payload"])
if data.get("device_id") == device_id and (
image_id in data.get("image_ids", [])
or data.get("source", {}).get("image_id") == image_id
):
return False
return True
def sweep(self):
if time.monotonic() - self.checked < 60:
return
self.checked = time.monotonic()
with self.service.lock:
if (
not self.service.settings.mock
and not self.service.settings.write_enabled
):
return
for row in self.db.all(
"SELECT * FROM managed_cloud_uploads WHERE cleanup_operation IS NULL ORDER BY uploaded_at"
):
device_id, image_id = row["device_id"], row["image_id"]
if self.service.blocked(device_id) or not self.eligible(
device_id, image_id
):
continue
device = self.service.registry.get(device_id).device(refresh=True)
if (
device.status != "online"
or not device.current_content.synced
or device.current_content.id == image_id
):
continue
id = self.service.submit(
"delete",
{
"image_id": image_id,
"confirm_current": False,
"cloud_cleanup": True,
},
device_id,
"cloud-library",
"cloud-cleanup-" + row["operation_id"],
"cloud-cleanup-"
+ row["operation_id"]
+ "-"
+ datetime.now(UTC).strftime("%Y%m%d%H%M"),
)
self.db.execute(
"UPDATE managed_cloud_uploads SET cleanup_operation=? WHERE device_id=? AND image_id=?",
(id, device_id, image_id),
)