mirror of
https://github.com/plexguide/Huntarr.io.git
synced 2026-04-20 20:16:52 -04:00
update
This commit is contained in:
@@ -2404,6 +2404,13 @@ let huntarrUI = {
|
||||
|
||||
// Initialize when document is ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize TMDB image cache first
|
||||
if (typeof tmdbImageCache !== 'undefined') {
|
||||
tmdbImageCache.init().catch(error => {
|
||||
console.error('[app.js] Failed to initialize TMDB image cache:', error);
|
||||
});
|
||||
}
|
||||
|
||||
huntarrUI.init();
|
||||
|
||||
// Initialize our enhanced UI features
|
||||
|
||||
@@ -862,6 +862,20 @@ export class RequestarrContent {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Load and cache image asynchronously after card is created
|
||||
if (posterUrl && !posterUrl.includes('./static/images/') && window.getCachedTMDBImage && window.tmdbImageCache) {
|
||||
const imgElement = card.querySelector('.media-card-poster img');
|
||||
if (imgElement) {
|
||||
window.getCachedTMDBImage(posterUrl, window.tmdbImageCache).then(cachedUrl => {
|
||||
if (cachedUrl && cachedUrl !== posterUrl) {
|
||||
imgElement.src = cachedUrl;
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('[RequestarrContent] Failed to cache image:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const posterDiv = card.querySelector('.media-card-poster');
|
||||
const requestBtn = card.querySelector('.media-card-request-btn');
|
||||
const hideBtn = card.querySelector('.media-card-hide-btn');
|
||||
|
||||
@@ -85,8 +85,10 @@ export class RequestarrModal {
|
||||
finalDefault: defaultInstance
|
||||
});
|
||||
|
||||
const backdropUrl = data.backdrop_path || '';
|
||||
|
||||
let modalHTML = `
|
||||
<div class="request-modal-header" style="background-image: url(${data.backdrop_path || ''});">
|
||||
<div class="request-modal-header" style="background-image: url(${backdropUrl});">
|
||||
<button class="modal-close-btn" onclick="window.RequestarrDiscover.modal.closeModal()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
@@ -646,6 +648,21 @@ export class RequestarrModal {
|
||||
}
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Cache backdrop image asynchronously after modal is in DOM
|
||||
const backdropUrl = this.core.currentModalData?.backdrop_path;
|
||||
if (backdropUrl && !backdropUrl.includes('./static/images/') && window.getCachedTMDBImage && window.tmdbImageCache) {
|
||||
const header = modal.querySelector('.request-modal-header');
|
||||
if (header) {
|
||||
window.getCachedTMDBImage(backdropUrl, window.tmdbImageCache).then(cachedUrl => {
|
||||
if (cachedUrl && cachedUrl !== backdropUrl) {
|
||||
header.style.backgroundImage = `url(${cachedUrl})`;
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('[RequestarrModal] Failed to cache backdrop:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
|
||||
@@ -34,9 +34,11 @@ export class RequestarrSettings {
|
||||
|
||||
if (data.requests && data.requests.length > 0) {
|
||||
container.innerHTML = '';
|
||||
data.requests.forEach(request => {
|
||||
container.appendChild(this.createHistoryItem(request));
|
||||
});
|
||||
// Use Promise.all to wait for all async createHistoryItem calls
|
||||
const items = await Promise.all(
|
||||
data.requests.map(request => this.createHistoryItem(request))
|
||||
);
|
||||
items.forEach(item => container.appendChild(item));
|
||||
} else {
|
||||
container.innerHTML = '<p style="color: #888; text-align: center; padding: 60px;">No request history</p>';
|
||||
}
|
||||
@@ -46,7 +48,7 @@ export class RequestarrSettings {
|
||||
}
|
||||
}
|
||||
|
||||
createHistoryItem(request) {
|
||||
async createHistoryItem(request) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'history-item';
|
||||
|
||||
@@ -66,6 +68,19 @@ export class RequestarrSettings {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Load and cache image asynchronously
|
||||
if (posterUrl && !posterUrl.includes('./static/images/') && window.getCachedTMDBImage && window.tmdbImageCache) {
|
||||
try {
|
||||
const cachedUrl = await window.getCachedTMDBImage(posterUrl, window.tmdbImageCache);
|
||||
if (cachedUrl && cachedUrl !== posterUrl) {
|
||||
const imgElement = item.querySelector('.history-poster img');
|
||||
if (imgElement) imgElement.src = cachedUrl;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[RequestarrSettings] Failed to cache history image:', err);
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -362,7 +377,7 @@ export class RequestarrSettings {
|
||||
};
|
||||
}
|
||||
|
||||
createHiddenMediaCard(item) {
|
||||
async createHiddenMediaCard(item) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'media-card';
|
||||
card.setAttribute('data-tmdb-id', item.tmdb_id);
|
||||
@@ -379,6 +394,19 @@ export class RequestarrSettings {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Load and cache image asynchronously
|
||||
if (posterUrl && !posterUrl.includes('./static/images/') && window.getCachedTMDBImage && window.tmdbImageCache) {
|
||||
try {
|
||||
const cachedUrl = await window.getCachedTMDBImage(posterUrl, window.tmdbImageCache);
|
||||
if (cachedUrl && cachedUrl !== posterUrl) {
|
||||
const imgElement = card.querySelector('.media-card-poster img');
|
||||
if (imgElement) imgElement.src = cachedUrl;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[RequestarrSettings] Failed to cache hidden media image:', err);
|
||||
}
|
||||
}
|
||||
|
||||
const unhideBtn = card.querySelector('.media-card-unhide-btn');
|
||||
if (unhideBtn) {
|
||||
unhideBtn.addEventListener('click', async (e) => {
|
||||
|
||||
@@ -757,6 +757,7 @@ window.SettingsForms = {
|
||||
enable_requestarr: getVal('enable_requestarr', true),
|
||||
low_usage_mode: getVal('low_usage_mode', true),
|
||||
show_trending: getVal('show_trending', true),
|
||||
tmdb_image_cache_days: parseInt(container.querySelector('#tmdb_image_cache_days')?.value || '7'),
|
||||
auth_mode: (container.querySelector('#auth_mode') && container.querySelector('#auth_mode').value) || 'login',
|
||||
ssl_verify: getVal('ssl_verify', true),
|
||||
base_url: getVal('base_url', ''),
|
||||
|
||||
@@ -246,6 +246,16 @@
|
||||
box-shadow: 0 4px 12px rgba(90, 109, 137, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
">
|
||||
<h3>Display Settings</h3>
|
||||
<div class="setting-item">
|
||||
<label for="tmdb_image_cache_days"><a href="https://plexguide.github.io/Huntarr.io/settings/settings.html#tmdb-image-cache" class="info-icon" title="Learn more about TMDB image caching" target="_blank" rel="noopener"><i class="fas fa-info-circle"></i></a>TMDB Image Cache:</label>
|
||||
<select id="tmdb_image_cache_days" class="control-select" style="width: 200px;">
|
||||
<option value="0" ${settings.tmdb_image_cache_days === 0 ? "selected" : ""}>Disabled (Always Load)</option>
|
||||
<option value="1" ${settings.tmdb_image_cache_days === 1 ? "selected" : ""}>1 Day</option>
|
||||
<option value="7" ${(settings.tmdb_image_cache_days === 7 || settings.tmdb_image_cache_days === undefined) ? "selected" : ""}>7 Days</option>
|
||||
<option value="30" ${settings.tmdb_image_cache_days === 30 ? "selected" : ""}>30 Days</option>
|
||||
</select>
|
||||
<p class="setting-help" style="margin-left: -3ch !important;">Cache TMDB images to reduce load times and API usage. Missing images will still attempt to load. Set to "Disabled" to always fetch fresh images.</p>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label for="enable_requestarr">Enable Requestarr:</label>
|
||||
<label class="toggle-switch" style="width:40px; height:20px; display:inline-block; position:relative;">
|
||||
|
||||
281
frontend/static/js/modules/utils/tmdb-image-cache-standalone.js
Normal file
281
frontend/static/js/modules/utils/tmdb-image-cache-standalone.js
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* TMDB Image Cache Utility (Non-Module Version)
|
||||
* Caches TMDB images in localStorage to reduce API calls and improve load times
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const CACHE_PREFIX = 'tmdb_img_';
|
||||
const CACHE_METADATA_KEY = 'tmdb_cache_metadata';
|
||||
|
||||
class TMDBImageCache {
|
||||
constructor() {
|
||||
this.cacheDays = 7; // Default to 7 days
|
||||
this.enabled = true;
|
||||
this.metadata = this.loadMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cache with settings from API
|
||||
*/
|
||||
async init() {
|
||||
try {
|
||||
const response = await fetch('./api/settings');
|
||||
const data = await response.json();
|
||||
if (data.success && data.settings && data.settings.general) {
|
||||
const cacheDays = data.settings.general.tmdb_image_cache_days;
|
||||
this.cacheDays = cacheDays !== undefined ? cacheDays : 7;
|
||||
this.enabled = this.cacheDays > 0;
|
||||
console.log(`[TMDBImageCache] Initialized with ${this.cacheDays} day cache ${this.enabled ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to load settings, using defaults:', error);
|
||||
}
|
||||
|
||||
// Clean up expired entries
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cache metadata from localStorage
|
||||
*/
|
||||
loadMetadata() {
|
||||
try {
|
||||
const stored = localStorage.getItem(CACHE_METADATA_KEY);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to load metadata:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save cache metadata to localStorage
|
||||
*/
|
||||
saveMetadata() {
|
||||
try {
|
||||
localStorage.setItem(CACHE_METADATA_KEY, JSON.stringify(this.metadata));
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to save metadata:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for an image URL
|
||||
*/
|
||||
getCacheKey(url) {
|
||||
if (!url) return null;
|
||||
// Extract just the image filename/path from TMDB URL
|
||||
const match = url.match(/\/(w\d+)\/(.+)$/);
|
||||
if (match) {
|
||||
return `${CACHE_PREFIX}${match[1]}_${match[2]}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cached image is still valid
|
||||
*/
|
||||
isCacheValid(key) {
|
||||
if (!this.enabled || this.cacheDays === 0) return false;
|
||||
|
||||
const meta = this.metadata[key];
|
||||
if (!meta || !meta.timestamp) return false;
|
||||
|
||||
const now = Date.now();
|
||||
const age = now - meta.timestamp;
|
||||
const maxAge = this.cacheDays * 24 * 60 * 60 * 1000; // Convert days to ms
|
||||
|
||||
return age < maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached image URL
|
||||
*/
|
||||
get(url) {
|
||||
if (!this.enabled || this.cacheDays === 0) return null;
|
||||
|
||||
const key = this.getCacheKey(url);
|
||||
if (!key) return null;
|
||||
|
||||
if (!this.isCacheValid(key)) {
|
||||
this.remove(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const cached = localStorage.getItem(key);
|
||||
if (cached) {
|
||||
console.log(`[TMDBImageCache] Cache HIT: ${url}`);
|
||||
return cached;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to get cached image:', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache an image URL
|
||||
*/
|
||||
async set(url, imageData) {
|
||||
if (!this.enabled || this.cacheDays === 0) return;
|
||||
|
||||
const key = this.getCacheKey(url);
|
||||
if (!key) return;
|
||||
|
||||
try {
|
||||
// Store the image data
|
||||
localStorage.setItem(key, imageData);
|
||||
|
||||
// Update metadata
|
||||
this.metadata[key] = {
|
||||
timestamp: Date.now(),
|
||||
url: url
|
||||
};
|
||||
this.saveMetadata();
|
||||
|
||||
console.log(`[TMDBImageCache] Cached: ${url}`);
|
||||
} catch (error) {
|
||||
// If we hit storage quota, try to cleanup old entries
|
||||
if (error.name === 'QuotaExceededError') {
|
||||
console.warn('[TMDBImageCache] Storage quota exceeded, cleaning up...');
|
||||
this.cleanup(true);
|
||||
|
||||
// Try again after cleanup
|
||||
try {
|
||||
localStorage.setItem(key, imageData);
|
||||
this.metadata[key] = {
|
||||
timestamp: Date.now(),
|
||||
url: url
|
||||
};
|
||||
this.saveMetadata();
|
||||
} catch (retryError) {
|
||||
console.error('[TMDBImageCache] Failed to cache even after cleanup:', retryError);
|
||||
}
|
||||
} else {
|
||||
console.error('[TMDBImageCache] Failed to cache image:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached image
|
||||
*/
|
||||
remove(key) {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
delete this.metadata[key];
|
||||
this.saveMetadata();
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to remove cached image:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up expired cache entries
|
||||
*/
|
||||
cleanup(force = false) {
|
||||
try {
|
||||
const keys = Object.keys(this.metadata);
|
||||
let removed = 0;
|
||||
|
||||
for (const key of keys) {
|
||||
if (force || !this.isCacheValid(key)) {
|
||||
this.remove(key);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
console.log(`[TMDBImageCache] Cleaned up ${removed} expired entries`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to cleanup cache:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached images
|
||||
*/
|
||||
clearAll() {
|
||||
try {
|
||||
const keys = Object.keys(this.metadata);
|
||||
for (const key of keys) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
this.metadata = {};
|
||||
this.saveMetadata();
|
||||
console.log('[TMDBImageCache] Cleared all cached images');
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to clear cache:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats() {
|
||||
const entries = Object.keys(this.metadata).length;
|
||||
let totalSize = 0;
|
||||
|
||||
try {
|
||||
for (const key of Object.keys(this.metadata)) {
|
||||
const data = localStorage.getItem(key);
|
||||
if (data) {
|
||||
totalSize += data.length;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to calculate cache size:', error);
|
||||
}
|
||||
|
||||
return {
|
||||
entries,
|
||||
totalSizeKB: Math.round(totalSize / 1024),
|
||||
cacheDays: this.cacheDays,
|
||||
enabled: this.enabled
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached TMDB image or fetch and cache it
|
||||
*/
|
||||
async function getCachedTMDBImage(url, cache) {
|
||||
if (!url || !cache) return url;
|
||||
|
||||
// Check cache first
|
||||
const cached = cache.get(url);
|
||||
if (cached) return cached;
|
||||
|
||||
// If not cached or cache disabled, fetch and cache
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
const blob = await response.blob();
|
||||
const reader = new FileReader();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
reader.onloadend = () => {
|
||||
const base64 = reader.result;
|
||||
// Cache the base64 data
|
||||
cache.set(url, base64);
|
||||
resolve(base64);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to fetch image:', error);
|
||||
}
|
||||
|
||||
// Return original URL if fetch fails
|
||||
return url;
|
||||
}
|
||||
|
||||
// Create singleton instance and make it globally available
|
||||
window.tmdbImageCache = new TMDBImageCache();
|
||||
window.getCachedTMDBImage = getCachedTMDBImage;
|
||||
})();
|
||||
284
frontend/static/js/modules/utils/tmdb-image-cache.js
Normal file
284
frontend/static/js/modules/utils/tmdb-image-cache.js
Normal file
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* TMDB Image Cache Utility
|
||||
* Caches TMDB images in localStorage to reduce API calls and improve load times
|
||||
*/
|
||||
|
||||
const CACHE_PREFIX = 'tmdb_img_';
|
||||
const CACHE_METADATA_KEY = 'tmdb_cache_metadata';
|
||||
|
||||
export class TMDBImageCache {
|
||||
constructor() {
|
||||
this.cacheDays = 7; // Default to 7 days
|
||||
this.enabled = true;
|
||||
this.metadata = this.loadMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cache with settings from API
|
||||
*/
|
||||
async init() {
|
||||
try {
|
||||
const response = await fetch('./api/settings');
|
||||
const data = await response.json();
|
||||
if (data.success && data.settings && data.settings.general) {
|
||||
const cacheDays = data.settings.general.tmdb_image_cache_days;
|
||||
this.cacheDays = cacheDays !== undefined ? cacheDays : 7;
|
||||
this.enabled = this.cacheDays > 0;
|
||||
console.log(`[TMDBImageCache] Initialized with ${this.cacheDays} day cache ${this.enabled ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to load settings, using defaults:', error);
|
||||
}
|
||||
|
||||
// Clean up expired entries
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cache metadata from localStorage
|
||||
*/
|
||||
loadMetadata() {
|
||||
try {
|
||||
const stored = localStorage.getItem(CACHE_METADATA_KEY);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to load metadata:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save cache metadata to localStorage
|
||||
*/
|
||||
saveMetadata() {
|
||||
try {
|
||||
localStorage.setItem(CACHE_METADATA_KEY, JSON.stringify(this.metadata));
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to save metadata:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for an image URL
|
||||
*/
|
||||
getCacheKey(url) {
|
||||
if (!url) return null;
|
||||
// Extract just the image filename/path from TMDB URL
|
||||
const match = url.match(/\/(w\d+)\/(.+)$/);
|
||||
if (match) {
|
||||
return `${CACHE_PREFIX}${match[1]}_${match[2]}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cached image is still valid
|
||||
*/
|
||||
isCacheValid(key) {
|
||||
if (!this.enabled || this.cacheDays === 0) return false;
|
||||
|
||||
const meta = this.metadata[key];
|
||||
if (!meta || !meta.timestamp) return false;
|
||||
|
||||
const now = Date.now();
|
||||
const age = now - meta.timestamp;
|
||||
const maxAge = this.cacheDays * 24 * 60 * 60 * 1000; // Convert days to ms
|
||||
|
||||
return age < maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached image URL
|
||||
*/
|
||||
get(url) {
|
||||
if (!this.enabled || this.cacheDays === 0) return null;
|
||||
|
||||
const key = this.getCacheKey(url);
|
||||
if (!key) return null;
|
||||
|
||||
if (!this.isCacheValid(key)) {
|
||||
this.remove(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const cached = localStorage.getItem(key);
|
||||
if (cached) {
|
||||
console.log(`[TMDBImageCache] Cache HIT: ${url}`);
|
||||
return cached;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to get cached image:', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache an image URL
|
||||
*/
|
||||
async set(url, imageData) {
|
||||
if (!this.enabled || this.cacheDays === 0) return;
|
||||
|
||||
const key = this.getCacheKey(url);
|
||||
if (!key) return;
|
||||
|
||||
try {
|
||||
// Store the image data
|
||||
localStorage.setItem(key, imageData);
|
||||
|
||||
// Update metadata
|
||||
this.metadata[key] = {
|
||||
timestamp: Date.now(),
|
||||
url: url
|
||||
};
|
||||
this.saveMetadata();
|
||||
|
||||
console.log(`[TMDBImageCache] Cached: ${url}`);
|
||||
} catch (error) {
|
||||
// If we hit storage quota, try to cleanup old entries
|
||||
if (error.name === 'QuotaExceededError') {
|
||||
console.warn('[TMDBImageCache] Storage quota exceeded, cleaning up...');
|
||||
this.cleanup(true);
|
||||
|
||||
// Try again after cleanup
|
||||
try {
|
||||
localStorage.setItem(key, imageData);
|
||||
this.metadata[key] = {
|
||||
timestamp: Date.now(),
|
||||
url: url
|
||||
};
|
||||
this.saveMetadata();
|
||||
} catch (retryError) {
|
||||
console.error('[TMDBImageCache] Failed to cache even after cleanup:', retryError);
|
||||
}
|
||||
} else {
|
||||
console.error('[TMDBImageCache] Failed to cache image:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached image
|
||||
*/
|
||||
remove(key) {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
delete this.metadata[key];
|
||||
this.saveMetadata();
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to remove cached image:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up expired cache entries
|
||||
*/
|
||||
cleanup(force = false) {
|
||||
try {
|
||||
const keys = Object.keys(this.metadata);
|
||||
let removed = 0;
|
||||
|
||||
for (const key of keys) {
|
||||
if (force || !this.isCacheValid(key)) {
|
||||
this.remove(key);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
console.log(`[TMDBImageCache] Cleaned up ${removed} expired entries`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to cleanup cache:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached images
|
||||
*/
|
||||
clearAll() {
|
||||
try {
|
||||
const keys = Object.keys(this.metadata);
|
||||
for (const key of keys) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
this.metadata = {};
|
||||
this.saveMetadata();
|
||||
console.log('[TMDBImageCache] Cleared all cached images');
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to clear cache:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats() {
|
||||
const entries = Object.keys(this.metadata).length;
|
||||
let totalSize = 0;
|
||||
|
||||
try {
|
||||
for (const key of Object.keys(this.metadata)) {
|
||||
const data = localStorage.getItem(key);
|
||||
if (data) {
|
||||
totalSize += data.length;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to calculate cache size:', error);
|
||||
}
|
||||
|
||||
return {
|
||||
entries,
|
||||
totalSizeKB: Math.round(totalSize / 1024),
|
||||
cacheDays: this.cacheDays,
|
||||
enabled: this.enabled
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached TMDB image or fetch and cache it
|
||||
*/
|
||||
export async function getCachedTMDBImage(url, cache) {
|
||||
if (!url || !cache) return url;
|
||||
|
||||
// Check cache first
|
||||
const cached = cache.get(url);
|
||||
if (cached) return cached;
|
||||
|
||||
// If not cached or cache disabled, fetch and cache
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
const blob = await response.blob();
|
||||
const reader = new FileReader();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
reader.onloadend = () => {
|
||||
const base64 = reader.result;
|
||||
// Cache the base64 data
|
||||
cache.set(url, base64);
|
||||
resolve(base64);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TMDBImageCache] Failed to fetch image:', error);
|
||||
}
|
||||
|
||||
// Return original URL if fetch fails
|
||||
return url;
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const tmdbImageCache = new TMDBImageCache();
|
||||
|
||||
// Make it globally available for non-module scripts
|
||||
if (typeof window !== 'undefined') {
|
||||
window.tmdbImageCache = tmdbImageCache;
|
||||
window.getCachedTMDBImage = getCachedTMDBImage;
|
||||
}
|
||||
@@ -55,6 +55,9 @@
|
||||
<!-- Movie Hunt (movies only, standalone from Requestarr) -->
|
||||
<script src="./static/js/modules/features/movie-hunt.js"></script>
|
||||
|
||||
<!-- TMDB Image Cache Utility (Standalone - must load before Requestarr modules) -->
|
||||
<script src="./static/js/modules/utils/tmdb-image-cache-standalone.js"></script>
|
||||
|
||||
<!-- Requestarr System -->
|
||||
<script src="./static/js/modules/features/requestarr/requestarr-controller.js" type="module"></script>
|
||||
<script src="./static/js/modules/features/requestarr/requestarr-home.js" type="module"></script>
|
||||
|
||||
@@ -173,6 +173,7 @@ SWAPARR_DEFAULTS = {
|
||||
|
||||
# General settings default configuration
|
||||
GENERAL_DEFAULTS = {
|
||||
"tmdb_image_cache_days": 7,
|
||||
"display_community_resources": True,
|
||||
"display_huntarr_support": True,
|
||||
"log_refresh_interval_seconds": 30,
|
||||
|
||||
Reference in New Issue
Block a user