'use client'; import React, { useState, useEffect, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { Progress } from '@/components/ui/progress'; import { Separator } from '@/components/ui/separator'; import { Switch } from '@/components/ui/switch'; import { Database, Trash2, RefreshCw, HardDrive, Download, Wifi, WifiOff, X, Music, Globe, Settings } from 'lucide-react'; import { CacheManager } from '@/lib/cache'; import { useOfflineDownloads, OfflineItem } from '@/hooks/use-offline-downloads'; import { useAudioPlayer, Track } from '@/app/components/AudioPlayerContext'; export function CacheManagement() { const [cacheStats, setCacheStats] = useState({ total: 0, expired: 0, size: '0 B' }); const [isClearing, setIsClearing] = useState(false); const [lastCleared, setLastCleared] = useState(null); const [offlineItems, setOfflineItems] = useState([]); const [offlineMode, setOfflineMode] = useState(false); const [autoDownloadQueue, setAutoDownloadQueue] = useState(false); const [isDownloadingQueue, setIsDownloadingQueue] = useState(false); const { isSupported: isOfflineSupported, isInitialized: isOfflineInitialized, downloadProgress, offlineStats, downloadQueue, enableOfflineMode, deleteOfflineContent, getOfflineItems, clearDownloadProgress } = useOfflineDownloads(); const { queue } = useAudioPlayer(); const loadCacheStats = () => { if (typeof window === 'undefined') return; let total = 0; let expired = 0; let totalSize = 0; const now = Date.now(); // Check localStorage for cache entries for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && (key.startsWith('cache-') || key.startsWith('navidrome-cache-') || key.startsWith('library-cache-'))) { total++; const value = localStorage.getItem(key); if (value) { totalSize += key.length + value.length; try { const parsed = JSON.parse(value); if (parsed.expiresAt && now > parsed.expiresAt) { expired++; } } catch (error) { expired++; } } } } // Convert bytes to human readable format const formatSize = (bytes: number): string => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; setCacheStats({ total, expired, size: formatSize(totalSize * 2) // *2 for UTF-16 encoding }); }; const loadOfflineItems = useCallback(() => { if (isOfflineInitialized) { const items = getOfflineItems(); setOfflineItems(items); } }, [isOfflineInitialized, getOfflineItems]); useEffect(() => { loadCacheStats(); loadOfflineItems(); // Load offline mode settings const storedOfflineMode = localStorage.getItem('offline-mode-enabled'); const storedAutoDownload = localStorage.getItem('auto-download-queue'); if (storedOfflineMode) { setOfflineMode(JSON.parse(storedOfflineMode)); } if (storedAutoDownload) { setAutoDownloadQueue(JSON.parse(storedAutoDownload)); } // Check if there's a last cleared timestamp const lastClearedTime = localStorage.getItem('cache-last-cleared'); if (lastClearedTime) { setLastCleared(new Date(parseInt(lastClearedTime)).toLocaleString()); } }, [loadOfflineItems]); const handleClearCache = async () => { setIsClearing(true); try { // Clear all cache using the CacheManager CacheManager.clearAll(); // Also clear any other cache-related localStorage items if (typeof window !== 'undefined') { const keys = Object.keys(localStorage); keys.forEach(key => { if (key.startsWith('cache-') || key.startsWith('navidrome-cache-') || key.startsWith('library-cache-') || key.includes('album') || key.includes('artist') || key.includes('song')) { localStorage.removeItem(key); } }); // Set last cleared timestamp localStorage.setItem('cache-last-cleared', Date.now().toString()); } // Update stats loadCacheStats(); setLastCleared(new Date().toLocaleString()); // Show success feedback setTimeout(() => { setIsClearing(false); }, 1000); } catch (error) { console.error('Failed to clear cache:', error); setIsClearing(false); } }; const handleCleanExpired = () => { if (typeof window === 'undefined') return; const now = Date.now(); const keysToRemove: string[] = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && (key.startsWith('cache-') || key.startsWith('navidrome-cache-') || key.startsWith('library-cache-'))) { try { const value = localStorage.getItem(key); if (value) { const parsed = JSON.parse(value); if (parsed.expiresAt && now > parsed.expiresAt) { keysToRemove.push(key); } } } catch (error) { // Invalid cache item, remove it keysToRemove.push(key); } } } keysToRemove.forEach(key => localStorage.removeItem(key)); loadCacheStats(); }; const handleDeleteOfflineItem = async (item: OfflineItem) => { try { await deleteOfflineContent(item.id, item.type); loadOfflineItems(); loadCacheStats(); } catch (error) { console.error('Failed to delete offline item:', error); } }; // Convert Track to Song format for offline downloads const convertTrackToSong = (track: Track) => ({ id: track.id, parent: track.albumId || '', isDir: false, title: track.name, album: track.album, artist: track.artist, size: 0, // Will be filled when downloaded contentType: 'audio/mpeg', suffix: 'mp3', duration: track.duration, path: '', created: new Date().toISOString(), albumId: track.albumId, artistId: track.artistId, type: 'music' }); const handleOfflineModeToggle = async (enabled: boolean) => { setOfflineMode(enabled); localStorage.setItem('offline-mode-enabled', JSON.stringify(enabled)); if (enabled && isOfflineSupported) { try { const convertedQueue = queue.map(convertTrackToSong); await enableOfflineMode({ forceOffline: enabled, autoDownloadQueue, currentQueue: convertedQueue }); } catch (error) { console.error('Failed to enable offline mode:', error); } } }; const handleAutoDownloadToggle = async (enabled: boolean) => { setAutoDownloadQueue(enabled); localStorage.setItem('auto-download-queue', JSON.stringify(enabled)); if (enabled && isOfflineSupported) { const convertedQueue = queue.map(convertTrackToSong); await enableOfflineMode({ forceOffline: offlineMode, autoDownloadQueue: enabled, currentQueue: convertedQueue }); } }; const handleDownloadCurrentQueue = async () => { if (!queue.length || !isOfflineSupported) return; setIsDownloadingQueue(true); try { const convertedQueue = queue.map(convertTrackToSong); await downloadQueue(convertedQueue); loadOfflineItems(); } catch (error) { console.error('Failed to download queue:', error); } finally { setIsDownloadingQueue(false); } }; const formatSize = (bytes: number): string => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; return ( Cache & Offline Downloads Manage application cache and offline content for better performance {/* Regular Cache Statistics */}

Application Cache

{cacheStats.total}

Total Items

{cacheStats.expired}

Expired

{cacheStats.size}

Storage Used

{/* Cache Actions */}
{/* Offline Downloads Section */}

{isOfflineSupported ? ( ) : ( )} Offline Downloads {!isOfflineSupported && ( (Limited Support) )}

{isOfflineSupported && (

{offlineStats.downloadedAlbums}

Albums

{offlineStats.downloadedSongs}

Songs

{formatSize(offlineStats.totalSize)}

Total Size

)} {/* Offline Mode Controls */} {isOfflineSupported && (

Force app to use only cached content (good for slow connections)

Automatically download songs when added to queue

{/* Queue Download Controls */}
Current Queue {queue.length} song{queue.length !== 1 ? 's' : ''}
{queue.length > 0 && ( )} {queue.length === 0 && (

Add songs to queue to enable downloading

)}
)} {/* Download Progress */} {downloadProgress.status !== 'idle' && (
{downloadProgress.status === 'downloading' && 'Downloading...'} {downloadProgress.status === 'starting' && 'Starting download...'} {downloadProgress.status === 'complete' && 'Download complete!'} {downloadProgress.status === 'error' && 'Download failed'}
{downloadProgress.total > 0 && (
{downloadProgress.completed} / {downloadProgress.total} songs {downloadProgress.failed > 0 && ` (${downloadProgress.failed} failed)`} {Math.round((downloadProgress.completed / downloadProgress.total) * 100)}%
)} {downloadProgress.currentSong && (

Current: {downloadProgress.currentSong}

)} {downloadProgress.error && (

Error: {downloadProgress.error}

)}
)} {/* Offline Items List */} {offlineItems.length > 0 && (
{offlineItems.map((item) => (

{item.name}

{item.artist} • {item.type}

))}
)} {offlineItems.length === 0 && (

No offline content downloaded

Visit an album page to download content for offline listening

)}
{/* Cache Info */}

Cache includes albums, artists, songs, and image URLs to improve loading times.

{isOfflineSupported && (

Offline downloads use Service Workers for true offline audio playback.

)} {!isOfflineSupported && (

Limited offline support - only metadata cached without Service Worker support.

)} {lastCleared && (

Last cleared: {lastCleared}

)}
); }