Add mount-free Synology Photos API sources with bounded image cache
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user