travel-proxy 이미지 썸네일 로딩 최적화, 페이지네이션 추가
This commit is contained in:
@@ -149,17 +149,20 @@ def scan_album(album: str) -> List[Dict[str, Any]]:
|
||||
return []
|
||||
|
||||
items = []
|
||||
for p in album_dir.iterdir():
|
||||
if p.is_file() and p.suffix.lower() in IMAGE_EXT:
|
||||
# ✅ 썸네일 생성 보장
|
||||
ensure_thumb(p, album)
|
||||
|
||||
items.append({
|
||||
"album": album,
|
||||
"file": p.name,
|
||||
"url": f"{MEDIA_BASE}/{album}/{p.name}",
|
||||
"thumb": f"{MEDIA_BASE}/.thumb/{album}/{p.name}",
|
||||
})
|
||||
# os.scandir이 iterdir보다 빠름 (파일 속성 한 번에 가져옴)
|
||||
with os.scandir(album_dir) as entries:
|
||||
for entry in entries:
|
||||
if entry.is_file() and Path(entry.name).suffix.lower() in IMAGE_EXT:
|
||||
# ⚡ 성능 핵심: 여기서 썸네일 생성(ensure_thumb) 절대 하지 않음!
|
||||
# 파일 존재 여부만 확인하고 바로 리턴
|
||||
items.append({
|
||||
"album": album,
|
||||
"file": entry.name,
|
||||
"url": f"{MEDIA_BASE}/{album}/{entry.name}",
|
||||
"thumb": f"{MEDIA_BASE}/.thumb/{album}/{entry.name}",
|
||||
# 정렬을 위해 수정시간/이름 필요하면 여기서 저장
|
||||
"mtime": entry.stat().st_mtime
|
||||
})
|
||||
return items
|
||||
|
||||
# -----------------------------
|
||||
@@ -174,40 +177,58 @@ def regions():
|
||||
@app.get("/api/travel/photos")
|
||||
def photos(
|
||||
region: str = Query(...),
|
||||
limit: int = Query(500, le=5000),
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(20, ge=1, le=100),
|
||||
):
|
||||
_meta_changed_invalidate_cache()
|
||||
|
||||
# 1. 캐시 키에 페이지 정보 포함하지 않음 (전체 리스트 캐싱 후 슬라이싱 전략)
|
||||
# 왜냐하면 파일 스캔 비용이 크지, 메모리 슬라이싱 비용은 작기 때문.
|
||||
now = time.time()
|
||||
cached = CACHE.get(region)
|
||||
if cached and now - cached["ts"] < CACHE_TTL:
|
||||
return cached["data"]
|
||||
|
||||
if not cached or now - cached["ts"] >= CACHE_TTL:
|
||||
region_map = load_region_map()
|
||||
albums = _get_albums_for_region(region, region_map)
|
||||
|
||||
region_map = load_region_map()
|
||||
albums = _get_albums_for_region(region, region_map)
|
||||
all_items = []
|
||||
matched = []
|
||||
|
||||
all_items = []
|
||||
matched = []
|
||||
for album in albums:
|
||||
items = scan_album(album)
|
||||
matched.append({"album": album, "count": len(items)})
|
||||
all_items.extend(items)
|
||||
|
||||
for album in albums:
|
||||
items = scan_album(album)
|
||||
matched.append({"album": album, "count": len(items)})
|
||||
all_items.extend(items)
|
||||
# 정렬: 앨범명 > 파일명 (또는 찍은 날짜)
|
||||
all_items.sort(key=lambda x: (x["album"], x["file"]))
|
||||
|
||||
cached_data = {
|
||||
"region": region,
|
||||
"matched_albums": matched,
|
||||
"items": all_items,
|
||||
"total": len(all_items),
|
||||
}
|
||||
CACHE[region] = {"ts": now, "data": cached_data}
|
||||
else:
|
||||
cached_data = cached["data"]
|
||||
|
||||
all_items.sort(key=lambda x: (x["album"], x["file"]))
|
||||
# 2. 페이지네이션 슬라이싱
|
||||
all_items = cached_data["items"]
|
||||
total = len(all_items)
|
||||
start = (page - 1) * size
|
||||
end = start + size
|
||||
|
||||
paged_items = all_items[start:end]
|
||||
|
||||
data = {
|
||||
return {
|
||||
"region": region,
|
||||
"matched_albums": matched,
|
||||
"items": all_items[:limit],
|
||||
"total": len(all_items),
|
||||
"cached_at": int(now),
|
||||
"cache_ttl": CACHE_TTL,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"total": total,
|
||||
"has_next": end < total,
|
||||
"items": paged_items,
|
||||
}
|
||||
|
||||
CACHE[region] = {"ts": now, "data": data}
|
||||
return data
|
||||
|
||||
|
||||
@app.get("/media/travel/.thumb/{album}/{filename}")
|
||||
def get_thumb(album: str, filename: str):
|
||||
|
||||
Reference in New Issue
Block a user