Files
pigway-frame-flow/scripts/verify-photos-api.py
T

113 lines
4.7 KiB
Python
Raw Normal View History

"""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()