Add mount-free Synology Photos API sources with bounded image cache

This commit is contained in:
渡口浪人
2026-10-16 22:31:13 +08:00
parent 2275974cde
commit 66ed1e2c9b
21 changed files with 1081 additions and 22 deletions
+112
View File
@@ -0,0 +1,112 @@
"""Read-only live Photos check. Never creates an app, worker, job or frame driver."""
import argparse
import io
import json
import os
from pathlib import Path
from PIL import Image
from pigway_frame_flow.errors import FlowError
from pigway_frame_flow.providers.photo_sources import PhotoSourceInput, PhotoSources
from pigway_frame_flow.settings import Settings
from pigway_frame_flow.storage.database import Database
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--bootstrap", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
if Path("/photos").exists():
raise SystemExit("Verification requires /photos to be absent")
bootstrap = json.loads(args.bootstrap.read_text())
os.environ["SYNOLOGY_PHOTOS_URL"] = bootstrap["url"]
os.environ["SYNOLOGY_PHOTOS_CERT_SHA256"] = bootstrap["pin"]
settings = Settings(mock=True, write_enabled=False, api_key="", api_token="")
settings.data_dir.mkdir(parents=True, exist_ok=True)
settings.config_dir.mkdir(parents=True, exist_ok=True)
session = settings.config_dir / "synology-photos-session.json"
session.write_text(json.dumps(bootstrap["session"]))
session.chmod(0o600)
db = Database(settings.data_dir / "verification.db")
photos = PhotoSources(db, settings, None)
report = {"no_photos_mount": True, "device_writes": 0, "spaces": {}}
try:
for space in ("shared", "personal"):
people = photos.synology.people(space)
candidates = sorted(
(p for p in people if p["count"] > 1), key=lambda p: p["count"]
)
if len(candidates) < 2:
raise RuntimeError("Need two accessible populated person albums")
selected = [p["id"] for p in candidates[:2]]
one = photos.synology.items(selected[:1], space=space)
two = photos.synology.items(selected[1:], space=space)
ids_one, ids_two = {r["id"] for r in one}, {r["id"] for r in two}
any_rows = photos.synology.items(selected, "any", space=space)
all_rows = photos.synology.items(selected, "all", space=space)
assert {r["id"] for r in any_rows} == ids_one | ids_two
assert {r["id"] for r in all_rows} == ids_one & ids_two
source_id = "live-" + space
photos.save(
source_id,
PhotoSourceInput(
name="Read-only verification",
kind="synology-photos",
location=".",
people_space=space,
people_filter=True,
person_ids=selected,
),
)
catalog = photos.catalog(source_id)
assert catalog
data, _, _ = photos.read(source_id, catalog[0]["id"])
with Image.open(io.BytesIO(data)) as image:
image.load()
decoded = {
"width": image.width,
"height": image.height,
"format": image.format,
}
cache = photos.synology.image_cache
before = cache.downloads
assert photos.read(source_id, catalog[0]["id"])[0] == data
assert cache.downloads == before
report["spaces"][space] = {
"people": len(people),
"any_count": len(any_rows),
"all_count": len(all_rows),
"catalog_count": len(catalog),
"bytes": len(data),
"decoded": decoded,
"cache_reused": True,
}
# A deliberately invalid session is isolated to this verification config;
# no logout request or change is made to the original NAS session.
photos.synology.path.write_text(
json.dumps({"sid": "frameflow-deliberately-invalid-session"})
)
try:
photos.synology.people()
except FlowError as error:
assert error.status == 409
report["expired_session"] = "explicit_reconnect_required"
else:
raise RuntimeError("Invalid session unexpectedly accepted")
report["cache_bytes"] = sum(
p.stat().st_size
for p in (settings.data_dir / "cache/synology-photos").glob("*.image")
)
assert report["cache_bytes"] <= settings.photos_cache_bytes
report["original_download"] = "not_supported_by_this_verified_implementation"
args.output.write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps(report, indent=2))
finally:
session.unlink(missing_ok=True)
if __name__ == "__main__":
main()