Family Home Lab: portal, dsh (chat+plugins), transcriber, music/media tools, home dash
This commit is contained in:
66
portal/s3.py
Normal file
66
portal/s3.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""S3 (Garage) helpers shared by the web layer and Celery workers."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import boto3
|
||||
from botocore.config import Config
|
||||
|
||||
from .config import settings
|
||||
|
||||
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def s3_client():
|
||||
"""Garage S3 client (S3v4 signatures)."""
|
||||
return boto3.client(
|
||||
"s3",
|
||||
endpoint_url=settings.S3_ENDPOINT_URL,
|
||||
region_name=settings.S3_REGION or None,
|
||||
aws_access_key_id=settings.S3_ACCESS_KEY or None,
|
||||
aws_secret_access_key=settings.S3_SECRET_KEY or None,
|
||||
config=Config(signature_version="s3v4"),
|
||||
)
|
||||
|
||||
|
||||
def s3_bucket_name(username: str) -> str:
|
||||
"""Valid S3 bucket name for a user.
|
||||
|
||||
S3 names must be >= 3 chars, lowercase, only letters/numbers/dots/hyphens.
|
||||
Short usernames (e.g. "jo") get a suffix so the bucket is legal.
|
||||
"""
|
||||
u = (username or "").strip().lower()
|
||||
if not u:
|
||||
return "shared-media"
|
||||
return u if len(u) >= 3 else f"{u}-media"
|
||||
|
||||
|
||||
def list_recent(bucket: str, max_keys: int = 20) -> list[dict]:
|
||||
"""Return the newest objects in a bucket, newest first.
|
||||
|
||||
Returns an empty list on any error (unreachable/misconfigured/empty), so the
|
||||
UI can degrade gracefully.
|
||||
"""
|
||||
try:
|
||||
resp = s3_client().list_objects_v2(Bucket=bucket, MaxKeys=max_keys)
|
||||
except Exception: # noqa: BLE001 - degrade to "no files"
|
||||
return []
|
||||
objects = [
|
||||
{
|
||||
"key": o.get("Key"),
|
||||
"size": o.get("Size", 0),
|
||||
"last_modified": o.get("LastModified"),
|
||||
}
|
||||
for o in resp.get("Contents", [])
|
||||
]
|
||||
objects.sort(key=lambda x: x["last_modified"] or _EPOCH, reverse=True)
|
||||
return objects
|
||||
|
||||
|
||||
def list_prefix(bucket: str, prefix: str, max_keys: int = 200) -> list[str]:
|
||||
"""List object keys under a prefix (best-effort)."""
|
||||
try:
|
||||
resp = s3_client().list_objects_v2(Bucket=bucket, Prefix=prefix, MaxKeys=max_keys)
|
||||
except Exception: # noqa: BLE001
|
||||
return []
|
||||
return [o.get("Key") for o in resp.get("Contents", [])]
|
||||
Reference in New Issue
Block a user