Add mount-free Synology Photos API sources with bounded image cache
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""Offline, explicit local-to-Photos-API source conversion; defaults to dry run."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from pigway_frame_flow.providers.photo_sources import PhotoSourceInput
|
||||
|
||||
|
||||
def convert(database, source_ids, *, apply=False):
|
||||
connection = sqlite3.connect(f"file:{database}?mode=rw", uri=True)
|
||||
try:
|
||||
updates = []
|
||||
for source_id in source_ids:
|
||||
key = "photo_source:" + source_id
|
||||
row = connection.execute(
|
||||
"SELECT payload FROM app_config WHERE key=?", (key,)
|
||||
).fetchone()
|
||||
if not row:
|
||||
raise ValueError("Source not found: " + source_id)
|
||||
source = PhotoSourceInput.model_validate_json(row[0])
|
||||
if source.kind != "local":
|
||||
raise ValueError(
|
||||
"Only explicitly selected local sources can be converted"
|
||||
)
|
||||
location = source.location.strip("/")
|
||||
if (
|
||||
source.location.startswith("/")
|
||||
or "\\" in location
|
||||
or ".." in location.split("/")
|
||||
):
|
||||
raise ValueError(
|
||||
"Review the source's relative Photos folder path first"
|
||||
)
|
||||
payload = source.model_copy(
|
||||
update={"kind": "synology-photos", "synology_previews": False}
|
||||
)
|
||||
updates.append((payload.model_dump_json(), key))
|
||||
backup = None
|
||||
if apply:
|
||||
backup = database.with_name(
|
||||
database.name
|
||||
+ ".before-photos-api-"
|
||||
+ datetime.now(UTC).strftime("%Y%m%dT%H%M%S%fZ")
|
||||
)
|
||||
with sqlite3.connect(backup) as snapshot:
|
||||
connection.backup(snapshot)
|
||||
backup.chmod(0o600)
|
||||
with connection:
|
||||
connection.executemany(
|
||||
"UPDATE app_config SET payload=? WHERE key=?", updates
|
||||
)
|
||||
return {
|
||||
"applied": apply,
|
||||
"sources": source_ids,
|
||||
"backup": str(backup) if backup else None,
|
||||
}
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--database", type=Path, required=True)
|
||||
parser.add_argument("--source", action="append", required=True)
|
||||
parser.add_argument("--apply", action="store_true")
|
||||
parser.add_argument(
|
||||
"--service-stopped",
|
||||
action="store_true",
|
||||
help="Confirm the target service is stopped before changing its database",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.apply and not args.service_stopped:
|
||||
parser.error(
|
||||
"Stop the target service and pass --service-stopped before applying"
|
||||
)
|
||||
print(json.dumps(convert(args.database, args.source, apply=args.apply), indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user