feat: content provenance and complete frame management workflows
This commit is contained in:
@@ -153,8 +153,20 @@ class DisplayService:
|
||||
|
||||
def content_asset(self, content):
|
||||
if isinstance(content, TemplateContent):
|
||||
data = self.renderer.render(content.template, content.data, content.profile)
|
||||
return self.assets.put(data, content.template + ".jpg", "generated")
|
||||
values = self.renderer.resolve(content.template, content.data)
|
||||
data = self.renderer.render(content.template, values, content.profile)
|
||||
asset = self.assets.put(data, content.template + ".jpg", "generated")
|
||||
source = {
|
||||
"type": "template",
|
||||
"template": content.template,
|
||||
"data": self.settings.sanitize(values),
|
||||
"profile": content.profile,
|
||||
}
|
||||
self.db.execute(
|
||||
"INSERT OR REPLACE INTO asset_sources VALUES(?,?)",
|
||||
(asset["asset_id"], encode(source)),
|
||||
)
|
||||
return asset
|
||||
if isinstance(content, URLContent):
|
||||
data = self.assets.fetch_url(content.url)
|
||||
elif isinstance(content, ImageContent) and content.asset_id:
|
||||
@@ -167,6 +179,38 @@ class DisplayService:
|
||||
convert(data, content.fit, content.profile), content.name
|
||||
)
|
||||
|
||||
def current_content(self, device_id):
|
||||
device = self.registry.get(device_id).device()
|
||||
image_id = device.current_content.id
|
||||
row = self.db.one(
|
||||
"SELECT * FROM image_sources WHERE device_id=? AND image_id=?",
|
||||
(device_id, image_id),
|
||||
)
|
||||
source = json.loads(row["payload"]) if row else None
|
||||
# Acknowledged upload is not proof that the frame has refreshed yet.
|
||||
if row:
|
||||
op = self.operation(row["operation_id"])
|
||||
if op["status"] not in ("COMPLETED", "SYNCING"):
|
||||
source = None
|
||||
state = self.state(device_id)
|
||||
return {
|
||||
"device_id": device_id,
|
||||
"image_id": image_id,
|
||||
"synced": device.current_content.synced,
|
||||
"source_known": source is not None,
|
||||
"source": source,
|
||||
"asset_id": row["asset_id"] if row and source else None,
|
||||
"operation_id": row["operation_id"] if row and source else None,
|
||||
"preview_url": f"/api/v1/devices/{device_id}/images/{image_id}/file?thumbnail=true"
|
||||
if image_id
|
||||
else None,
|
||||
"previous_image_id": state["previous_image_id"],
|
||||
"overrides": self.db.all(
|
||||
"SELECT id,image_id,priority,expires_at FROM overrides WHERE device_id=? ORDER BY priority DESC,created_at DESC",
|
||||
(device_id,),
|
||||
),
|
||||
}
|
||||
|
||||
def top(self, device_id):
|
||||
return self.db.one(
|
||||
"SELECT * FROM overrides WHERE device_id=? AND expires_at>? ORDER BY priority DESC,created_at DESC LIMIT 1",
|
||||
@@ -268,6 +312,14 @@ class DisplayService:
|
||||
raise FlowError(E.CONFLICT, "Current image needs confirmation", 409)
|
||||
self._start_write(id)
|
||||
driver.delete(image_id)
|
||||
for suffix in [".original", "-thumb.jpg"]:
|
||||
(
|
||||
self.settings.data_dir / "cache" / f"{device_id}-{image_id}{suffix}"
|
||||
).unlink(missing_ok=True)
|
||||
self.db.execute(
|
||||
"DELETE FROM image_sources WHERE device_id=? AND image_id=?",
|
||||
(device_id, image_id),
|
||||
)
|
||||
driver.invalidate()
|
||||
self.update(id, "COMPLETED", {"image_id": image_id})
|
||||
self.db.history(op, "COMPLETED")
|
||||
@@ -334,6 +386,15 @@ class DisplayService:
|
||||
if asset:
|
||||
self.assets.promote(asset["asset_id"])
|
||||
result["rendered_asset_id"] = asset["asset_id"]
|
||||
source = self.db.one(
|
||||
"SELECT payload FROM asset_sources WHERE asset_id=?",
|
||||
(asset["asset_id"],),
|
||||
)
|
||||
descriptor = source["payload"] if source else encode({"type": "image"})
|
||||
self.db.execute(
|
||||
"INSERT OR REPLACE INTO image_sources VALUES(?,?,?,?,?)",
|
||||
(device_id, image_id, asset["asset_id"], id, descriptor),
|
||||
)
|
||||
self.update(id, "SYNCING", result)
|
||||
driver.invalidate()
|
||||
if request.options.restore_previous:
|
||||
|
||||
Reference in New Issue
Block a user