first commit
This commit is contained in:
99
src/libraryCache.ts
Normal file
99
src/libraryCache.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { Track } from './types'
|
||||
|
||||
type CacheServerDescriptor = {
|
||||
id: string
|
||||
host: string
|
||||
port?: string | number
|
||||
ssl?: boolean
|
||||
}
|
||||
|
||||
type PersistedTrack = Omit<Track, 'id'>
|
||||
|
||||
type LibraryCacheRecord = {
|
||||
cacheKey: string
|
||||
updatedAt: number
|
||||
tracks: PersistedTrack[]
|
||||
searchCache: Record<string, number[]>
|
||||
}
|
||||
|
||||
export type LibraryCacheSnapshot = {
|
||||
tracks: PersistedTrack[]
|
||||
searchCache: Record<string, number[]>
|
||||
}
|
||||
|
||||
const DATABASE_NAME = 'api-mediaplayer-library-cache'
|
||||
const DATABASE_VERSION = 1
|
||||
const STORE_NAME = 'snapshots'
|
||||
const MAX_TRACKS = 5000
|
||||
const MAX_SEARCHES = 250
|
||||
|
||||
export function buildLibraryCacheKey(servers: CacheServerDescriptor[]) {
|
||||
return servers.map((server) => `${server.id}:${server.host}:${server.port ?? ''}:${server.ssl ? 'https' : 'http'}`).join('|')
|
||||
}
|
||||
|
||||
function openLibraryCacheDatabase() {
|
||||
return new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION)
|
||||
|
||||
request.onupgradeneeded = () => {
|
||||
const database = request.result
|
||||
if (!database.objectStoreNames.contains(STORE_NAME)) {
|
||||
database.createObjectStore(STORE_NAME, { keyPath: 'cacheKey' })
|
||||
}
|
||||
}
|
||||
|
||||
request.onsuccess = () => resolve(request.result)
|
||||
request.onerror = () => reject(request.error ?? new Error('Failed to open library cache database'))
|
||||
})
|
||||
}
|
||||
|
||||
function withStore<T>(mode: IDBTransactionMode, handler: (store: IDBObjectStore) => IDBRequest<T>) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
openLibraryCacheDatabase()
|
||||
.then((database) => {
|
||||
const transaction = database.transaction(STORE_NAME, mode)
|
||||
const store = transaction.objectStore(STORE_NAME)
|
||||
const request = handler(store)
|
||||
|
||||
request.onsuccess = () => resolve(request.result)
|
||||
request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed'))
|
||||
|
||||
transaction.oncomplete = () => database.close()
|
||||
transaction.onerror = () => reject(transaction.error ?? new Error('IndexedDB transaction failed'))
|
||||
transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB transaction aborted'))
|
||||
})
|
||||
.catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
export async function loadLibraryCache(cacheKey: string): Promise<LibraryCacheSnapshot | null> {
|
||||
if (!cacheKey || typeof indexedDB === 'undefined') return null
|
||||
|
||||
const record = await withStore<LibraryCacheRecord | undefined>('readonly', (store) => store.get(cacheKey))
|
||||
if (!record) return null
|
||||
|
||||
return {
|
||||
tracks: Array.isArray(record.tracks) ? record.tracks : [],
|
||||
searchCache: record.searchCache && typeof record.searchCache === 'object' ? record.searchCache : {},
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveLibraryCache(cacheKey: string, tracks: Track[], searchCache: Record<string, number[]>) {
|
||||
if (!cacheKey || typeof indexedDB === 'undefined') return
|
||||
|
||||
const trimmedTracks = tracks
|
||||
.filter((track) => track.serverId && track.fileId != null && track.url)
|
||||
.slice(-MAX_TRACKS)
|
||||
.map(({ id: _id, ...track }) => track)
|
||||
|
||||
const trimmedSearchCache = Object.fromEntries(Object.entries(searchCache).slice(-MAX_SEARCHES))
|
||||
|
||||
const record: LibraryCacheRecord = {
|
||||
cacheKey,
|
||||
updatedAt: Date.now(),
|
||||
tracks: trimmedTracks,
|
||||
searchCache: trimmedSearchCache,
|
||||
}
|
||||
|
||||
await withStore('readwrite', (store) => store.put(record))
|
||||
}
|
||||
Reference in New Issue
Block a user