fileCache.ts 5.38 KB
/**
 * IndexedDB 文件缓存服务
 * 用于持久化存储下载的文件内容(Blob/ArrayBuffer),提高二次打开速度
 */

const DB_NAME = 'LinkMed_File_Cache';
const STORE_NAME = 'files';
const OO_CONVERTED_STORE = 'onlyoffice_converted';
const DB_VERSION = 2;

export interface CachedFile {
  fileId: string | number;
  content: Blob | ArrayBuffer | string;
  type: string; // 文件类型: 'pdf', 'image', 'markdown', 'word', 'spreadsheet' 等
  updatedAt: number; // 缓存时间戳
  fileName: string;
}

export interface ConvertedOnlyOffice {
  fileId: string | number;
  binData: ArrayBuffer | ArrayBufferLike;
  media: Record<string, ArrayBuffer | ArrayBufferLike>;
  updatedAt: number;
}

class FileCacheService {
  private db: IDBDatabase | null = null;

  private async getDB(): Promise<IDBDatabase> {
    if (this.db) return this.db;

    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, DB_VERSION);

      request.onupgradeneeded = (event: any) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(STORE_NAME)) {
          db.createObjectStore(STORE_NAME, { keyPath: 'fileId' });
        }
        if (!db.objectStoreNames.contains(OO_CONVERTED_STORE)) {
          db.createObjectStore(OO_CONVERTED_STORE, { keyPath: 'fileId' });
        }
      };

      request.onsuccess = (event: any) => {
        this.db = event.target.result;
        resolve(this.db!);
      };

      request.onerror = (event: any) => {
        console.error('IndexedDB 初始化失败:', event.target.error);
        reject('IndexedDB 初始化失败');
      };
    });
  }

  /**
   * 获取缓存文件
   * @param fileId 文件ID
   * @returns 缓存的文件对象或 null
   */
  async getFile(fileId: string | number): Promise<CachedFile | null> {
    try {
      const db = await this.getDB();
      return new Promise((resolve) => {
        const transaction = db.transaction(STORE_NAME, 'readonly');
        const store = transaction.objectStore(STORE_NAME);
        const request = store.get(String(fileId));
        
        request.onsuccess = () => resolve(request.result || null);
        request.onerror = () => resolve(null);
      });
    } catch (error) {
      console.warn('读取缓存失败:', error);
      return null;
    }
  }

  /**
   * 设置缓存文件
   * @param file 缓存对象
   */
  async setFile(file: Omit<CachedFile, 'updatedAt'>): Promise<void> {
    try {
      const db = await this.getDB();
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(STORE_NAME, 'readwrite');
        const store = transaction.objectStore(STORE_NAME);
        
        const data: CachedFile = {
          ...file,
          fileId: String(file.fileId),
          updatedAt: Date.now()
        };
        
        const request = store.put(data);
        request.onsuccess = () => resolve();
        request.onerror = () => reject('缓存写入失败');
      });
    } catch (error) {
      console.warn('写入缓存失败:', error);
    }
  }

  /**
   * 获取 OnlyOffice 转换后的缓存
   */
  async getOOConverted(fileId: string | number): Promise<ConvertedOnlyOffice | null> {
    try {
      const db = await this.getDB();
      return new Promise((resolve) => {
        const transaction = db.transaction(OO_CONVERTED_STORE, 'readonly');
        const store = transaction.objectStore(OO_CONVERTED_STORE);
        const request = store.get(String(fileId));
        
        request.onsuccess = () => resolve(request.result || null);
        request.onerror = () => resolve(null);
      });
    } catch (error) {
      console.warn('读取 OO 转换缓存失败:', error);
      return null;
    }
  }

  /**
   * 设置 OnlyOffice 转换后的缓存
   */
  async setOOConverted(data: Omit<ConvertedOnlyOffice, 'updatedAt'>): Promise<void> {
    try {
      const db = await this.getDB();
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(OO_CONVERTED_STORE, 'readwrite');
        const store = transaction.objectStore(OO_CONVERTED_STORE);
        
        const finalData: ConvertedOnlyOffice = {
          ...data,
          fileId: String(data.fileId),
          updatedAt: Date.now()
        };
        
        const request = store.put(finalData);
        request.onsuccess = () => resolve();
        request.onerror = () => reject('OO 转换缓存写入失败');
      });
    } catch (error) {
      console.warn('写入 OO 转换缓存失败:', error);
    }
  }

  /**
   * 删除指定文件的缓存
   * @param fileId 文件ID
   */
  async deleteFile(fileId: string | number): Promise<void> {
    try {
      const db = await this.getDB();
      const transaction = db.transaction([STORE_NAME, OO_CONVERTED_STORE], 'readwrite');
      transaction.objectStore(STORE_NAME).delete(String(fileId));
      transaction.objectStore(OO_CONVERTED_STORE).delete(String(fileId));
    } catch (error) {
      console.warn('删除缓存失败:', error);
    }
  }

  /**
   * 清空所有缓存
   */
  async clearAll(): Promise<void> {
    try {
      const db = await this.getDB();
      const transaction = db.transaction([STORE_NAME, OO_CONVERTED_STORE], 'readwrite');
      transaction.objectStore(STORE_NAME).clear();
      transaction.objectStore(OO_CONVERTED_STORE).clear();
    } catch (error) {
      console.warn('清空缓存失败:', error);
    }
  }
}

export const fileCache = new FileCacheService();