🏗️ Build and Test JellyTau / Run Tests (push) Successful in 29m23s
🏗️ Build and Test JellyTau / Supply Chain (push) Successful in 44s
📱 Test APK / Build test APK (push) Successful in 43m47s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 6m33s
Traceability Validation / Check Requirement Traces (push) Successful in 14s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 5m21s
Two frontend halves of the version-compatibility work.
The login flow now renders ServerCompatibility. A server below the floor blocks
with a message naming the minimum; a server newer than this build gets a
non-blocking note and proceeds; an unreadable version says nothing at all,
because refusing — or even warning — on a version string we could not parse would
punish the user for a limitation of ours.
The frontend never receives a version number to reason about, only the opaque
verdict, for the same reason it never receives an item-type list. Rust decides
whether the server is usable; the frontend decides only how that reads.
Separately, imageCache.getCachedImageUrl is deleted. It built
${serverUrl}/Items/${itemId}/Images/${imageType} in Svelte — a Jellyfin route in
the presentation layer, which is domain logic by this project's own litmus test
(would it change if Jellyfin changed its API?). check:boundary does not catch it:
the tripwire flags item-type array literals, not route strings.
It was also entirely unused. Nothing outside its own file and test ever called
it; the live path is CachedImage.svelte -> commands.imageGetUrl -> Rust, which
was already correct. So the leak was in dead code and the fix is a deletion
rather than a migration.
One consequence left deliberately unacted: that function was the last
convertFileSrc caller, so the asset-protocol grant narrowed to
$APPDATA/thumbnails/** under DR-198 now has no caller at all. Dropping a
capability grant is a security change that deserves its own commit and its own
testing on Android, not a side effect of deleting dead code. Noted in the file.
TRACES: UR-012, UR-085 | DR-285, DR-286
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
89 lines
2.7 KiB
TypeScript
89 lines
2.7 KiB
TypeScript
// Image cache service — cache statistics, limits and eviction.
|
|
//
|
|
// This module used to also export `getCachedImageUrl`, which built
|
|
// `${serverUrl}/Items/${itemId}/Images/${imageType}` in the frontend. That was a
|
|
// Jellyfin route in the presentation layer — domain logic by this project's own
|
|
// litmus test (would it change if Jellyfin changed its API?) — and it was
|
|
// **dead**: nothing outside this file and its test ever called it. The live path
|
|
// is CachedImage.svelte -> commands.imageGetUrl -> Rust, which was already
|
|
// correct. It was deleted rather than migrated (DR-285).
|
|
//
|
|
// Note for whoever touches the CSP next: that function was the last
|
|
// `convertFileSrc` caller, so the asset-protocol grant narrowed to
|
|
// `$APPDATA/thumbnails/**` under DR-198 now has no caller at all and is a
|
|
// candidate for removal. Left in place here deliberately — dropping a capability
|
|
// grant is a security change that deserves its own commit and its own testing on
|
|
// Android, not a side effect of deleting dead code.
|
|
//
|
|
// TRACES: UR-007, UR-085 | DR-016, DR-285
|
|
|
|
import { commands } from "$lib/api/bindings";
|
|
|
|
/**
|
|
* Statistics about the thumbnail cache
|
|
*/
|
|
export interface ImageCacheStats {
|
|
totalSizeBytes: number;
|
|
itemCount: number;
|
|
limitBytes: number;
|
|
}
|
|
|
|
/**
|
|
* Get thumbnail cache statistics
|
|
*/
|
|
export async function getCacheStats(): Promise<ImageCacheStats> {
|
|
return commands.thumbnailGetStats();
|
|
}
|
|
|
|
/**
|
|
* Set cache storage limit in bytes
|
|
*
|
|
* @param limitBytes - The maximum cache size in bytes (0 = unlimited)
|
|
*/
|
|
export async function setCacheLimit(limitBytes: number): Promise<void> {
|
|
await commands.thumbnailSetLimit(limitBytes);
|
|
}
|
|
|
|
/**
|
|
* Clear all cached thumbnails
|
|
*/
|
|
export async function clearCache(): Promise<void> {
|
|
await commands.thumbnailClearCache();
|
|
}
|
|
|
|
/**
|
|
* Delete cached thumbnails for a specific item
|
|
*
|
|
* @param itemId - The Jellyfin item ID
|
|
*/
|
|
export async function deleteItemCache(itemId: string): Promise<void> {
|
|
await commands.thumbnailDeleteItem(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);
|
|
}
|