fix: close library upload flow and harden image storage
This commit is contained in:
@@ -155,7 +155,9 @@ def install_ai_images(app, service):
|
||||
image_data = base64.b64decode(result[0]["b64_json"], validate=True)
|
||||
else:
|
||||
image_data = service.assets.fetch_url(result[0]["url"])
|
||||
with Image.open(BytesIO(image_data)) as image:
|
||||
with Image.open(
|
||||
BytesIO(image_data), formats=("JPEG", "PNG", "WEBP")
|
||||
) as image:
|
||||
if image.width * image.height > MAX_IMAGE_PIXELS:
|
||||
raise ValueError()
|
||||
rgba = ImageOps.exif_transpose(image).convert("RGBA")
|
||||
|
||||
@@ -76,7 +76,9 @@ def install_extensions(app, service):
|
||||
if len(data) > settings.max_bytes:
|
||||
raise FlowError(E.INVALID_REQUEST, "Image exceeds size limit")
|
||||
try:
|
||||
with Image.open(BytesIO(data)) as image:
|
||||
with Image.open(
|
||||
BytesIO(data), formats=("JPEG", "PNG", "WEBP")
|
||||
) as image:
|
||||
if (
|
||||
image.format not in {"JPEG", "PNG", "WEBP"}
|
||||
or image.width * image.height > MAX_IMAGE_PIXELS
|
||||
@@ -92,11 +94,11 @@ def install_extensions(app, service):
|
||||
raise FlowError(E.INVALID_REQUEST, "Invalid image") from None
|
||||
result = []
|
||||
for data, name in prepared:
|
||||
asset = service.assets.put(data, name)
|
||||
db.execute(
|
||||
"INSERT INTO asset_metadata(asset_id,origin) VALUES(?,'manual')",
|
||||
(asset["asset_id"],),
|
||||
)
|
||||
with service.assets.creating(data, name) as asset:
|
||||
db.execute(
|
||||
"INSERT INTO asset_metadata(asset_id,origin) VALUES(?,'manual')",
|
||||
(asset["asset_id"],),
|
||||
)
|
||||
result.append(asset)
|
||||
return result
|
||||
|
||||
@@ -220,6 +222,10 @@ def install_extensions(app, service):
|
||||
for row in db.all("SELECT * FROM clients")
|
||||
]
|
||||
|
||||
@app.get("/api/v1/client-scopes", dependencies=[Depends(require("admin"))])
|
||||
def client_scopes():
|
||||
return ALL_SCOPES
|
||||
|
||||
@app.post(
|
||||
"/api/v1/clients",
|
||||
status_code=201,
|
||||
|
||||
@@ -37,7 +37,9 @@ def prepare(service, content, device_id=None):
|
||||
if source.avoid_duplicates and quality["fingerprint"] in hashes:
|
||||
continue
|
||||
hashes.update([quality["sha256"], quality["fingerprint"]])
|
||||
with Image.open(BytesIO(data)) as image:
|
||||
with Image.open(
|
||||
BytesIO(data), formats=("JPEG", "PNG", "WEBP")
|
||||
) as image:
|
||||
photo = ImageOps.exif_transpose(image).convert("RGB")
|
||||
photo.thumbnail((2400, 2400))
|
||||
output = BytesIO()
|
||||
|
||||
@@ -126,7 +126,9 @@ class LayoutStudio:
|
||||
plan = local_plan(request)
|
||||
metadata = []
|
||||
for asset_id in request.asset_ids:
|
||||
with Image.open(self.renderer.assets.path(asset_id)) as image:
|
||||
with Image.open(
|
||||
self.renderer.assets.path(asset_id), formats=("JPEG", "PNG", "WEBP")
|
||||
) as image:
|
||||
metadata.append(
|
||||
{"asset_id": asset_id, "width": image.width, "height": image.height}
|
||||
)
|
||||
@@ -203,7 +205,10 @@ class LayoutStudio:
|
||||
plan = LayoutPlan.model_validate(data["layout"])
|
||||
canvas = Image.new("RGB", (plan.width, plan.height), "white")
|
||||
for region in plan.regions:
|
||||
with Image.open(self.renderer.assets.path(region.asset_id)) as source:
|
||||
with Image.open(
|
||||
self.renderer.assets.path(region.asset_id),
|
||||
formats=("JPEG", "PNG", "WEBP"),
|
||||
) as source:
|
||||
image = ImageOps.exif_transpose(source).convert("RGB")
|
||||
image = ImageOps.fit(
|
||||
image,
|
||||
|
||||
@@ -7,7 +7,7 @@ from PIL import Image, ImageFilter, ImageOps, ImageStat
|
||||
|
||||
|
||||
def photo_quality(data):
|
||||
with Image.open(BytesIO(data)) as source:
|
||||
with Image.open(BytesIO(data), formats=("JPEG", "PNG", "WEBP")) as source:
|
||||
image = ImageOps.exif_transpose(source).convert("L")
|
||||
image.thumbnail((256, 256))
|
||||
brightness = ImageStat.Stat(image).mean[0]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Opt-in local retention, protecting stars, current content and durable references."""
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
@@ -80,7 +81,19 @@ class LocalRetention:
|
||||
def stats(self):
|
||||
rows = self.inspect()
|
||||
protected = self.protected()
|
||||
disk = shutil.disk_usage(self.service.settings.data_dir)
|
||||
low_space = disk.free < max(512 * 1024 * 1024, int(disk.total * 0.05))
|
||||
return {
|
||||
"disk": {
|
||||
"total_bytes": disk.total,
|
||||
"used_bytes": disk.used,
|
||||
"free_bytes": disk.free,
|
||||
"low_space": low_space,
|
||||
},
|
||||
"pending_file_cleanup": len(
|
||||
self.service.db.all("SELECT id FROM asset_file_journal")
|
||||
)
|
||||
+ len(self.service.db.all("SELECT id FROM asset_file_moves")),
|
||||
"policy": self.config().model_dump(),
|
||||
"total_bytes": sum(x[2] for x in rows),
|
||||
"asset_count": len(rows),
|
||||
@@ -92,6 +105,7 @@ class LocalRetention:
|
||||
return {}
|
||||
if not dry_run:
|
||||
self.last = time.monotonic()
|
||||
self.service.assets.recover_files()
|
||||
config = self.config()
|
||||
if not config.enabled:
|
||||
return {"deleted": 0, "bytes_freed": 0, "disabled": True}
|
||||
@@ -101,7 +115,7 @@ class LocalRetention:
|
||||
total = sum(x[2] for x in rows)
|
||||
removed = []
|
||||
freed = 0
|
||||
for row, path, size in rows:
|
||||
for row, _path, size in rows:
|
||||
# Manual uploaded photos are never removed by automatic retention.
|
||||
if row["id"] in protected or row["kind"] not in ("generated", "cache"):
|
||||
continue
|
||||
@@ -119,15 +133,7 @@ class LocalRetention:
|
||||
total -= size
|
||||
freed += size
|
||||
if not dry_run:
|
||||
path.unlink(missing_ok=True)
|
||||
for table, key in [
|
||||
("asset_metadata", "asset_id"),
|
||||
("asset_sources", "asset_id"),
|
||||
("assets", "id"),
|
||||
]:
|
||||
self.service.db.execute(
|
||||
f"DELETE FROM {table} WHERE {key}=?", (row["id"],)
|
||||
)
|
||||
self.service.libraries.remove_asset(row["id"])
|
||||
result = {"deleted": len(removed), "bytes_freed": freed, "dry_run": dry_run}
|
||||
if not dry_run:
|
||||
self.service.db.execute(
|
||||
|
||||
Reference in New Issue
Block a user