Spaces:
Sleeping
Sleeping
| /** | |
| * HuggingFace Spaces環境での画像メモリキャッシュ | |
| * ファイルシステム書き込み制限を回避するためメモリ内で管理 | |
| */ | |
| export class ImageMemoryCache { | |
| private static instance: ImageMemoryCache; | |
| private cache = new Map<string, { buffer: Buffer; contentType: string; createdAt: number }>(); | |
| private maxSize = 100 * 1024 * 1024; // 100MB制限 | |
| private maxAge = 30 * 60 * 1000; // 30分 | |
| private currentSize = 0; | |
| private constructor() { | |
| // 定期的に古いエントリを削除 | |
| setInterval(() => this.cleanup(), 5 * 60 * 1000); // 5分ごと | |
| } | |
| static getInstance(): ImageMemoryCache { | |
| if (!ImageMemoryCache.instance) { | |
| ImageMemoryCache.instance = new ImageMemoryCache(); | |
| } | |
| return ImageMemoryCache.instance; | |
| } | |
| /** | |
| * 画像をキャッシュに保存 | |
| */ | |
| store(id: string, buffer: Buffer, contentType: string = 'image/webp'): string { | |
| const size = buffer.length; | |
| // サイズ制限チェック | |
| if (this.currentSize + size > this.maxSize) { | |
| this.evictOldest(); | |
| } | |
| this.cache.set(id, { | |
| buffer, | |
| contentType, | |
| createdAt: Date.now(), | |
| }); | |
| this.currentSize += size; | |
| console.log(`[ImageCache] Stored ${id} (${(size / 1024).toFixed(1)}KB), total: ${(this.currentSize / 1024 / 1024).toFixed(1)}MB`); | |
| // APIエンドポイント経由のURL返却 | |
| return `/api/cached-images/${id}`; | |
| } | |
| /** | |
| * 画像を取得 | |
| */ | |
| get(id: string): { buffer: Buffer; contentType: string } | null { | |
| const entry = this.cache.get(id); | |
| if (!entry) return null; | |
| // 有効期限チェック | |
| if (Date.now() - entry.createdAt > this.maxAge) { | |
| this.remove(id); | |
| return null; | |
| } | |
| return { | |
| buffer: entry.buffer, | |
| contentType: entry.contentType, | |
| }; | |
| } | |
| /** | |
| * 画像を削除 | |
| */ | |
| private remove(id: string): void { | |
| const entry = this.cache.get(id); | |
| if (entry) { | |
| this.currentSize -= entry.buffer.length; | |
| this.cache.delete(id); | |
| } | |
| } | |
| /** | |
| * 最古のエントリを削除 | |
| */ | |
| private evictOldest(): void { | |
| let oldest: { id: string; createdAt: number } | null = null; | |
| for (const [id, entry] of this.cache.entries()) { | |
| if (!oldest || entry.createdAt < oldest.createdAt) { | |
| oldest = { id, createdAt: entry.createdAt }; | |
| } | |
| } | |
| if (oldest) { | |
| console.log(`[ImageCache] Evicting oldest entry: ${oldest.id}`); | |
| this.remove(oldest.id); | |
| } | |
| } | |
| /** | |
| * 期限切れエントリをクリーンアップ | |
| */ | |
| private cleanup(): void { | |
| const now = Date.now(); | |
| const expired: string[] = []; | |
| for (const [id, entry] of this.cache.entries()) { | |
| if (now - entry.createdAt > this.maxAge) { | |
| expired.push(id); | |
| } | |
| } | |
| if (expired.length > 0) { | |
| console.log(`[ImageCache] Cleaning up ${expired.length} expired entries`); | |
| expired.forEach((id) => this.remove(id)); | |
| } | |
| } | |
| /** | |
| * キャッシュ状態を取得 | |
| */ | |
| getStats() { | |
| return { | |
| entries: this.cache.size, | |
| sizeBytes: this.currentSize, | |
| sizeMB: (this.currentSize / 1024 / 1024).toFixed(2), | |
| maxSizeMB: (this.maxSize / 1024 / 1024).toFixed(2), | |
| }; | |
| } | |
| } |