First working POC
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
// Image cache service - Handles lazy caching of thumbnails with LRU eviction
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
|
||||
/**
|
||||
* Statistics about the thumbnail cache
|
||||
*/
|
||||
export interface ImageCacheStats {
|
||||
totalSizeBytes: number;
|
||||
itemCount: number;
|
||||
limitBytes: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an image URL, checking cache first then falling back to server.
|
||||
* Triggers background caching if not cached.
|
||||
*
|
||||
* @param serverUrl - The Jellyfin server base URL
|
||||
* @param itemId - The Jellyfin item ID
|
||||
* @param imageType - The image type (Primary, Backdrop, etc.)
|
||||
* @param options - Image options (maxWidth, maxHeight, quality, tag)
|
||||
* @returns The image URL (local asset URL if cached, server URL otherwise)
|
||||
*/
|
||||
export async function getCachedImageUrl(
|
||||
serverUrl: string,
|
||||
itemId: string,
|
||||
imageType: string = "Primary",
|
||||
options: {
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
quality?: number;
|
||||
tag?: string;
|
||||
} = {}
|
||||
): Promise<string> {
|
||||
const tag = options.tag || "default";
|
||||
|
||||
// Try to get cached version
|
||||
try {
|
||||
const cachedPath = await invoke<string | null>("thumbnail_get_cached", {
|
||||
itemId,
|
||||
imageType,
|
||||
tag,
|
||||
});
|
||||
|
||||
if (cachedPath) {
|
||||
// Convert file path to asset URL for Tauri
|
||||
return convertFileSrc(cachedPath);
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug("Failed to check thumbnail cache:", e);
|
||||
}
|
||||
|
||||
// Build server URL
|
||||
const params = new URLSearchParams();
|
||||
if (options.maxWidth) params.set("maxWidth", options.maxWidth.toString());
|
||||
if (options.maxHeight) params.set("maxHeight", options.maxHeight.toString());
|
||||
if (options.quality) params.set("quality", options.quality.toString());
|
||||
if (options.tag) params.set("tag", options.tag);
|
||||
|
||||
const serverImageUrl = `${serverUrl}/Items/${itemId}/Images/${imageType}?${params.toString()}`;
|
||||
|
||||
// Trigger background caching (fire and forget)
|
||||
invoke("thumbnail_save", {
|
||||
itemId,
|
||||
imageType,
|
||||
tag,
|
||||
url: serverImageUrl,
|
||||
}).catch((e) => {
|
||||
// Silently fail - caching is best-effort
|
||||
console.debug("Background thumbnail cache failed:", e);
|
||||
});
|
||||
|
||||
// Return server URL for immediate display
|
||||
return serverImageUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous version that returns server URL immediately
|
||||
* and triggers background caching. Useful for initial render.
|
||||
*
|
||||
* @param serverUrl - The Jellyfin server base URL
|
||||
* @param itemId - The Jellyfin item ID
|
||||
* @param imageType - The image type (Primary, Backdrop, etc.)
|
||||
* @param options - Image options
|
||||
* @returns The server image URL
|
||||
*/
|
||||
export function getImageUrlSync(
|
||||
serverUrl: string,
|
||||
itemId: string,
|
||||
imageType: string = "Primary",
|
||||
options: {
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
quality?: number;
|
||||
tag?: string;
|
||||
} = {}
|
||||
): string {
|
||||
const tag = options.tag || "default";
|
||||
|
||||
// Build server URL
|
||||
const params = new URLSearchParams();
|
||||
if (options.maxWidth) params.set("maxWidth", options.maxWidth.toString());
|
||||
if (options.maxHeight) params.set("maxHeight", options.maxHeight.toString());
|
||||
if (options.quality) params.set("quality", options.quality.toString());
|
||||
if (options.tag) params.set("tag", options.tag);
|
||||
|
||||
const serverImageUrl = `${serverUrl}/Items/${itemId}/Images/${imageType}?${params.toString()}`;
|
||||
|
||||
// Trigger background caching (fire and forget)
|
||||
invoke("thumbnail_save", {
|
||||
itemId,
|
||||
imageType,
|
||||
tag,
|
||||
url: serverImageUrl,
|
||||
}).catch(() => {
|
||||
// Silently fail
|
||||
});
|
||||
|
||||
return serverImageUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thumbnail cache statistics
|
||||
*/
|
||||
export async function getCacheStats(): Promise<ImageCacheStats> {
|
||||
return invoke("thumbnail_get_stats");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache storage limit in bytes
|
||||
*
|
||||
* @param limitBytes - The maximum cache size in bytes (0 = unlimited)
|
||||
*/
|
||||
export async function setCacheLimit(limitBytes: number): Promise<void> {
|
||||
return invoke("thumbnail_set_limit", { limitBytes });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached thumbnails
|
||||
*/
|
||||
export async function clearCache(): Promise<void> {
|
||||
return invoke("thumbnail_clear_cache");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cached thumbnails for a specific item
|
||||
*
|
||||
* @param itemId - The Jellyfin item ID
|
||||
*/
|
||||
export async function deleteItemCache(itemId: string): Promise<void> {
|
||||
return invoke("thumbnail_delete_item", { itemId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Format bytes to human-readable string
|
||||
*
|
||||
* @param bytes - Number of bytes
|
||||
* @returns Human-readable string (e.g., "1.5 GB")
|
||||
*/
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
if (bytes < 1024 * 1024 * 1024)
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert gigabytes to bytes
|
||||
*/
|
||||
export function gbToBytes(gb: number): number {
|
||||
return gb * 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bytes to gigabytes
|
||||
*/
|
||||
export function bytesToGb(bytes: number): number {
|
||||
return bytes / (1024 * 1024 * 1024);
|
||||
}
|
||||
Reference in New Issue
Block a user