import * as FileSystem from 'expo-file-system/legacy' /** * 基于 Expo FileSystem 的持久化缓存系统,用于存储 .ani 文件数据 * 键是 URL,值是 ani 文件内容 */ // 定义 ani 文件内容的类型 type AniData = object | string const CACHE_FOLDER = `${FileSystem.cacheDirectory}ani_cache/` /** * 将 URL 转换为合法的文件名 * * 为什么需要这样做? * 1. URL 中包含文件系统不允许的特殊字符(如 /, :, ?, & 等)。 * 2. URL 的长度可能超过操作系统对文件名的长度限制(通常为 255 字符)。 * 3. 简单的哈希算法可以将任意长度的 URL 转换为简短且唯一的标识符。 */ const hashUrl = (url: string): string => { let hash = 0 for (let i = 0; i < url.length; i++) { const char = url.charCodeAt(i) hash = (hash << 5) - hash + char hash |= 0 // Convert to 32bit integer } // 使用 hex 字符串和长度作为后缀来减少碰撞概率 return `${hash.toString(16)}_${url.length}` } class AniStorage { constructor() { this.ensureDir() } /** * 确保存储目录存在 */ private async ensureDir() { try { // 直接尝试创建目录,intermediates: true 会处理父目录不存在的情况 // 如果目录已存在,通常不会报错,或者我们在 catch 中忽略 await FileSystem.makeDirectoryAsync(CACHE_FOLDER, { intermediates: true }) } catch (error) { // 忽略目录已存在的错误 } } /** * 获取缓存文件路径 */ private getFilePath(url: string): string { const filename = hashUrl(url) return `${CACHE_FOLDER}${filename}.json` } /** * 获取缓存中的 ani 数据 * @param url 资源的 URL * @returns 缓存的数据,如果不存在则返回 undefined */ async get(url: string): Promise { try { const filePath = this.getFilePath(url) // 移除 getInfoAsync 检查,直接尝试读取 // 如果文件不存在,readAsStringAsync 会抛出错误,我们在 catch 中处理 const content = await FileSystem.readAsStringAsync(filePath) try { return JSON.parse(content) } catch { return content } } catch (error) { // 文件不存在或读取失败时返回 undefined return undefined } } /** * 设置 ani 数据到缓存中 * @param url 资源的 URL * @param data ani 文件数据 */ async set(url: string, data: AniData): Promise { try { await this.ensureDir() const filePath = this.getFilePath(url) const content = typeof data === 'string' ? data : JSON.stringify(data) await FileSystem.writeAsStringAsync(filePath, content) } catch (error) { console.warn('AniCache set error:', error) } } /** * 检查缓存中是否存在该 URL (异步) * @param url 资源的 URL */ async has(url: string): Promise { try { const filePath = this.getFilePath(url) // 由于 getInfoAsync 被弃用,这里尝试读取文件来检查是否存在 await FileSystem.readAsStringAsync(filePath) return true } catch { return false } } /** * 清除所有缓存 */ async clear(): Promise { try { // idempotent: true 使得如果文件/目录不存在也不会报错 await FileSystem.deleteAsync(CACHE_FOLDER, { idempotent: true }) await this.ensureDir() } catch (error) { console.warn('AniCache clear error:', error) } } /** * 删除指定 URL 的缓存 * @param url 资源的 URL */ async delete(url: string): Promise { try { const filePath = this.getFilePath(url) await FileSystem.deleteAsync(filePath, { idempotent: true }) } catch (error) { console.warn('AniCache delete error:', error) } } } // 导出单例实例 export const aniStorage = new AniStorage()