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