@file-ud.js/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/logger/index.ts","../src/utils/fileCache.ts","../src/utils/network.ts","../src/utils/md5Worker.ts","../src/utils/index.ts","../../../node_modules/eventemitter3/index.js","../src/utils/banner.ts","../src/transfer/Transfer.ts","../src/utils/event-emitter.ts","../src/uploader/index.ts","../../../node_modules/eventemitter3/index.mjs","../../../node_modules/p-timeout/index.js","../../../node_modules/p-queue/dist/lower-bound.js","../../../node_modules/p-queue/dist/priority-queue.js","../../../node_modules/p-queue/dist/index.js","../src/concurrency/FileConcurrencyController.ts","../src/uploader/UploadFile.ts","../src/uploader/UploadChunkManager.ts","../../../node_modules/idb/build/index.js","../src/utils/transferDB.ts","../src/chunkManager/index.ts","../src/transfer/TransferFile.ts","../src/fileUD/errors.ts","../src/xhr-intercepto/index.ts","../src/downloader/index.ts","../src/downloader/DownloadFile.ts","../src/downloader/DownloadChunkManager.ts","../src/fileUD/index.ts","../src/index.ts"],"names":["LogLevel","calculateFileMD5InWorker","loadNext","EventEmitter","index","_queue","reject","openDB","version","target","DB_NAME","STORE_NAME","dbInstance","saveFileToCache","up","removeFileFromCache","ErrorCode","ErrorLevel","axios","loadDownloadProgress","removeDownloadProgress","saveDownloadProgress","defaultConfig"],"mappings":"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","sourcesContent":["/**\n * 统一日志工具模块\n * \n * 提供环境感知的日志输出,支持级别控制和格式化\n * \n * @example\n * ```typescript\n * import { logger } from './utils/logger';\n * \n * logger.info('uploadChunkManager', '开始上传分片');\n * logger.debug('uploadChunkManager', '分片详情', { index: 0, size: 1024 });\n * logger.warn('uploadChunkManager', '更新状态失败:', error);\n * logger.error('uploadChunkManager', '上传失败', error);\n * ```\n */\n\n// Node.js process 类型声明(兼容浏览器环境)\ndeclare const process: {\n env?: {\n NODE_ENV?: string;\n VITE_LOG_LEVEL?: string;\n LOG_LEVEL?: string;\n };\n};\n\n/**\n * 日志级别枚举\n */\nexport enum LogLevel {\n DEBUG = 0, // 调试信息(仅开发环境)\n INFO = 1, // 一般信息\n WARN = 2, // 警告信息\n ERROR = 3, // 错误信息\n}\n\n/**\n * 日志配置选项\n */\nexport interface LoggerOptions {\n /** 是否启用日志输出(默认 true) */\n enabled?: boolean;\n /** 最低日志级别 */\n level?: LogLevel;\n /** 是否显示时间戳 */\n showTimestamp?: boolean;\n /** 是否启用颜色输出 */\n enableColors?: boolean;\n}\n\n/**\n * 日志条目接口\n */\nexport interface LogEntry {\n /** 时间戳 */\n timestamp: number;\n /** 日志级别 */\n level: LogLevel;\n /** 模块名称 */\n module: string;\n /** 消息内容 */\n message: string;\n /** 额外参数 */\n args?: any[];\n /** 错误堆栈(如果是 Error 对象) */\n stack?: string;\n}\n\n/**\n * 日志收集器回调函数类型\n */\nexport type LogCollectorCallback = (entry: LogEntry) => void | Promise<void>;\n\n/**\n * 日志级别标签映射\n */\nconst LEVEL_LABELS: Record<LogLevel, string> = {\n [LogLevel.DEBUG]: 'DEBUG',\n [LogLevel.INFO]: 'INFO',\n [LogLevel.WARN]: 'WARN',\n [LogLevel.ERROR]: 'ERROR',\n};\n\n/**\n * 日志级别颜色映射(ANSI 颜色代码)\n */\nconst LEVEL_COLORS: Record<LogLevel, string> = {\n [LogLevel.DEBUG]: '\\x1b[36m', // 青色\n [LogLevel.INFO]: '\\x1b[32m', // 绿色\n [LogLevel.WARN]: '\\x1b[33m', // 黄色\n [LogLevel.ERROR]: '\\x1b[31m', // 红色\n};\n\nconst RESET_COLOR = '\\x1b[0m';\n\n/**\n * 默认配置\n */\nconst DEFAULT_OPTIONS: LoggerOptions = {\n enabled: false,\n level: typeof process !== 'undefined' && process.env?.NODE_ENV === 'production' \n ? LogLevel.WARN \n : LogLevel.DEBUG,\n showTimestamp: true,\n enableColors: typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production',\n};\n\n/**\n * 当前日志配置\n */\nlet currentOptions: LoggerOptions = { ...DEFAULT_OPTIONS };\n\n/**\n * 日志收集器回调列表\n */\nconst logCollectors: LogCollectorCallback[] = [];\n\n/**\n * 从环境变量读取日志级别\n */\nfunction getLogLevelFromEnv(): LogLevel | undefined {\n if (typeof process === 'undefined' || !process.env) return undefined;\n \n const envLevel = process.env.VITE_LOG_LEVEL || process.env.LOG_LEVEL;\n if (!envLevel) return undefined;\n\n const levelMap: Record<string, LogLevel> = {\n debug: LogLevel.DEBUG,\n info: LogLevel.INFO,\n warn: LogLevel.WARN,\n error: LogLevel.ERROR,\n };\n\n return levelMap[envLevel.toLowerCase()];\n}\n\n/**\n * 初始化日志配置\n * @param options 日志配置选项\n */\nexport function initLogger(options?: LoggerOptions): void {\n const envLevel = getLogLevelFromEnv();\n \n currentOptions = {\n ...DEFAULT_OPTIONS,\n ...(options || {}),\n // 环境变量优先级最高\n ...(envLevel !== undefined ? { level: envLevel } : {}),\n };\n\n console.log(`[Logger] Initialized with level: ${LEVEL_LABELS[currentOptions.level!]}`);\n}\n\n/**\n * 动态设置日志级别\n * @param level 日志级别\n */\nexport function setLogLevel(level: LogLevel): void {\n currentOptions.level = level;\n}\n\n/**\n * 注册日志收集器\n * @param callback 收集器回调函数\n * @returns 取消注册的函数\n * \n * @example\n * ```typescript\n * // 注册收集器\n * const unsubscribe = addLogCollector((entry) => {\n * if (entry.level === LogLevel.ERROR) {\n * // 上报到监控平台\n * reportToSentry(entry);\n * }\n * });\n * \n * // 取消注册\n * unsubscribe();\n * ```\n */\nexport function addLogCollector(callback: LogCollectorCallback): () => void {\n logCollectors.push(callback);\n \n // 返回取消注册的函数\n return () => {\n const index = logCollectors.indexOf(callback);\n if (index > -1) {\n logCollectors.splice(index, 1);\n }\n };\n}\n\n/**\n * 清除所有日志收集器\n */\nexport function clearLogCollectors(): void {\n logCollectors.length = 0;\n}\n\n/**\n * 格式化日志消息\n * @param level 日志级别\n * @param module 模块名称\n * @param message 消息内容\n * @param args 额外参数\n */\nfunction formatMessage(\n level: LogLevel,\n module: string,\n message: string,\n args: any[]\n): string {\n const parts: string[] = [];\n\n // 时间戳\n if (currentOptions.showTimestamp) {\n const now = new Date();\n // ✅ 使用本地时间格式化,而不是 toISOString()(UTC 时间)\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n const hours = String(now.getHours()).padStart(2, '0');\n const minutes = String(now.getMinutes()).padStart(2, '0');\n const seconds = String(now.getSeconds()).padStart(2, '0');\n const milliseconds = String(now.getMilliseconds()).padStart(3, '0');\n const timeStr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;\n parts.push(`[${timeStr}]`);\n }\n\n // 级别标签\n const label = LEVEL_LABELS[level];\n if (currentOptions.enableColors) {\n parts.push(`${LEVEL_COLORS[level]}[${label}]${RESET_COLOR}`);\n } else {\n parts.push(`[${label}]`);\n }\n\n // 模块名称\n parts.push(`[${module}]`);\n\n // 消息内容\n parts.push(message);\n\n return parts.join(' ');\n}\n\n/**\n * 提取错误堆栈\n * @param arg 参数\n * @returns 堆栈字符串或 null\n */\nfunction extractStack(arg: any): string | null {\n if (arg instanceof Error) {\n return arg.stack || arg.message;\n }\n return null;\n}\n\n/**\n * 输出日志并触发收集器\n * @param level 日志级别\n * @param module 模块名称\n * @param message 消息内容\n * @param args 额外参数\n */\nfunction log(level: LogLevel, module: string, message: string, ...args: any[]): void {\n // 检查是否启用日志\n if (!currentOptions.enabled) {\n return;\n }\n\n // 检查日志级别\n if (level < (currentOptions.level ?? LogLevel.DEBUG)) {\n return;\n }\n\n const formattedMessage = formatMessage(level, module, message, args);\n\n // ✅ 关键使用 Error().stack 获取真实的调用位置\n // 这样浏览器控制台的代码跳转会指向调用日志的位置,而不是 logger 文件\n let stackInfo: string | undefined;\n try {\n const error = new Error();\n const stackLines = error.stack?.split('\\n') || [];\n // stack 格式: \n // Error\n // at log (logger/index.ts:xxx:yy) ← 当前函数\n // at Object.info (logger/index.ts:xxx:yy) ← logger 对象方法\n // at actualCaller (caller.ts:xxx:yy) ← 真实调用者(我们要这个!)\n // 跳过前两行,取第三行作为调用位置\n if (stackLines.length >= 3) {\n stackInfo = stackLines[3].trim();\n }\n } catch (e) {\n // 忽略错误\n }\n\n // ✅ 将调用位置信息添加到参数中\n // 浏览器控制台会自动解析这个位置并支持点击跳转\n const outputArgs = stackInfo ? [...args, `\\n ${stackInfo}`] : args;\n\n // 根据级别选择输出方法\n switch (level) {\n case LogLevel.DEBUG:\n console.debug(formattedMessage, ...outputArgs);\n break;\n case LogLevel.INFO:\n console.info(formattedMessage, ...outputArgs);\n break;\n case LogLevel.WARN:\n console.warn(formattedMessage, ...outputArgs);\n break;\n case LogLevel.ERROR:\n console.error(formattedMessage, ...outputArgs);\n break;\n }\n\n // 触发日志收集器\n if (logCollectors.length > 0) {\n const entry: LogEntry = {\n timestamp: Date.now(),\n level,\n module,\n message,\n args,\n };\n\n // 提取错误堆栈\n const stack = args.find(extractStack);\n if (stack) {\n entry.stack = extractStack(stack) || undefined;\n }\n\n // 异步调用所有收集器\n logCollectors.forEach((collector) => {\n try {\n const result = collector(entry);\n // 如果返回 Promise,捕获可能的错误\n if (result instanceof Promise) {\n result.catch((error) => {\n console.error('[Logger] Collector error:', error);\n });\n }\n } catch (error) {\n console.error('[Logger] Collector error:', error);\n }\n });\n }\n}\n\n/**\n * 日志工具对象\n */\nexport const logger = {\n /**\n * 调试日志(仅开发环境)\n * @param module 模块名称\n * @param message 消息内容\n * @param args 额外参数\n */\n debug: (module: string, message: string, ...args: any[]) => {\n message = `🔍 ${message}`;\n log(LogLevel.DEBUG, module, message, ...args);\n },\n\n /**\n * 信息日志\n * @param module 模块名称\n * @param message 消息内容\n * @param args 额外参数\n */\n info: (module: string, message: string, ...args: any[]) => {\n message = `✅ ${message}`;\n log(LogLevel.INFO, module, message, ...args);\n },\n\n /**\n * 警告日志\n * @param module 模块名称\n * @param message 消息内容\n * @param args 额外参数\n */\n warn: (module: string, message: string, ...args: any[]) => {\n message = `⚠️ ${message}`;\n log(LogLevel.WARN, module, message, ...args);\n },\n\n /**\n * 错误日志\n * @param module 模块名称\n * @param message 消息内容\n * @param args 额外参数\n */\n error: (module: string, message: string, ...args: any[]) => {\n message = `❌ ${message}`;\n log(LogLevel.ERROR, module, message, ...args);\n },\n};\n\n","/**\n * IndexedDB 文件缓存工具\n * 用于持久化存储 File 对象,支持断点续传时的文件恢复\n */\n\nimport { formatFileSize } from '.';\nimport { logger } from './logger';\n\nconst DB_NAME = 'file-ud-cache';\nconst DB_VERSION = 3;\nconst STORE_NAME = 'files';\nconst DOWNLOAD_PROGRESS_STORE = 'download-progress';\nconst FILE_HANDLE_STORE = 'file-handles';\n\n/**\n * 文件缓存记录结构\n */\ninterface FileCacheRecord {\n fileHash: string; // 文件哈希(主键)\n fileName: string; // 文件名\n fileType: string; // 文件类型\n fileSize: number; // 文件大小\n data: ArrayBuffer; // 文件二进制数据\n createdAt: number; // 创建时间戳\n lastAccessedAt: number; // 最后访问时间戳\n}\n\n// ✅ 数据库连接单例\nlet dbInstance: IDBDatabase | null = null;\nlet dbOpeningPromise: Promise<IDBDatabase> | null = null;\n\n/**\n * 打开或创建 IndexedDB 数据库(单例模式)\n */\nfunction openDB(): Promise<IDBDatabase> {\n // ✅ 如果已有有效连接,直接返回\n if (dbInstance && dbInstance.version === DB_VERSION) {\n try {\n // 测试连接是否仍然有效\n dbInstance.transaction([STORE_NAME], 'readonly');\n return Promise.resolve(dbInstance);\n } catch (error) {\n // 连接已失效,重置\n logger.warn('FileCache', '数据库连接已失效,重新打开', error);\n dbInstance = null;\n dbOpeningPromise = null;\n }\n }\n\n // ✅ 如果正在打开中,返回同一个 Promise\n if (dbOpeningPromise) {\n return dbOpeningPromise;\n }\n\n // ✅ 创建新的打开请求\n dbOpeningPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => {\n logger.error('FileCache', '打开 IndexedDB 失败', request.error);\n dbOpeningPromise = null;\n reject(request.error);\n };\n\n request.onsuccess = () => {\n dbInstance = request.result;\n dbOpeningPromise = null;\n \n // ✅ 监听连接关闭事件\n dbInstance.onclose = () => {\n logger.warn('FileCache', '数据库连接已关闭');\n dbInstance = null;\n };\n \n // ✅ 监听连接异常事件\n dbInstance.onerror = (event) => {\n logger.error('FileCache', '数据库连接错误', event);\n dbInstance = null;\n };\n \n resolve(dbInstance);\n };\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // 创建文件缓存存储空间\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n const store = db.createObjectStore(STORE_NAME, { keyPath: 'fileHash' });\n\n // 创建索引\n store.createIndex('fileName', 'fileName', { unique: false });\n store.createIndex('createdAt', 'createdAt', { unique: false });\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\n\n logger.info('FileCache', 'IndexedDB 文件缓存存储初始化成功');\n }\n\n // 创建下载进度存储空间\n if (!db.objectStoreNames.contains(DOWNLOAD_PROGRESS_STORE)) {\n const progressStore = db.createObjectStore(DOWNLOAD_PROGRESS_STORE, { keyPath: 'fileHash' });\n progressStore.createIndex('updatedAt', 'updatedAt', { unique: false });\n\n logger.info('FileCache', 'IndexedDB 下载进度存储初始化成功');\n }\n\n // 🔑 创建文件句柄存储空间(跨会话复用 File Handle)\n if (!db.objectStoreNames.contains(FILE_HANDLE_STORE)) {\n const handleStore = db.createObjectStore(FILE_HANDLE_STORE, { keyPath: 'fileName' });\n handleStore.createIndex('updatedAt', 'updatedAt', { unique: false });\n\n logger.info('FileCache', 'IndexedDB 文件句柄存储初始化成功');\n }\n };\n });\n\n return dbOpeningPromise;\n}\n\n/**\n * 关闭数据库连接(用于清理资源)\n */\nexport function closeDB(): void {\n if (dbInstance) {\n try {\n dbInstance.close();\n logger.debug('FileCache', '数据库连接已关闭');\n } catch (error) {\n logger.warn('FileCache', '关闭数据库连接时出错', error);\n } finally {\n dbInstance = null;\n dbOpeningPromise = null;\n }\n }\n}\n\n/**\n * 执行 IndexedDB 事务的通用包装器\n * @param operation - 事务操作函数\n * @returns Promise<T>\n */\nasync function executeTransaction<T>(\n operation: (db: IDBDatabase) => Promise<T>\n): Promise<T> {\n try {\n const db = await openDB();\n return await operation(db);\n } catch (error) {\n // ✅ 捕获 InvalidStateError,重置连接并重试一次\n if (error instanceof DOMException && error.name === 'InvalidStateError') {\n logger.warn('FileCache', '检测到无效状态错误,重置连接后重试', error);\n closeDB();\n \n // 重试一次\n const db = await openDB();\n return await operation(db);\n }\n throw error;\n }\n}\n\n/**\n * 保存 File 对象到 IndexedDB\n * \n * @param fileHash - 文件哈希值(作为主键)\n * @param file - File 对象\n * @returns Promise<void>\n */\nexport async function saveFileToCache(fileHash: string, file: File): Promise<void> {\n try {\n // 将 File 转换为 ArrayBuffer\n const arrayBuffer = await file.arrayBuffer();\n \n const record: FileCacheRecord = {\n fileHash,\n fileName: file.name,\n fileType: file.type,\n fileSize: file.size,\n data: arrayBuffer,\n createdAt: Date.now(),\n lastAccessedAt: Date.now(),\n };\n\n await executeTransaction(async (db) => {\n return new Promise<void>((resolve, reject) => {\n const transaction = db.transaction([STORE_NAME], 'readwrite');\n const store = transaction.objectStore(STORE_NAME);\n const request = store.put(record);\n\n request.onsuccess = () => {\n logger.debug('FileCache', `文件已缓存: ${file.name} (${formatFileSize(file.size)})`, {\n fileHash,\n fileSize: file.size,\n });\n resolve();\n };\n\n request.onerror = () => {\n logger.error('FileCache', '保存文件缓存失败', request.error);\n reject(request.error);\n };\n \n transaction.onerror = () => {\n logger.error('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.error('FileCache', '保存文件缓存异常', error);\n throw error;\n }\n}\n\n/**\n * 从 IndexedDB 恢复 File 对象\n * \n * @param fileHash - 文件哈希值\n * @returns Promise<File | null> - 如果找到则返回 File 对象,否则返回 null\n */\nexport async function restoreFileFromCache(fileHash: string): Promise<File | null> {\n try {\n return await executeTransaction(async (db) => {\n return new Promise((resolve, reject) => {\n const transaction = db.transaction([STORE_NAME], 'readonly');\n const store = transaction.objectStore(STORE_NAME);\n const request = store.get(fileHash);\n\n request.onsuccess = () => {\n const record: FileCacheRecord | undefined = request.result;\n \n if (!record) {\n logger.debug('FileCache', `未找到缓存文件: ${fileHash}`);\n resolve(null);\n return;\n }\n\n // 更新最后访问时间\n updateLastAccessedTime(fileHash);\n\n // 将 ArrayBuffer 转换回 File\n const blob = new Blob([record.data], { type: record.fileType });\n const file = new File([blob], record.fileName, { type: record.fileType });\n\n logger.debug('FileCache', `成功恢复缓存文件: ${record.fileName}`, {\n fileHash,\n fileSize: record.fileSize,\n });\n\n resolve(file);\n };\n\n request.onerror = () => {\n logger.error('FileCache', '恢复文件缓存失败', request.error);\n reject(request.error);\n };\n \n transaction.onerror = () => {\n logger.error('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.error('FileCache', '恢复文件缓存异常', error);\n throw error;\n }\n}\n\n/**\n * 删除指定文件的缓存\n * \n * @param fileHash - 文件哈希值\n * @returns Promise<void>\n */\nexport async function removeFileFromCache(fileHash: string): Promise<void> {\n try {\n await executeTransaction(async (db) => {\n return new Promise<void>((resolve, reject) => {\n const transaction = db.transaction([STORE_NAME], 'readwrite');\n const store = transaction.objectStore(STORE_NAME);\n const request = store.delete(fileHash);\n\n request.onsuccess = () => {\n logger.debug('FileCache', `已删除文件缓存: ${fileHash}`);\n resolve();\n };\n\n request.onerror = () => {\n logger.error('FileCache', '删除文件缓存失败', request.error);\n reject(request.error);\n };\n \n transaction.onerror = () => {\n logger.error('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.error('FileCache', '删除文件缓存异常', error);\n throw error;\n }\n}\n\n/**\n * 清空所有文件缓存\n * \n * @returns Promise<void>\n */\nexport async function clearAllFileCache(): Promise<void> {\n try {\n await executeTransaction(async (db) => {\n return new Promise<void>((resolve, reject) => {\n const transaction = db.transaction([STORE_NAME], 'readwrite');\n const store = transaction.objectStore(STORE_NAME);\n const request = store.clear();\n\n request.onsuccess = () => {\n logger.info('FileCache', '已清空所有文件缓存');\n resolve();\n };\n\n request.onerror = () => {\n logger.error('FileCache', '清空文件缓存失败', request.error);\n reject(request.error);\n };\n \n transaction.onerror = () => {\n logger.error('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.error('FileCache', '清空文件缓存异常', error);\n throw error;\n }\n}\n\n/**\n * 获取缓存统计信息\n * \n * @returns Promise<{ count: number; totalSize: number }>\n */\nexport async function getCacheStats(): Promise<{ count: number; totalSize: number }> {\n try {\n return await executeTransaction(async (db) => {\n return new Promise((resolve, reject) => {\n const transaction = db.transaction([STORE_NAME], 'readonly');\n const store = transaction.objectStore(STORE_NAME);\n const request = store.getAll();\n\n request.onsuccess = () => {\n const records: FileCacheRecord[] = request.result || [];\n const totalSize = records.reduce((sum, record) => sum + record.fileSize, 0);\n \n resolve({\n count: records.length,\n totalSize,\n });\n };\n\n request.onerror = () => {\n logger.error('FileCache', '获取缓存统计失败', request.error);\n reject(request.error);\n };\n \n transaction.onerror = () => {\n logger.error('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.error('FileCache', '获取缓存统计异常', error);\n throw error;\n }\n}\n\n/**\n * 清理过期缓存(超过指定天数的缓存)\n * \n * @param days - 保留天数,默认 7 天\n * @returns Promise<number> - 删除的记录数\n */\nexport async function cleanExpiredCache(days: number = 7): Promise<number> {\n try {\n const cutoffTime = Date.now() - days * 24 * 60 * 60 * 1000;\n\n return await executeTransaction(async (db) => {\n return new Promise((resolve, reject) => {\n const transaction = db.transaction([STORE_NAME], 'readwrite');\n const store = transaction.objectStore(STORE_NAME);\n const index = store.index('lastAccessedAt');\n const request = index.getAll();\n\n request.onsuccess = () => {\n const records: FileCacheRecord[] = request.result || [];\n const expiredRecords = records.filter(\n (record) => record.lastAccessedAt < cutoffTime\n );\n\n let deletedCount = 0;\n\n // 删除过期记录\n const deletePromises = expiredRecords.map((record) => {\n return new Promise<void>((resolveDelete, rejectDelete) => {\n const deleteRequest = store.delete(record.fileHash);\n deleteRequest.onsuccess = () => {\n deletedCount++;\n resolveDelete();\n };\n deleteRequest.onerror = () => {\n logger.error('FileCache', `删除过期缓存失败: ${record.fileHash}`, deleteRequest.error);\n resolveDelete(); // 继续处理其他记录\n };\n });\n });\n\n Promise.all(deletePromises).then(() => {\n if (deletedCount > 0) {\n logger.info('FileCache', `已清理 ${deletedCount} 个过期缓存`);\n }\n resolve(deletedCount);\n });\n };\n\n request.onerror = () => {\n logger.error('FileCache', '查询过期缓存失败', request.error);\n reject(request.error);\n };\n \n transaction.onerror = () => {\n logger.error('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.error('FileCache', '清理过期缓存异常', error);\n throw error;\n }\n}\n\n/**\n * 更新文件的最后访问时间\n * \n * @param fileHash - 文件哈希值\n */\nasync function updateLastAccessedTime(fileHash: string): Promise<void> {\n try {\n await executeTransaction(async (db) => {\n return new Promise<void>((resolve, reject) => {\n const transaction = db.transaction([STORE_NAME], 'readwrite');\n const store = transaction.objectStore(STORE_NAME);\n const request = store.get(fileHash);\n\n request.onsuccess = () => {\n const record: FileCacheRecord | undefined = request.result;\n \n if (record) {\n record.lastAccessedAt = Date.now();\n const updateRequest = store.put(record);\n \n updateRequest.onsuccess = () => resolve();\n updateRequest.onerror = () => {\n logger.warn('FileCache', '更新访问时间失败', updateRequest.error);\n resolve(); // 不阻塞主流程\n };\n } else {\n resolve();\n }\n };\n\n request.onerror = () => {\n logger.warn('FileCache', '查询记录失败', request.error);\n resolve(); // 不阻塞主流程\n };\n \n transaction.onerror = () => {\n logger.warn('FileCache', '事务执行失败', transaction.error);\n resolve(); // 不阻塞主流程\n };\n });\n });\n } catch (error) {\n logger.warn('FileCache', '更新访问时间异常', error);\n // 不抛出错误,避免影响主流程\n }\n}\n\n// ==================== 下载进度缓存 ====================\n\n/**\n * 下载进度缓存记录结构\n */\ninterface DownloadProgressRecord {\n fileHash: string; // 文件唯一标识\n fileName: string;\n fileSize: number;\n totalChunks: number;\n completedChunks: number;\n chunkIndexes: number[]; // 已下载的分片索引\n updatedAt: number;\n}\n\n/**\n * 保存下载进度到 IndexedDB\n */\nexport async function saveDownloadProgress(\n fileHash: string,\n fileName: string,\n fileSize: number,\n totalChunks: number,\n chunkIndexes: number[],\n): Promise<void> {\n try {\n const record: DownloadProgressRecord = {\n fileHash,\n fileName,\n fileSize,\n totalChunks,\n completedChunks: chunkIndexes.length,\n chunkIndexes,\n updatedAt: Date.now(),\n };\n\n await executeTransaction(async (db) => {\n return new Promise<void>((resolve, reject) => {\n const transaction = db.transaction([DOWNLOAD_PROGRESS_STORE], 'readwrite');\n const store = transaction.objectStore(DOWNLOAD_PROGRESS_STORE);\n const request = store.put(record);\n\n request.onsuccess = () => {\n logger.debug('FileCache', `下载进度已保存: ${fileName} (${chunkIndexes.length}/${totalChunks})`);\n resolve();\n };\n\n request.onerror = () => {\n logger.warn('FileCache', '保存下载进度失败', request.error);\n reject(request.error);\n };\n\n transaction.onerror = () => {\n logger.warn('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.warn('FileCache', '保存下载进度异常', error);\n }\n}\n\n/**\n * 从 IndexedDB 加载下载进度\n * @returns 已完成分片索引数组,未找到则返回 null\n */\nexport async function loadDownloadProgress(\n fileHash: string,\n): Promise<{\n fileName: string;\n fileSize: number;\n totalChunks: number;\n chunkIndexes: number[];\n} | null> {\n try {\n return await executeTransaction(async (db) => {\n return new Promise((resolve, reject) => {\n const transaction = db.transaction([DOWNLOAD_PROGRESS_STORE], 'readonly');\n const store = transaction.objectStore(DOWNLOAD_PROGRESS_STORE);\n const request = store.get(fileHash);\n\n request.onsuccess = () => {\n const record: DownloadProgressRecord | undefined = request.result;\n\n if (!record) {\n logger.debug('FileCache', `未找到下载进度: ${fileHash}`);\n resolve(null);\n return;\n }\n\n logger.info('FileCache', `加载下载进度: ${record.fileName} (${record.completedChunks}/${record.totalChunks})`);\n\n resolve({\n fileName: record.fileName,\n fileSize: record.fileSize,\n totalChunks: record.totalChunks,\n chunkIndexes: record.chunkIndexes,\n });\n };\n\n request.onerror = () => {\n logger.warn('FileCache', '加载下载进度失败', request.error);\n reject(request.error);\n };\n\n transaction.onerror = () => {\n logger.warn('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.warn('FileCache', '加载下载进度异常', error);\n return null;\n }\n}\n\n/**\n * 删除指定文件的下载进度记录\n */\nexport async function removeDownloadProgress(fileHash: string): Promise<void> {\n try {\n await executeTransaction(async (db) => {\n return new Promise<void>((resolve, reject) => {\n const transaction = db.transaction([DOWNLOAD_PROGRESS_STORE], 'readwrite');\n const store = transaction.objectStore(DOWNLOAD_PROGRESS_STORE);\n const request = store.delete(fileHash);\n\n request.onsuccess = () => {\n logger.debug('FileCache', `已删除下载进度: ${fileHash}`);\n resolve();\n };\n\n request.onerror = () => {\n logger.warn('FileCache', '删除下载进度失败', request.error);\n reject(request.error);\n };\n\n transaction.onerror = () => {\n logger.warn('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.warn('FileCache', '删除下载进度异常', error);\n }\n}\n\n// ==================== 文件句柄持久化(跨会话复用 File System Access Handle) ====================\n\n/**\n * 文件句柄缓存记录结构\n */\ninterface FileHandleRecord {\n fileName: string; // 文件名(主键)\n fileHandle: FileSystemFileHandle; // 文件系统句柄(IndexedDB 可序列化)\n updatedAt: number;\n}\n\n/**\n * 将 FileSystemFileHandle 持久化到 IndexedDB(跨页面刷新复用)\n *\n * 关键是避免重复调用 showSaveFilePicker() 导致 Chrome 截断已有文件为 0 字节。\n */\nexport async function saveFileHandle(\n fileName: string,\n fileHandle: FileSystemFileHandle,\n): Promise<void> {\n try {\n const record: FileHandleRecord = {\n fileName,\n fileHandle,\n updatedAt: Date.now(),\n };\n\n await executeTransaction(async (db) => {\n return new Promise<void>((resolve, reject) => {\n const transaction = db.transaction([FILE_HANDLE_STORE], 'readwrite');\n const store = transaction.objectStore(FILE_HANDLE_STORE);\n const request = store.put(record);\n\n request.onsuccess = () => {\n logger.debug('FileCache', `文件句柄已持久化: ${fileName}`);\n resolve();\n };\n\n request.onerror = () => {\n logger.warn('FileCache', '保存文件句柄失败', request.error);\n reject(request.error);\n };\n\n transaction.onerror = () => {\n logger.warn('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.warn('FileCache', '保存文件句柄异常', error);\n }\n}\n\n/**\n * 从 IndexedDB 加载持久化的 FileSystemFileHandle\n * @returns FileSystemFileHandle(未找到或已失效则返回 null)\n */\nexport async function loadFileHandle(\n fileName: string,\n): Promise<FileSystemFileHandle | null> {\n try {\n return await executeTransaction(async (db) => {\n return new Promise((resolve, reject) => {\n const transaction = db.transaction([FILE_HANDLE_STORE], 'readonly');\n const store = transaction.objectStore(FILE_HANDLE_STORE);\n const request = store.get(fileName);\n\n request.onsuccess = () => {\n const record: FileHandleRecord | undefined = request.result;\n\n if (!record) {\n logger.debug('FileCache', `未找到持久化文件句柄: ${fileName}`);\n resolve(null);\n return;\n }\n\n // 🔑 验证句柄仍有效\n try {\n // 简单的权限检查:尝试获取文件信息\n record.fileHandle.getFile().then(\n () => {\n logger.debug('FileCache', `文件句柄有效: ${fileName}`);\n resolve(record.fileHandle);\n },\n () => {\n logger.warn('FileCache', `文件句柄已失效(权限撤销): ${fileName}`);\n // 异步清理失效句柄\n removeFileHandle(fileName);\n resolve(null);\n },\n );\n } catch {\n // 同步方式失败\n removeFileHandle(fileName);\n resolve(null);\n }\n };\n\n request.onerror = () => {\n logger.warn('FileCache', '加载文件句柄失败', request.error);\n reject(request.error);\n };\n\n transaction.onerror = () => {\n logger.warn('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.warn('FileCache', '加载文件句柄异常', error);\n return null;\n }\n}\n\n/**\n * 删除指定文件的句柄记录\n */\nexport async function removeFileHandle(fileName: string): Promise<void> {\n try {\n await executeTransaction(async (db) => {\n return new Promise<void>((resolve, reject) => {\n const transaction = db.transaction([FILE_HANDLE_STORE], 'readwrite');\n const store = transaction.objectStore(FILE_HANDLE_STORE);\n const request = store.delete(fileName);\n\n request.onsuccess = () => {\n logger.debug('FileCache', `已删除文件句柄: ${fileName}`);\n resolve();\n };\n\n request.onerror = () => {\n logger.warn('FileCache', '删除文件句柄失败', request.error);\n reject(request.error);\n };\n\n transaction.onerror = () => {\n logger.warn('FileCache', '事务执行失败', transaction.error);\n reject(transaction.error);\n };\n });\n });\n } catch (error) {\n logger.warn('FileCache', '删除文件句柄异常', error);\n }\n}\n","/**\n * 网络状态检查工具\n * \n * 提供上传前的网络状态检查功能\n */\n\n/**\n * 网络检查结果\n */\nexport interface NetworkCheckResult {\n /** 是否在线 */\n online: boolean;\n /** 检查时间戳 */\n timestamp: number;\n /** 错误信息(如果离线) */\n error?: string;\n}\n\n/**\n * 检查网络连接状态\n * @returns 网络检查结果\n */\nexport function checkNetworkStatus(): NetworkCheckResult {\n const result: NetworkCheckResult = {\n online: navigator.onLine,\n timestamp: Date.now(),\n };\n\n if (!result.online) {\n result.error = '网络连接已断开,请检查网络设置后重试';\n }\n\n return result;\n}\n\n/**\n * 异步检查网络连通性(通过实际请求测试)\n * @param testUrl 测试用的 URL(可选,默认为当前域名)\n * @param timeout 超时时间(毫秒),默认 5000ms\n * @returns Promise<NetworkCheckResult>\n */\nexport async function checkNetworkConnectivity(\n testUrl?: string,\n timeout: number = 5000\n): Promise<NetworkCheckResult> {\n // 首先检查基本在线状态\n const basicCheck = checkNetworkStatus();\n if (!basicCheck.online) {\n return basicCheck;\n }\n\n // 如果没有提供测试 URL,使用当前域名\n const url = testUrl || window.location.origin;\n\n try {\n // 使用 HEAD 请求进行轻量级测试\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n await fetch(url, {\n method: 'HEAD',\n signal: controller.signal,\n cache: 'no-cache',\n });\n\n clearTimeout(timeoutId);\n\n return {\n online: true,\n timestamp: Date.now(),\n };\n } catch (error) {\n return {\n online: false,\n timestamp: Date.now(),\n error: error instanceof Error ? error.message : '网络连接测试失败',\n };\n }\n}\n\n/**\n * 监听网络状态变化\n * @param callback 网络状态变化回调\n * @returns 取消监听的函数\n */\nexport function watchNetworkStatus(\n callback: (online: boolean) => void\n): () => void {\n const handleOnline = () => callback(true);\n const handleOffline = () => callback(false);\n\n window.addEventListener('online', handleOnline);\n window.addEventListener('offline', handleOffline);\n\n // 返回取消监听的函数\n return () => {\n window.removeEventListener('online', handleOnline);\n window.removeEventListener('offline', handleOffline);\n };\n}\n","/**\n * Web Worker 内联 MD5 计算 — 将 CPU 密集型哈希计算移出主线程,避免 UI 卡顿。\n *\n * 策略:\n * - 文件 ≤ 50MB:通过 Blob URL Worker 异步计算,主线程零阻塞\n * - 文件 > 50MB 或 Worker 不可用:回退到主线程 SparkMD5 流式计算\n * - 小文件 (<5MB):直接用主线程,省去 Worker 创建开销\n */\n\n// ---- 内联 MD5 实现(RFC 1321,纯 JS,无依赖)----\n\nconst MD5_IMPL = `\nfunction md5(data) {\n function rotateLeft(v, n) { return (v << n) | (v >>> (32 - n)); }\n function addUnsigned(x, y) { return ((x & 0x7fffffff) + (y & 0x7fffffff)) ^ (x & 0x80000000) ^ (y & 0x80000000); }\n\n var F = function(x,y,z) { return (x & y) | ((~x) & z); };\n var G = function(x,y,z) { return (x & z) | (y & (~z)); };\n var H = function(x,y,z) { return x ^ y ^ z; };\n var I = function(x,y,z) { return y ^ (x | (~z)); };\n\n function transform(func, a, b, c, d, x, s, ac) {\n a = addUnsigned(a, addUnsigned(addUnsigned(func(b, c, d), x), ac));\n return addUnsigned(rotateLeft(a, s), b);\n }\n\n // Convert string/array to word array\n var len = data.length;\n var words = [];\n for (var i = 0; i < len; i++) {\n words[i >> 2] |= data[i] << ((i % 4) * 8);\n }\n words[len >> 2] |= 0x80 << ((len % 4) * 8);\n words[(((len + 8) >>> 6) << 4) + 14] = len * 8;\n\n var a = 0x67452301, b = 0xefcdab89, c = 0x98badcfe, d = 0x10325476;\n\n for (var i = 0; i < words.length; i += 16) {\n var oldA = a, oldB = b, oldC = c, oldD = d;\n // Round 1\n a = transform(F, a, b, c, d, words[i+0], 7, 0xd76aa478);\n d = transform(F, d, a, b, c, words[i+1], 12, 0xe8c7b756);\n c = transform(F, c, d, a, b, words[i+2], 17, 0x242070db);\n b = transform(F, b, c, d, a, words[i+3], 22, 0xc1bdceee);\n a = transform(F, a, b, c, d, words[i+4], 7, 0xf57c0faf);\n d = transform(F, d, a, b, c, words[i+5], 12, 0x4787c62a);\n c = transform(F, c, d, a, b, words[i+6], 17, 0xa8304613);\n b = transform(F, b, c, d, a, words[i+7], 22, 0xfd469501);\n a = transform(F, a, b, c, d, words[i+8], 7, 0x698098d8);\n d = transform(F, d, a, b, c, words[i+9], 12, 0x8b44f7af);\n c = transform(F, c, d, a, b, words[i+10],17, 0xffff5bb1);\n b = transform(F, b, c, d, a, words[i+11],22, 0x895cd7be);\n a = transform(F, a, b, c, d, words[i+12], 7, 0x6b901122);\n d = transform(F, d, a, b, c, words[i+13],12, 0xfd987193);\n c = transform(F, c, d, a, b, words[i+14],17, 0xa679438e);\n b = transform(F, b, c, d, a, words[i+15],22, 0x49b40821);\n // Round 2\n a = transform(G, a, b, c, d, words[i+1], 5, 0xf61e2562);\n d = transform(G, d, a, b, c, words[i+6], 9, 0xc040b340);\n c = transform(G, c, d, a, b, words[i+11],14, 0x265e5a51);\n b = transform(G, b, c, d, a, words[i+0], 20, 0xe9b6c7aa);\n a = transform(G, a, b, c, d, words[i+5], 5, 0xd62f105d);\n d = transform(G, d, a, b, c, words[i+10], 9, 0x02441453);\n c = transform(G, c, d, a, b, words[i+15],14, 0xd8a1e681);\n b = transform(G, b, c, d, a, words[i+4], 20, 0xe7d3fbc8);\n a = transform(G, a, b, c, d, words[i+9], 5, 0x21e1cde6);\n d = transform(G, d, a, b, c, words[i+14], 9, 0xc33707d6);\n c = transform(G, c, d, a, b, words[i+3], 14, 0xf4d50d87);\n b = transform(G, b, c, d, a, words[i+8], 20, 0x455a14ed);\n a = transform(G, a, b, c, d, words[i+13], 5, 0xa9e3e905);\n d = transform(G, d, a, b, c, words[i+2], 9, 0xfcefa3f8);\n c = transform(G, c, d, a, b, words[i+7], 14, 0x676f02d9);\n b = transform(G, b, c, d, a, words[i+12],20, 0x8d2a4c8a);\n // Round 3\n a = transform(H, a, b, c, d, words[i+5], 4, 0xfffa3942);\n d = transform(H, d, a, b, c, words[i+8], 11, 0x8771f681);\n c = transform(H, c, d, a, b, words[i+11],16, 0x6d9d6122);\n b = transform(H, b, c, d, a, words[i+14],23, 0xfde5380c);\n a = transform(H, a, b, c, d, words[i+1], 4, 0xa4beea44);\n d = transform(H, d, a, b, c, words[i+4], 11, 0x4bdecfa9);\n c = transform(H, c, d, a, b, words[i+7], 16, 0xf6bb4b60);\n b = transform(H, b, c, d, a, words[i+10],23, 0xbebfbc70);\n a = transform(H, a, b, c, d, words[i+13], 4, 0x289b7ec6);\n d = transform(H, d, a, b, c, words[i+0], 11, 0xeaa127fa);\n c = transform(H, c, d, a, b, words[i+3], 16, 0xd4ef3085);\n b = transform(H, b, c, d, a, words[i+6], 23, 0x04881d05);\n a = transform(H, a, b, c, d, words[i+9], 4, 0xd9d4d039);\n d = transform(H, d, a, b, c, words[i+12],11, 0xe6db99e5);\n c = transform(H, c, d, a, b, words[i+15],16, 0x1fa27cf8);\n b = transform(H, b, c, d, a, words[i+2], 23, 0xc4ac5665);\n // Round 4\n a = transform(I, a, b, c, d, words[i+0], 6, 0xf4292244);\n d = transform(I, d, a, b, c, words[i+7], 10, 0x432aff97);\n c = transform(I, c, d, a, b, words[i+14],15, 0xab9423a7);\n b = transform(I, b, c, d, a, words[i+5], 21, 0xfc93a039);\n a = transform(I, a, b, c, d, words[i+12], 6, 0x655b59c3);\n d = transform(I, d, a, b, c, words[i+3], 10, 0x8f0ccc92);\n c = transform(I, c, d, a, b, words[i+10],15, 0xffeff47d);\n b = transform(I, b, c, d, a, words[i+1], 21, 0x85845dd1);\n a = transform(I, a, b, c, d, words[i+8], 6, 0x6fa87e4f);\n d = transform(I, d, a, b, c, words[i+15],10, 0xfe2ce6e0);\n c = transform(I, c, d, a, b, words[i+6], 15, 0xa3014314);\n b = transform(I, b, c, d, a, words[i+13],21, 0x4e0811a1);\n a = transform(I, a, b, c, d, words[i+4], 6, 0xf7537e82);\n d = transform(I, d, a, b, c, words[i+11],10, 0xbd3af235);\n c = transform(I, c, d, a, b, words[i+2], 15, 0x2ad7d2bb);\n b = transform(I, b, c, d, a, words[i+9], 21, 0xeb86d391);\n a = addUnsigned(a, oldA);\n b = addUnsigned(b, oldB);\n c = addUnsigned(c, oldC);\n d = addUnsigned(d, oldD);\n }\n\n function toHex(v) {\n var h = ''; for (var j = 0; j < 4; j++) h += ((v >> (j * 8)) & 0xff).toString(16).padStart(2, '0'); return h;\n }\n return toHex(a) + toHex(b) + toHex(c) + toHex(d);\n}\n\nself.onmessage = async function(e) {\n try {\n var fileBuffer = e.data.fileBuffer;\n var hash = md5(new Uint8Array(fileBuffer));\n self.postMessage({ error: null, hash: hash });\n } catch (err) {\n self.postMessage({ error: err.message, hash: null });\n }\n};\n`;\n\n// ---- Worker 创建与管理 ----\n\n/** 已缓存的 Blob URL,避免每次调用都创建新 URL */\nlet _workerBlobUrl: string | null = null;\n\nfunction getWorkerBlobUrl(): string {\n if (!_workerBlobUrl) {\n const blob = new Blob([MD5_IMPL], { type: \"text/javascript\" });\n _workerBlobUrl = URL.createObjectURL(blob);\n }\n return _workerBlobUrl;\n}\n\n/**\n * 使用 Web Worker 异步计算文件 MD5。\n *\n * 仅在上传端使用(有 File 对象时);下载端正用此函数的地方\n * 传入的是 FileSystemFileHandle.getFile() 返回的 File,同样可用。\n *\n * @returns MD5 hex 字符串\n */\nexport async function calculateFileMD5InWorker(\n file: File,\n signal?: AbortSignal,\n): Promise<string> {\n const WORKER_TIMEOUT_MS = 60_000; // 1 分钟超时\n\n return new Promise((resolve, reject) => {\n // 1. 读取文件为 ArrayBuffer(在 Worker 外部,因为 FileReader 在主线程更快)\n const reader = new FileReader();\n\n const abortHandler = () => {\n reader.abort();\n reject(new Error(\"MD5 计算已取消\"));\n };\n\n if (signal) {\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n reader.onload = () => {\n if (signal) {\n signal.removeEventListener(\"abort\", abortHandler);\n }\n\n const fileBuffer = reader.result as ArrayBuffer;\n\n // 2. 创建 Worker\n const workerUrl = getWorkerBlobUrl();\n const worker = new Worker(workerUrl);\n\n const timeout = setTimeout(() => {\n worker.terminate();\n reject(new Error(\"MD5 Worker 计算超时\"));\n }, WORKER_TIMEOUT_MS);\n\n worker.onmessage = (e: MessageEvent<{ error: string | null; hash: string | null }>) => {\n clearTimeout(timeout);\n worker.terminate();\n const { error, hash } = e.data;\n if (error) {\n reject(new Error(error));\n } else {\n resolve(hash!);\n }\n };\n\n worker.onerror = (err) => {\n clearTimeout(timeout);\n worker.terminate();\n reject(new Error(err.message || \"MD5 Worker 异常\"));\n };\n\n // 3. 将 ArrayBuffer 转移给 Worker(零拷贝)\n worker.postMessage({ fileBuffer }, [fileBuffer]);\n };\n\n reader.onerror = () => {\n if (signal) {\n signal.removeEventListener(\"abort\", abortHandler);\n }\n reject(new Error(\"文件读取失败\"));\n };\n\n // 一次性读取整个文件(已由调用方保证 ≤50MB)\n reader.readAsArrayBuffer(file);\n });\n}\n\n/**\n * 全局清理:释放缓存的 Blob URL(SPA 卸载时调用)\n */\nexport function disposeMD5Worker(): void {\n if (_workerBlobUrl) {\n URL.revokeObjectURL(_workerBlobUrl);\n _workerBlobUrl = null;\n }\n}\n","import Downloader from \"../downloader\";\nimport DownloadFile from \"../downloader/DownloadFile\";\nimport Transfer from \"../transfer/Transfer\";\nimport TransferFile from \"../transfer/TransferFile\";\nimport { IFile, TimeInfo } from \"../types\";\nimport Uploader from \"../uploader\";\nimport UploadFile from \"../uploader/UploadFile\";\nimport SparkMD5 from \"spark-md5\";\n// 导出日志工具\nexport {\n logger,\n initLogger,\n setLogLevel,\n LogLevel,\n addLogCollector,\n clearLogCollectors,\n} from \"./logger\";\nexport type { LoggerOptions, LogEntry, LogCollectorCallback } from \"./logger\";\n\n// ✅ 导出文件缓存工具\nexport {\n saveFileToCache,\n restoreFileFromCache,\n removeFileFromCache,\n clearAllFileCache,\n getCacheStats,\n cleanExpiredCache,\n // 🔑 文件句柄持久化(跨会话复用)\n saveFileHandle,\n loadFileHandle,\n removeFileHandle,\n} from \"./fileCache\";\n\n// 导出上传监控工具\nexport { uploadMonitor } from \"./upload-monitor\";\nexport type { UploadStats, UploadRecord } from \"./upload-monitor\";\n\n// ✅ 导出网络检查工具\nexport {\n checkNetworkStatus,\n checkNetworkConnectivity,\n watchNetworkStatus,\n} from \"./network\";\nexport type { NetworkCheckResult } from \"./network\";\n\nexport function generateFileId() {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 10);\n return `file_${timestamp}_${random}`;\n}\n\nexport function mergeObjects<T extends Record<string, any>>(\n target: T,\n source?: Partial<T>,\n): T {\n // Type checking for target\n if (!target || typeof target !== \"object\" || Array.isArray(target)) {\n throw new TypeError(\"Target must be a valid object\");\n }\n\n // Type checking for source (allow undefined/null)\n if (source && (typeof source !== \"object\" || Array.isArray(source))) {\n throw new TypeError(\"Source must be an object, undefined, or null\");\n }\n\n // If source is empty, return shallow copy of target\n if (!source) {\n return { ...target };\n }\n\n // Perform shallow merge\n return { ...target, ...source };\n}\n\nexport function handleFile(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n if (file.size > 10 * 1024 * 1024) {\n try {\n resolve(URL.createObjectURL(file));\n } catch (error: any) {\n reject(new Error(`创建Object URL失败: ${error.message}`));\n }\n } else {\n const reader = new FileReader();\n reader.onload = (e) => {\n resolve(e.target?.result as string);\n };\n reader.onerror = () => {\n reject(new Error(\"文件读取错误\"));\n };\n\n reader.onabort = () => {\n reject(new Error(\"文件读取被中止\"));\n };\n reader.readAsDataURL(file);\n }\n });\n}\n\nexport function computeTransferTime(transferTime: TimeInfo) {\n return {\n start() {\n // 记录全局上传开始时间\n const now = Date.now();\n // ✅ 直接修改对象属性,而不是重新赋值引用\n transferTime.startTime = now;\n transferTime.endTime = 0;\n transferTime.duration = 0;\n transferTime.durationFormatted = \"0s\";\n },\n end() {\n // 记录上传结束时间并计算持续时间\n const endTime = Date.now();\n const duration = endTime - transferTime.startTime;\n // ✅ 直接修改对象属性,而不是重新赋值引用\n transferTime.endTime = endTime;\n transferTime.duration = duration;\n transferTime.durationFormatted = formatDuration(duration);\n },\n };\n}\n\nexport function getFileExtension(filename?: string) {\n const parts = filename && filename?.split(\".\");\n return parts && parts.length > 1 ? parts[parts.length - 1] : \"\";\n}\n\n/**\n * 判断文件是否处于活跃上传状态(统一判断逻辑)\n * @param file 文件实例\n * @returns 是否为活跃上传状态\n * @private\n */\nexport function isFileActive(file: TransferFile<any>): boolean {\n return [\"UDLoading\", \"paused\", \"fail\", \"merging\"].includes(file.status!);\n}\n/**\n * 将文件大小转换为易读的格式\n * @param {number} bytes - 文件大小的字节数\n * @param {number} [decimals=2] - 保留的小数位数,默认为2\n * @returns {string} 转换后的文件大小字符串\n */\nexport function formatFileSize(bytes: any, decimals = 2) {\n // ✅ 防御性检查:处理 undefined、null、NaN 等情况\n if (bytes === null || bytes === undefined || isNaN(Number(bytes))) {\n return \"0 B\";\n }\n\n // 转换为数字\n bytes = Number(bytes);\n\n // 检查是否为有效数字\n if (isNaN(bytes)) return \"0 B\";\n if (bytes === 0) return \"0 B\";\n\n // ✅ 处理负数情况(理论上不应该出现,但做防御性编程)\n if (bytes < 0) {\n console.warn(\"formatFileSize: 接收到负数值\", bytes);\n return \"0 B\";\n }\n\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n // ✅ 关键修复:去掉 parseFloat(),保留 toFixed() 的精度\n // parseFloat(\"1.90\") → 1.9 ← 会去掉末尾的 0,导致精度不一致\n return (bytes / Math.pow(k, i)).toFixed(decimals) + \" \" + sizes[i];\n}\n\nexport const validator = {\n limit(limit: number, length: number) {\n return length < limit;\n },\n size(fileSize: number, maxSize: number) {\n return fileSize <= maxSize;\n },\n type(accept: string[], file: TransferFile<any>) {\n if (accept.length > 0) {\n return accept.some((acceptType) => {\n // 处理通配符,如 'image/*'\n if (acceptType.endsWith(\"/*\")) {\n const category = acceptType.split(\"/\")[0];\n return file.File?.type.startsWith(category);\n }\n // 处理扩展名,如 '.jpg'\n if (acceptType.startsWith(\".\")) {\n return file.fileName.toLowerCase().endsWith(acceptType.toLowerCase());\n }\n // 处理 MIME 类型\n return file.File?.type === acceptType;\n });\n }\n return true;\n },\n};\n\n/**\n * 创建响应式文件实例的通用工厂函数\n * @param file 原始的文件实例(UploadFile 或 DownloadFile)\n * @param manager 管理器实例(Uploader 或 Downloader)\n * @param managerKey 管理器在文件上的属性名('transfer' 或 '__downloader__')\n * @returns Proxy 包装后的文件实例\n */\nfunction createReactiveFile<\n T extends object,\n M extends { triggerUpdate: () => void },\n>(file: T, manager: M): T {\n // 添加一个内部属性指向管理器\n Object.defineProperty(file, \"transfer\", {\n value: manager,\n enumerable: false, // 不可枚举,避免循环引用\n writable: false,\n configurable: true,\n });\n\n return new Proxy(file, {\n get(target, prop, _receiver) {\n // 🔑 不传 receiver,避免 Vue reactive proxy 双重代理时\n // 对 non-configurable / non-writable 属性的读取报错\n const value = Reflect.get(target, prop);\n\n // 🔑 函数必须绑定 target 作为 this!\n // 否则 row.resume() 中 this 是 Vue proxy → this.transfer 触发 Proxy 报错\n if (typeof value === \"function\") {\n return function (...args: any[]) {\n const result = value.apply(target, args);\n return result;\n };\n }\n\n return value;\n },\n\n set(target, prop, value, _receiver) {\n // 🔑 不传 receiver,原因同上\n const oldValue = Reflect.get(target, prop);\n\n // 设置新值\n const result = Reflect.set(target, prop, value);\n\n // 如果值真的变化了,触发更新\n if (oldValue !== value) {\n // 触发 update 回调(使用防抖优化)\n manager.triggerUpdate();\n }\n\n return result;\n },\n\n // 添加 deleteProperty 陷阱,支持删除操作\n deleteProperty(target, prop) {\n const result = Reflect.deleteProperty(target, prop);\n // 触发更新回调\n manager.triggerUpdate();\n return result;\n },\n }) as T;\n}\n\n/**\n * 创建响应式的 UploadFile 实例\n * @param uploadFile 原始的 UploadFile 实例\n * @param uploader Uploader 实例\n * @returns Proxy 包装后的 UploadFile\n */\nexport function createReactiveUploadFile(\n file: UploadFile,\n uploader: Transfer,\n): UploadFile {\n return createReactiveFile(file, uploader);\n}\n\n/**\n * 创建响应式的 DownloadFile 实例\n * @param file 原始的 DownloadFile 实例\n * @param downloader Downloader 实例\n * @returns Proxy 包装后的 DownloadFile\n */\nexport function createReactiveDownloadFile(\n file: DownloadFile,\n downloader: Transfer,\n): DownloadFile {\n return createReactiveFile(file, downloader);\n}\n\n/**\n * 提取函数中的路径\n * @param str 函数字符串\n * @returns 提取到的路径,如果没有找到返回 null\n */\nexport function extractPathFromFunction(str: string): string | null {\n // 匹配以 / 开头的路径(支持单引号、双引号、反引号)\n const pattern = /['\"`](\\/[^'\"`]*)['\"`]/;\n const match = str.match(pattern);\n\n return match ? match[1] : null;\n}\n\n/** 判断一个值是否为纯对象(非 null、非数组、typeof === \"object\") */\nexport function isPlainObject(value: any): value is Record<string, any> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * 格式化上传/下载速度显示\n * @param bytesPerSecond 每秒字节数\n * @returns 格式化后的速度字符串\n */\nexport function formatSpeed(bytesPerSecond: number): string {\n if (bytesPerSecond === 0) {\n return \"0 B/s\";\n }\n\n const units = [\"B/s\", \"KB/s\", \"MB/s\", \"GB/s\"];\n let value = bytesPerSecond;\n let unitIndex = 0;\n\n while (value >= 1024 && unitIndex < units.length - 1) {\n value /= 1024;\n unitIndex++;\n }\n\n return `${value.toFixed(2)} ${units[unitIndex]}`;\n}\n\n/**\n * 格式化时间 duration 显示\n * @param milliseconds 毫秒数\n * @returns 格式化后的时间字符串\n */\nexport function formatDuration(milliseconds: number): string {\n if (milliseconds === 0) {\n return \"0s\";\n }\n\n const seconds = Math.floor(milliseconds / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n const remainingMinutes = minutes % 60;\n const remainingSeconds = seconds % 60;\n return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`;\n } else if (minutes > 0) {\n const remainingSeconds = seconds % 60;\n return `${minutes}m ${remainingSeconds}s`;\n } else {\n return `${seconds}s`;\n }\n}\n\n/**\n * 睡眠工具函数\n * @param ms 毫秒数\n * @returns Promise\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// 🔧 Worker 模式的 MD5 计算(内部导入,避免循环引用)\nlet _md5WorkerModule: typeof import(\"./md5Worker\") | null = null;\nasync function getMD5WorkerModule() {\n if (!_md5WorkerModule) {\n _md5WorkerModule = await import(\"./md5Worker\");\n }\n return _md5WorkerModule;\n}\n\n/**\n * 计算文件的 MD5 哈希值\n *\n * 策略(自动选择最优路径):\n * - 文件 > 50MB:Worker 路径(完整读取至 Worker 计算,主线程零阻塞)\n * - 文件 5~50MB:Worker 路径,无进度回调时优先\n * - 文件 ≤ 5MB 或有 onProgress 回调:主线程 SparkMD5 流式计算\n * - Worker 不可用:回退到主线程 SparkMD5\n *\n * @param file 文件对象\n * @param onProgress 可选的进度回调函数,接收当前进度百分比 (0-100)\n * @returns MD5 哈希字符串\n */\nexport async function calculateFileMD5(\n file: File,\n onProgress?: (percent: number) => void,\n signal?: AbortSignal,\n): Promise<string> {\n const WORKER_MIN_SIZE = 5 * 1024 * 1024; // 5MB 以上才启用 Worker(省去小文件创建开销)\n const useWorker =\n !onProgress && // 有进度回调时不用 Worker(Worker 不提供分片进度)\n typeof Worker !== \"undefined\" &&\n file.size >= WORKER_MIN_SIZE;\n\n if (useWorker) {\n try {\n const { calculateFileMD5InWorker } = await getMD5WorkerModule();\n return await calculateFileMD5InWorker(file, signal);\n } catch (workerErr) {\n // Worker 创建/计算失败 → 静默回退到主线程\n console.warn(\"[calculateFileMD5] Worker 路径失败,回退到主线程:\", workerErr);\n }\n }\n\n // ---- 主线程 SparkMD5 流式回退 ----\n return new Promise(async (resolve, reject) => {\n try {\n const chunkSize = 2 * 1024 * 1024; // 2MB 分片读取\n const chunks = Math.ceil(file.size / chunkSize);\n let currentChunk = 0;\n\n const spark = new SparkMD5.ArrayBuffer();\n const fileReader = new FileReader();\n\n if (signal) {\n signal.addEventListener(\"abort\", () => {\n fileReader.abort();\n reject(new Error(\"MD5 计算已取消\"));\n });\n }\n\n fileReader.onload = (e) => {\n if (signal?.aborted) {\n fileReader.abort();\n reject(new Error(\"MD5 计算已取消\"));\n return;\n }\n\n if (e.target?.result) {\n spark.append(e.target.result as ArrayBuffer);\n currentChunk++;\n\n if (onProgress) {\n const percent = Math.round((currentChunk / chunks) * 100);\n onProgress(percent);\n }\n\n if (currentChunk < chunks) {\n loadNext();\n } else {\n resolve(spark.end());\n }\n }\n };\n\n fileReader.onerror = () => {\n reject(new Error(\"文件读取失败\"));\n };\n\n function loadNext() {\n if (signal?.aborted) {\n reject(new Error(\"MD5 计算已取消\"));\n return;\n }\n\n const start = currentChunk * chunkSize;\n const end = Math.min(start + chunkSize, file.size);\n fileReader.readAsArrayBuffer(file.slice(start, end));\n }\n\n loadNext();\n } catch (error) {\n console.error(\"MD5 计算库加载失败:\", error);\n reject(new Error(\"MD5 计算库加载失败\"));\n }\n });\n}\n\n// 导出 Worker 相关工具(供 App 层清理资源)\nexport { calculateFileMD5InWorker, disposeMD5Worker } from \"./md5Worker\";\n","'use strict';\n\nvar has = Object.prototype.hasOwnProperty\n , prefix = '~';\n\n/**\n * Constructor to create a storage for our `EE` objects.\n * An `Events` instance is a plain object whose properties are event names.\n *\n * @constructor\n * @private\n */\nfunction Events() {}\n\n//\n// We try to not inherit from `Object.prototype`. In some engines creating an\n// instance in this way is faster than calling `Object.create(null)` directly.\n// If `Object.create(null)` is not supported we prefix the event names with a\n// character to make sure that the built-in object properties are not\n// overridden or used as an attack vector.\n//\nif (Object.create) {\n Events.prototype = Object.create(null);\n\n //\n // This hack is needed because the `__proto__` property is still inherited in\n // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.\n //\n if (!new Events().__proto__) prefix = false;\n}\n\n/**\n * Representation of a single event listener.\n *\n * @param {Function} fn The listener function.\n * @param {*} context The context to invoke the listener with.\n * @param {Boolean} [once=false] Specify if the listener is a one-time listener.\n * @constructor\n * @private\n */\nfunction EE(fn, context, once) {\n this.fn = fn;\n this.context = context;\n this.once = once || false;\n}\n\n/**\n * Add a listener for a given event.\n *\n * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} context The context to invoke the listener with.\n * @param {Boolean} once Specify if the listener is a one-time listener.\n * @returns {EventEmitter}\n * @private\n */\nfunction addListener(emitter, event, fn, context, once) {\n if (typeof fn !== 'function') {\n throw new TypeError('The listener must be a function');\n }\n\n var listener = new EE(fn, context || emitter, once)\n , evt = prefix ? prefix + event : event;\n\n if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;\n else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);\n else emitter._events[evt] = [emitter._events[evt], listener];\n\n return emitter;\n}\n\n/**\n * Clear event by name.\n *\n * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.\n * @param {(String|Symbol)} evt The Event name.\n * @private\n */\nfunction clearEvent(emitter, evt) {\n if (--emitter._eventsCount === 0) emitter._events = new Events();\n else delete emitter._events[evt];\n}\n\n/**\n * Minimal `EventEmitter` interface that is molded against the Node.js\n * `EventEmitter` interface.\n *\n * @constructor\n * @public\n */\nfunction EventEmitter() {\n this._events = new Events();\n this._eventsCount = 0;\n}\n\n/**\n * Return an array listing the events for which the emitter has registered\n * listeners.\n *\n * @returns {Array}\n * @public\n */\nEventEmitter.prototype.eventNames = function eventNames() {\n var names = []\n , events\n , name;\n\n if (this._eventsCount === 0) return names;\n\n for (name in (events = this._events)) {\n if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);\n }\n\n if (Object.getOwnPropertySymbols) {\n return names.concat(Object.getOwnPropertySymbols(events));\n }\n\n return names;\n};\n\n/**\n * Return the listeners registered for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Array} The registered listeners.\n * @public\n */\nEventEmitter.prototype.listeners = function listeners(event) {\n var evt = prefix ? prefix + event : event\n , handlers = this._events[evt];\n\n if (!handlers) return [];\n if (handlers.fn) return [handlers.fn];\n\n for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {\n ee[i] = handlers[i].fn;\n }\n\n return ee;\n};\n\n/**\n * Return the number of listeners listening to a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Number} The number of listeners.\n * @public\n */\nEventEmitter.prototype.listenerCount = function listenerCount(event) {\n var evt = prefix ? prefix + event : event\n , listeners = this._events[evt];\n\n if (!listeners) return 0;\n if (listeners.fn) return 1;\n return listeners.length;\n};\n\n/**\n * Calls each of the listeners registered for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Boolean} `true` if the event had listeners, else `false`.\n * @public\n */\nEventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {\n var evt = prefix ? prefix + event : event;\n\n if (!this._events[evt]) return false;\n\n var listeners = this._events[evt]\n , len = arguments.length\n , args\n , i;\n\n if (listeners.fn) {\n if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);\n\n switch (len) {\n case 1: return listeners.fn.call(listeners.context), true;\n case 2: return listeners.fn.call(listeners.context, a1), true;\n case 3: return listeners.fn.call(listeners.context, a1, a2), true;\n case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;\n case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;\n case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;\n }\n\n for (i = 1, args = new Array(len -1); i < len; i++) {\n args[i - 1] = arguments[i];\n }\n\n listeners.fn.apply(listeners.context, args);\n } else {\n var length = listeners.length\n , j;\n\n for (i = 0; i < length; i++) {\n if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);\n\n switch (len) {\n case 1: listeners[i].fn.call(listeners[i].context); break;\n case 2: listeners[i].fn.call(listeners[i].context, a1); break;\n case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;\n case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;\n default:\n if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {\n args[j - 1] = arguments[j];\n }\n\n listeners[i].fn.apply(listeners[i].context, args);\n }\n }\n }\n\n return true;\n};\n\n/**\n * Add a listener for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} [context=this] The context to invoke the listener with.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.on = function on(event, fn, context) {\n return addListener(this, event, fn, context, false);\n};\n\n/**\n * Add a one-time listener for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} [context=this] The context to invoke the listener with.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.once = function once(event, fn, context) {\n return addListener(this, event, fn, context, true);\n};\n\n/**\n * Remove the listeners of a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn Only remove the listeners that match this function.\n * @param {*} context Only remove the listeners that have this context.\n * @param {Boolean} once Only remove one-time listeners.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {\n var evt = prefix ? prefix + event : event;\n\n if (!this._events[evt]) return this;\n if (!fn) {\n clearEvent(this, evt);\n return this;\n }\n\n var listeners = this._events[evt];\n\n if (listeners.fn) {\n if (\n listeners.fn === fn &&\n (!once || listeners.once) &&\n (!context || listeners.context === context)\n ) {\n clearEvent(this, evt);\n }\n } else {\n for (var i = 0, events = [], length = listeners.length; i < length; i++) {\n if (\n listeners[i].fn !== fn ||\n (once && !listeners[i].once) ||\n (context && listeners[i].context !== context)\n ) {\n events.push(listeners[i]);\n }\n }\n\n //\n // Reset the array, or remove it completely if we have no more listeners.\n //\n if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;\n else clearEvent(this, evt);\n }\n\n return this;\n};\n\n/**\n * Remove all listeners, or those of the specified event.\n *\n * @param {(String|Symbol)} [event] The event name.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {\n var evt;\n\n if (event) {\n evt = prefix ? prefix + event : event;\n if (this._events[evt]) clearEvent(this, evt);\n } else {\n this._events = new Events();\n this._eventsCount = 0;\n }\n\n return this;\n};\n\n//\n// Alias methods names because people roll like that.\n//\nEventEmitter.prototype.off = EventEmitter.prototype.removeListener;\nEventEmitter.prototype.addListener = EventEmitter.prototype.on;\n\n//\n// Expose the prefix.\n//\nEventEmitter.prefixed = prefix;\n\n//\n// Allow `EventEmitter` to be imported as module namespace.\n//\nEventEmitter.EventEmitter = EventEmitter;\n\n//\n// Expose the module.\n//\nif ('undefined' !== typeof module) {\n module.exports = EventEmitter;\n}\n","declare const __FILE_UD_PACKAGE_NAME__: string;\ndeclare const __FILE_UD_VERSION__: string;\ndeclare const __FILE_UD_HOMEPAGE__: string;\n\nconst GLOBAL_BANNER_FLAG = \"__FILE_UD_BANNER_PRINTED__\";\n\nconst packageName =\n typeof __FILE_UD_PACKAGE_NAME__ === \"string\"\n ? __FILE_UD_PACKAGE_NAME__\n : \"@file-ud.js/core\";\nconst version =\n typeof __FILE_UD_VERSION__ === \"string\" ? __FILE_UD_VERSION__ : \"0.0.0\";\nconst homepage =\n typeof __FILE_UD_HOMEPAGE__ === \"string\" ? __FILE_UD_HOMEPAGE__ : \"\";\n\nfunction getPackageDisplayName(name: string): string {\n if (!name) return \"SDK\";\n\n const withoutScope = name.startsWith(\"@\") ? name.slice(1).split(\"/\")[0] : name;\n return withoutScope.split(\"/\")[0] || name;\n}\n\nconst displayName = getPackageDisplayName(packageName);\n\nfunction hasPrintedBanner(): boolean {\n return Boolean((globalThis as any)[GLOBAL_BANNER_FLAG]);\n}\n\nfunction markBannerPrinted() {\n try {\n Object.defineProperty(globalThis, GLOBAL_BANNER_FLAG, {\n value: true,\n configurable: true,\n });\n } catch {\n (globalThis as any)[GLOBAL_BANNER_FLAG] = true;\n }\n}\n\nexport function printFileUDBanner() {\n if (hasPrintedBanner()) return;\n\n markBannerPrinted();\n\n if (typeof console === \"undefined\" || typeof console.info !== \"function\") {\n return;\n }\n\n const titleStyle =\n \"background:#111827;color:#fff;padding:2px 8px;border-radius:4px 0 0 4px;font-weight:700\";\n const versionStyle =\n \"background:#2563eb;color:#fff;padding:2px 8px;border-radius:0 4px 4px 0;font-weight:700\";\n const textStyle = \"color:#64748b\";\n\n try {\n console.info(\n \"%c %s %c v%s \",\n titleStyle,\n displayName,\n versionStyle,\n version,\n );\n if (homepage) {\n console.info(\"%cDocs: %s\", textStyle, homepage);\n }\n } catch {\n console.info(`[${displayName}] v${version}`);\n }\n}\n","import {\n beforeTransferCallBack,\n ErrorCallBack,\n EventName,\n IFile,\n IUDPlugin,\n onInitChunkCallback,\n OnMergeChunkCallBack,\n speedInfo,\n successCallback,\n TimeInfo,\n UpdateCallBack,\n} from \"../types\";\nimport { formatDuration, formatFileSize, formatSpeed, isFileActive } from \"../utils\";\nimport { EventEmitter } from \"../utils/event-emitter\";\nimport TransferFile from \"./TransferFile\";\n\nexport default class Transfer<\n T extends TransferFile<T> = TransferFile<any>,\n D = any,\n> extends EventEmitter {\n /** 当前传输任务的文件列表 */\n public files: T[] = [];\n /** 当前正在上传或下载任务的文件列表 用来判断当前文件是否全部上传完成 */\n public activeFiles: T[] = [];\n\n /** 总字节数 */\n public totalBytes: number = 0;\n\n /** 已传输的总字节数 */\n public transferredBytes: number = 0;\n /** 总的已传输的总字节数 */\n public totalTransferredBytes: number = 0;\n /** 全局已传输的总大小(格式化字符串),如 \"125.50 MB\" */\n public transferredFormatSize: string = \"0 B\";\n public lastLoadedMap = new Map();\n public plugins: IUDPlugin<T>[] = [];\n /** 插件间共享数据(如 MD5 计算结果等),由插件自行存取 */\n public pluginSharedData = new Map<string, any>();\n /** 全局待传输的总大小(格式化字符串),如 \"256.80 MB\" */\n public totalFormatSize: string = \"0 B\";\n\n /** 全局传输速率信息 (瞬时速度、平均速度等) */\n public speed: speedInfo = {\n currentSpeed: 0,\n averageSpeed: 0,\n currentSpeedFormatted: \"0 B/s\",\n averageSpeedFormatted: \"0 B/s\",\n estimatedTimeRemaining: -1,\n estimatedTimeFormatted: \"计算中...\",\n };\n\n /** 全局传输进度百分比 (0-100) */\n public totalPercent: number = 0;\n\n /** 是否处于加载/传输状态 */\n public loading: boolean = false;\n\n /** 传输时间统计信息 (开始时间、结束时间、耗时) */\n public transferTime: TimeInfo = {\n startTime: 0,\n endTime: 0,\n duration: 0,\n durationFormatted: \"0s\",\n };\n\n /** 内部定时器 ID,用于防抖更新 */\n public __updateTimer__: number | undefined = undefined;\n public updateCallback: UpdateCallBack<T> | null | undefined = null;\n public onInitChunkCallback: onInitChunkCallback<T> | null = null;\n public OnMergeChunkCallBack: OnMergeChunkCallBack | null = null;\n public beforeTransferCallback: beforeTransferCallBack<T> | null | undefined =\n null;\n\n public static onError: ErrorCallBack;\n public successCallback: successCallback<D> = () => null;\n /**\n * 触发更新回调(requestAnimationFrame 防抖)\n *\n * 用 rAF 替代 setTimeout,确保同一帧内多次调用只执行一次,\n * 多文件并发场景下所有文件的 Proxy set 共享同一个 rAF 调度。\n * @private\n */\n public triggerUpdate(): void {\n if (this.__updateTimer__ !== undefined) return; // 已调度,跳过\n this.__updateTimer__ = requestAnimationFrame(() => {\n this.__updateTimer__ = undefined;\n // 计算并更新全局上传速率\n this.speed = this.calculateGlobalUploadSpeed();\n\n this.updateCallback?.([...this.files]);\n });\n }\n\n /**\n * 更新全局统计信息(内部方法)\n * @private\n */\n public updateGlobalStats() {\n // 重新计算总字节数(兼容下载场景:下载文件没有 File 对象,fallback 到 size 属性)\n this.totalBytes = this.files.reduce((sum, file) => {\n return sum + (file.File?.size || file.size || 0);\n }, 0);\n\n // 更新格式化后的总大小\n this.totalFormatSize = formatFileSize(this.totalBytes);\n\n // 计算总进度(按文件大小加权,避免简单平均导致的进度不准)\n if (this.files.length > 0 && this.totalBytes > 0) {\n let weightedPercent = 0;\n this.files.forEach((file) => {\n const fileSize = file.File?.size || file.size || 0;\n if (fileSize > 0) {\n weightedPercent += (file.percent || 0) * fileSize;\n }\n });\n this.totalPercent = Math.round(weightedPercent / this.totalBytes);\n } else {\n this.totalPercent = 0;\n }\n }\n /**\n * 重置上状态为初始值(统一重置逻辑)\n * @private\n */\n public resetState(): void {\n this.transferredBytes = 0;\n this.totalBytes = 0;\n this.totalFormatSize = \"0 B\";\n this.transferredFormatSize = \"0 B\";\n\n this.totalTransferredBytes = 0;\n this.speed = {\n currentSpeedFormatted: \"\",\n averageSpeedFormatted: \"\",\n currentSpeed: 0,\n averageSpeed: 0,\n estimatedTimeRemaining: -1,\n estimatedTimeFormatted: \"计算中...\",\n };\n this.transferTime = {\n startTime: 0,\n endTime: 0,\n duration: 0,\n durationFormatted: \"0s\",\n };\n this.totalPercent = 0;\n this.activeFiles = [];\n // 注意:不重置 this.events,保留用户已注册的事件监听器\n this.files = [];\n // Object.create 场景下可能为 undefined,忽略\n this.lastLoadedMap?.clear();\n this.plugins = [];\n // ✅ 重置回调为安全的默认值,避免 Object.create 场景下出现 undefined\n this.successCallback = () => null;\n this.updateCallback = null;\n this.beforeTransferCallback = null;\n this.onInitChunkCallback = null;\n this.OnMergeChunkCallBack = null;\n }\n set onUpdate(callback: UpdateCallBack<T>) {\n this.updateCallback = callback;\n }\n\n set onInitChunk(callback: onInitChunkCallback<T>) {\n this.onInitChunkCallback = callback;\n }\n\n set onMergeChunk(callback: OnMergeChunkCallBack) {\n this.OnMergeChunkCallBack = callback;\n }\n set onSuccess(callback: successCallback<D>) {\n this.successCallback = callback;\n }\n set onbeforeTransfer(callback: beforeTransferCallBack<T>) {\n this.beforeTransferCallback = callback;\n }\n /**\n * 注册插件\n * @param plugin 插件实例\n * @param options 插件配置\n */\n use(plugin: IUDPlugin<T> | IUDPlugin<T>[], options?: any): this {\n const plugins = Array.isArray(plugin) ? plugin : [plugin];\n\n for (const p of plugins) {\n // 检查是否已注册\n if (this.plugins.some((existing) => existing.name === p.name)) {\n console.warn(`插件 ${p.name} 已存在,跳过注册`);\n continue;\n }\n\n // 调用插件的 install 方法\n if (p.install) {\n p.install(this, options);\n }\n p.created?.(this);\n this.plugins.push(p);\n console.log(`✅ 插件已注册: ${p.name} v${p.version || \"1.0.0\"}`);\n }\n\n // 按优先级排序\n this.plugins.sort((a, b) => (a.priority || 50) - (b.priority || 50));\n\n return this;\n }\n\n /**\n * 移除插件\n * @param name 插件名称\n */\n unuse(name: string): this {\n const index = this.plugins.findIndex((p) => p.name === name);\n if (index !== -1) {\n const plugin = this.plugins[index];\n plugin.destroy?.();\n this.plugins.splice(index, 1);\n console.log(`🗑️ 插件已移除: ${name}`);\n }\n return this;\n }\n\n /**\n * 获取插件\n * @param name 插件名称\n */\n getPlugin(name?: string): IUDPlugin<T> | IUDPlugin<T>[] | undefined {\n if (name) {\n return this.plugins.find((p) => p.name === name);\n }\n return [...this.plugins];\n }\n\n /**\n * 执行插件钩子\n * @param hook 钩子名称\n * @param args 参数\n */\n async runHook<K extends keyof IUDPlugin>(\n hook: K,\n ...args: any[]\n ): Promise<any[]> {\n const results: any[] = [];\n\n for (const plugin of this.plugins) {\n const handler = plugin[hook];\n if (handler && typeof handler === \"function\") {\n const result = await (handler as any).apply(plugin, args);\n results.push(result);\n }\n }\n\n return results;\n }\n /**\n * 计算全局上传速率(所有文件的聚合)\n * @returns 全局速率对象\n * @private\n */\n private calculateGlobalUploadSpeed(): speedInfo {\n let totalCurrentSpeed = 0;\n let transferTotaldBytes = 0;\n let totalFileSize = 0;\n let activeFileCount = 0;\n\n // 遍历所有正在上传的文件,累加速率和字节数\n this.files.forEach((file) => {\n if (isFileActive(file)) {\n activeFileCount++;\n\n // 累加文件大小(用于计算平均速度)\n totalFileSize += file.File?.size || file.size || 0;\n\n // 使用统一方法获取已上传字节数\n transferTotaldBytes += this.getFileBytes(file);\n // 如果文件有速度信息,累加瞬时速度\n if (file.speed) {\n totalCurrentSpeed += file.speed.currentSpeed;\n }\n } else if (file.status === \"success\") {\n // 已完成文件也计入总大小(用于计算整体平均速度)\n totalFileSize += file.File?.size || file.size || 0;\n transferTotaldBytes += file.File?.size || file.size || 0;\n }\n });\n\n // 计算全局平均速度:总已上传字节 / 总耗时\n let globalAverageSpeed = 0;\n if (transferTotaldBytes > 0 && activeFileCount > 0) {\n // 找到最早开始上传的文件的时间\n let earliestStartTime = Date.now();\n this.files.forEach((file) => {\n if (isFileActive(file) && file.transferTime.startTime > 0) {\n earliestStartTime = Math.min(\n earliestStartTime,\n file.transferTime.startTime,\n );\n }\n });\n\n const totalTime = (Date.now() - earliestStartTime) / 1000;\n if (totalTime > 0) {\n globalAverageSpeed = transferTotaldBytes / totalTime;\n }\n }\n\n // 更新全局已上传大小(使用 formatFileSize 格式化)\n this.transferredFormatSize = formatFileSize(transferTotaldBytes);\n\n // 计算全局预计剩余时间\n const remainingBytes = Math.max(0, totalFileSize - transferTotaldBytes);\n let estimatedTimeRemaining = -1;\n let estimatedTimeFormatted = \"计算中...\";\n\n if (globalAverageSpeed > 0 && remainingBytes > 0) {\n estimatedTimeRemaining = Math.ceil(remainingBytes / globalAverageSpeed);\n estimatedTimeFormatted = formatDuration(estimatedTimeRemaining * 1000);\n } else if (remainingBytes <= 0 && transferTotaldBytes > 0) {\n estimatedTimeRemaining = 0;\n estimatedTimeFormatted = \"即将完成\";\n }\n\n return {\n currentSpeed: totalCurrentSpeed,\n averageSpeed: globalAverageSpeed,\n currentSpeedFormatted: formatSpeed(totalCurrentSpeed),\n averageSpeedFormatted: formatSpeed(globalAverageSpeed),\n estimatedTimeRemaining,\n estimatedTimeFormatted,\n };\n }\n\n /**\n * 获取文件的已上传字节数(统一获取逻辑)\n * @param file 文件实例\n * @returns 已上传字节数\n * @private\n */\n private getFileBytes(file: T): number {\n return file.chunkManager\n ? file.chunkManager.totalChunkSize\n : file.__transferBytes__ || 0;\n }\n}\n","import { EventCallback, EventName } from \"../types\";\n\nexport class EventEmitter {\n events?: Map<EventName, Set<Function>>;\n\n /** 确保 events Map 已初始化(兼容 Object.create 不执行字段初始化器) */\n private ensureEvents(): Map<EventName, Set<Function>> {\n if (!this.events) {\n this.events = new Map<EventName, Set<Function>>();\n }\n return this.events;\n }\n\n // 订阅事件\n on<K extends EventName>(eventName: K, callback: EventCallback<K>): this {\n const events = this.ensureEvents();\n if (!events.has(eventName)) {\n events.set(eventName, new Set());\n }\n events.get(eventName)!.add(callback);\n return this;\n }\n // 发布事件\n emit<K extends EventName>(\n eventName: K,\n ...args: Parameters<EventCallback<K>>\n ) {\n const events = this.ensureEvents();\n const callbacks = events.get(eventName);\n if (!callbacks || callbacks.size === 0) {\n return false;\n }\n\n callbacks.forEach((callback) => {\n try {\n callback.call(this, ...args);\n } catch (error) {\n console.error(`Error in event listener for \"${eventName}\":`, error);\n }\n });\n\n return true;\n }\n\n // 只执行一次\n once<K extends EventName>(eventName: K, callback: EventCallback<K>): this {\n const onceWrapper = ((...args: any[]) => {\n (callback as any)(...args);\n this.off(eventName, onceWrapper as any);\n }) as any as EventCallback<K>;\n\n return this.on(eventName, onceWrapper);\n }\n\n // 移除事件监听器\n off<K extends EventName>(eventName: K, callback?: EventCallback<K>): this {\n const events = this.ensureEvents();\n const callbacks = events.get(eventName);\n if (callbacks) {\n if (callback) {\n callbacks.delete(callback);\n } else {\n callbacks.clear();\n }\n }\n return this;\n }\n\n // 获取事件监听器数量\n listenerCount(eventName: EventName): number {\n const events = this.ensureEvents();\n return events.get(eventName)?.size || 0;\n }\n\n // 移除所有事件监听器\n removeAllListeners(eventName?: EventName): this {\n const events = this.ensureEvents();\n if (eventName) {\n events.delete(eventName);\n } else {\n events.clear();\n }\n return this;\n }\n}\n","import { Errors } from \"..\";\nimport Transfer from \"../transfer/Transfer\";\nimport {\n beforeTransferCallBack,\n ErrorCallBack,\n EventName,\n UploaderConfig,\n IFile,\n IUDPlugin,\n onInitChunkCallback,\n OnMergeChunkCallBack,\n OpenFileCallback,\n PluginContext,\n SelectCallBack,\n UpdateCallBack,\n successCallback,\n} from \"../types\";\nimport {\n checkNetworkStatus,\n formatFileSize,\n generateFileId,\n getFileExtension,\n handleFile,\n logger,\n mergeObjects,\n validator,\n} from \"../utils\";\nimport FileConcurrencyController from \"../concurrency/FileConcurrencyController\";\n\nimport UploadFile from \"./UploadFile\";\nexport const defaultConfig: UploaderConfig = {\n multiple: false,\n accept: [],\n show: false,\n elementId: undefined,\n autoUpload: true,\n action: \"\",\n file: \"file\",\n};\n\nexport default class Uploader<T = any> extends Transfer<UploadFile, T> {\n public inputHTML: HTMLInputElement | null;\n public static objectUrls: any[] = [];\n public static baseConfig: UploaderConfig;\n public config: UploaderConfig | null = null;\n public static instances: Uploader | null = null;\n public static uploadFile: UploadFile | null;\n\n // 标记是否已安装全局拦截器\n public static isInterceptorInstalled = false;\n public totalPercent: number = 0;\n\n public static fileIndex = 0;\n // 添加拦截器相关属性\n public static originalXHR?: typeof XMLHttpRequest | null;\n public static interceptorActive = false;\n\n // 静态默认插件\n private static defaultPlugins: IUDPlugin<UploadFile>[] = [];\n public openCallBack: OpenFileCallback = () => null;\n\n public selectCallback: SelectCallBack | null | undefined = null;\n\n /* \n 清空文件\n*/\n public clearFiles() {\n this.files.forEach((file) => {\n file.abort?.();\n });\n this.files = [];\n Uploader.fileIndex = 0;\n Uploader.uploadFile = null;\n Uploader.objectUrls = [];\n this.totalPercent = 0;\n this.totalBytes = 0;\n this.totalFormatSize = \"0 B\";\n this.triggerUpdate();\n }\n /**\n * 设置全局默认插件(影响之后创建的所有实例)\n */\n static setDefaultPlugins(plugins: IUDPlugin<UploadFile>[]): void {\n Uploader.defaultPlugins = plugins;\n }\n /**\n * 设置文件列表(用于回显已上传的文件)\n * @param files - 要回显的文件列表,可以是 UploadFile 实例或文件信息对象\n * @example\n * ```typescript\n * //从服务端获取文件列表后回显\n * const savedFiles = await fetchSavedFiles();\n * uploader.setFiles(savedFiles.map(fileInfo => ({\n * fileId: fileInfo.id,\n * fileName: fileInfo.name,\n * url: fileInfo.url,\n * size: fileInfo.size,\n * percent: 100,\n * status: 'success',\n * formatSize: formatFileSize(fileInfo.size)\n * })));\n *\n */\n public setFiles(files: IFile[]) {\n if (!files || !Array.isArray(files)) {\n logger.warn(\"Uploader\", \"setFiles: 传入的参数不是数组\", files);\n return;\n }\n\n // 清空现有文件\n this.clearFiles();\n\n // 遍历并创建 UploadFile 实例\n files.forEach((fileData, index) => {\n // 创建 UploadFile 实例\n const uploadFile = new UploadFile(\n {\n fileId: fileData.fileId,\n url: fileData.url || \"\",\n fileName: fileData.fileName,\n File: fileData.File,\n percent: fileData.percent ?? 0,\n status: fileData.status || \"pending\",\n extension: fileData.extension || getFileExtension(fileData.fileName),\n formatSize:\n fileData.formatSize || formatFileSize(fileData.File?.size),\n index: fileData.index ?? Uploader.fileIndex++,\n // 保留其他可能的属性\n ...(fileData as any),\n },\n this as unknown as Transfer,\n );\n\n // 添加到文件列表\n this.files.push(uploadFile);\n this.activeFiles.push(uploadFile);\n });\n\n // 更新全局统计信息\n this.updateGlobalStats();\n\n // 触发更新事件\n this.triggerUpdate();\n\n logger.info(\"Uploader\", `setFiles: 成功回显 ${this.files.length} 个文件`);\n }\n\n constructor(config?: UploaderConfig) {\n super();\n this.inputHTML = null;\n try {\n if (!Uploader.instances) {\n this.config = mergeObjects(Uploader.baseConfig, config);\n\n Uploader.instances = this.create(this.config);\n }\n return Uploader.instances!;\n } catch (error: any) {\n throw new Error(`Failed to initialize uploader: ${error.message}`);\n }\n }\n /**\n * @description: 打开文件\n * @param {OpenFileCallback} fn\n * @return {*}\n */\n public open(fn?: OpenFileCallback): any {\n if (this.inputHTML) {\n this.inputHTML.click();\n } else {\n logger.warn(\"Uploader\", \"The uploader does not exist. Please create one\");\n }\n if (fn) {\n this.openCallBack = fn;\n }\n }\n\n private init() {\n // 使用统一的重置方法\n this.resetState();\n\n // 继承默认插件\n this.plugins = [...Uploader.defaultPlugins];\n }\n private createInput() {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.setAttribute(\"hidden\", Boolean(this.config?.show).toString());\n input.accept = this.config?.accept?.toString() || \"*\";\n this.plugins = [];\n console.info(\"创建文件输入框\");\n\n input.multiple = this.config?.multiple || false;\n this.inputHTML = input;\n if (!this.config?.elementId) {\n document.body.appendChild(input);\n } else {\n const target = document.getElementById(this.config.elementId);\n if (target) {\n target.appendChild(input);\n }\n }\n return input;\n }\n create(config?: UploaderConfig) {\n Uploader.baseConfig = Object.assign(defaultConfig, Uploader.baseConfig);\n this.config = { ...Uploader.baseConfig, ...config };\n this.init();\n\n // 🔑 配置文件级并发\n if (this.config.maxFileConcurrent !== undefined) {\n FileConcurrencyController.getInstance().maxUploadConcurrent =\n this.config.maxFileConcurrent;\n }\n\n this.createInput().onchange = async (e) => {\n const FileList = (e.target as HTMLInputElement)?.files;\n const filesList = Array.from(FileList!);\n\n await this.processSelectedFiles(filesList);\n\n this.inputHTML!.value = \"\";\n };\n\n return this;\n }\n public updateConfig(config: Partial<UploaderConfig>) {\n this.config = mergeObjects(this.config!, config);\n }\n set onbeforeTransfer(callback: beforeTransferCallBack<UploadFile>) {\n this.beforeTransferCallback = callback;\n }\n\n set onSelect(callback: SelectCallBack) {\n this.selectCallback = callback;\n }\n\n public remObjectUrls(url: string) {\n Uploader.objectUrls.splice(\n Uploader.objectUrls.findIndex((item) => item === url),\n 1,\n );\n }\n\n // 全部取消上传\n public cancelAll() {\n this.files.forEach((file) => {\n file.cancel();\n });\n }\n\n // 全部暂停\n public pauseAll() {\n this.files.forEach((file) => {\n file.pause();\n });\n }\n // 全部继续\n public resumeAll() {\n this.files.forEach((file) => {\n file.resume();\n });\n }\n\n // 全部重试\n public retryAll() {\n this.files.forEach((file) => {\n file.retry();\n });\n }\n\n /**\n * @description: 手动提交上传所有文件(当 autoUpload 为 false 时)\n * @return {Promise<void>}\n */\n public async submit(): Promise<void> {\n // 上传前检查网络状态\n try {\n const networkCheck = checkNetworkStatus();\n if (!networkCheck.online) {\n const error = new Error(networkCheck.error || \"网络连接异常\");\n logger.error(\"uploadChunkManager\", `网络检查失败: ${error.message}`);\n this.cancelAll();\n throw error;\n }\n } catch (error) {\n this.cancelAll();\n logger.error(\"uploadChunkManager\", \"网络检查异常\", error);\n }\n // 检查是否有待上传的文件\n if (this.files.length === 0) {\n console.warn(\"没有待上传的文件\");\n return Promise.reject();\n }\n\n // 如果是第一次调用 submit,触发批量开始事件并记录开始时间\n const isFirstBatch =\n this.activeFiles.filter((f) => f.status === \"UDLoading\").length === 0;\n if (isFirstBatch) {\n this.emit(\"files-start\", this.files);\n\n // 并行上传所有文件\n const uploadPromises = this.files.map(async (file) => {\n try {\n // 如果文件已经是成功或上传中状态,跳过\n if (file.status === \"success\" || file.status === \"UDLoading\") {\n return Promise.resolve();\n }\n\n // ✅ 使用统一的 start() 方法\n\n return file.start(file.uploadChunkManager);\n } catch (error) {\n console.error(file.fileName + \"文件传输失败:\", error);\n }\n });\n await Promise.all(uploadPromises);\n }\n }\n\n /**\n * 验证文件是否符合上传条件(统一验证逻辑)\n * @param file 原始文件对象\n * @param uploadFileInstance 上传文件实例\n * @returns 验证结果 { valid: boolean, error?: any }\n * @private\n */\n private validateFile(\n file: File,\n uploadFileInstance: UploadFile,\n ): { valid: boolean; error?: any } {\n // 大小限制检查\n if (\n this.config?.maxSize &&\n !validator.size(file.size, this.config.maxSize)\n ) {\n return {\n valid: false,\n error: Errors.fileTooLarge.call(this, this.config?.maxSize),\n };\n }\n\n // 类型限制检查\n if (\n this.config?.accept?.toString() &&\n !validator.type(this.config?.accept, uploadFileInstance)\n ) {\n return {\n valid: false,\n error: Errors.fileTooType.call(this, {\n accept: this.config?.accept,\n fileName: file.name,\n }),\n };\n }\n\n // 数量限制检查\n if (\n this.config?.limit &&\n !validator.limit(this.config?.limit, this.files.length)\n ) {\n return {\n valid: false,\n error: Errors.fileTooLimit.call(this, this.config?.limit),\n };\n }\n\n return { valid: true };\n }\n\n /**\n * 处理文件选择(内部方法)\n */\n private async processSelectedFiles(files: File[]) {\n for (let i = 0; i < files.length; i++) {\n const file = files[i];\n\n // 创建插件上下文\n const pluginContext: PluginContext<UploadFile> = {\n transfer: this as unknown as Transfer<UploadFile>,\n shared: new Map(),\n config: this.config,\n };\n\n const uploadFileInstance = new UploadFile<T>(\n {\n fileId: generateFileId(),\n url: \"\",\n fileName: file.name,\n File: file,\n percent: 0,\n status: \"pending\",\n extension: getFileExtension(file.name),\n formatSize: formatFileSize(file.size),\n size: file.size,\n index: Uploader.fileIndex++,\n },\n this as unknown as Transfer,\n );\n let processedFile: UploadFile | null = null;\n\n try {\n // 执行插件钩子\n const selectResults = await this.runHook(\n \"onFileSelect\",\n uploadFileInstance,\n pluginContext,\n );\n processedFile =\n selectResults.filter((r) => r instanceof UploadFile).pop() || null;\n if (processedFile instanceof UploadFile) {\n // 更新实例的引用\n Object.assign(uploadFileInstance, processedFile);\n }\n } catch (error) {\n console.error(\"处理选中文件时发生错误:\", error);\n continue;\n }\n Uploader.uploadFile = uploadFileInstance;\n\n // 调用选择回调\n if (this.selectCallback) {\n try {\n const result = await this.selectCallback.call(this, file);\n if (!result) {\n continue;\n }\n } catch (error) {\n continue;\n }\n }\n\n // 使用统一的验证方法\n const validation = this.validateFile(file, uploadFileInstance);\n if (!validation.valid) {\n this.emit(\"error\", validation.error);\n // 如果是数量限制,跳出循环\n if (validation.error?.code === \"FILE_TOO_LIMIT\") {\n break;\n }\n continue;\n }\n\n // 保存插件上下文\n (uploadFileInstance as any).__pluginContext = pluginContext;\n\n // 生成预览 URL\n handleFile(uploadFileInstance.File).then((url) => {\n uploadFileInstance.url = url;\n Uploader.objectUrls.push(url);\n if (this.inputHTML) {\n this.inputHTML.value = \"\";\n }\n this.emit(\"change\", uploadFileInstance);\n this.openCallBack?.(uploadFileInstance);\n });\n\n const beforeResults = await this.runHook(\n \"beforeTransfer\",\n uploadFileInstance,\n pluginContext,\n );\n const shouldContinue = beforeResults.every((r) => r !== false);\n if (!shouldContinue) continue;\n\n // 添加到文件列表\n this.files.push(uploadFileInstance);\n this.activeFiles.push(uploadFileInstance);\n if (this.config?.autoUpload) {\n uploadFileInstance.start(uploadFileInstance.uploadChunkManager);\n }\n }\n }\n}\n","import EventEmitter from './index.js'\n\nexport { EventEmitter }\nexport default EventEmitter\n","export class TimeoutError extends Error {\n\tname = 'TimeoutError';\n\n\tconstructor(message, options) {\n\t\tsuper(message, options);\n\t\tError.captureStackTrace?.(this, TimeoutError);\n\t}\n}\n\nconst getAbortedReason = signal => signal.reason ?? new DOMException('This operation was aborted.', 'AbortError');\n\nexport default function pTimeout(promise, options) {\n\tconst {\n\t\tmilliseconds,\n\t\tfallback,\n\t\tmessage,\n\t\tcustomTimers = {setTimeout, clearTimeout},\n\t\tsignal,\n\t} = options;\n\n\tlet timer;\n\tlet abortHandler;\n\n\tconst wrappedPromise = new Promise((resolve, reject) => {\n\t\tif (typeof milliseconds !== 'number' || Math.sign(milliseconds) !== 1) {\n\t\t\tthrow new TypeError(`Expected \\`milliseconds\\` to be a positive number, got \\`${milliseconds}\\``);\n\t\t}\n\n\t\tif (signal?.aborted) {\n\t\t\treject(getAbortedReason(signal));\n\t\t\treturn;\n\t\t}\n\n\t\tif (signal) {\n\t\t\tabortHandler = () => {\n\t\t\t\treject(getAbortedReason(signal));\n\t\t\t};\n\n\t\t\tsignal.addEventListener('abort', abortHandler, {once: true});\n\t\t}\n\n\t\t// Use .then() instead of async IIFE to preserve stack traces\n\t\t// eslint-disable-next-line promise/prefer-await-to-then, promise/prefer-catch\n\t\tpromise.then(resolve, reject);\n\n\t\tif (milliseconds === Number.POSITIVE_INFINITY) {\n\t\t\treturn;\n\t\t}\n\n\t\t// We create the error outside of `setTimeout` to preserve the stack trace.\n\t\tconst timeoutError = new TimeoutError();\n\n\t\t// `.call(undefined, ...)` is needed for custom timers to avoid context issues\n\t\ttimer = customTimers.setTimeout.call(undefined, () => {\n\t\t\tif (fallback) {\n\t\t\t\ttry {\n\t\t\t\t\tresolve(fallback());\n\t\t\t\t} catch (error) {\n\t\t\t\t\treject(error);\n\t\t\t\t}\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (typeof promise.cancel === 'function') {\n\t\t\t\tpromise.cancel();\n\t\t\t}\n\n\t\t\tif (message === false) {\n\t\t\t\tresolve();\n\t\t\t} else if (message instanceof Error) {\n\t\t\t\treject(message);\n\t\t\t} else {\n\t\t\t\ttimeoutError.message = message ?? `Promise timed out after ${milliseconds} milliseconds`;\n\t\t\t\treject(timeoutError);\n\t\t\t}\n\t\t}, milliseconds);\n\t});\n\n\t// eslint-disable-next-line promise/prefer-await-to-then\n\tconst cancelablePromise = wrappedPromise.finally(() => {\n\t\tcancelablePromise.clear();\n\t\tif (abortHandler && signal) {\n\t\t\tsignal.removeEventListener('abort', abortHandler);\n\t\t}\n\t});\n\n\tcancelablePromise.clear = () => {\n\t\t// `.call(undefined, ...)` is needed for custom timers to avoid context issues\n\t\tcustomTimers.clearTimeout.call(undefined, timer);\n\t\ttimer = undefined;\n\t};\n\n\treturn cancelablePromise;\n}\n","// Port of lower_bound from https://en.cppreference.com/w/cpp/algorithm/lower_bound\n// Used to compute insertion index to keep queue sorted after insertion\nexport default function lowerBound(array, value, comparator) {\n let first = 0;\n let count = array.length;\n while (count > 0) {\n const step = Math.trunc(count / 2);\n let it = first + step;\n if (comparator(array[it], value) <= 0) {\n first = ++it;\n count -= step + 1;\n }\n else {\n count = step;\n }\n }\n return first;\n}\n","import lowerBound from './lower-bound.js';\nconst compactionThreshold = 100;\nexport default class PriorityQueue {\n #queue = [];\n // The queue is stored as a sorted array, but dequeued items are left before `#head` until compaction. Only items from `#head` onward are live, which keeps repeated dequeues amortized O(1).\n #head = 0;\n enqueue(run, options) {\n const { priority = 0, id, } = options ?? {};\n const { size } = this;\n const element = {\n priority,\n id,\n run,\n };\n if (size === 0) {\n // When the queue is logically empty, discard any consumed prefix before accepting new work.\n this.#queue.length = 0;\n this.#head = 0;\n this.#queue.push(element);\n return;\n }\n if (this.#queue.at(-1).priority >= priority) {\n // Same-priority and lower-priority items belong after the current tail. Appending preserves FIFO order for equal priorities.\n this.#queue.push(element);\n return;\n }\n // Binary insertion must run on the live sorted range only.\n this.#compact();\n const index = lowerBound(this.#queue, element, (a, b) => b.priority - a.priority);\n this.#queue.splice(index, 0, element);\n }\n setPriority(id, priority) {\n // A dequeued item with the same id is no longer part of the queue.\n const index = this.#queue.findIndex((element, index) => index >= this.#head && element.id === id);\n if (index === -1) {\n throw new ReferenceError(`No promise function with the id \"${id}\" exists in the queue.`);\n }\n const [item] = this.#queue.splice(index, 1);\n this.enqueue(item.run, { priority, id });\n }\n remove(idOrRun) {\n const index = this.#queue.findIndex((element, index) => {\n // The consumed prefix may still contain references that should not be removable.\n if (index < this.#head) {\n return false;\n }\n if (typeof idOrRun === 'string') {\n return element.id === idOrRun;\n }\n return element.run === idOrRun;\n });\n if (index !== -1) {\n this.#queue.splice(index, 1);\n }\n }\n dequeue() {\n if (this.#head === this.#queue.length) {\n return undefined;\n }\n const item = this.#queue[this.#head];\n this.#head++;\n if (this.#head === this.#queue.length) {\n // Fully drained queues are reset immediately so the next enqueue starts from a clean array.\n this.#queue.length = 0;\n this.#head = 0;\n }\n else if (this.#head > compactionThreshold && this.#head > this.#queue.length / 2) {\n // Keep repeated dequeues cheap, but stop the consumed prefix from growing without bound.\n this.#compact();\n }\n return item?.run;\n }\n filter(options) {\n const result = [];\n for (let index = this.#head; index < this.#queue.length; index++) {\n const element = this.#queue[index];\n if (element.priority === options.priority) {\n result.push(element.run);\n }\n }\n return result;\n }\n get size() {\n return this.#queue.length - this.#head;\n }\n #compact() {\n // Compaction restores the invariant that the whole array is the live sorted range.\n if (this.#head === 0) {\n return;\n }\n this.#queue.splice(0, this.#head);\n this.#head = 0;\n }\n}\n","import { EventEmitter } from 'eventemitter3';\nimport pTimeout from 'p-timeout';\nimport PriorityQueue from './priority-queue.js';\n/**\nPromise queue with concurrency control.\n*/\nexport default class PQueue extends EventEmitter {\n #carryoverIntervalCount;\n #isIntervalIgnored;\n #intervalCount = 0;\n #intervalCap;\n #rateLimitedInInterval = false;\n #rateLimitFlushScheduled = false;\n #interval;\n #intervalEnd = 0;\n #lastExecutionTime = 0;\n #intervalId;\n #timeoutId;\n #strict;\n // Circular buffer implementation for better performance\n #strictTicks = [];\n #strictTicksStartIndex = 0;\n #queue;\n #queueClass;\n #pending = 0;\n // The `!` is needed because of https://github.com/microsoft/TypeScript/issues/32194\n #concurrency;\n #isPaused;\n // Use to assign a unique identifier to a promise function, if not explicitly specified\n #idAssigner = 1n;\n // Track currently running tasks for debugging\n #runningTasks = new Map();\n #queueAbortListenerCleanupFunctions = new Set();\n /**\n Get or set the default timeout for all tasks. Can be changed at runtime.\n\n Operations will throw a `TimeoutError` if they don't complete within the specified time.\n\n The timeout begins when the operation is dequeued and starts execution, not while it's waiting in the queue.\n\n @example\n ```\n const queue = new PQueue({timeout: 5000});\n\n // Change timeout for all future tasks\n queue.timeout = 10000;\n ```\n */\n timeout;\n constructor(options) {\n super();\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n options = {\n carryoverIntervalCount: false,\n intervalCap: Number.POSITIVE_INFINITY,\n interval: 0,\n concurrency: Number.POSITIVE_INFINITY,\n autoStart: true,\n queueClass: PriorityQueue,\n strict: false,\n ...options,\n };\n if (!(typeof options.intervalCap === 'number' && options.intervalCap >= 1)) {\n throw new TypeError(`Expected \\`intervalCap\\` to be a number from 1 and up, got \\`${options.intervalCap?.toString() ?? ''}\\` (${typeof options.intervalCap})`);\n }\n if (options.interval === undefined || !(Number.isFinite(options.interval) && options.interval >= 0)) {\n throw new TypeError(`Expected \\`interval\\` to be a finite number >= 0, got \\`${options.interval?.toString() ?? ''}\\` (${typeof options.interval})`);\n }\n if (options.strict && options.interval === 0) {\n throw new TypeError('The `strict` option requires a non-zero `interval`');\n }\n if (options.strict && options.intervalCap === Number.POSITIVE_INFINITY) {\n throw new TypeError('The `strict` option requires a finite `intervalCap`');\n }\n // TODO: Remove this fallback in the next major version\n // eslint-disable-next-line @typescript-eslint/no-deprecated\n this.#carryoverIntervalCount = options.carryoverIntervalCount ?? options.carryoverConcurrencyCount ?? false;\n this.#isIntervalIgnored = options.intervalCap === Number.POSITIVE_INFINITY || options.interval === 0;\n this.#intervalCap = options.intervalCap;\n this.#interval = options.interval;\n this.#strict = options.strict;\n this.#queue = new options.queueClass();\n this.#queueClass = options.queueClass;\n this.concurrency = options.concurrency;\n if (options.timeout !== undefined && !(Number.isFinite(options.timeout) && options.timeout > 0)) {\n throw new TypeError(`Expected \\`timeout\\` to be a positive finite number, got \\`${options.timeout}\\` (${typeof options.timeout})`);\n }\n this.timeout = options.timeout;\n this.#isPaused = options.autoStart === false;\n this.#setupRateLimitTracking();\n }\n #cleanupStrictTicks(now) {\n // Remove ticks outside the current interval window using circular buffer approach\n while (this.#strictTicksStartIndex < this.#strictTicks.length) {\n const oldestTick = this.#strictTicks[this.#strictTicksStartIndex];\n if (oldestTick !== undefined && now - oldestTick >= this.#interval) {\n this.#strictTicksStartIndex++;\n }\n else {\n break;\n }\n }\n // Compact the array when it becomes inefficient or fully consumed\n // Compact when: (start index is large AND more than half wasted) OR all ticks expired\n const shouldCompact = (this.#strictTicksStartIndex > 100 && this.#strictTicksStartIndex > this.#strictTicks.length / 2)\n || this.#strictTicksStartIndex === this.#strictTicks.length;\n if (shouldCompact) {\n this.#strictTicks = this.#strictTicks.slice(this.#strictTicksStartIndex);\n this.#strictTicksStartIndex = 0;\n }\n }\n // Helper methods for interval consumption\n #consumeIntervalSlot(now) {\n if (this.#strict) {\n this.#strictTicks.push(now);\n }\n else {\n this.#intervalCount++;\n }\n }\n #rollbackIntervalSlot() {\n if (this.#strict) {\n // Pop from the end of the actual data (not from start index)\n if (this.#strictTicks.length > this.#strictTicksStartIndex) {\n this.#strictTicks.pop();\n }\n }\n else if (this.#intervalCount > 0) {\n this.#intervalCount--;\n }\n }\n #getActiveTicksCount() {\n return this.#strictTicks.length - this.#strictTicksStartIndex;\n }\n get #doesIntervalAllowAnother() {\n if (this.#isIntervalIgnored) {\n return true;\n }\n if (this.#strict) {\n // Cleanup already done by #isIntervalPausedAt before this is called\n return this.#getActiveTicksCount() < this.#intervalCap;\n }\n return this.#intervalCount < this.#intervalCap;\n }\n get #doesConcurrentAllowAnother() {\n return this.#pending < this.#concurrency;\n }\n #next() {\n this.#pending--;\n if (this.#pending === 0) {\n this.emit('pendingZero');\n }\n this.#tryToStartAnother();\n this.emit('next');\n }\n #onResumeInterval() {\n // Clear timeout ID before processing to prevent race condition\n // Must clear before #onInterval to allow new timeouts to be scheduled\n this.#timeoutId = undefined;\n this.#onInterval();\n this.#initializeIntervalIfNeeded();\n }\n #isIntervalPausedAt(now) {\n // Strict mode: check if we need to wait for oldest tick to age out\n if (this.#strict) {\n this.#cleanupStrictTicks(now);\n // If at capacity, need to wait for oldest tick to age out\n const activeTicksCount = this.#getActiveTicksCount();\n if (activeTicksCount >= this.#intervalCap) {\n const oldestTick = this.#strictTicks[this.#strictTicksStartIndex];\n // After cleanup, remaining ticks are within interval, so delay is always > 0\n const delay = this.#interval - (now - oldestTick);\n this.#createIntervalTimeout(delay);\n return true;\n }\n return false;\n }\n // Fixed window mode (original logic)\n if (this.#intervalId === undefined) {\n const delay = this.#intervalEnd - now;\n if (delay < 0) {\n // If the interval has expired while idle, check if we should enforce the interval\n // from the last task execution. This ensures proper spacing between tasks even\n // when the queue becomes empty and then new tasks are added.\n if (this.#lastExecutionTime > 0) {\n const timeSinceLastExecution = now - this.#lastExecutionTime;\n if (timeSinceLastExecution < this.#interval) {\n // Not enough time has passed since the last task execution\n this.#createIntervalTimeout(this.#interval - timeSinceLastExecution);\n return true;\n }\n }\n // Enough time has passed or no previous execution, allow execution\n this.#intervalCount = (this.#carryoverIntervalCount) ? this.#pending : 0;\n }\n else {\n // Act as the interval is pending\n this.#createIntervalTimeout(delay);\n return true;\n }\n }\n return false;\n }\n #createIntervalTimeout(delay) {\n if (this.#timeoutId !== undefined) {\n return;\n }\n this.#timeoutId = setTimeout(() => {\n this.#onResumeInterval();\n }, delay);\n }\n #clearIntervalTimer() {\n if (this.#intervalId) {\n clearInterval(this.#intervalId);\n this.#intervalId = undefined;\n }\n }\n #clearTimeoutTimer() {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId);\n this.#timeoutId = undefined;\n }\n }\n #tryToStartAnother() {\n if (this.#queue.size === 0) {\n // We can clear the interval (\"pause\")\n // Because we can redo it later (\"resume\")\n this.#clearIntervalTimer();\n this.emit('empty');\n if (this.#pending === 0) {\n // Clear timeout as well when completely idle\n this.#clearTimeoutTimer();\n // Compact strict ticks when idle to free memory\n if (this.#strict && this.#strictTicksStartIndex > 0) {\n const now = Date.now();\n this.#cleanupStrictTicks(now);\n }\n this.emit('idle');\n }\n return false;\n }\n let taskStarted = false;\n if (!this.#isPaused) {\n const now = Date.now();\n const canInitializeInterval = !this.#isIntervalPausedAt(now);\n if (this.#doesIntervalAllowAnother && this.#doesConcurrentAllowAnother) {\n const job = this.#queue.dequeue();\n if (!this.#isIntervalIgnored) {\n this.#consumeIntervalSlot(now);\n this.#scheduleRateLimitUpdate();\n }\n this.emit('active');\n job();\n if (canInitializeInterval) {\n this.#initializeIntervalIfNeeded();\n }\n taskStarted = true;\n }\n }\n return taskStarted;\n }\n #initializeIntervalIfNeeded() {\n if (this.#isIntervalIgnored || this.#intervalId !== undefined) {\n return;\n }\n // Strict mode uses timeouts instead of interval timers\n if (this.#strict) {\n return;\n }\n this.#intervalId = setInterval(() => {\n this.#onInterval();\n }, this.#interval);\n this.#intervalEnd = Date.now() + this.#interval;\n }\n #onInterval() {\n // Non-strict mode uses interval timers and intervalCount\n if (!this.#strict) {\n if (this.#intervalCount === 0 && this.#pending === 0 && this.#intervalId) {\n this.#clearIntervalTimer();\n }\n this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;\n }\n this.#processQueue();\n this.#scheduleRateLimitUpdate();\n }\n /**\n Executes all queued functions until it reaches the limit.\n */\n #processQueue() {\n // eslint-disable-next-line no-empty\n while (this.#tryToStartAnother()) { }\n }\n get concurrency() {\n return this.#concurrency;\n }\n set concurrency(newConcurrency) {\n if (!(typeof newConcurrency === 'number' && newConcurrency >= 1)) {\n throw new TypeError(`Expected \\`concurrency\\` to be a number from 1 and up, got \\`${newConcurrency}\\` (${typeof newConcurrency})`);\n }\n this.#concurrency = newConcurrency;\n this.#processQueue();\n }\n /**\n Updates the priority of a promise function by its id, affecting its execution order. Requires a defined concurrency limit to take effect.\n\n For example, this can be used to prioritize a promise function to run earlier.\n\n ```js\n import PQueue from 'p-queue';\n\n const queue = new PQueue({concurrency: 1});\n\n queue.add(async () => '🦄', {priority: 1});\n queue.add(async () => '🦀', {priority: 0, id: '🦀'});\n queue.add(async () => '🦄', {priority: 1});\n queue.add(async () => '🦄', {priority: 1});\n\n queue.setPriority('🦀', 2);\n ```\n\n In this case, the promise function with `id: '🦀'` runs second.\n\n You can also deprioritize a promise function to delay its execution:\n\n ```js\n import PQueue from 'p-queue';\n\n const queue = new PQueue({concurrency: 1});\n\n queue.add(async () => '🦄', {priority: 1});\n queue.add(async () => '🦀', {priority: 1, id: '🦀'});\n queue.add(async () => '🦄');\n queue.add(async () => '🦄', {priority: 0});\n\n queue.setPriority('🦀', -1);\n ```\n Here, the promise function with `id: '🦀'` executes last.\n */\n setPriority(id, priority) {\n if (typeof priority !== 'number' || !Number.isFinite(priority)) {\n throw new TypeError(`Expected \\`priority\\` to be a finite number, got \\`${priority}\\` (${typeof priority})`);\n }\n this.#queue.setPriority(id, priority);\n }\n async add(function_, options = {}) {\n // Create a copy to avoid mutating the original options object\n options = {\n timeout: this.timeout,\n ...options,\n // Assign unique ID if not provided\n id: options.id ?? (this.#idAssigner++).toString(),\n };\n return new Promise((resolve, reject) => {\n // Create a unique symbol for tracking this task\n const taskSymbol = Symbol(`task-${options.id}`);\n let cleanupQueueAbortHandler = () => undefined;\n const run = async () => {\n // Task is now running — remove the queued-state abort listener\n cleanupQueueAbortHandler();\n this.#pending++;\n // Track this running task\n this.#runningTasks.set(taskSymbol, {\n id: options.id,\n priority: options.priority ?? 0, // Match priority-queue default\n startTime: Date.now(),\n timeout: options.timeout,\n });\n let eventListener;\n try {\n // Check abort signal - if aborted, need to decrement the counter\n // that was incremented in tryToStartAnother\n try {\n options.signal?.throwIfAborted();\n }\n catch (error) {\n this.#rollbackIntervalConsumption();\n // Clean up tracking before throwing\n this.#runningTasks.delete(taskSymbol);\n throw error;\n }\n this.#lastExecutionTime = Date.now();\n let operation = function_({ signal: options.signal });\n if (options.timeout) {\n operation = pTimeout(Promise.resolve(operation), {\n milliseconds: options.timeout,\n message: `Task timed out after ${options.timeout}ms (queue has ${this.#pending} running, ${this.#queue.size} waiting)`,\n });\n }\n if (options.signal) {\n const { signal } = options;\n operation = Promise.race([operation, new Promise((_resolve, reject) => {\n eventListener = () => {\n reject(signal.reason);\n };\n signal.addEventListener('abort', eventListener, { once: true });\n })]);\n }\n const result = await operation;\n resolve(result);\n this.emit('completed', result);\n }\n catch (error) {\n reject(error);\n this.emit('error', error);\n }\n finally {\n // Clean up abort event listener\n if (eventListener) {\n options.signal?.removeEventListener('abort', eventListener);\n }\n // Remove from running tasks\n this.#runningTasks.delete(taskSymbol);\n // Use queueMicrotask to prevent deep recursion while maintaining timing\n queueMicrotask(() => {\n this.#next();\n });\n }\n };\n this.#queue.enqueue(run, options);\n const removeQueuedTask = () => {\n if (this.#queue instanceof PriorityQueue) {\n this.#queue.remove(run);\n return;\n }\n this.#queue.remove?.(options.id); // Intentionally best-effort: queued abort removal is only supported for queue classes that implement `.remove()`.\n };\n // Handle abort while task is waiting in the queue\n if (options.signal) {\n const { signal } = options;\n const queueAbortHandler = () => {\n cleanupQueueAbortHandler();\n removeQueuedTask();\n reject(signal.reason);\n this.#tryToStartAnother();\n this.emit('next');\n };\n cleanupQueueAbortHandler = () => {\n signal.removeEventListener('abort', queueAbortHandler);\n this.#queueAbortListenerCleanupFunctions.delete(cleanupQueueAbortHandler);\n };\n if (signal.aborted) {\n queueAbortHandler();\n return;\n }\n signal.addEventListener('abort', queueAbortHandler, { once: true });\n this.#queueAbortListenerCleanupFunctions.add(cleanupQueueAbortHandler);\n }\n this.emit('add');\n this.#tryToStartAnother();\n });\n }\n async addAll(functions, options) {\n return Promise.all(functions.map(async (function_) => this.add(function_, options)));\n }\n /**\n Start (or resume) executing enqueued tasks within concurrency limit. No need to call this if queue is not paused (via `options.autoStart = false` or by `.pause()` method.)\n */\n start() {\n if (!this.#isPaused) {\n return this;\n }\n this.#isPaused = false;\n this.#processQueue();\n return this;\n }\n /**\n Put queue execution on hold.\n */\n pause() {\n this.#isPaused = true;\n }\n /**\n Clear the queue.\n */\n clear() {\n for (const cleanupQueueAbortHandler of this.#queueAbortListenerCleanupFunctions) {\n cleanupQueueAbortHandler();\n }\n this.#queue = new this.#queueClass();\n // Clear interval timer since queue is now empty (consistent with #tryToStartAnother)\n this.#clearIntervalTimer();\n // Note: We preserve strict mode rate-limiting state (ticks and timeout)\n // because clear() only clears queued tasks, not rate limit history.\n // This ensures that rate limits are still enforced after clearing the queue.\n // Note: We don't clear #runningTasks as those tasks are still running\n // They will be removed when they complete in the finally block\n // Force synchronous update since clear() should have immediate effect\n this.#updateRateLimitState();\n // Emit events so waiters (onEmpty, onIdle, onSizeLessThan) can resolve\n this.emit('empty');\n if (this.#pending === 0) {\n this.#clearTimeoutTimer();\n this.emit('idle');\n }\n this.emit('next');\n }\n /**\n Can be called multiple times. Useful if you for example add additional items at a later time.\n\n @returns A promise that settles when the queue becomes empty.\n */\n async onEmpty() {\n // Instantly resolve if the queue is empty\n if (this.#queue.size === 0) {\n return;\n }\n await this.#onEvent('empty');\n }\n /**\n @returns A promise that settles when the queue size is less than the given limit: `queue.size < limit`.\n\n If you want to avoid having the queue grow beyond a certain size you can `await queue.onSizeLessThan()` before adding a new item.\n\n Note that this only limits the number of items waiting to start. There could still be up to `concurrency` jobs already running that this call does not include in its calculation.\n */\n async onSizeLessThan(limit) {\n // Instantly resolve if the queue is empty.\n if (this.#queue.size < limit) {\n return;\n }\n await this.#onEvent('next', () => this.#queue.size < limit);\n }\n /**\n The difference with `.onEmpty` is that `.onIdle` guarantees that all work from the queue has finished. `.onEmpty` merely signals that the queue is empty, but it could mean that some promises haven't completed yet.\n\n @returns A promise that settles when the queue becomes empty, and all promises have completed; `queue.size === 0 && queue.pending === 0`.\n */\n async onIdle() {\n // Instantly resolve if none pending and if nothing else is queued\n if (this.#pending === 0 && this.#queue.size === 0) {\n return;\n }\n await this.#onEvent('idle');\n }\n /**\n The difference with `.onIdle` is that `.onPendingZero` only waits for currently running tasks to finish, ignoring queued tasks.\n\n @returns A promise that settles when all currently running tasks have completed; `queue.pending === 0`.\n */\n async onPendingZero() {\n if (this.#pending === 0) {\n return;\n }\n await this.#onEvent('pendingZero');\n }\n /**\n @returns A promise that settles when the queue becomes rate-limited due to intervalCap.\n */\n async onRateLimit() {\n if (this.isRateLimited) {\n return;\n }\n await this.#onEvent('rateLimit');\n }\n /**\n @returns A promise that settles when the queue is no longer rate-limited.\n */\n async onRateLimitCleared() {\n if (!this.isRateLimited) {\n return;\n }\n await this.#onEvent('rateLimitCleared');\n }\n /**\n @returns A promise that rejects when any task in the queue errors.\n\n Use with `Promise.race([queue.onError(), queue.onIdle()])` to fail fast on the first error while still resolving normally when the queue goes idle.\n\n Important: The promise returned by `add()` still rejects. You must handle each `add()` promise (for example, `.catch(() => {})`) to avoid unhandled rejections.\n\n @example\n ```\n import PQueue from 'p-queue';\n\n const queue = new PQueue({concurrency: 2});\n\n queue.add(() => fetchData(1)).catch(() => {});\n queue.add(() => fetchData(2)).catch(() => {});\n queue.add(() => fetchData(3)).catch(() => {});\n\n // Stop processing on first error\n try {\n await Promise.race([\n queue.onError(),\n queue.onIdle()\n ]);\n } catch (error) {\n queue.pause(); // Stop processing remaining tasks\n console.error('Queue failed:', error);\n }\n ```\n */\n // eslint-disable-next-line @typescript-eslint/promise-function-async\n onError() {\n return new Promise((_resolve, reject) => {\n const handleError = (error) => {\n this.off('error', handleError);\n reject(error);\n };\n this.on('error', handleError);\n });\n }\n async #onEvent(event, filter) {\n return new Promise(resolve => {\n const listener = () => {\n if (filter && !filter()) {\n return;\n }\n this.off(event, listener);\n resolve();\n };\n this.on(event, listener);\n });\n }\n /**\n Size of the queue, the number of queued items waiting to run.\n */\n get size() {\n return this.#queue.size;\n }\n /**\n Size of the queue, filtered by the given options.\n\n For example, this can be used to find the number of items remaining in the queue with a specific priority level.\n */\n sizeBy(options) {\n // eslint-disable-next-line unicorn/no-array-callback-reference\n return this.#queue.filter(options).length;\n }\n /**\n Number of running items (no longer in the queue).\n */\n get pending() {\n return this.#pending;\n }\n /**\n Whether the queue is currently paused.\n */\n get isPaused() {\n return this.#isPaused;\n }\n #setupRateLimitTracking() {\n // Only schedule updates when rate limiting is enabled\n if (this.#isIntervalIgnored) {\n return;\n }\n // Wire up to lifecycle events that affect rate limit state\n // Only 'add' and 'next' can actually change rate limit state\n this.on('add', () => {\n if (this.#queue.size > 0) {\n this.#scheduleRateLimitUpdate();\n }\n });\n this.on('next', () => {\n this.#scheduleRateLimitUpdate();\n });\n }\n #scheduleRateLimitUpdate() {\n // Skip if rate limiting is not enabled or already scheduled\n if (this.#isIntervalIgnored || this.#rateLimitFlushScheduled) {\n return;\n }\n this.#rateLimitFlushScheduled = true;\n queueMicrotask(() => {\n this.#rateLimitFlushScheduled = false;\n this.#updateRateLimitState();\n });\n }\n #rollbackIntervalConsumption() {\n if (this.#isIntervalIgnored) {\n return;\n }\n this.#rollbackIntervalSlot();\n this.#scheduleRateLimitUpdate();\n }\n #updateRateLimitState() {\n const previous = this.#rateLimitedInInterval;\n // Early exit if rate limiting is disabled or queue is empty\n if (this.#isIntervalIgnored || this.#queue.size === 0) {\n if (previous) {\n this.#rateLimitedInInterval = false;\n this.emit('rateLimitCleared');\n }\n return;\n }\n // Get the current count based on mode\n let count;\n if (this.#strict) {\n const now = Date.now();\n this.#cleanupStrictTicks(now);\n count = this.#getActiveTicksCount();\n }\n else {\n count = this.#intervalCount;\n }\n const shouldBeRateLimited = count >= this.#intervalCap;\n if (shouldBeRateLimited !== previous) {\n this.#rateLimitedInInterval = shouldBeRateLimited;\n this.emit(shouldBeRateLimited ? 'rateLimit' : 'rateLimitCleared');\n }\n }\n /**\n Whether the queue is currently rate-limited due to intervalCap.\n */\n get isRateLimited() {\n return this.#rateLimitedInInterval;\n }\n /**\n Whether the queue is saturated. Returns `true` when:\n - All concurrency slots are occupied and tasks are waiting, OR\n - The queue is rate-limited and tasks are waiting\n\n Useful for detecting backpressure and potential hanging tasks.\n\n ```js\n import PQueue from 'p-queue';\n\n const queue = new PQueue({concurrency: 2});\n\n // Backpressure handling\n if (queue.isSaturated) {\n console.log('Queue is saturated, waiting for capacity...');\n await queue.onSizeLessThan(queue.concurrency);\n }\n\n // Monitoring for stuck tasks\n setInterval(() => {\n if (queue.isSaturated) {\n console.warn(`Queue saturated: ${queue.pending} running, ${queue.size} waiting`);\n }\n }, 60000);\n ```\n */\n get isSaturated() {\n return (this.#pending === this.#concurrency && this.#queue.size > 0)\n || (this.isRateLimited && this.#queue.size > 0);\n }\n /**\n The tasks currently being executed. Each task includes its `id`, `priority`, `startTime`, `timeout` (if set), and `timeoutRemaining` (milliseconds until the task times out, or `undefined` if no timeout is set).\n\n Returns an array of task info objects.\n\n ```js\n import PQueue from 'p-queue';\n\n const queue = new PQueue({concurrency: 2, timeout: 10000});\n\n // Add tasks with IDs for better debugging\n queue.add(() => fetchUser(123), {id: 'user-123'});\n queue.add(() => fetchPosts(456), {id: 'posts-456', priority: 1});\n\n // Check what's running\n console.log(queue.runningTasks);\n // => [{\n // id: 'user-123',\n // priority: 0,\n // startTime: 1759253001716,\n // timeout: 10000,\n // timeoutRemaining: 9700\n // }, {\n // id: 'posts-456',\n // priority: 1,\n // startTime: 1759253001916,\n // timeout: 10000,\n // timeoutRemaining: 9900\n // }]\n ```\n */\n get runningTasks() {\n // Return fresh array with fresh objects to prevent mutations\n return [...this.#runningTasks.values()].map(task => ({\n ...task,\n timeoutRemaining: task.timeout ? Math.max(0, task.startTime + task.timeout - Date.now()) : undefined,\n }));\n }\n}\nexport { default as PriorityQueue } from './priority-queue.js';\n/**\nError thrown when a task times out.\n\n@example\n```\nimport PQueue, {TimeoutError} from 'p-queue';\n\nconst queue = new PQueue({timeout: 1000});\n\ntry {\n await queue.add(() => someTask());\n} catch (error) {\n if (error instanceof TimeoutError) {\n console.log('Task timed out');\n }\n}\n```\n*/\nexport { TimeoutError } from 'p-timeout';\n","import PQueue from \"p-queue\";\n\n/**\n * 全局文件级并发控制器(单例)\n *\n * 支持三层配置(优先级从高到低):\n * 1. maxUploadConcurrent / maxDownloadConcurrent — 上传/下载独立限制\n * 2. maxSharedConcurrent — 全局共享限制(上传+下载平等共享)\n * 3. 无限制(都不设置或设为 0)\n *\n * 例如:\n * - 只设 maxUploadConcurrent=2:上传最多 2 个文件并行,下载无限制\n * - 只设 maxSharedConcurrent=3:上传+下载加起来最多 3 个文件并行\n * - 同时设 maxUploadConcurrent=2 + maxDownloadConcurrent=1:上传 2 个 + 下载 1 个,互不影响\n *\n * 不控制分片并发,分片并发由 ChunkManager 内部的 PQueue 独立管理。\n */\nexport default class FileConcurrencyController {\n private static instance: FileConcurrencyController;\n\n // 上传专用队列\n private uploadQueue: PQueue | null = null;\n private _maxUploadConcurrent: number = 0;\n\n // 下载专用队列\n private downloadQueue: PQueue | null = null;\n private _maxDownloadConcurrent: number = 0;\n\n // 全局共享 fallback 队列\n private sharedQueue: PQueue | null = null;\n private _maxSharedConcurrent: number = 0;\n\n static getInstance(): FileConcurrencyController {\n if (!FileConcurrencyController.instance) {\n FileConcurrencyController.instance = new FileConcurrencyController();\n }\n return FileConcurrencyController.instance;\n }\n\n // ==================== 上传专用 ====================\n\n /** 上传专用文件并发数(0 = 不限制,会回退到 sharedQueue 或完全不限制) */\n set maxUploadConcurrent(n: number) {\n if (n === this._maxUploadConcurrent) return;\n this._maxUploadConcurrent = n;\n if (n > 0) {\n this.uploadQueue = new PQueue({ concurrency: n });\n (this.uploadQueue as any).__v_skip = true;\n } else {\n this.uploadQueue = null;\n }\n }\n\n get maxUploadConcurrent(): number {\n return this._maxUploadConcurrent;\n }\n\n // ==================== 下载专用 ====================\n\n /** 下载专用文件并发数(0 = 不限制,会回退到 sharedQueue 或完全不限制) */\n set maxDownloadConcurrent(n: number) {\n if (n === this._maxDownloadConcurrent) return;\n this._maxDownloadConcurrent = n;\n if (n > 0) {\n this.downloadQueue = new PQueue({ concurrency: n });\n (this.downloadQueue as any).__v_skip = true;\n } else {\n this.downloadQueue = null;\n }\n }\n\n get maxDownloadConcurrent(): number {\n return this._maxDownloadConcurrent;\n }\n\n // ==================== 共享 fallback ====================\n\n /** 全局共享文件并发数(上传+下载平等共享,当独立限制未设置时使用) */\n set maxSharedConcurrent(n: number) {\n if (n === this._maxSharedConcurrent) return;\n this._maxSharedConcurrent = n;\n if (n > 0) {\n this.sharedQueue = new PQueue({ concurrency: n });\n (this.sharedQueue as any).__v_skip = true;\n } else {\n this.sharedQueue = null;\n }\n }\n\n get maxSharedConcurrent(): number {\n return this._maxSharedConcurrent;\n }\n\n /** @deprecated 兼容旧 API,等同于 maxSharedConcurrent */\n set maxConcurrent(n: number) {\n this.maxSharedConcurrent = n;\n }\n\n get maxConcurrent(): number {\n return this._maxSharedConcurrent;\n }\n\n // ==================== 运行任务 ====================\n\n /** 获取上传实际使用的队列 */\n private getUploadQueue(): PQueue | null {\n return this.uploadQueue || this.sharedQueue;\n }\n\n /** 获取下载实际使用的队列 */\n private getDownloadQueue(): PQueue | null {\n return this.downloadQueue || this.sharedQueue;\n }\n\n /**\n * 在\"上传\"文件并发池中运行任务\n * 优先级:uploadQueue > sharedQueue > 无限制\n */\n async runAsUpload<T>(fn: () => Promise<T>): Promise<T> {\n const queue = this.getUploadQueue();\n if (!queue) return fn();\n return queue.add(fn) as Promise<T>;\n }\n\n /**\n * 在\"下载\"文件并发池中运行任务\n * 优先级:downloadQueue > sharedQueue > 无限制\n */\n async runAsDownload<T>(fn: () => Promise<T>): Promise<T> {\n const queue = this.getDownloadQueue();\n if (!queue) return fn();\n return queue.add(fn) as Promise<T>;\n }\n}\n","import axios from \"axios\";\nimport { IFile } from \"../types\";\nimport Uploader from \".\";\nimport ChunkManager from \"../chunkManager\";\nimport UploadChunkManager from \"./UploadChunkManager\";\nimport {\n createReactiveUploadFile,\n formatFileSize,\n logger,\n computeTransferTime,\n} from \"../utils\";\nimport TransferFile from \"../transfer/TransferFile\";\nimport Transfer from \"../transfer/Transfer\";\nimport { XHRInterceptor } from \"../xhr-intercepto\";\n\n/**\n * 文件传输实例类\n * 负责单个文件的上传逻辑、状态管理、速率计算和生命周期控制\n *\n * @template T - 上传成功后的响应数据类型\n */\nexport default class UploadFile<T = any> extends TransferFile<UploadFile, T> {\n public up: Uploader<T>;\n /**\n * 🔑 文件创建时保存 action 快照,防止切换模式后重试时用到错误的 action\n */\n private _action: NonNullable<UploadFile[\"up\"][\"config\"]>[\"action\"] | undefined;\n /**\n * 分片管理器实例\n * 每个文件拥有独立的 uploadChunkManager,实现并发控制、断点续传和失败隔离\n */\n public uploadChunkManager: UploadChunkManager | null = null;\n public static interceptor: XHRInterceptor<UploadFile>;\n\n /**\n * 全局共享的 WeakMap\n * 用于存储 XHR 实例与文件上下文的映射关系,支持拦截器透明注入\n */\n private static xhrToFileMap = new WeakMap<XMLHttpRequest, UploadFile>();\n\n /**\n * 全局共享的当前上传文件队列\n * 用于在拦截器中查找匹配的 FormData\n */\n private static currentQueue: UploadFile[] = [];\n\n /**\n * 跟踪已分配到 XHR 的文件集合\n * 用于按顺序分配 XHR 实例,避免冲突\n */\n private static assignedFiles = new Set<UploadFile>();\n\n constructor(file: IFile, transfer: Transfer) {\n super(file, transfer);\n this.up = transfer as unknown as Uploader<T>;\n // 🔑 文件创建时保存 action 快照,重试/恢复时始终使用创建时的 action\n this._action = this.up.config?.action;\n this.proxy = createReactiveUploadFile(this, transfer);\n // 如果是分片上传(检查 chunkOptions 配置),在构造时就创建 uploadChunkManager\n if (this.up.config?.chunkOptions) {\n this.uploadChunkManager = new UploadChunkManager(\n this.up.config.chunkOptions,\n this,\n );\n this.chunkManager = this.uploadChunkManager;\n // ✅ 如果回显数据中包含分片信息,初始化 uploadChunkManager 状态\n if (file.totalChunks !== undefined) {\n this.initChunkManagerFromRestore(file);\n }\n }\n\n // 关键:在发起请求前设置拦截器,建立当前文件与 XHR 的映射\n this.initInterceptor();\n\n // 添加到队列\n UploadFile.currentQueue.push(this);\n \n UploadFile.interceptor.install();\n return this.proxy;\n }\n\n /**\n * 从回显数据初始化 uploadChunkManager 状态\n * 用于后端接口返回的文件列表回显场景\n *\n * @param file - 包含分片上传信息的文件对象\n */\n private async initChunkManagerFromRestore(file: IFile): Promise<void> {\n if (!this.uploadChunkManager) {\n logger.warn(\n \"UploadFile\",\n \"initChunkManagerFromRestore: uploadChunkManager 不存在\",\n );\n return;\n }\n\n logger.info(\n \"UploadFile\",\n `从回显数据初始化分片上传状态: ${this.fileName}`,\n {\n fileId: this.fileId,\n totalChunks: file.totalChunks,\n completedChunks: file.completedChunks,\n chunkIndexes: file.chunkIndexes?.length || 0,\n fileHash: file.fileHash,\n uploadId: file.uploadId,\n },\n );\n\n // 1. 设置总分片数\n if (file.totalChunks !== undefined) {\n this.uploadChunkManager.totalChunks = file.totalChunks;\n }\n\n // 2. 设置已完成分片数\n if (file.completedChunks !== undefined) {\n this.uploadChunkManager.completedChunks = file.completedChunks;\n }\n\n // 3. 初始化已上传分片数组\n if (file.totalChunks !== undefined) {\n this.uploadChunkManager.chunks = new Array(file.totalChunks).fill(false);\n }\n\n // 4. 标记已上传的分片\n if (file.chunkIndexes && file.chunkIndexes.length > 0) {\n file.chunkIndexes.forEach((index) => {\n if (\n this.uploadChunkManager &&\n index >= 0 &&\n index < this.uploadChunkManager.totalChunks\n ) {\n this.uploadChunkManager.chunks[index] = true;\n }\n });\n } else if (file.completedChunks !== undefined && this.uploadChunkManager) {\n // 如果没有具体的索引数组,但有完成数量,假设前 N 个分片已完成\n for (\n let i = 0;\n i < file.completedChunks && i < this.uploadChunkManager.totalChunks;\n i++\n ) {\n this.uploadChunkManager.chunks[i] = true;\n }\n }\n\n // 5. 设置文件哈希和上传ID(用于断点续传)\n if (file.fileHash) {\n this.uploadChunkManager.fileHash = file.fileHash;\n }\n\n // 6. 计算并设置进度百分比\n if (this.uploadChunkManager.totalChunks > 0) {\n const percent = Math.round(\n (this.uploadChunkManager.completedChunks /\n this.uploadChunkManager.totalChunks) *\n 100,\n );\n this.percent = percent;\n this.proxy.percent = percent;\n }\n\n // 7. 如果所有分片都已完成,设置状态为 success\n if (\n this.uploadChunkManager.completedChunks ===\n this.uploadChunkManager.totalChunks &&\n this.uploadChunkManager.totalChunks > 0\n ) {\n this.status = \"success\";\n this.proxy.status = \"success\";\n this.percent = 100;\n this.proxy.percent = 100;\n }\n\n // ✅ 8. 尝试从 IndexedDB 恢复 File 对象(如果启用了文件缓存)\n const up = this.transfer;\n if (\n file.fileHash &&\n this.up.config?.chunkOptions?.enableFileCache &&\n (!this.File || this.File.size === 0)\n ) {\n logger.info(\"UploadFile\", `尝试从缓存恢复文件: ${this.fileName}`, {\n fileHash: file.fileHash,\n });\n\n try {\n const cachedFile = await import(\"../utils/fileCache\").then((m) =>\n m.restoreFileFromCache(file.fileHash!),\n );\n\n if (cachedFile) {\n // 验证缓存文件的完整性\n if (\n cachedFile.name === this.fileName &&\n cachedFile.size === this.File.size\n ) {\n this.File = cachedFile;\n logger.info(\n \"UploadFile\",\n `✅ 成功从缓存恢复文件: ${this.fileName}`,\n { fileSize: cachedFile.size },\n );\n } else {\n logger.warn(\n \"UploadFile\",\n `⚠️ 缓存文件不匹配: 期望 ${this.fileName} (${this.File.size}), 实际 ${cachedFile.name} (${cachedFile.size})`,\n );\n }\n } else {\n logger.debug(\n \"UploadFile\",\n `未找到缓存文件,用户需要重新选择: ${this.fileName}`,\n );\n }\n } catch (error) {\n logger.error(\n \"UploadFile\",\n `从缓存恢复文件失败: ${this.fileName}`,\n error,\n );\n }\n }\n\n logger.info(\"UploadFile\", `分片上传状态初始化完成: ${this.fileName}`, {\n totalChunks: this.uploadChunkManager.totalChunks,\n completedChunks: this.uploadChunkManager.completedChunks,\n percent: this.percent,\n status: this.status,\n });\n\n // ✅ 关键修复:更新全局统计信息(总进度、总大小)\n up.updateGlobalStats();\n up.triggerUpdate();\n }\n\n /**\n * 执行普通上传的重试(模板方法覆写)\n */\n protected async doRetryTransfer(): Promise<T> {\n return this.upload();\n }\n\n remove() {\n // 取消上传\n if (this.uploadChunkManager) {\n this.uploadChunkManager.cancelUpload();\n } else {\n this.abort?.();\n }\n\n const up = this.transfer;\n\n // 从文件列表中移除\n up.files = up.files.filter((f) => f.fileId !== this.fileId);\n\n // ✅ 重新计算全局统计信息(包括总进度、总大小等)\n up.updateGlobalStats();\n\n this.up.remObjectUrls(this.url);\n // ✅ 删除重复的手动更新,updateGlobalStats 已经处理了\n up.triggerUpdate();\n up.emit(\"remove\", this.proxy);\n }\n\n setFile(file: File | Blob, formData: FormData, index?: number) {\n if (typeof this.up.config?.file === \"function\") {\n this.up.config?.file({\n data: file,\n formData,\n uploadFile: this,\n chunkIndex: index,\n });\n } else {\n formData.append(this.up.config?.file!, file);\n }\n }\n\n /**\n * 上传文件(支持普通上传和分片上传)\n *\n * @param onChunkComplete - 分片上传完成时的回调函数\n * @param signal - AbortSignal,用于取消请求(超时控制)\n * @param chunkFormData - 分片上传的 FormData(可选,优先使用此参数而不是 this.formData)\n * @returns Promise<T>\n */\n async upload(\n onChunkComplete?: (res: T) => void,\n signal?: AbortSignal,\n chunkFormData?: FormData, // 新增参数:接收分片的 FormData\n ): Promise<T> {\n this.proxy.loading = true;\n return new Promise(async (resolve, reject) => {\n const up = this.transfer;\n up.loading = true;\n // 重置已上传字节数(避免重新上传时使用旧值)\n this.__transferBytes__ = 0;\n\n // 🔑 重试场景:取消/失败后拦截器的 assignedFiles 残留旧记录,\n // 新 XHR 匹配时会被跳过 → 回退到队列第一个文件,导致进度事件绑错文件\n UploadFile.interceptor?.cleanupFile?.(this as any);\n if (!UploadFile.currentQueue.includes(this as any)) {\n UploadFile.currentQueue.push(this as any);\n }\n\n if (!this._action) {\n console.warn(\"请设置上传地址\");\n return;\n }\n\n // 记录上传开始(用于监控模块追踪)\n // 注意:对于分片上传,这个日志会在 uploadChunkManager.initUpload() 中输出\n // 这里只输出普通上传的开始日志,避免分片上传时每个分片都输出一次\n if (!this.uploadChunkManager) {\n logger.info(\"UploadFile\", `开始上传文件: ${this.fileName}`, {\n fileId: this.fileId,\n fileName: this.fileName,\n fileSize: this.File.size,\n isChunkUpload: false,\n });\n }\n\n // 记录上传开始时间\n if (!this.uploadChunkManager) {\n computeTransferTime(this.proxy.transferTime).start();\n }\n\n // 获取插件上下文\n this.context = (this as any).__pluginContext || {\n transfer: up,\n shared: this.up[\"pluginSharedData\"],\n config: this.up.config,\n };\n if (!up.transferTime.startTime) {\n computeTransferTime(up.transferTime).start();\n }\n if (!this.uploadChunkManager) {\n this.formData = new FormData();\n this.setFile(this.File, this.formData);\n }\n\n if (this.up.beforeTransferCallback) {\n try {\n const result = await this.up.beforeTransferCallback(this);\n if (!result) {\n return;\n }\n } catch (error) {\n return;\n }\n }\n\n this.proxy.status !== \"cancelled\" && (this.proxy.status = \"UDLoading\");\n\n // 只在首次上传时累加 totalBytes,避免重试时重复累加\n // 对于分片上传,应该在 uploadChunkManager.initUpload() 中初始化 totalBytes\n // 对于普通上传,在这里初始化\n if (!this.uploadChunkManager && this.up.config?.autoUpload) {\n // 普通上传:只在首次上传时累加\n if (up.totalBytes === 0 || !this.__hasCountedTotalBytes__) {\n up.totalTransferredBytes += this.File.size;\n up.totalBytes += this.File.size;\n up.totalFormatSize = formatFileSize(up.totalBytes);\n this.__hasCountedTotalBytes__ = true; // 标记已计数\n }\n }\n\n let promise: Promise<T>;\n // 优先使用传入的 chunkFormData,避免使用共享的 this.formData\n // 对于分片上传,每个分片都有独立的 FormData,通过参数传递\n // 对于普通上传,使用 this.formData\n const requestData = chunkFormData || this.formData || new FormData();\n\n if (typeof this._action === \"string\") {\n // 直接使用文件创建时的 action URL\n const axiosPromise = (this.up.config?.axiosInstance || axios).post<T>(\n this._action,\n requestData,\n { signal },\n );\n promise = axiosPromise.then((response) => response.data);\n } else {\n // 🔑 函数 action:通过 XHR 拦截器机制处理取消,不暴露 signal 给用户\n if (signal) {\n signal.addEventListener(\n \"abort\",\n () => this.abort?.(),\n { once: true },\n );\n }\n const actionResult = this._action!(requestData, this);\n // 支持函数返回字符串 URL:核心代码自动发起 axios 请求\n promise = Promise.resolve(actionResult).then((result) => {\n if (typeof result === \"string\") {\n const axiosPromise = (this.up.config?.axiosInstance || axios).post<T>(\n result,\n requestData,\n { signal },\n );\n return axiosPromise.then((response) => response.data);\n }\n return result;\n });\n }\n\n promise\n .then((res) => {\n if (this.uploadChunkManager) {\n // 分片上传:只调用分片完成回调,不调用文件成功回调\n // 文件成功回调会在 uploadChunkManager.checkStatistics 中所有分片完成后调用\n onChunkComplete?.(res);\n } else {\n // 普通上传:直接设置为成功并调用成功回调\n this.onSuccess(res);\n this.proxy.status = \"success\";\n this.proxy.percent = 100;\n }\n resolve(res);\n })\n .catch((err) => {\n if (!this.uploadChunkManager) {\n this.isCancel !== true && (this.proxy.status = \"error\");\n }\n this.onError(err);\n reject(err);\n })\n .finally(() => {\n // 记录上传结束时间和耗时\n if (!this.uploadChunkManager) {\n computeTransferTime(this.proxy.transferTime).end();\n }\n\n this.proxy.loading = false;\n up.loading = false;\n });\n });\n }\n\n /**\n * 返回上传的分片管理器\n */\n protected getChunkManager(): ChunkManager | null {\n return this.uploadChunkManager;\n }\n\n /**\n * 获取文件大小(上传场景:直接用 File.size)\n */\n public getFileSize(): number {\n return this.File?.size || 0;\n }\n\n /**\n * 更新当前文件的上传进度和已传输字节数(模板方法)\n *\n * 根据上传类型采用不同的进度计算策略:\n * - 普通上传:从 event.loaded/event.total 直接计算百分比和字节数\n * - 分片上传:由 uploadChunkManager 独立维护进度,此处无需额外操作\n */\n protected updateLocalProgress(event: ProgressEvent): void {\n if (!this.uploadChunkManager) {\n if (this.File.size > 0) {\n this.proxy.percent = Math.floor((event.loaded * 100) / event.total);\n\n this.__transferBytes__ = Math.floor(\n this.File.size * (this.proxy.percent / 100),\n );\n } else {\n this.__transferBytes__ = 0;\n }\n }\n }\n\n\n}\n","import { AxiosProgressEvent } from \"axios\";\nimport { ChunkOptions, onInitChunkCallback } from \"../types\";\nimport UploadFile from \"./UploadFile\";\nimport PQueue from \"p-queue\";\nimport {\n calculateFileMD5,\n formatSpeed,\n formatFileSize,\n formatDuration,\n logger,\n computeTransferTime,\n} from \"../utils\";\nimport { getCachedHash, saveFileHash } from \"../utils/transferDB\";\nimport ChunkManager from \"../chunkManager\";\n\nexport default class UploadChunkManager extends ChunkManager {\n uploadFile: UploadFile;\n\n uploadErrorFileCallBack: (() => void)[] = [];\n /**\n * 初始化上传任务\n */\n constructor(ChunkOptions: ChunkOptions, file: UploadFile) {\n super(ChunkOptions, file);\n\n this.uploadFile = file;\n\n logger.debug(\"uploadChunkManager\", \"创建新的 uploadChunkManager 实例\", {\n fileId: file.fileId,\n fileName: file.fileName,\n totalChunks: this.totalChunks,\n completedChunks: this.completedChunks,\n });\n }\n\n // ==================== 抽象方法实现(适配 ChunkManager 基类) ====================\n\n protected getTag(): string {\n return \"uploadChunkManager\";\n }\n\n protected async computeFileIdentifier(): Promise<string> {\n return this.getFileHash(this.uploadFile.File);\n }\n\n protected async doInit(): Promise<any> {\n return this.initUpload();\n }\n\n protected async doChunkTransfer(\n chunkIndex: number,\n signal?: AbortSignal,\n ): Promise<{ data: any; chunkSize: number }> {\n await this.uploadChunk(chunkIndex, signal);\n const file = this.uploadFile.File;\n const start = chunkIndex * this.chunkSize;\n const end = Math.min(start + this.chunkSize, file.size) - 1;\n return { data: this.response, chunkSize: end - start + 1 };\n }\n\n protected async doMergeChunks(): Promise<any> {\n return this.mergeChunks();\n }\n\n /**\n * 计算文件MD5哈希值\n * 优化: 使用与分片上传相同的 chunkSize,减少重复I/O\n */\n private async computeFileHash(): Promise<string> {\n if (!this.fileHash) {\n // ✅ 创建 AbortController,用于在文件被删除时中断 MD5 计算\n const hashAbortController = new AbortController();\n\n // ✅ 将 controller 保存到实例中,以便 cancelUpload 可以调用 abort()\n (this as any).__hashAbortController__ = hashAbortController;\n\n try {\n const up = this.uploadFile.up;\n\n // 获取插件上下文\n const context = (this.uploadFile as any).__pluginContext || {\n transfer: up,\n file: this.uploadFile,\n shared: up[\"pluginSharedData\"],\n config: up.config,\n };\n this.uploadFile.proxy.hashLoading = true;\n\n // 调用工具函数计算 MD5,并传入进度回调和中断信号\n this.fileHash = await calculateFileMD5(\n this.uploadFile.File,\n async (percent: number) => {\n // ✅ 关键检查:如果文件已被取消,立即中止计算\n if (this.isCancelled) {\n hashAbortController.abort();\n throw new Error(\"文件已取消,停止计算指纹\");\n }\n\n // 更新 context 状态为 hashing\n context.status = \"hashing\";\n this.uploadFile.proxy.status = \"hashing\";\n context.message = `正在计算文件指纹: ${percent}%`;\n logger.debug(\n \"uploadChunkManager\",\n `computeFileHash 被调用: ${context.message}`,\n );\n // 通过 runHook 调用插件的 onProgress 钩子\n await up[\"runHook\"](\n \"onProgress\",\n percent,\n this.uploadFile,\n context,\n );\n\n this.uploadFile.proxy.hashPercent = percent;\n },\n hashAbortController.signal, // ✅ 传递中断信号\n );\n } catch (error) {\n // ✅ 如果是取消导致的错误,不记录为错误日志,直接重新抛出\n if (error instanceof Error && error.message.includes(\"取消\")) {\n logger.info(\"uploadChunkManager\", \"MD5 计算已取消\", {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n });\n throw error; // ✅ 重新抛出,让上层知道被取消了\n }\n\n logger.error(\n \"uploadChunkManager\",\n \"计算文件哈希失败,使用备用标识\",\n error,\n );\n // 如果计算失败,使用时间戳作为备用标识\n this.fileHash = `fallback_${Date.now()}_${Math.random()\n .toString(36)\n .substring(2)}`;\n } finally {\n this.uploadFile.proxy.hashLoading = false;\n // ✅ 清理 AbortController\n delete (this as any).__hashAbortController__;\n }\n }\n return this.fileHash;\n }\n\n private async getFileHash(file: File): Promise<string> {\n // 1. 生成快速指纹\n const quickFingerprint = `${file.name}_${file.lastModified}_${file.size}`;\n\n // 添加调试日志:记录每次调用 getFileHash 的文件名\n logger.debug(\"uploadChunkManager\", `getFileHash 被调用: ${file.name}`, {\n fileName: file.name,\n fileSize: file.size,\n lastModified: file.lastModified,\n fingerprint: quickFingerprint,\n fileId: this.uploadFile.fileId,\n });\n\n // 2. 从 IndexedDB 中查找缓存\n const cached = await getCachedHash(quickFingerprint);\n if (cached) {\n logger.info(\"uploadChunkManager\", `Hash 缓存命中:: ${file.name}`, {\n fileName: file.name,\n fileId: this.uploadFile.fileId,\n });\n this.uploadFile.hashPercent = 100;\n return cached;\n }\n\n // 3. 未命中,实际计算 Hash\n logger.info(\n \"uploadChunkManager\",\n `Hash 缓存未命中,开始计算: ${file.name}`,\n );\n const hash = await this.computeFileHash(); // 计算文件哈希\n\n // 4. 存入缓存\n await saveFileHash(file, hash);\n\n // ✅ 5. 如果启用了文件缓存,将 File 对象保存到 IndexedDB\n const up = this.uploadFile.up;\n if (up.config?.chunkOptions?.enableFileCache) {\n try {\n const { saveFileToCache } = await import(\"../utils/fileCache\");\n await saveFileToCache(hash, file);\n logger.debug(\n \"uploadChunkManager\",\n `✅ 文件已缓存到 IndexedDB: ${file.name}`,\n { fileHash: hash, fileSize: file.size },\n );\n } catch (error) {\n logger.warn(\n \"uploadChunkManager\",\n `⚠️ 文件缓存失败(不影响上传): ${file.name}`,\n error,\n );\n }\n }\n\n return hash;\n }\n /**\n * 初始化上传\n */\n private async initUpload(): Promise<\n ReturnType<onInitChunkCallback<any>> | undefined\n > {\n const up = this.uploadFile.transfer;\n this.fileHash = await this.getFileHash(this.uploadFile.File);\n // 添加文件级别的开始上传日志(用于监控模块)\n logger.info(\n \"uploadChunkManager\",\n `开始上传文件: ${this.uploadFile.fileName}`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n fileSize: this.uploadFile.File.size,\n totalChunks: this.totalChunks,\n chunkSize: this.chunkSize,\n },\n );\n\n // 秒传检查:如果配置了 onInitChunkCallback,优先调用服务端接口检查文件是否存在\n if (up.onInitChunkCallback) {\n try {\n logger.debug(\n \"uploadChunkManager\",\n \"调用 onInitChunk 回调检查秒传/断点续传\",\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n fileHash: this.fileHash,\n },\n );\n\n const initResult = await up.onInitChunkCallback(\n this.uploadFile,\n this.totalChunks,\n this.fileHash,\n );\n\n if (initResult?.fileHash) {\n logger.info(\"uploadChunkManager\", \"初始化成功\", {\n fileHash: this.fileHash,\n fileId: this.uploadFile.fileId,\n });\n\n // ✅ 检查是否为真正的秒传(后端明确标记)\n if (initResult.isInstantUpload === true) {\n logger.info(\"uploadChunkManager\", \"⚡ 检测到秒传标记,文件已存在\", {\n fileHash: this.fileHash,\n fileName: this.uploadFile.fileName,\n shouldRemove: initResult.shouldRemove,\n });\n\n // 标记为真正的秒传\n this.isInstantTransfer = true;\n this.completedChunks = this.totalChunks;\n this.totalChunkSize = this.uploadFile.File.size;\n this.countedChunks = new Set(\n Array.from({ length: this.totalChunks }, (_, i) => i),\n );\n this.chunks = new Array(this.totalChunks).fill(true);\n this.uploadFile.proxy.percent = 100;\n this.uploadFile.proxy.status = \"success\";\n\n // 秒传文件设置速度(瞬时完成,无实际传输耗时)\n this.uploadFile.proxy.speed = {\n currentSpeed: 0,\n averageSpeed: 0,\n currentSpeedFormatted: \"0 B/s\",\n averageSpeedFormatted: \"0 B/s\",\n estimatedTimeRemaining: 0,\n estimatedTimeFormatted: \"秒传完成\",\n };\n\n // ✅ 关键修复:更新全局统计信息(总进度、总大小)\n const up = this.uploadFile.transfer;\n up.updateGlobalStats();\n\n // 更新上传速度\n up.triggerUpdate();\n\n // ✅ 如果需要移除文件,则从文件列表中移除\n if (initResult.shouldRemove === true) {\n logger.info(\"uploadChunkManager\", \"🗑️ 秒传成功,自动移除文件\", {\n fileName: this.uploadFile.fileName,\n });\n\n // 触发秒传成功事件\n up.emit(\"instant-upload\", {\n file: this.uploadFile.proxy,\n reason: \"文件已存在,自动移除\",\n });\n\n // 移除文件(调用 UploadFile 实例的 remove 方法)\n this.uploadFile.remove();\n }\n\n return Promise.resolve(initResult);\n }\n\n // 秒传判断:如果服务端返回的已上传分片数量等于总分片数,说明文件已存在\n if (\n initResult.chunks &&\n Array.isArray(initResult.chunks) &&\n (initResult.chunks?.length === this.totalChunks ||\n initResult.chunks === undefined ||\n initResult.chunks === null)\n ) {\n logger.info(\n \"uploadChunkManager\",\n \"✅ 秒传成功!文件已存在于服务端\",\n {\n fileHash: this.fileHash,\n fileName: this.uploadFile.fileName,\n uploadedChunksCount: initResult.chunks?.length,\n totalChunks: this.totalChunks,\n shouldRemove: initResult.shouldRemove,\n },\n );\n\n this.completedChunks = this.totalChunks;\n this.totalChunkSize = this.uploadFile.File.size;\n this.countedChunks = new Set(\n Array.from({ length: this.totalChunks }, (_, i) => i),\n ); // 标记所有分片为已累加\n this.chunks = new Array(this.totalChunks).fill(true);\n\n // 🔑 全部分片已上传但未合并,进度卡在 99%,留给 completeMerge() 设 100%\n this.uploadFile.proxy.percent = 99;\n\n // ✅ 关键修复:更新全局统计信息(总进度、总大小)\n const up = this.uploadFile.transfer;\n up.updateGlobalStats();\n\n // 更新上传速度\n up.triggerUpdate();\n\n // ✅ 如果需要移除文件,则从文件列表中移除\n if (initResult.shouldRemove === true) {\n logger.info(\"uploadChunkManager\", \"🗑️ 秒传成功,自动移除文件\", {\n fileName: this.uploadFile.fileName,\n });\n\n // 触发秒传成功事件\n up.emit(\"instant-upload\", {\n file: this.uploadFile.proxy,\n reason: \"文件已存在,自动移除\",\n });\n\n // 移除文件(调用 UploadFile 实例的 remove 方法)\n this.uploadFile.remove();\n }\n\n return Promise.resolve(initResult);\n }\n\n // 断点续传:如果服务端返回部分已上传分片\n if (\n initResult.chunks &&\n Array.isArray(initResult.chunks) &&\n initResult.chunks?.length > 0\n ) {\n logger.info(\"uploadChunkManager\", \"恢复已上传分片(断点续传)\", {\n uploadedChunksCount: initResult.chunks.length,\n totalChunks: this.totalChunks,\n percent: Math.round(\n (initResult.chunks.length / this.totalChunks) * 100,\n ),\n });\n this.completedChunks = 0;\n initResult.chunks.forEach((index: number) => {\n if (index >= 0 && index < this.totalChunks) {\n this.chunks[index] = true;\n this.completedChunks++;\n this.countedChunks.add(index);\n this.totalChunkSize += this.chunkSize;\n }\n });\n\n // 更新文件进度\n this.updateProgress();\n\n // ✅ 关键修复:更新全局统计信息(总进度、总大小)\n const up = this.uploadFile.transfer;\n up.updateGlobalStats();\n up.triggerUpdate();\n\n return; // 已恢复进度,直接返回\n }\n }\n } catch (error) {\n logger.warn(\"uploadChunkManager\", \"初始化失败\", error);\n this.uploadFile.proxy.status = \"error\";\n // 降级到本地 IndexedDB 存储\n return Promise.reject(error);\n }\n }\n }\n\n /**\n * 带重试机制的分片上传\n */\n private async uploadChunkWithRetry(chunkIndex: number): Promise<void> {\n const maxRetries = this.retries;\n let retryCount = this.retryCountMap.get(chunkIndex) || 0;\n\n // 提取公共的上传执行逻辑(避免重复代码)\n const executeUpload = async (): Promise<void> => {\n // 使用 AbortController 实现真正的超时控制\n const abortController = new AbortController();\n\n // ✅ 保存 AbortController 引用,以便取消时可以中止\n this.abortControllers.push(abortController);\n\n const timeoutId = this.timeout > 0\n ? setTimeout(() => {\n abortController.abort();\n }, this.timeout)\n : (null as any);\n\n try {\n // 传递 signal 给 UploadFile.upload,实现真正的超时取消\n await this.uploadChunk(chunkIndex, abortController.signal);\n\n // 上传成功,清除超时定时器\n if (timeoutId !== null) clearTimeout(timeoutId);\n\n // ✅ 从数组中移除已完成的 AbortController\n const index = this.abortControllers.indexOf(abortController);\n if (index > -1) {\n this.abortControllers.splice(index, 1);\n }\n } catch (error) {\n // 确保清除定时器\n if (timeoutId !== null) clearTimeout(timeoutId);\n\n // ✅ 从数组中移除已完成的 AbortController\n const index = this.abortControllers.indexOf(abortController);\n if (index > -1) {\n this.abortControllers.splice(index, 1);\n }\n\n // 添加详细的错误日志\n if (error instanceof Error && error.name === \"AbortError\") {\n logger.error(\n \"uploadChunkManager\",\n `分片 ${chunkIndex + 1} 被取消(可能是超时或手动取消)`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n chunkIndex,\n timeout: this.timeout,\n error: error.message,\n },\n );\n }\n\n throw error;\n }\n };\n\n // 如果 retries 为 null,禁用自动重试,只尝试一次\n if (maxRetries === null) {\n try {\n await executeUpload();\n // 上传成功,重置重试计数\n this.retryCountMap.set(chunkIndex, 0);\n return;\n } catch (error: any) {\n // 自动重试被禁用,直接标记为失败\n this.failedChunks.push(chunkIndex);\n\n logger.error(\n \"uploadChunkManager\",\n `文件 ${this.uploadFile.fileName} 的分片 ${chunkIndex + 1}/${\n this.totalChunks\n } 上传失败(自动重试已禁用,请手动重试)`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n chunkIndex,\n error: error instanceof Error ? error.message : String(error),\n },\n );\n this.setFileStatusToFail();\n throw error;\n }\n }\n\n // 正常重试逻辑\n while (retryCount <= maxRetries) {\n try {\n if (retryCount > 0) {\n logger.warn(\n \"uploadChunkManager\",\n `文件 ${this.uploadFile.fileName} 的分片 ${chunkIndex + 1}/${\n this.totalChunks\n } 第 ${retryCount} 次重试`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n chunkIndex,\n retryCount,\n },\n );\n }\n\n await executeUpload();\n\n // 上传成功,重置重试计数\n this.retryCountMap.set(chunkIndex, 0);\n\n if (retryCount > 0) {\n logger.info(\n \"uploadChunkManager\",\n `文件 ${this.uploadFile.fileName} 的分片 ${chunkIndex + 1}/${\n this.totalChunks\n } 重试成功(共重试 ${retryCount} 次)`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n chunkIndex,\n retryCount,\n },\n );\n }\n\n return;\n } catch (error: any) {\n retryCount++;\n this.retryCountMap.set(chunkIndex, retryCount);\n\n if (retryCount > maxRetries) {\n this.failedChunks.push(chunkIndex);\n\n logger.error(\n \"uploadChunkManager\",\n `文件 ${this.uploadFile.fileName} 的分片 ${chunkIndex + 1}/${\n this.totalChunks\n } 最终失败(已重试 ${maxRetries} 次)`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n chunkIndex,\n retryCount: maxRetries,\n error: error instanceof Error ? error.message : String(error),\n },\n );\n\n return;\n }\n\n // 指数退避重试延迟\n const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000);\n logger.debug(\n \"uploadChunkManager\",\n `分片 ${chunkIndex} 将在 ${delay}ms 后重试`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n /**\n * 设置文件状态为失败(统一处理,避免重复代码)\n */\n protected setFileStatusToFail(): void {\n if (this.retries === null) {\n setTimeout(() => {\n this.uploadFile.proxy.status = \"fail\";\n }, 0);\n }\n }\n\n /**\n * 上传单个分片\n */\n private async uploadChunk(\n chunkIndex: number,\n signal?: AbortSignal,\n ): Promise<void> {\n const start = chunkIndex * this.chunkSize;\n const end = Math.min(start + this.chunkSize, this.uploadFile.File.size);\n const chunk = this.uploadFile.File.slice(start, end);\n this.chunk = chunk; // 暂存当前分片,供外部访问\n const chunkSizeValue = chunk.size;\n\n logger.debug(\n \"uploadChunkManager\",\n `开始上传分片 ${chunkIndex + 1}/${this.totalChunks}`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n chunkIndex,\n size: chunkSizeValue,\n start,\n end,\n },\n );\n\n // 创建独立的 FormData,作为参数传递给 upload 方法\n // 不要保存到实例级别,避免多文件并发时被覆盖\n const chunkFormData = new FormData();\n this.uploadFile.setFile(chunk, chunkFormData, chunkIndex);\n\n try {\n // 传递 signal 实现真正的超时控制\n // 传递 chunkFormData 作为请求参数,避免使用共享的 this.uploadFile.formData\n await this.uploadFile.upload(\n (res) => {\n // 使用 countedChunks 来防止重复累加\n this.response = res; // 暂存最新的响应数据,供外部访问\n\n // 关键只有当该分片未被标记为已上传时,才进行累加\n if (!this.chunks[chunkIndex]) {\n this.chunks[chunkIndex] = true;\n\n // 先累加字节数\n this.totalChunkSize += chunkSizeValue;\n\n // 再累加完成分片计数\n this.completedChunks++;\n\n // 标记为已计数,防止重试时重复累加\n this.countedChunks.add(chunkIndex);\n\n // 更新进度\n this.updateProgress();\n\n // 触发分片上传成功事件\n const up = this.uploadFile.transfer;\n up.emit(\"chunk-success\", {\n chunkIndex,\n totalChunks: this.totalChunks,\n completedChunks: this.completedChunks,\n percent: this.uploadFile.proxy.percent || 0,\n file: this.uploadFile.proxy,\n });\n\n logger.info(\n \"uploadChunkManager\",\n `分片 ${chunkIndex + 1}/${this.totalChunks} 上传成功`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n chunkIndex,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n percent: this.uploadFile.proxy.percent,\n },\n );\n } else {\n logger.debug(\n \"uploadChunkManager\",\n `分片 ${chunkIndex + 1}/${this.totalChunks} 已上传,跳过累加`,\n {\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n },\n );\n }\n\n // 从失败列表中移除\n const failIndex = this.failedChunks.indexOf(chunkIndex);\n if (failIndex > -1) {\n this.failedChunks.splice(failIndex, 1);\n }\n },\n signal,\n chunkFormData,\n ); // 传递 chunkFormData 作为参数\n\n return Promise.resolve();\n } catch (error) {\n // 分片失败时,不要回退 completedChunks!\n // 只标记 chunks 为 false,等待重试成功后再累加\n // 这样进度只会增加不会减少,避免进度回退导致用户体验差\n this.chunks[chunkIndex] = false;\n\n logger.error(\n \"uploadChunkManager\",\n `分片 ${chunkIndex + 1}/${this.totalChunks} 上传失败`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n chunkIndex,\n error: error instanceof Error ? error.message : String(error),\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n progress: this.uploadFile.proxy.percent,\n },\n );\n\n // 触发分片上传失败事件\n const up = this.uploadFile.transfer;\n up.emit(\"chunk-error\", {\n chunkIndex,\n totalChunks: this.totalChunks,\n error: error instanceof Error ? error.message : String(error),\n file: this.uploadFile.proxy,\n });\n\n throw error;\n }\n }\n\n /**\n * 合并所有分片\n */\n private async mergeChunks(): Promise<any> {\n const up = this.uploadFile.up;\n\n try {\n // 调用用户提供的合并回调\n const onMerge = up.OnMergeChunkCallBack;\n const response = onMerge ? await onMerge(this) : undefined;\n\n // ✅ 上传成功后清理文件缓存(如果启用了缓存)\n if (up.config?.chunkOptions?.enableFileCache && this.fileHash) {\n try {\n const { removeFileFromCache } = await import(\"../utils/fileCache\");\n await removeFileFromCache(this.fileHash);\n logger.debug(\n \"uploadChunkManager\",\n `✅ 上传成功,已清理文件缓存: ${this.uploadFile.fileName}`,\n { fileHash: this.fileHash },\n );\n } catch (error) {\n logger.warn(\n \"uploadChunkManager\",\n `⚠️ 清理文件缓存失败(不影响上传): ${this.uploadFile.fileName}`,\n error,\n );\n }\n }\n\n // 触发合并成功事件\n up.emit(\"merge-success\", {\n file: this.uploadFile.proxy,\n response,\n });\n\n return response;\n } catch (error) {\n this.uploadFile.proxy.status = \"fail\";\n\n // 触发合并失败事件\n up.emit(\"merge-error\", {\n file: this.uploadFile.proxy,\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw error;\n }\n }\n\n /**\n * 计算上传统计信息(统一处理,避免重复代码)\n */\n private calculateUploadStats(): void {\n this.chunkStatsInfos = {\n totalTime: this.totalChunkTime,\n fileSize: this.uploadFile.File.size,\n completedChunks: this.completedChunks,\n averageSpeed:\n this.uploadFile.File.size / (this.totalChunkTime / 1000) / 1024, // KB/s\n };\n }\n\n /**\n * 检查统计信息并触发合并\n */\n protected async checkStatistics() {\n // 计算总耗时\n this.chunkEndTime = performance.now();\n this.totalChunkTime = this.chunkEndTime - this.chunkStartTime;\n\n // 基于 completedChunks 判断是否所有分片都完成了\n // chunks 可能会因为重试、断点续传等场景导致状态不一致\n // completedChunks 是可靠的计数器,只要累加了就说明分片确实上传成功了\n const allSuccess = this.completedChunks === this.totalChunks;\n\n logger.debug(\"uploadChunkManager\", `检查统计信息`, {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n allSuccess,\n uploadedChunksTrue: this.chunks.filter((u) => u).length,\n failedChunks: this.failedChunks.length,\n });\n if (allSuccess) {\n // 所有分片上传成功,执行合并\n await this.handleAllChunksSuccess();\n } else if (this.failedChunks.length > 0) {\n // 如果已取消或已暂停,跳过自动重试\n if (this.isCancelled || this.isPaused) {\n logger.info(\"uploadChunkManager\", \"上传已取消或暂停,跳过自动重试\", {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n isCancelled: this.isCancelled,\n isPaused: this.isPaused,\n failedChunks: this.failedChunks.length,\n });\n return;\n }\n // 有失败分片,执行重试逻辑\n await this.handleFailedChunks();\n } else {\n // 没有失败分片,但也没有全部完成(可能是被取消了)\n logger.warn(\"uploadChunkManager\", `上传未完成`, {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n }\n }\n\n /**\n * 处理所有分片成功的情况\n */\n protected async handleAllChunksSuccess(): Promise<void> {\n // 调用合并接口\n try {\n // 🔑 进入合并阶段\n this.uploadFile.proxy.status = \"merging\";\n this.uploadFile.transfer.emit(\"merging\", {\n file: this.uploadFile.proxy,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n\n const mergeResult = await this.mergeChunks();\n\n // 🔑 合并完成后才推到 100%\n this.uploadFile.proxy.percent = 100;\n\n this.uploadFile.onSuccess(mergeResult);\n } catch (error) {\n this.uploadFile.onError(error);\n }\n\n // 🔑 更新速度必须在 merge 之后,避免 merge 请求的 XHR 进度事件覆盖速度\n this.calculateAndUpdateSpeed(this.uploadFile.File.size);\n\n const totalTime = this.totalChunkTime / 1000;\n const averageSpeed =\n totalTime > 0 ? this.uploadFile.File.size / totalTime : 0;\n this.uploadFile.proxy.speed = {\n currentSpeed: 0, // 上传已完成,瞬时速度为 0\n averageSpeed,\n currentSpeedFormatted: \"0 B/s\",\n averageSpeedFormatted: formatSpeed(averageSpeed),\n estimatedTimeRemaining: 0,\n estimatedTimeFormatted: \"已完成\",\n };\n\n // 使用统一方法计算上传统计信息\n this.calculateUploadStats();\n }\n\n /**\n * 处理失败分片的情况(自动重试)\n */\n protected async handleFailedChunks(): Promise<void> {\n // 如果 retries 为 null,禁用自动重试,直接报错\n if (this.retries === null) {\n logger.warn(\n \"uploadChunkManager\",\n `发现 ${this.failedChunks.length} 个失败分片,自动重试已禁用,请手动重试`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n failedChunks: this.failedChunks,\n },\n );\n\n // this.setFileStatusToFail();\n this.uploadFile.onError(new Error(\"部分分片上传失败\"));\n return;\n }\n\n // 正常重试逻辑(retries >= 0)\n logger.warn(\n \"uploadChunkManager\",\n `发现 ${this.failedChunks.length} 个失败分片,开始自动重试`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n failedChunks: this.failedChunks,\n },\n );\n\n await this.retryFailedChunks();\n\n // ✅ 重试完成后,检查是否还有失败分片\n if (this.failedChunks.length > 0) {\n // 重试后仍然有失败分片,设置文件状态为失败\n this.handleRetryFailure();\n } else {\n // 重试成功,所有分片都完成了,继续检查统计信息\n await this.checkStatistics();\n }\n }\n\n /**\n * 处理重试失败的情况(统一错误处理)\n */\n private handleRetryFailure(): void {\n logger.error(\n \"uploadChunkManager\",\n `重试后仍有 ${this.failedChunks.length} 个分片失败,设置文件状态为失败`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n failedChunks: this.failedChunks,\n },\n );\n\n // this.setFileStatusToFail();\n this.uploadFile.onError(new Error(\"部分分片上传失败,重试后仍未成功\"));\n }\n\n /**\n * 重试失败的分片\n */\n public async retryFailedChunks(): Promise<void> {\n // ✅ 重置取消和暂停标志,允许重试\n this.isCancelled = false;\n this.isPaused = false;\n\n const failedChunksCopy = [...this.failedChunks];\n this.failedChunks = [];\n\n for (const chunkIndex of failedChunksCopy) {\n // 重置该分片的重试计数,允许手动重试时重新尝试\n this.retryCountMap.set(chunkIndex, 0);\n\n try {\n await this.uploadChunkWithRetry(chunkIndex);\n } catch (error) {\n this.failedChunks.push(chunkIndex);\n }\n }\n\n // 重试完成后,检查统计信息并触发合并或错误处理\n await this.checkStatistics();\n }\n\n /**\n * 取消上传\n *\n * 取消所有正在进行的分片上传任务,并清理相关资源。\n * 与 pause 不同,cancel 会完全停止上传,无法恢复。\n *\n * @example\n * ```typescript\n * file.cancel();\n * console.log(file.status); // \"cancelled\"\n * ```\n */\n public cancelUpload(): void {\n logger.info(\n \"uploadChunkManager\",\n `开始取消文件 ${this.uploadFile.fileName} 的上传`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n activeControllers: this.abortControllers.length,\n },\n );\n\n // ✅ 关键:设置取消标志,阻止新分片启动\n this.isCancelled = true;\n\n // ✅ 关键修复:如果正在计算 MD5,立即中断\n const hashController = (this as any).__hashAbortController__;\n if (hashController) {\n try {\n hashController.abort();\n logger.debug(\"uploadChunkManager\", `已中止 MD5 计算`);\n } catch (error) {\n logger.warn(\"uploadChunkManager\", `中止 MD5 计算时出错:`, error);\n }\n }\n\n // ✅ 中止所有活跃的 HTTP 请求\n this.abortControllers.forEach((controller) => {\n try {\n controller.abort();\n logger.debug(\"uploadChunkManager\", `已中止一个分片的 HTTP 请求`);\n } catch (error) {\n logger.warn(\"uploadChunkManager\", `中止分片请求时出错:`, error);\n }\n });\n\n // 清空 AbortController 数组\n this.abortControllers = [];\n\n // 设置为暂停状态,阻止新分片启动\n this.isPaused = true;\n\n // 唤醒所有等待中的分片上传任务,让它们检查取消状态\n if (this.pauseResolves.length > 0) {\n this.pauseResolves.forEach((resolve) => resolve());\n this.pauseResolves = [];\n }\n\n // 清空活跃上传任务集合\n this.activeUploads.clear();\n\n // 更新文件状态为已取消\n this.uploadFile.proxy.status = \"cancelled\";\n\n logger.info(\n \"uploadChunkManager\",\n `文件 ${this.uploadFile.fileName} 已取消上传`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n },\n );\n\n // 触发取消事件\n const up = this.uploadFile.transfer;\n up.emit(\"cancel\", this.uploadFile.proxy);\n }\n\n /**\n * 暂停上传\n *\n * 暂停所有正在进行的分片上传,但保持当前进度。\n * 会等待当前活跃的分片完成后暂停新分片的启动。\n *\n * @example\n * ```typescript\n * file.pause();\n * console.log(file.status); // \"paused\"\n * ```\n */\n public pause(): void {\n if (this.isPaused) {\n return;\n }\n\n this.isPaused = true;\n this.uploadFile.proxy.status = \"paused\";\n logger.info(\n \"uploadChunkManager\",\n `文件 ${this.uploadFile.fileName} 已暂停`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n },\n );\n }\n\n /**\n * 恢复上传\n *\n * 从暂停的位置继续上传分片。\n *\n * @example\n * ```typescript\n * file.resume();\n * console.log(file.status); // \"UDLoading\"\n * ```\n */\n public async resume(): Promise<void> {\n if (!this.isPaused) {\n return;\n }\n\n this.isPaused = false;\n this.uploadFile.proxy.status = \"UDLoading\";\n\n // 触发恢复事件\n const up = this.uploadFile.transfer;\n up.emit(\"resume\", this.uploadFile.proxy);\n\n logger.info(\n \"uploadChunkManager\",\n `文件 ${this.uploadFile.fileName} 已恢复`,\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n },\n );\n\n // 唤醒所有等待中的分片上传任务\n if (this.pauseResolves.length > 0) {\n this.pauseResolves.forEach((resolve) => resolve());\n this.pauseResolves = [];\n }\n }\n\n /**\n * 检查是否处于暂停状态\n * @returns 是否暂停\n */\n public getPaused(): boolean {\n return this.isPaused;\n }\n\n /**\n * 获取失败分片的数量\n * @returns 失败分片数量\n */\n public getFailedChunksCount(): number {\n return this.failedChunks.length;\n }\n\n /**\n * 等待恢复(用于暂停/恢复机制)\n * @private\n */\n protected async waitForResume(): Promise<void> {\n if (!this.isPaused) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n this.pauseResolves.push(() => resolve());\n });\n\n // ✅ 关键修复:唤醒后检查是否已取消\n if (this.isCancelled) {\n logger.info(\"uploadChunkManager\", `分片上传被取消,停止执行`, {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n });\n throw new Error(\"Upload cancelled\");\n }\n }\n\n /**\n * 更新上传进度\n *\n * ✅ 简单直接的进度计算:completedChunks / totalChunks * 100%\n * completedChunks 只在分片真正上传成功时才累加\n */\n public updateProgress() {\n // 不再在此处自增 completedChunks,而是由调用方(uploadChunk 成功回调)确保状态一致\n // 此处仅负责根据当前的 completedChunks 计算百分比\n\n // 防止除以0\n if (this.totalChunks === 0) {\n this.uploadFile.proxy.percent = 100;\n return;\n }\n\n // 简单直接的进度计算\n let percent = Math.floor((this.completedChunks / this.totalChunks) * 100);\n\n // 边界保护:确保不超过 100%\n percent = Math.min(100, Math.max(0, percent));\n\n // 🔑 分片传输阶段进度最高 99%,100% 留给合并完成后\n if (this.completedChunks >= this.totalChunks) {\n percent = 99;\n }\n\n // 🔑 先更新速度,再设置 percent(percent 的 Proxy set 会触发 rAF 渲染,确保 speed 已就绪)\n this.updateChunkUploadSpeed();\n this.uploadFile.proxy.percent = percent;\n }\n\n /**\n * 计算并更新单个文件的上传速度\n *\n * 🔑 内联实现,每次分片完成时直接写入 proxy.speed,不做首次跳过也不做防抖。\n * 分片上传本身是低频事件(间隔取决于分片大小/网速),不需要防抖反而会导致\n * 相邻调用间的 speed 值振荡。(calculateSpeed 的 100ms 防抖适用于 XHR progress\n * 高频回调场景,但对分片上传反而有害。)\n *\n * 💡 瞬时速度在首次调用时为 0(无上次采样可比较),从第二次起有真实值。\n * 平均速度基于 chunkStartTime,首轮即有效(虽然包含了 hash 开销会偏低,\n * 但这在几次分片后即趋于稳定,比跳变好得多)。\n */\n private updateChunkUploadSpeed(): void {\n const now = performance.now();\n const loadedBytes = this.totalChunkSize;\n const file = this.uploadFile;\n const fileSize = file.getFileSize();\n\n // 瞬时速度(bytes/s)—— 首次调用 currentSpeed = 0(无历史采样)\n let currentSpeed = 0;\n if (this.lastUpdateTime > 0 && this.lastUpdateTime <= now) {\n const timeDiff = (now - this.lastUpdateTime) / 1000;\n const bytesDiff = Math.max(0, loadedBytes - this.lastChunkBytes);\n if (timeDiff > 0) {\n currentSpeed = bytesDiff / timeDiff;\n }\n }\n\n // 平均速度(bytes/s)\n const totalElapsed = (now - this.chunkStartTime) / 1000;\n const averageSpeed =\n totalElapsed > 0 ? loadedBytes / totalElapsed : 0;\n\n // 预计剩余时间\n const remainingBytes = Math.max(0, fileSize - loadedBytes);\n let estimatedTimeRemaining = -1;\n let estimatedTimeFormatted = \"计算中...\";\n\n if (averageSpeed > 0 && remainingBytes > 0) {\n estimatedTimeRemaining = Math.ceil(remainingBytes / averageSpeed);\n estimatedTimeFormatted = formatDuration(estimatedTimeRemaining * 1000);\n } else if (remainingBytes <= 0) {\n estimatedTimeRemaining = 0;\n estimatedTimeFormatted = \"即将完成\";\n }\n\n file.proxy.speed = {\n currentSpeed,\n averageSpeed,\n currentSpeedFormatted: formatSpeed(currentSpeed),\n averageSpeedFormatted: formatSpeed(averageSpeed),\n estimatedTimeRemaining,\n estimatedTimeFormatted,\n };\n\n file.transfer.triggerUpdate();\n\n this.lastUpdateTime = now;\n this.lastChunkBytes = loadedBytes;\n }\n\n public async startUpload() {\n const up = this.uploadFile.transfer;\n\n // 触发分片上传开始事件\n up.emit(\"chunk-upload-start\", {\n file: this.uploadFile.proxy,\n totalChunks: this.totalChunks,\n chunkSize: this.chunkSize,\n });\n\n // 重置所有状态(避免重新上传时使用旧值)\n this.totalChunkSize = 0;\n this.uploadFile.__transferBytes__ = 0;\n this.chunkStartTime = performance.now(); // 重置开始时间\n this.countedChunks.clear(); // 重置已计数分片集合\n\n // 重置速度计算状态(避免重新上传时使用旧值)\n this.lastUpdateTime = 0;\n this.lastChunkBytes = 0;\n\n // ✅ 关键修复:重置取消和暂停标志,允许重新开始上传\n this.isCancelled = false;\n this.isPaused = false;\n\n // 重新创建 PQueue,确保使用正确的并发配置\n this.queue = new PQueue({ concurrency: this.maxConcurrent });\n\n // 初始化总字节数(只在首次上传时)\n if (!this.uploadFile.__hasCountedTotalBytes__) {\n up.totalTransferredBytes += this.uploadFile.File.size;\n up.totalBytes += this.uploadFile.File.size;\n up.totalFormatSize = formatFileSize(up.totalBytes);\n this.uploadFile.__hasCountedTotalBytes__ = true;\n }\n\n // 🔑 根据分片大小动态计算超时下限(假设最低网速 50 KB/s)\n // 20MB 分片 → 约 409 秒,1MB 分片 → 约 20 秒\n // timeout 为 0 时跳过,永不超时\n if (this.timeout > 0) {\n const minTimeoutBySize = Math.ceil((this.chunkSize / 51200) * 1000);\n this.timeout = Math.max(this.timeout, 60000, minTimeoutBySize);\n }\n\n try {\n const res = await this.initUpload();\n\n // ✅ 关键修复:区分真正的秒传和断点续传\n if (this.completedChunks === this.totalChunks) {\n if (this.isInstantTransfer) {\n // 真正的秒传:文件已存在,无需合并,直接成功\n logger.info(\n \"uploadChunkManager\",\n \"⚡ 秒传成功,文件已存在,跳过合并\",\n {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n fileHash: this.fileHash,\n },\n );\n\n // 记录开始和结束时间(秒传耗时几乎为0)\n this.chunkStartTime = performance.now();\n this.chunkEndTime = performance.now();\n this.totalChunkTime = 0;\n\n // 计算上传统计信息\n this.calculateUploadStats();\n\n // 触发成功回调,传递秒传标记\n this.uploadFile.onSuccess({\n isInstantUpload: true,\n url: res?.url,\n message: \"文件已存在,秒传成功\",\n });\n\n return;\n } else {\n // 断点续传:所有分片已上传但未合并,需要执行合并\n logger.info(\"uploadChunkManager\", \"所有分片已上传,执行合并\", {\n fileId: this.uploadFile.fileId,\n fileName: this.uploadFile.fileName,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n\n // 记录开始时间(如果是断点续传,需要重新计算)\n if (!this.chunkStartTime) {\n this.chunkStartTime = performance.now();\n }\n\n try {\n // 🔑 进入合并阶段\n this.uploadFile.proxy.status = \"merging\";\n this.uploadFile.transfer.emit(\"merging\", {\n file: this.uploadFile.proxy,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n\n const mergeResult = await this.mergeChunks();\n\n // 🔑 合并完成后才推到 100%\n this.uploadFile.proxy.percent = 100;\n\n // 记录总耗时\n this.chunkEndTime = performance.now();\n this.totalChunkTime = this.chunkEndTime - this.chunkStartTime;\n\n // 🔑 更新速度(断点续传也可能触发 merge XHR 覆盖速度,必须在 merge 之后)\n this.calculateAndUpdateSpeed(this.uploadFile.File.size);\n const resumeTotalTime = this.totalChunkTime / 1000;\n const resumeAvgSpeed =\n resumeTotalTime > 0\n ? this.uploadFile.File.size / resumeTotalTime\n : 0;\n this.uploadFile.proxy.speed = {\n currentSpeed: 0,\n averageSpeed: resumeAvgSpeed,\n currentSpeedFormatted: \"0 B/s\",\n averageSpeedFormatted: formatSpeed(resumeAvgSpeed),\n estimatedTimeRemaining: 0,\n estimatedTimeFormatted: \"已完成\",\n };\n\n // 使用统一方法计算上传统计信息\n this.calculateUploadStats();\n\n // 触发成功回调\n this.uploadFile.onSuccess(mergeResult);\n\n logger.info(\"uploadChunkManager\", \"文件传输完成(断点续传)\", {\n totalTime: this.totalChunkTime,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n } catch (error) {\n // 合并失败,触发错误回调\n this.chunkEndTime = performance.now();\n this.totalChunkTime = this.chunkEndTime - this.chunkStartTime;\n this.uploadFile.onError(error);\n }\n\n return;\n }\n }\n computeTransferTime(this.uploadFile.proxy.transferTime).start();\n // 使用信号量控制并发\n await this.uploadWithConcurrency();\n\n computeTransferTime(this.uploadFile.proxy.transferTime).end();\n\n // ✅ 关键修复:上传完成后检查统计信息并触发合并\n await this.checkStatistics();\n } catch (error) {\n // 错误时也要检查统计信息,确保正确更新状态和进度\n\n await this.checkStatistics();\n\n // 记录耗时\n this.chunkEndTime = performance.now();\n this.totalChunkTime = this.chunkEndTime - this.chunkStartTime;\n\n // 如果 checkStatistics 没有处理错误(比如还有失败分片),则抛出错误\n if (this.failedChunks.length > 0) {\n throw error;\n }\n }\n }\n\n /**\n * 使用信号量控制并发上传\n */\n protected async uploadWithConcurrency(): Promise<void> {\n const uploadPromises: Promise<void>[] = [];\n const chunkStartTimes: number[] = [];\n const chunkDurations: number[] = [];\n\n // 确保队列已初始化\n if (!this.queue) {\n throw new Error(\"PQueue not initialized. Call startUpload() first.\");\n }\n\n for (let chunkIndex = 0; chunkIndex < this.totalChunks; chunkIndex++) {\n // 跳过已上传的分片(断点续传场景)\n if (this.chunks[chunkIndex]) {\n continue;\n }\n\n this.chunkIndex = chunkIndex + 1;\n\n // 在每个分片上传前检查暂停状态\n uploadPromises.push(\n this.queue\n .add(async () => {\n // ✅ 在真正开始上传前检查是否暂停或取消\n await this.waitForResume();\n\n // 记录分片开始时间\n chunkStartTimes[chunkIndex] = performance.now();\n\n try {\n await this.uploadChunkWithRetry(chunkIndex);\n // 记录分片耗时\n chunkDurations[chunkIndex] =\n performance.now() - chunkStartTimes[chunkIndex];\n } catch (error) {\n // 记录失败分片的耗时(如果有)\n if (chunkStartTimes[chunkIndex]) {\n chunkDurations[chunkIndex] =\n performance.now() - chunkStartTimes[chunkIndex];\n }\n throw error;\n }\n })\n .catch((_error) => {\n if (\n _error instanceof Error &&\n (_error.message === \"Upload cancelled\" ||\n _error.message === \"Transfer cancelled\")\n ) {\n this.failedChunks.push(chunkIndex);\n }\n throw _error;\n }),\n );\n }\n\n await Promise.allSettled(uploadPromises);\n\n // 计算分片上传统计信息\n this.chunkStats = {\n averageTime:\n chunkDurations.filter((d) => d > 0).reduce((a, b) => a + b, 0) /\n chunkDurations.filter((d) => d > 0).length || 0,\n maxTime: Math.max(...chunkDurations.filter((d) => d > 0)),\n minTime: Math.min(...chunkDurations.filter((d) => d > 0)),\n };\n }\n}\n","const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);\n\nlet idbProxyableTypes;\nlet cursorAdvanceMethods;\n// This is a function to prevent it throwing up in node environments.\nfunction getIdbProxyableTypes() {\n return (idbProxyableTypes ||\n (idbProxyableTypes = [\n IDBDatabase,\n IDBObjectStore,\n IDBIndex,\n IDBCursor,\n IDBTransaction,\n ]));\n}\n// This is a function to prevent it throwing up in node environments.\nfunction getCursorAdvanceMethods() {\n return (cursorAdvanceMethods ||\n (cursorAdvanceMethods = [\n IDBCursor.prototype.advance,\n IDBCursor.prototype.continue,\n IDBCursor.prototype.continuePrimaryKey,\n ]));\n}\nconst transactionDoneMap = new WeakMap();\nconst transformCache = new WeakMap();\nconst reverseTransformCache = new WeakMap();\nfunction promisifyRequest(request) {\n const promise = new Promise((resolve, reject) => {\n const unlisten = () => {\n request.removeEventListener('success', success);\n request.removeEventListener('error', error);\n };\n const success = () => {\n resolve(wrap(request.result));\n unlisten();\n };\n const error = () => {\n reject(request.error);\n unlisten();\n };\n request.addEventListener('success', success);\n request.addEventListener('error', error);\n });\n // This mapping exists in reverseTransformCache but doesn't exist in transformCache. This\n // is because we create many promises from a single IDBRequest.\n reverseTransformCache.set(promise, request);\n return promise;\n}\nfunction cacheDonePromiseForTransaction(tx) {\n // Early bail if we've already created a done promise for this transaction.\n if (transactionDoneMap.has(tx))\n return;\n const done = new Promise((resolve, reject) => {\n const unlisten = () => {\n tx.removeEventListener('complete', complete);\n tx.removeEventListener('error', error);\n tx.removeEventListener('abort', error);\n };\n const complete = () => {\n resolve();\n unlisten();\n };\n const error = () => {\n reject(tx.error || new DOMException('AbortError', 'AbortError'));\n unlisten();\n };\n tx.addEventListener('complete', complete);\n tx.addEventListener('error', error);\n tx.addEventListener('abort', error);\n });\n // Cache it for later retrieval.\n transactionDoneMap.set(tx, done);\n}\nlet idbProxyTraps = {\n get(target, prop, receiver) {\n if (target instanceof IDBTransaction) {\n // Special handling for transaction.done.\n if (prop === 'done')\n return transactionDoneMap.get(target);\n // Make tx.store return the only store in the transaction, or undefined if there are many.\n if (prop === 'store') {\n return receiver.objectStoreNames[1]\n ? undefined\n : receiver.objectStore(receiver.objectStoreNames[0]);\n }\n }\n // Else transform whatever we get back.\n return wrap(target[prop]);\n },\n set(target, prop, value) {\n target[prop] = value;\n return true;\n },\n has(target, prop) {\n if (target instanceof IDBTransaction &&\n (prop === 'done' || prop === 'store')) {\n return true;\n }\n return prop in target;\n },\n};\nfunction replaceTraps(callback) {\n idbProxyTraps = callback(idbProxyTraps);\n}\nfunction wrapFunction(func) {\n // Due to expected object equality (which is enforced by the caching in `wrap`), we\n // only create one new func per func.\n // Cursor methods are special, as the behaviour is a little more different to standard IDB. In\n // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the\n // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense\n // with real promises, so each advance methods returns a new promise for the cursor object, or\n // undefined if the end of the cursor has been reached.\n if (getCursorAdvanceMethods().includes(func)) {\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n func.apply(unwrap(this), args);\n return wrap(this.request);\n };\n }\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n return wrap(func.apply(unwrap(this), args));\n };\n}\nfunction transformCachableValue(value) {\n if (typeof value === 'function')\n return wrapFunction(value);\n // This doesn't return, it just creates a 'done' promise for the transaction,\n // which is later returned for transaction.done (see idbObjectHandler).\n if (value instanceof IDBTransaction)\n cacheDonePromiseForTransaction(value);\n if (instanceOfAny(value, getIdbProxyableTypes()))\n return new Proxy(value, idbProxyTraps);\n // Return the same value back if we're not going to transform it.\n return value;\n}\nfunction wrap(value) {\n // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because\n // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.\n if (value instanceof IDBRequest)\n return promisifyRequest(value);\n // If we've already transformed this value before, reuse the transformed value.\n // This is faster, but it also provides object equality.\n if (transformCache.has(value))\n return transformCache.get(value);\n const newValue = transformCachableValue(value);\n // Not all types are transformed.\n // These may be primitive types, so they can't be WeakMap keys.\n if (newValue !== value) {\n transformCache.set(value, newValue);\n reverseTransformCache.set(newValue, value);\n }\n return newValue;\n}\nconst unwrap = (value) => reverseTransformCache.get(value);\n\n/**\n * Open a database.\n *\n * @param name Name of the database.\n * @param version Schema version.\n * @param callbacks Additional callbacks.\n */\nfunction openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {\n const request = indexedDB.open(name, version);\n const openPromise = wrap(request);\n if (upgrade) {\n request.addEventListener('upgradeneeded', (event) => {\n upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);\n });\n }\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event.newVersion, event));\n }\n openPromise\n .then((db) => {\n if (terminated)\n db.addEventListener('close', () => terminated());\n if (blocking) {\n db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));\n }\n })\n .catch(() => { });\n return openPromise;\n}\n/**\n * Delete a database.\n *\n * @param name Name of the database.\n */\nfunction deleteDB(name, { blocked } = {}) {\n const request = indexedDB.deleteDatabase(name);\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event));\n }\n return wrap(request).then(() => undefined);\n}\n\nconst readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];\nconst writeMethods = ['put', 'add', 'delete', 'clear'];\nconst cachedMethods = new Map();\nfunction getMethod(target, prop) {\n if (!(target instanceof IDBDatabase &&\n !(prop in target) &&\n typeof prop === 'string')) {\n return;\n }\n if (cachedMethods.get(prop))\n return cachedMethods.get(prop);\n const targetFuncName = prop.replace(/FromIndex$/, '');\n const useIndex = prop !== targetFuncName;\n const isWrite = writeMethods.includes(targetFuncName);\n if (\n // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.\n !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||\n !(isWrite || readMethods.includes(targetFuncName))) {\n return;\n }\n const method = async function (storeName, ...args) {\n // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(\n const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');\n let target = tx.store;\n if (useIndex)\n target = target.index(args.shift());\n // Must reject if op rejects.\n // If it's a write operation, must reject if tx.done rejects.\n // Must reject with op rejection first.\n // Must resolve with op value.\n // Must handle both promises (no unhandled rejections)\n return (await Promise.all([\n target[targetFuncName](...args),\n isWrite && tx.done,\n ]))[0];\n };\n cachedMethods.set(prop, method);\n return method;\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),\n has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),\n}));\n\nconst advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];\nconst methodMap = {};\nconst advanceResults = new WeakMap();\nconst ittrProxiedCursorToOriginalProxy = new WeakMap();\nconst cursorIteratorTraps = {\n get(target, prop) {\n if (!advanceMethodProps.includes(prop))\n return target[prop];\n let cachedFunc = methodMap[prop];\n if (!cachedFunc) {\n cachedFunc = methodMap[prop] = function (...args) {\n advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));\n };\n }\n return cachedFunc;\n },\n};\nasync function* iterate(...args) {\n // tslint:disable-next-line:no-this-assignment\n let cursor = this;\n if (!(cursor instanceof IDBCursor)) {\n cursor = await cursor.openCursor(...args);\n }\n if (!cursor)\n return;\n cursor = cursor;\n const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);\n ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);\n // Map this double-proxy back to the original, so other cursor methods work.\n reverseTransformCache.set(proxiedCursor, unwrap(cursor));\n while (cursor) {\n yield proxiedCursor;\n // If one of the advancing methods was not called, call continue().\n cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());\n advanceResults.delete(proxiedCursor);\n }\n}\nfunction isIteratorProp(target, prop) {\n return ((prop === Symbol.asyncIterator &&\n instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||\n (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get(target, prop, receiver) {\n if (isIteratorProp(target, prop))\n return iterate;\n return oldTraps.get(target, prop, receiver);\n },\n has(target, prop) {\n return isIteratorProp(target, prop) || oldTraps.has(target, prop);\n },\n}));\n\nexport { deleteDB, openDB, unwrap, wrap };\n","import { openDB, IDBPDatabase } from \"idb\";\n\n// 文件分块信息,索引和是否已传输的状态\nexport interface ChunkRecord {\n index: number;\n uploaded: boolean;\n}\n\n// 整个传输任务的记录,包含文件名,大小,分块大小,总分块数等信息,以及所有分块传输的状态\nexport interface TransferTaskRecord {\n id: string; // 文件标识(MD5 或指纹)\n filename: string; // 文件名\n size: number; // 文件大小\n chunkSize: number; // 分块大小\n totalChunks: number; // 总分块数\n chunks: ChunkRecord[]; // 所有分块传输的状态\n createdAt: number; // 创建时间\n updatedAt: number; // 更新时间\n}\n\n// 文件 Hash 缓存记录\nexport interface FileHashRecord {\n id: string; // 快速指纹: filename_lastModified_size\n hash: string; // 文件 MD5\n filename: string;\n size: number;\n lastModified: number;\n createdAt: number;\n updatedAt: number;\n}\n\nconst DB_NAME = \"FileTransferDB\"; // 数据库名\nconst STORE_NAME = \"transferTasks\"; // 传输任务存储对象名\nconst VERSION = 1; // 数据库版本\nlet dbInstance: IDBPDatabase<TransferTaskRecord> | null = null;\nconst HASH_STORE_NAME = \"fileHashes\"; // Hash 缓存存储对象名\n\n/**\n * 检查数据库连接是否有效\n */\nfunction isDbValid(db: IDBPDatabase<TransferTaskRecord>): boolean {\n try {\n // 尝试创建一个简单的事务来测试连接\n const tx = db.transaction(STORE_NAME, \"readonly\");\n return tx.objectStore(STORE_NAME) !== null;\n } catch (error) {\n console.warn(\"数据库连接已失效:\", error);\n return false;\n }\n}\n\n// 初始化数据库(单例模式,避免重复打开)\nexport const initDB = async (): Promise<IDBPDatabase<TransferTaskRecord>> => {\n // ✅ 如果已有有效连接,直接返回\n if (dbInstance && isDbValid(dbInstance)) {\n return dbInstance;\n }\n\n // ✅ 连接失效,重置实例\n if (dbInstance) {\n console.warn(\"检测到无效的数据库连接,重新初始化...\");\n try {\n dbInstance.close();\n } catch (error) {\n // 忽略关闭错误\n }\n dbInstance = null;\n }\n\n try {\n dbInstance = await openDB<TransferTaskRecord>(DB_NAME, VERSION, {\n upgrade(db) {\n // 创建传输任务存储对象\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n const store = db.createObjectStore(STORE_NAME, { keyPath: \"id\" });\n // 创建索引以便按更新时间查询\n store.createIndex(\"updatedAt\", \"updatedAt\", { unique: false });\n }\n // 创建 Hash 缓存存储对象\n if (!db.objectStoreNames.contains(HASH_STORE_NAME)) {\n const hashStore = db.createObjectStore(HASH_STORE_NAME, {\n keyPath: \"id\",\n });\n hashStore.createIndex(\"hash\", \"hash\", { unique: false });\n hashStore.createIndex(\"updatedAt\", \"updatedAt\", { unique: false });\n hashStore.createIndex(\"size\", \"size\", { unique: false });\n }\n },\n });\n \n // ✅ 监听连接关闭事件\n dbInstance.addEventListener(\"close\", () => {\n console.warn(\"IndexedDB 连接已关闭\");\n dbInstance = null;\n });\n \n return dbInstance;\n } catch (error) {\n console.error(\"IndexedDB 初始化失败:\", error);\n dbInstance = null;\n throw new Error(\"IndexedDB 初始化失败\");\n }\n};\n\n/**\n * 执行数据库操作的通用包装器,自动处理 InvalidStateError\n * @param operation - 数据库操作函数\n * @returns Promise<T>\n */\nasync function executeDbOperation<T>(\n operation: (db: IDBPDatabase<TransferTaskRecord>) => Promise<T>\n): Promise<T> {\n try {\n const db = await initDB();\n return await operation(db);\n } catch (error) {\n // ✅ 捕获 InvalidStateError,重置连接并重试一次\n if (error instanceof DOMException && error.name === \"InvalidStateError\") {\n console.warn(\"检测到 InvalidStateError,重置数据库连接后重试\", error);\n \n // 强制重置连接\n if (dbInstance) {\n try {\n dbInstance.close();\n } catch (e) {\n // 忽略关闭错误\n }\n }\n dbInstance = null;\n \n // 重试一次\n const db = await initDB();\n return await operation(db);\n }\n throw error;\n }\n}\n\n// 保存传输任务(新增或更新)\nexport const saveTransferTask = async (\n task: Omit<TransferTaskRecord, \"updatedAt\"> & Partial<TransferTaskRecord>,\n): Promise<void> => {\n try {\n await executeDbOperation(async (db) => {\n const now = Date.now();\n await db.put(STORE_NAME, {\n ...task,\n updatedAt: now,\n createdAt: task.createdAt || now,\n });\n });\n } catch (error) {\n console.error(\"保存传输任务失败:\", error);\n throw error;\n }\n};\n\n// 获取传输任务\nexport const getTransferTask = async (\n id: string,\n): Promise<TransferTaskRecord | null> => {\n try {\n return await executeDbOperation(async (db) => {\n return await db.get(STORE_NAME, id);\n });\n } catch (error) {\n console.error(\"获取传输任务失败:\", error);\n return null;\n }\n};\n\n/**\n * 生成文件的快速指纹(使用文件属性)\n * @param filename 文件名\n * @param lastModified 最后修改时间\n * @param size 文件大小\n * @returns 快速指纹字符串\n */\nexport const generateQuickFingerprintFromProps = (\n filename: string,\n lastModified: number,\n size: number,\n): string => {\n return `${filename}_${lastModified}_${size}`;\n};\n\n/**\n * 生成文件的快速指纹(用于缓存查找)\n * @param file 文件对象\n * @returns 快速指纹字符串\n */\nexport const generateQuickFingerprint = (file: File): string => {\n return `${file.name}_${file.lastModified}_${file.size}`;\n};\n\n/**\n * 获取缓存的文件 Hash\n * @param file 文件对象或指纹字符串\n * @returns MD5 Hash 或 null\n */\nexport const getCachedHash = async (\n file: File | string,\n): Promise<string | null> => {\n try {\n return await executeDbOperation(async (db) => {\n const fingerprint =\n typeof file === \"string\" ? file : generateQuickFingerprint(file);\n const record = await db.get(HASH_STORE_NAME, fingerprint);\n\n if (record) {\n return record.hash;\n }\n\n return null;\n });\n } catch (error) {\n console.error(\"获取缓存 Hash 失败:\", error);\n return null;\n }\n};\n\n// 删除传输任务\nexport const deleteTransferTask = async (id: string): Promise<void> => {\n try {\n await executeDbOperation(async (db) => {\n await db.delete(STORE_NAME, id);\n });\n } catch (error) {\n console.error(\"删除传输任务失败:\", error);\n throw error;\n }\n};\n\n/**\n * 保存文件 Hash 到缓存\n * @param file 文件对象\n * @param hash 文件 MD5 Hash\n */\nexport const saveFileHash = async (file: File, hash: string): Promise<void> => {\n try {\n await executeDbOperation(async (db) => {\n const fingerprint = generateQuickFingerprint(file);\n const now = Date.now();\n\n const record: FileHashRecord = {\n id: fingerprint,\n hash: hash,\n filename: file.name,\n size: file.size,\n lastModified: file.lastModified,\n createdAt: now,\n updatedAt: now,\n };\n\n await db.put(HASH_STORE_NAME, record);\n console.log(`💾 Hash 已缓存: ${fingerprint} -> ${hash}`);\n });\n } catch (error) {\n console.error(\"保存 Hash 缓存失败:\", error);\n throw error;\n }\n};\n\n/**\n * 通过指纹字符串直接保存 Hash 缓存(适用于下载等无 File 对象的场景)\n * @param fingerprint 文件指纹字符串\n * @param hash 要缓存的 Hash\n * @param meta 额外元信息(文件名/大小等,可选)\n */\nexport const saveHashByFingerprint = async (\n fingerprint: string,\n hash: string,\n meta?: { filename?: string; size?: number },\n): Promise<void> => {\n try {\n await executeDbOperation(async (db) => {\n const now = Date.now();\n\n const record: FileHashRecord = {\n id: fingerprint,\n hash,\n filename: meta?.filename || \"\",\n size: meta?.size || 0,\n lastModified: 0,\n createdAt: now,\n updatedAt: now,\n };\n\n await db.put(HASH_STORE_NAME, record);\n });\n } catch (error) {\n console.error(\"保存 Hash 缓存失败:\", error);\n throw error;\n }\n};\n\n/**\n * 更新单个分片的传输状态\n * @param taskId 任务ID\n * @param chunkIndex 分片索引\n * @param uploaded 是否已传输\n */\nexport const updateChunkStatus = async (\n taskId: string,\n chunkIndex: number,\n uploaded: boolean,\n): Promise<void> => {\n try {\n await executeDbOperation(async (db) => {\n const task = await db.get(STORE_NAME, taskId);\n\n if (!task) {\n console.warn(`任务 ${taskId} 不存在`);\n return;\n }\n\n // 更新对应分片的状态\n const chunk = task.chunks.find((c: ChunkRecord) => c.index === chunkIndex);\n if (chunk) {\n chunk.uploaded = uploaded;\n } else {\n task.chunks.push({ index: chunkIndex, uploaded });\n }\n\n // 保存更新后的任务\n await db.put(STORE_NAME, {\n ...task,\n updatedAt: Date.now(),\n });\n });\n } catch (error) {\n console.error(\"更新分片状态失败:\", error);\n throw error;\n }\n};\n\n/**\n * 获取所有未完成的传输任务\n * @param maxAge 最大年龄(毫秒),超过此时间的任务视为过期,默认为 7 天\n */\nexport const getIncompleteTransferTasks = async (\n maxAge?: number,\n): Promise<TransferTaskRecord[]> => {\n try {\n return await executeDbOperation(async (db) => {\n const allTasks = await db.getAll(STORE_NAME);\n\n // 过滤出未完成的任务\n const incompleteTasks = allTasks.filter((task: TransferTaskRecord) => {\n const isCompleted = task.chunks.every(\n (chunk: ChunkRecord) => chunk.uploaded,\n );\n return !isCompleted;\n });\n\n // 如果指定了最大年龄,过滤过期任务\n if (maxAge !== undefined) {\n const now = Date.now();\n return incompleteTasks.filter((task) => now - task.updatedAt < maxAge);\n }\n\n return incompleteTasks;\n });\n } catch (error) {\n console.error(\"获取未完成任务失败:\", error);\n return [];\n }\n};\n\n/**\n * 清理过期的传输任务\n * @param maxAge 最大年龄(毫秒),默认为 7 天\n */\nexport const cleanupExpiredTransferTasks = async (\n maxAge: number = 7 * 24 * 60 * 60 * 1000,\n): Promise<number> => {\n try {\n return await executeDbOperation(async (db) => {\n const allTasks = await db.getAll(STORE_NAME);\n const now = Date.now();\n let deletedCount = 0;\n\n for (const task of allTasks) {\n // 删除已完成或过期的任务\n const isCompleted = task.chunks.every(\n (chunk: ChunkRecord) => chunk.uploaded,\n );\n const isExpired = now - task.updatedAt > maxAge;\n\n if (isCompleted || isExpired) {\n await db.delete(STORE_NAME, task.id);\n deletedCount++;\n }\n }\n\n console.log(`清理了 ${deletedCount} 个过期/已完成的任务`);\n return deletedCount;\n });\n } catch (error) {\n console.error(\"清理过期任务失败:\", error);\n return 0;\n }\n};\n\n/**\n * 关闭数据库连接(通常在应用卸载时调用)\n */\nexport const closeDB = (): void => {\n if (dbInstance) {\n try {\n dbInstance.close();\n } catch (error) {\n console.warn(\"关闭数据库连接时出错:\", error);\n } finally {\n dbInstance = null;\n }\n }\n};\n\n/**\n * 重置数据库连接(用于测试或异常情况)\n */\nexport const resetDB = (): void => {\n if (dbInstance) {\n try {\n dbInstance.close();\n } catch (error) {\n console.warn(\"关闭数据库连接时出错:\", error);\n }\n }\n dbInstance = null;\n console.log(\"数据库连接已重置\");\n};\n","import PQueue from \"p-queue\";\nimport { ChunkOptions } from \"../types\";\nimport TransferFile from \"../transfer/TransferFile\";\nimport {\n formatDuration,\n formatFileSize,\n formatSpeed,\n computeTransferTime,\n logger,\n} from \"../utils\";\n\n/**\n * 分片管理器基类\n *\n * 提取上传/下载分片管理器的所有公共逻辑:\n * - 并发分片传输调度\n * - 暂停/恢复/取消\n * - 重试机制(含 AbortController + 超时)\n * - 进度计算 & 速率统计\n * - 分片完成/失败后的事件触发与状态聚合\n *\n * 子类只需实现以下抽象方法:\n * - {@link getTag} 日志标签\n * - {@link getTransferFile} 关联的传输文件实例\n * - {@link doInit} 初始化传输(获取已传分片列表等)\n * - {@link doChunkTransfer} 执行单个分片的实际传输\n * - {@link doMergeChunks} 合并所有分片\n */\nexport default abstract class ChunkManager<\n T extends TransferFile<T> = TransferFile<any, any>,\n> {\n chunkSize: number = 0;\n maxConcurrent: number = 5;\n /** 最大速率限制(bytes/秒),0 = 不限制。下载场景由 maxDownloadSpeed 设置 */\n maxSpeed: number = 0;\n public chunkIndex: number = 0;\n retries: number | null = 0;\n retryDelay: number = 1000;\n timeout: number = 30000;\n chunk: Blob | null = null;\n\n public totalChunks: number = 0;\n public chunks: boolean[] = [];\n public chunkEndTime = 0;\n public completedChunks = 0;\n public totalChunkTime = 0;\n public response: any = null;\n public queue: PQueue | null = null;\n public chunkStartTime = performance.now();\n\n public chunkStatsInfos: {\n totalTime: number;\n fileSize: number;\n completedChunks: number | undefined;\n averageSpeed: number;\n } = {\n totalTime: 0,\n fileSize: 0,\n completedChunks: undefined,\n averageSpeed: 0,\n };\n\n totalChunkSize: number = 0;\n config: ChunkOptions;\n\n public countedChunks: Set<number> = new Set();\n\n public fileHash: string = \"\";\n public failedChunks: number[] = [];\n public retryCountMap: Map<number, number> = new Map();\n public isPaused: boolean = false;\n public isCancelled: boolean = false;\n public pauseResolves: Array<() => void> = [];\n public activeUploads: Set<Promise<void>> = new Set();\n public abortControllers: AbortController[] = [];\n public lastUpdateTime: number = 0;\n public lastChunkBytes: number = 0;\n\n /** 每次 start() 递增的代数计数器,防止旧 run 的异步回调污染新 run 的状态 */\n private _startGeneration = 0;\n\n // ==================== 速率限制 ====================\n /** 速率限制:累计已传输字节数(用于限速计算) */\n private _throttleBytes: number = 0;\n /** 速率限制:起始时间戳 */\n private _throttleStartTime: number = 0;\n\n public chunkStats: {\n averageTime: number;\n maxTime: number;\n minTime: number;\n } | null = null;\n\n /** 是否秒传/秒下(无需实际传输,文件已在目标端) */\n public isInstantTransfer = false;\n\n /** 关联的传输文件(UploadFile / DownloadFile) */\n protected transferFile: T;\n\n // ==================== 抽象方法(子类必须实现) ====================\n\n /** 日志标签,如 \"uploadChunkManager\" / \"DownloadChunkManager\" */\n protected abstract getTag(): string;\n\n /** 计算文件唯一标识:上传=本地MD5,下载=服务端Hash / 指纹 */\n protected abstract computeFileIdentifier(): Promise<string>;\n\n /** 返回关联的传输文件实例 */\n protected getTransferFile(): T {\n return this.transferFile;\n }\n\n /** 初始化传输:检查已上传/已下载分片(断点续传) */\n protected abstract doInit(): Promise<any>;\n\n /** 执行单个分片的实际传输(上传/下载) */\n protected abstract doChunkTransfer(\n chunkIndex: number,\n signal?: AbortSignal,\n ): Promise<{ data: any; chunkSize: number }>;\n\n /** 合并所有分片 */\n protected abstract doMergeChunks(): Promise<any>;\n\n // ==================== 可选钩子(子类覆写) ====================\n\n /** start() 重置后的额外清理,如下载场景清空 downloadedChunks */\n protected async doAfterStartReset(): Promise<void> {}\n\n /** 分片传输成功后保存结果数据(上传存 response,下载存 blob 到 Map / 流式写入磁盘) */\n protected async doSaveChunkResult(\n _chunkIndex: number,\n _data: any,\n ): Promise<void> {}\n\n /** onSuccess 之前的额外操作,如下载场景调用 saveBlob */\n protected async doBeforeOnSuccess(_mergeResult: any): Promise<void> {}\n\n /** start() 发现所有分片已完成时的处理(上传需区分秒传/断点续传) */\n protected async doOnAllChunksAlreadyDone(_initResult: any): Promise<void> {\n await this.completeMerge();\n }\n\n // ==================== 通用 Helper(子类在 doInit 中复用) ====================\n\n /**\n * 标记所有分片为已完成(秒传/秒下、服务端已存在全部分片)\n */\n protected applyAllChunksComplete(fileSize: number): void {\n this.completedChunks = this.totalChunks;\n this.totalChunkSize = fileSize;\n this.chunks = new Array(this.totalChunks).fill(true);\n this.countedChunks = new Set(\n Array.from({ length: this.totalChunks }, (_, i) => i),\n );\n this.getTransferFile().proxy.percent = 100;\n }\n\n /**\n * 断点续传恢复:从 initResult.chunks 标记已完成的分片\n * @param initResult - 服务端返回的初始化结果,包含 chunks 数组\n */\n protected applyBreakpointResume(initResult: {\n chunks?: number[] | null;\n }): void {\n if (\n !initResult.chunks ||\n !Array.isArray(initResult.chunks) ||\n initResult.chunks.length === 0\n ) {\n return;\n }\n\n this.completedChunks = 0;\n initResult.chunks.forEach((index: number) => {\n if (index >= 0 && index < this.totalChunks && !this.chunks[index]) {\n this.chunks[index] = true;\n this.completedChunks++;\n this.countedChunks.add(index);\n this.totalChunkSize += this.chunkSize;\n }\n });\n this.updateProgress();\n\n const tr = this.getTransferFile().transfer;\n tr.updateGlobalStats();\n tr.triggerUpdate();\n }\n\n // ==================== 构造函数 ====================\n\n constructor(ChunkOptions: ChunkOptions, file: T) {\n this.config = ChunkOptions;\n this.transferFile = file;\n this.chunkSize = ChunkOptions.chunkSize ?? 1024 * 1024 * 5;\n this.maxConcurrent = ChunkOptions.maxConcurrent ?? 5;\n this.retries =\n ChunkOptions.retries !== undefined ? ChunkOptions.retries : 5;\n this.retryDelay = ChunkOptions.retryDelay ?? 1000;\n this.timeout = ChunkOptions.timeout ?? 30000;\n this.totalChunks = Math.ceil(file.getFileSize() / this.chunkSize);\n this.chunks = [];\n this.completedChunks = 0;\n }\n\n // ==================== 生命周期控制 ====================\n\n /**\n * 暂停传输(上传/下载统一实现)\n */\n public pause(): void {\n if (this.isPaused) return;\n\n this.isPaused = true;\n const file = this.getTransferFile();\n file.proxy.status = \"paused\";\n logger.info(this.getTag(), `文件 ${file.fileName} 已暂停`, {\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n }\n\n /**\n * 恢复传输(上传/下载统一实现)\n */\n public async resume(): Promise<void> {\n if (!this.isPaused) return;\n\n this.isPaused = false;\n const file = this.getTransferFile();\n file.proxy.status = \"UDLoading\";\n\n file.transfer.emit(\"resume\", file.proxy as any);\n\n logger.info(this.getTag(), `文件 ${file.fileName} 已恢复`, {\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n\n // 唤醒等待中的分片传输任务\n if (this.pauseResolves.length > 0) {\n this.pauseResolves.forEach((resolve) => resolve());\n this.pauseResolves = [];\n }\n }\n\n /**\n * 取消传输(上传/下载统一实现)\n */\n public cancel(): void {\n const file = this.getTransferFile();\n logger.info(this.getTag(), `取消文件 ${file.fileName} 的传输`, {\n activeControllers: this.abortControllers.length,\n });\n\n this.isCancelled = true;\n\n // 🔑 清空 PQueue 中所有排队任务,避免旧任务干扰新 run\n if (this.queue) {\n this.queue.clear();\n this.queue = null;\n }\n\n // 中止所有活跃的 HTTP 请求\n\n this.abortControllers.forEach((controller) => {\n try {\n controller.abort();\n } catch (_error) {\n console.log(\"🚀 ~ ChunkManager ~ cancel ~ _error:\", _error);\n }\n });\n this.abortControllers = [];\n\n // 阻止新分片启动\n this.isPaused = true;\n\n // 唤醒等待中的分片\n if (this.pauseResolves.length > 0) {\n this.pauseResolves.forEach((resolve) => resolve());\n this.pauseResolves = [];\n }\n\n this.activeUploads.clear();\n\n file.proxy.status = \"cancelled\";\n file.transfer.emit(\"cancel\", file.proxy as any);\n }\n\n /**\n * 是否处于暂停状态\n */\n public getPaused(): boolean {\n return this.isPaused;\n }\n\n /**\n * 获取失败分片的数量\n */\n public getFailedChunksCount(): number {\n return this.failedChunks.length;\n }\n\n // ==================== 进度 & 速率 ====================\n\n /**\n * 更新传输进度百分比\n */\n public updateProgress(): void {\n const file = this.getTransferFile();\n\n if (this.totalChunks === 0) {\n file.proxy.percent = 100;\n file.transfer.updateGlobalStats();\n file.transfer.triggerUpdate();\n return;\n }\n\n let percent = Math.floor((this.completedChunks / this.totalChunks) * 100);\n percent = Math.min(100, Math.max(0, percent));\n\n // 🔑 分片传输阶段进度最高 99%,100% 留给合并完成后\n if (this.completedChunks >= this.totalChunks) {\n percent = 99;\n }\n\n file.proxy.percent = percent;\n file.transfer.updateGlobalStats();\n this.calculateAndUpdateSpeed(this.totalChunkSize);\n file.transfer.triggerUpdate();\n }\n\n /**\n * 计算并触发全局速率更新\n */\n protected calculateAndUpdateSpeed(currentTransferredBytes: number): void {\n const now = performance.now();\n\n if (this.lastUpdateTime === 0 || this.lastUpdateTime > now) {\n this.lastUpdateTime = now;\n this.lastChunkBytes = currentTransferredBytes;\n return;\n }\n\n const timeDiff = (now - this.lastUpdateTime) / 1000;\n if (timeDiff < 0.1) return;\n\n const file = this.getTransferFile();\n const fileSize = file.getFileSize();\n const bytesDiff = currentTransferredBytes - this.lastChunkBytes;\n\n // 瞬时速度(bytes/s)\n const currentSpeed = (bytesDiff / timeDiff);\n // 平均速度(bytes/s):基于 chunkStartTime 计算总耗时\n const totalElapsed = (now - this.chunkStartTime) / 1000;\n const averageSpeed = totalElapsed > 0\n ? currentTransferredBytes / totalElapsed\n : 0;\n\n // 预计剩余时间\n const remainingBytes = Math.max(0, fileSize - currentTransferredBytes);\n let estimatedTimeRemaining = -1;\n let estimatedTimeFormatted = \"计算中...\";\n\n if (averageSpeed > 0 && remainingBytes > 0) {\n estimatedTimeRemaining = Math.ceil(remainingBytes / averageSpeed);\n estimatedTimeFormatted = formatDuration(estimatedTimeRemaining * 1000);\n } else if (remainingBytes <= 0) {\n estimatedTimeRemaining = 0;\n estimatedTimeFormatted = \"即将完成\";\n }\n\n // 🔑 更新单文件的 speed 信息(分片传输期间原来不更新,导致列表行一直显示\"计算中...\")\n file.proxy.speed = {\n currentSpeed,\n averageSpeed,\n currentSpeedFormatted: formatSpeed(currentSpeed),\n averageSpeedFormatted: formatSpeed(averageSpeed),\n estimatedTimeRemaining,\n estimatedTimeFormatted,\n };\n\n file.transfer.triggerUpdate();\n\n this.lastUpdateTime = now;\n this.lastChunkBytes = currentTransferredBytes;\n }\n\n /**\n * 计算传输统计信息\n */\n protected calculateStats(): void {\n const fileSize = this.getTransferFile().getFileSize();\n this.chunkStatsInfos = {\n totalTime: this.totalChunkTime,\n fileSize,\n completedChunks: this.completedChunks,\n averageSpeed: fileSize / (this.totalChunkTime / 1000) / 1024,\n };\n }\n\n // ==================== 核心传输流程 ====================\n\n /**\n * 统一的 entry:启动分片传输\n */\n public async start(): Promise<void> {\n const file = this.getTransferFile();\n\n // 🔑 清除旧 PQueue 中的排队任务,避免干扰新 run\n if (this.queue) {\n this.queue.clear();\n }\n\n // 🔑 递增代数,使上一轮 cancel 残留的异步回调解散\n // 避免旧的 handleChunkSuccess/handleChunkError 在 reset 后仍修改状态\n const currentGen = ++this._startGeneration;\n\n // 触发开始事件(由子类覆写 getStartEventName 指定事件名)\n (file.transfer as any).emit(this.getStartEventName(), {\n file: file.proxy,\n totalChunks: this.totalChunks,\n chunkSize: this.chunkSize,\n });\n\n // 重置所有状态\n this.totalChunkSize = 0;\n (file as any).__transferBytes__ = 0;\n this.chunkStartTime = performance.now();\n this.countedChunks.clear();\n this.lastUpdateTime = 0;\n this.lastChunkBytes = 0;\n this.isCancelled = false;\n this.isPaused = false;\n // 重置限流状态\n this._throttleBytes = 0;\n this._throttleStartTime = 0;\n // 🔑 重置秒下/秒传标志,避免上一次 run 的 markInstantDownload() 残留影响\n // 新的 doInit() 会重新判断是否需要走秒下流程\n this.isInstantTransfer = false;\n // 🔑 清除取消后残留的失败分片记录(这些分片在 cancel 的异步清理中被标记为失败,\n // 但并未经过真正的错误重试),避免 checkStatistics() 误判。\n this.failedChunks = [];\n this.retryCountMap.clear();\n await this.doAfterStartReset();\n\n // 🔑 等待上次的 writable 完全关闭(下载场景),避免 createWritable 文件锁冲突\n // 若因锁冲突失败,错误会在 start() 的 catch 中被静默吞掉,导致重试无反应\n if (typeof (this as any).ensureWritableClosed === \"function\") {\n await (this as any).ensureWritableClosed();\n }\n\n // 🔑 doAfterStartReset 清空了 chunkBlobs 等数据,需同步重置分片完成状态\n // 后续 doInit() 会从服务端 / IndexedDB 恢复真实的已完成分片\n this.completedChunks = 0;\n this.chunks = new Array(this.totalChunks).fill(false);\n\n // 重新创建 PQueue\n // 🔑 必须标记 __v_skip=true,防止 Vue reactive() 包装 PQueue\n // 否则 PQueue v9 的 ES2022 私有字段(#queue, #idAssigner)无法通过 Vue Proxy 访问\n // → TypeError: Cannot read private member from an object whose class did not declare it\n const queue = new PQueue({ concurrency: this.maxConcurrent });\n (queue as any).__v_skip = true;\n this.queue = queue;\n\n // 初始化全局字节统计\n if (!(file as any).__hasCountedTotalBytes__) {\n const tr = file.transfer;\n tr.totalTransferredBytes += file.getFileSize();\n tr.totalBytes += file.getFileSize();\n tr.totalFormatSize = formatFileSize(tr.totalBytes);\n (file as any).__hasCountedTotalBytes__ = true;\n }\n\n // 🔑 根据分片大小动态计算超时下限(假设最低网速 50 KB/s)\n // 20MB 分片 → 约 409 秒,1MB 分片 → 约 20 秒\n // timeout 为 0 时跳过,永不超时\n if (this.timeout > 0) {\n const minTimeoutBySize = Math.ceil((this.chunkSize / 51200) * 1000);\n this.timeout = Math.max(this.timeout, 60000, minTimeoutBySize);\n }\n\n // 🔑 在 doInit() 之前就设置 startTime,确保所有路径(普通 / 已全完成 / 异常)都有合法起始时间\n computeTransferTime(file.proxy.transferTime).start();\n\n try {\n const res = await this.doInit();\n\n if (this.completedChunks === this.totalChunks) {\n await this.doOnAllChunksAlreadyDone(res);\n return;\n }\n\n // 标记文件状态为传输中(分片传输路径统一入口,普通传输由子类在 download/upload 方法中设置)\n file.proxy.status = \"UDLoading\";\n\n await this.transferWithConcurrency(currentGen);\n\n await this.checkStatistics();\n } catch (error) {\n await this.checkStatistics();\n this.chunkEndTime = performance.now();\n this.totalChunkTime = this.chunkEndTime - this.chunkStartTime;\n // 🔑 无论是否有 failedChunks,始终向上抛出错误\n // 若没有 failedChunks(如 doInit 中 createWritable 失败),\n // 之前的实现会静默吞掉错误 → retry() 的 .catch() 永不触发 → 用户看不出任何反应\n throw error;\n } finally {\n // 🔑 finally 确保所有出口(正常返回 / 早期 return / 异常)都会记录结束时间\n computeTransferTime(file.proxy.transferTime).end();\n }\n }\n\n /** 子类覆写以返回不同的开始事件名 */\n protected getStartEventName(): string {\n return \"chunk-transfer-start\";\n }\n\n /**\n * 重试失败的分片\n */\n public async retryFailedChunks(): Promise<void> {\n // 🔑 递增代数,防止并发 start() 调用污染当前重试的状态变更\n const retryGen = ++this._startGeneration;\n this.isCancelled = false;\n this.isPaused = false;\n\n const failedChunksCopy = [...this.failedChunks];\n this.failedChunks = [];\n\n for (const chunkIndex of failedChunksCopy) {\n // 🔑 代数检查:如果 start() 在此期间被重新调用,立即停止重试\n if (this._startGeneration !== retryGen) break;\n\n this.retryCountMap.set(chunkIndex, 0);\n try {\n await this.chunkWithRetry(chunkIndex);\n } catch (_error) {\n if (this._startGeneration !== retryGen) break;\n this.failedChunks.push(chunkIndex);\n }\n }\n\n // 🔑 仅当代数仍然匹配时才触发统计检查(避免污染新 run)\n if (this._startGeneration === retryGen) {\n await this.checkStatistics();\n }\n }\n\n /**\n * 带超时 & 重试机制的单个分片传输\n */\n protected async chunkWithRetry(chunkIndex: number): Promise<void> {\n const maxRetries = this.retries;\n let retryCount = this.retryCountMap.get(chunkIndex) || 0;\n\n const executeOnce = async (): Promise<void> => {\n // 🔑 速率限流:传输前检查是否需要延迟\n await this.throttleBeforeChunk(this.chunkSize);\n\n const abortController = new AbortController();\n this.abortControllers.push(abortController);\n\n const timeoutId = this.timeout > 0\n ? setTimeout(() => {\n abortController.abort();\n }, this.timeout)\n : (null as any);\n\n try {\n const result = await this.doChunkTransfer(\n chunkIndex,\n abortController.signal,\n );\n if (timeoutId !== null) clearTimeout(timeoutId);\n this.removeAbortController(abortController);\n\n // 处理分片成功\n await this.handleChunkSuccess(chunkIndex, result);\n } catch (error) {\n if (timeoutId !== null) clearTimeout(timeoutId);\n this.removeAbortController(abortController);\n\n // 处理分片失败\n this.handleChunkError(chunkIndex, error);\n throw error;\n }\n };\n\n // 禁用自动重试\n if (maxRetries === null) {\n try {\n await executeOnce();\n this.retryCountMap.set(chunkIndex, 0);\n return;\n } catch (error) {\n this.failedChunks.push(chunkIndex);\n this.setFileStatusToFail();\n throw error;\n }\n }\n\n // 正常重试循环\n while (retryCount <= maxRetries) {\n // 🔑 每次重试前检查取消状态,避免取消后继续重试成功\n if (this.isCancelled) {\n logger.info(\n this.getTag(),\n `分片 ${chunkIndex + 1}/${this.totalChunks} 取消重试(传输已取消)`,\n );\n throw new Error(\"Transfer cancelled\");\n }\n\n try {\n if (retryCount > 0) {\n logger.warn(\n this.getTag(),\n `分片 ${chunkIndex + 1}/${\n this.totalChunks\n } 第 ${retryCount} 次重试`,\n );\n }\n\n await executeOnce();\n this.retryCountMap.set(chunkIndex, 0);\n\n if (retryCount > 0) {\n logger.info(\n this.getTag(),\n `分片 ${chunkIndex + 1}/${\n this.totalChunks\n } 重试成功(共 ${retryCount} 次)`,\n );\n }\n return;\n } catch (_error: any) {\n retryCount++;\n this.retryCountMap.set(chunkIndex, retryCount);\n\n if (retryCount > maxRetries) {\n this.failedChunks.push(chunkIndex);\n this.setFileStatusToFail();\n logger.error(\n this.getTag(),\n `分片 ${chunkIndex + 1}/${\n this.totalChunks\n } 最终失败(已重试 ${maxRetries} 次)`,\n );\n return;\n }\n\n // 指数退避(基数使用用户配置的 retryDelay,默认 1000ms)\n const baseDelay = this.retryDelay || 1000;\n const delay = Math.min(baseDelay * Math.pow(2, retryCount - 1), 10000);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n /**\n * 分片传输成功后的统一处理\n */\n protected async handleChunkSuccess(\n chunkIndex: number,\n result: { data: any; chunkSize: number },\n ): Promise<void> {\n const file = this.getTransferFile();\n\n if (!this.chunks[chunkIndex]) {\n // 保存分片结果(子类钩子)\n await this.doSaveChunkResult(chunkIndex, result.data);\n this.response = result.data;\n\n this.chunks[chunkIndex] = true;\n this.totalChunkSize += result.chunkSize;\n this.completedChunks++;\n this.countedChunks.add(chunkIndex);\n\n this.updateProgress();\n\n file.transfer.emit(\"chunk-success\", {\n chunkIndex,\n totalChunks: this.totalChunks,\n completedChunks: this.completedChunks,\n percent: file.proxy.percent || 0,\n file: file.proxy as any,\n });\n\n logger.info(\n this.getTag(),\n `分片 ${chunkIndex + 1}/${this.totalChunks} 传输成功`,\n {\n chunkIndex,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n percent: file.proxy.percent,\n size: result.chunkSize,\n },\n );\n }\n\n // 从失败列表移除\n const failIdx = this.failedChunks.indexOf(chunkIndex);\n if (failIdx > -1) {\n this.failedChunks.splice(failIdx, 1);\n }\n }\n\n /**\n * 分片传输失败后的统一处理\n */\n protected handleChunkError(chunkIndex: number, error: any): void {\n const file = this.getTransferFile();\n this.chunks[chunkIndex] = false;\n\n logger.error(\n this.getTag(),\n `分片 ${chunkIndex + 1}/${this.totalChunks} 传输失败`,\n {\n chunkIndex,\n error: error instanceof Error ? error.message : String(error),\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n },\n );\n\n file.transfer.emit(\"chunk-error\", {\n chunkIndex,\n totalChunks: this.totalChunks,\n error: error instanceof Error ? error.message : String(error),\n file: file.proxy as any,\n });\n throw new Error(error);\n }\n\n /**\n * 并发传输所有分片\n *\n * @param startGen - 当前 start() 调用的代数,用于过滤上一轮残留的异步回调\n */\n protected async transferWithConcurrency(startGen?: number): Promise<void> {\n const transferPromises: Promise<void>[] = [];\n const chunkStartTimes: number[] = [];\n const chunkDurations: number[] = [];\n let queuedCount = 0;\n\n if (!this.queue) {\n throw new Error(\"PQueue not initialized. Call start() first.\");\n }\n\n for (let chunkIndex = 0; chunkIndex < this.totalChunks; chunkIndex++) {\n if (this.isCancelled) {\n logger.info(this.getTag(), \"检测到取消标志,停止入队新分片\", {\n currentChunk: chunkIndex,\n totalChunks: this.totalChunks,\n });\n break;\n }\n\n // 跳过已传输的分片\n if (this.chunks[chunkIndex]) {\n continue;\n }\n\n this.chunkIndex = chunkIndex + 1;\n queuedCount++;\n\n const capturedChunkIndex = chunkIndex;\n transferPromises.push(\n this.queue.add(async () => {\n // 🔑 代数检查:如果 start() 被重新调用,跳过旧 run 的残留回调\n if (startGen !== undefined && this._startGeneration !== startGen) {\n return;\n }\n\n await this.waitForResume();\n\n chunkStartTimes[capturedChunkIndex] = performance.now();\n\n try {\n await this.chunkWithRetry(capturedChunkIndex);\n chunkDurations[capturedChunkIndex] =\n performance.now() - chunkStartTimes[capturedChunkIndex];\n } catch (_error) {\n if (chunkStartTimes[capturedChunkIndex]) {\n chunkDurations[capturedChunkIndex] =\n performance.now() - chunkStartTimes[capturedChunkIndex];\n }\n throw _error;\n }\n }),\n );\n }\n\n await Promise.allSettled(transferPromises);\n\n // 计算分片耗时统计\n const validDurations = chunkDurations.filter((d) => d > 0);\n this.chunkStats = {\n averageTime:\n validDurations.reduce((a, b) => a + b, 0) / validDurations.length || 0,\n maxTime: Math.max(...validDurations, 0),\n minTime: Math.min(...validDurations, Infinity),\n };\n }\n\n // ==================== 统计检查 & 合并 ====================\n\n /**\n * 检查统计信息并触发合并或失败处理\n */\n protected async checkStatistics(): Promise<void> {\n this.chunkEndTime = performance.now();\n this.totalChunkTime = this.chunkEndTime - this.chunkStartTime;\n\n const allSuccess = this.completedChunks === this.totalChunks;\n\n logger.debug(this.getTag(), \"检查统计信息\", {\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n allSuccess,\n failedChunks: this.failedChunks.length,\n });\n\n if (allSuccess) {\n await this.handleAllChunksSuccess();\n } else if (this.failedChunks.length > 0) {\n await this.handleFailedChunks();\n } else {\n logger.warn(this.getTag(), \"传输未完成\", {\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n }\n }\n\n /**\n * 处理所有分片成功\n */\n protected async handleAllChunksSuccess(): Promise<void> {\n const file = this.getTransferFile();\n const fileSize = file.getFileSize();\n\n this.calculateAndUpdateSpeed(fileSize);\n\n // 更新文件速率(使用 checkStatistics 已算好的 totalChunkTime,避免 chunkStartTime 被重置导致失真)\n const totalTime = this.totalChunkTime / 1000;\n const averageSpeed = totalTime > 0 ? fileSize / totalTime : 0;\n file.proxy.speed = {\n currentSpeed: 0,\n averageSpeed,\n currentSpeedFormatted: \"0 B/s\",\n averageSpeedFormatted: formatSpeed(averageSpeed),\n estimatedTimeRemaining: 0,\n estimatedTimeFormatted: \"已完成\",\n };\n\n try {\n // 🔑 分片全部传输完成,进入合并阶段\n file.proxy.status = \"merging\";\n file.transfer.emit(\"merging\", {\n file: file.proxy as any,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n\n const mergeResult = await this.doMergeChunks();\n\n await this.doBeforeOnSuccess(mergeResult);\n\n // 🔑 合并 + 保存全部完成后才推到 100%\n file.proxy.percent = 100;\n\n file.onSuccess(mergeResult);\n } catch (error) {\n file.onError(error);\n }\n\n this.calculateStats();\n }\n\n /**\n * 处理失败分片\n */\n protected async handleFailedChunks(): Promise<void> {\n const file = this.getTransferFile();\n\n if (this.retries === null) {\n logger.warn(\n this.getTag(),\n `发现 ${this.failedChunks.length} 个失败分片,自动重试已禁用`,\n );\n file.onError(new Error(\"部分分片传输失败\"));\n return;\n }\n\n logger.warn(\n this.getTag(),\n `发现 ${this.failedChunks.length} 个失败分片,开始自动重试`,\n );\n await this.retryFailedChunks();\n\n if (this.failedChunks.length > 0) {\n file.onError(new Error(\"部分分片传输失败,重试后仍未成功\"));\n } else {\n await this.checkStatistics();\n }\n }\n\n // ==================== 辅助方法 ====================\n\n /**\n * 设置文件状态为失败\n */\n protected setFileStatusToFail(): void {\n if (this.retries === null) {\n setTimeout(() => {\n this.getTransferFile().proxy.status = \"fail\";\n }, 0);\n }\n }\n\n /**\n * 等待恢复(暂停/恢复机制)\n */\n protected async waitForResume(): Promise<void> {\n if (!this.isPaused) return;\n\n await new Promise<void>((resolve) => {\n this.pauseResolves.push(() => resolve());\n });\n\n if (this.isCancelled) {\n logger.info(this.getTag(), \"分片传输被取消,停止执行\");\n throw new Error(\"Transfer cancelled\");\n }\n }\n\n // ==================== 速率限制 ====================\n\n /**\n * 下载速率限流:在传输每个分片前检查是否需要延迟\n *\n * 采用平均速率算法:累计已传输字节数 / 已用时间 必须 ≤ maxSpeed。\n * 如果当前速率超过限制,sleep 差值后继续。\n *\n * @param chunkSize - 待传输分片的预计大小(字节)\n */\n protected async throttleBeforeChunk(chunkSize: number): Promise<void> {\n if (this.maxSpeed <= 0) return;\n\n const now = performance.now();\n\n if (this._throttleStartTime === 0) {\n this._throttleStartTime = now;\n this._throttleBytes = 0;\n }\n\n // 预累加本分片大小\n this._throttleBytes += chunkSize;\n\n // 理想耗时(ms)= 累计字节数 / 目标速率 * 1000\n const idealElapsed = (this._throttleBytes / this.maxSpeed) * 1000;\n const actualElapsed = now - this._throttleStartTime;\n\n if (actualElapsed < idealElapsed) {\n const delay = idealElapsed - actualElapsed;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n /**\n * 从活跃控制器列表中移除指定的 AbortController\n */\n protected removeAbortController(controller: AbortController): void {\n const idx = this.abortControllers.indexOf(controller);\n if (idx > -1) this.abortControllers.splice(idx, 1);\n }\n\n /**\n * 合并完成后的收尾(可分片数量完整时直接走合并流程)\n */\n protected async completeMerge(): Promise<void> {\n const file = this.getTransferFile();\n\n // 🔑 进入合并阶段\n file.proxy.status = \"merging\";\n file.transfer.emit(\"merging\", {\n file: file.proxy as any,\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n });\n\n try {\n const mergeResult = await this.doMergeChunks();\n this.chunkEndTime = performance.now();\n this.totalChunkTime = this.chunkEndTime - this.chunkStartTime;\n this.calculateStats();\n\n // 🔑 同步更新 proxy.speed(与 handleAllChunksSuccess 一致),修复秒下路径中\n // markInstantDownload() 将 speed 置零后 completeMerge() 只算 chunkStatsInfos\n // 却不更新 proxy.speed → UI 显示陈旧/错误的速率\n const fileSize = file.getFileSize();\n const totalTime = this.totalChunkTime / 1000;\n const averageSpeed = totalTime > 0 ? fileSize / totalTime : 0;\n file.proxy.speed = {\n currentSpeed: 0,\n averageSpeed,\n currentSpeedFormatted: \"0 B/s\",\n averageSpeedFormatted: formatSpeed(averageSpeed),\n estimatedTimeRemaining: 0,\n estimatedTimeFormatted: \"已完成\",\n };\n\n await this.doBeforeOnSuccess(mergeResult);\n\n // 🔑 合并 + 保存全部完成后才推到 100%\n file.proxy.percent = 100;\n\n file.onSuccess(mergeResult);\n } catch (error) {\n this.chunkEndTime = performance.now();\n this.totalChunkTime = this.chunkEndTime - this.chunkStartTime;\n file.onError(error);\n }\n }\n}\n","import ChunkManager from \"../chunkManager\";\nimport { IFile, speedInfo, TimeInfo } from \"../types\";\nimport Transfer from \"./Transfer\";\nimport {\n computeTransferTime,\n formatDuration,\n formatFileSize,\n formatSpeed,\n generateFileId,\n logger,\n} from \"../utils\";\nimport { FileUDError, ErrorCode } from \"../fileUD/errors\";\nimport UploadFile from \"../uploader/UploadFile\";\nimport Uploader from \"../uploader\";\nimport DownloadFile from \"../downloader/DownloadFile\";\nimport { XHRInterceptor } from \"../xhr-intercepto\";\nimport FileConcurrencyController from \"../concurrency/FileConcurrencyController\";\n\nexport default class TransferFile<T extends TransferFile<T, any>, D = any> {\n /**\n * 上传速率统计信息\n * 包含瞬时速度、平均速度及其格式化字符串\n * 通过 Proxy 自动触发全局速率聚合\n */\n public speed: speedInfo = {\n currentSpeed: 0,\n averageSpeed: 0,\n currentSpeedFormatted: \"0 B/s\",\n averageSpeedFormatted: \"0 B/s\",\n estimatedTimeRemaining: -1,\n estimatedTimeFormatted: \"计算中...\",\n };\n\n /**\n * 上传下载时间统计信息\n * 记录文件从开始到结束的完整耗时\n * 通过 Proxy 自动触发 UI 更新\n */\n public transferTime: TimeInfo = {\n startTime: 0,\n endTime: 0,\n duration: 0,\n durationFormatted: \"0s\",\n };\n\n /** 文件唯一标识符 */\n fileId: string;\n /** Proxy 代理对象,用于实现响应式更新 */\n proxy!: T;\n\n /** 文件预览 URL (Object URL) */\n url: string;\n\n /** 文件名称 */\n fileName: string;\n\n /** 原始 File 对象 */\n File: File;\n\n /** 上传进度百分比 (0-100) */\n percent: number | undefined;\n /** 所属的 Uploader 实例 */\n public transfer: Transfer;\n /** 是否正在上传 */\n loading: boolean;\n\n /** 文件传输状态 */\n status: IFile[\"status\"];\n isCancel: boolean = false;\n /** 文件扩展名 */\n extension: string | undefined;\n\n public hashPercent = 0;\n public hashLoading = false;\n /** 格式化后的文件大小,如 \"5.23 MB\" */\n formatSize: string | undefined;\n\n /** 当前文件已上传的大小(格式化字符串),如 \"45.23 MB\" */\n transferFormatSize: string = \"0 B\";\n context: any;\n /** 表单数据对象,用于携带上传参数 */\n formData: FormData | null = null;\n chunkManager: ChunkManager | null = null;\n /** 是否处于重试状态 */\n isRetry?: boolean;\n size: number = 0;\n /** Promise resolve 回调引用 */\n resolve: ((value: any) => void | undefined) | undefined;\n\n /** Promise reject 回调引用 */\n reject: ((reason?: any) => void | undefined) | undefined;\n\n /** 文件在队列中的索引 */\n index?: number;\n /** 插件可写入的自定义元数据 */\n metadata?: Record<string, any>;\n sub: T | null;\n /** 取消上传的函数 */\n abort: IFile[\"abort\"];\n /** 标记是否已计入总字节数(避免重试时重复累加) */\n public __hasCountedTotalBytes__: boolean = false;\n /** 当前文件已上传的字节数(用于普通上传计算总进度) */\n public __transferBytes__: number = 0;\n public type: string = \"\";\n\n // ==================== 速率计算内部状态 ====================\n\n /** 上次计算速率的时间戳 (毫秒) */\n protected lastUpdateTime: number = 0;\n\n /** 上次已传输的字节数 */\n protected lastTransferBytes: number = 0;\n\n /** 传输开始的时间戳 (毫秒) */\n protected transferStartTime: number = 0;\n constructor(file: IFile, transfer: Transfer) {\n this.fileId = generateFileId();\n this.url = file.url;\n this.transfer = transfer;\n this.fileName = file.fileName;\n this.File = file.File!;\n this.percent = file.percent;\n this.size = file.size || this.File?.size || 0;\n this.status = file.status;\n this.loading = false;\n this.extension = file.extension;\n this.sub = null;\n this.formatSize = file.formatSize;\n this.abort = file.abort;\n this.index = file.index!;\n this.metadata = file.metadata;\n this.isRetry = file.isRetry || false;\n }\n // 判断类型的辅助方法\n public isUploader(): boolean {\n return this.transfer.constructor.name === \"Uploader\";\n }\n\n public isDownloader(): boolean {\n return this.transfer.constructor.name === \"Downloader\";\n }\n /**\n * 开始传输\n *\n * 统一的传输入口,自动根据配置选择分片传输或普通传输:\n * - **分片传输**: 调用 chunkManager.start()\n * - **普通传输**: 由子类实现具体逻辑\n *\n * @example\n * ```typescript\n * // 手动传输模式\n * const file = uploader.addFile(fileObject);\n *\n * // 用户点击按钮时\n * button.onclick = () => {\n * file.start();\n * };\n * ```\n *\n * @remarks\n * - 如果文件已经在传输中,会忽略此次调用\n * - 如果文件已传输成功,会抛出警告\n * - 这是异步操作,建议在 UI 上显示加载状态\n */\n public async start(chunkManager: ChunkManager | null): Promise<D> {\n // 分片传输\n const isDownload = this.isDownloader();\n const type = isDownload ? \"下载\" : \"上传\";\n\n return new Promise<D>((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n\n (async () => {\n try {\n // 🔑 文件级并发控制:上传/下载各自独立并发池(可分别设置或共享)\n const controller = FileConcurrencyController.getInstance();\n const runTask = isDownload\n ? controller.runAsDownload.bind(controller)\n : controller.runAsUpload.bind(controller);\n\n await runTask(async () => {\n if (chunkManager) {\n logger.info(\n isDownload ? \"DownloadFile\" : \"UploadFile\",\n `开始分片${type}: ${this.fileName}`,\n {\n fileId: this.fileId,\n fileName: this.fileName,\n },\n );\n await (chunkManager as any).start();\n } else {\n logger.info(\n isDownload ? \"DownloadFile\" : \"UploadFile\",\n `开始普通${type}: ${this.fileName}`,\n {\n fileId: this.fileId,\n fileName: this.fileName,\n },\n );\n let data: D;\n if (this instanceof UploadFile) {\n data = await this.upload();\n } else {\n // 下载\n data = await (this as unknown as DownloadFile).download();\n }\n resolve(data);\n }\n });\n } catch (error) {\n logger.error(\n isDownload ? \"DownloadFile\" : \"UploadFile\",\n `文件 ${this.fileName} ${type}失败`,\n error,\n );\n this.onError(error);\n reject(error);\n }\n })();\n });\n }\n\n // ==================== 取消 / 暂停 / 恢复 / 重试(基类统一实现) ====================\n\n /**\n * 取消当前传输\n */\n public cancel(fn: ((next: () => void) => void) | void): void {\n console.log(\n `[TransferFile] cancel() 被调用, fileName=${this.fileName}, proxyStatus=${this.proxy.status}`,\n );\n\n if (this.proxy.status !== \"UDLoading\" && this.proxy.status !== \"paused\") {\n const type = this.isDownloader() ? \"下载\" : \"上传\";\n console.warn(`[TransferFile] 没有需要取消的${type}: ${this.fileName}, 当前状态=${this.proxy.status}`);\n return;\n }\n\n console.log(`[TransferFile] ✅ 状态检查通过,执行取消: ${this.fileName}`);\n\n // 🔑 标记取消状态,防止 onError() 中被 abort 触发的异步错误覆盖 proxy.status\n this.isCancel = true;\n this.proxy.isCancel = true;\n this.proxy.status = \"cancelled\";\n\n const next = () => {\n const cm = this.getChunkManager();\n if (cm) {\n console.log(`[TransferFile] 调用 chunkManager.cancel(): ${this.fileName}`);\n cm.cancel();\n } else {\n console.log(`[TransferFile] 无 chunkManager,调用 abort(): ${this.fileName}`);\n this.abort?.();\n }\n this.transfer.totalTransferredBytes -= this.getFileSize();\n this.transfer.emit(\"cancel\", this.proxy as any);\n console.log(`[TransferFile] cancel() 完成, proxyStatus=${this.proxy.status}`);\n };\n\n fn ? fn(next) : next();\n }\n\n /**\n * 暂停传输(仅分片传输支持)\n */\n public pause(): void {\n if (this.proxy.status !== \"UDLoading\") {\n console.warn(\n `文件 ${this.fileName} 当前状态为 ${this.proxy.status},无法暂停`,\n );\n return;\n }\n\n const cm = this.getChunkManager();\n if (cm) {\n (cm as any).pause();\n } else {\n console.warn(\"该模式不支持暂停\", this.fileName);\n }\n this.transfer.emit(\"pause\", this.proxy as any);\n }\n\n /**\n * 恢复传输(仅分片传输支持)\n */\n public async resume(): Promise<void> {\n if (this.proxy.status !== \"paused\") {\n console.warn(\n `文件 ${this.fileName} 当前状态为 ${this.proxy.status},无法恢复`,\n );\n return;\n }\n\n const cm = this.getChunkManager();\n if (cm) {\n await (cm as any).resume();\n } else {\n console.warn(\"该模式不支持恢复\", this.fileName);\n }\n this.transfer.emit(\"resume\", this.proxy as any);\n }\n\n /**\n * 重试传输\n */\n public async retry(): Promise<void> {\n console.log(\n `[TransferFile] retry() 被调用, fileName=${this.fileName}, proxyStatus=${this.proxy.status}`,\n );\n\n if (![\"cancelled\", \"fail\", \"error\"].includes(this.proxy.status!)) {\n const type = this.isDownloader() ? \"下载\" : \"上传\";\n console.warn(\n `[TransferFile] 没有需要重试的${type}: ${this.fileName}, 当前状态=${this.proxy.status}`,\n );\n return;\n }\n\n console.log(`[TransferFile] ✅ 状态检查通过,开始重试: ${this.fileName}`);\n\n const cm = this.getChunkManager();\n console.log(`[TransferFile] getChunkManager() 返回:`, cm ? \"有(分片模式)\" : \"null(普通模式)\");\n\n if (cm) {\n try {\n const hasFailedChunks = cm.getFailedChunksCount() > 0;\n const allChunksCompleted = cm.completedChunks === cm.totalChunks;\n\n console.log(\n `[TransferFile] 重试详情: hasFailedChunks=${hasFailedChunks}, ` +\n `allChunksCompleted=${allChunksCompleted}, ` +\n `completedChunks=${cm.completedChunks}/${cm.totalChunks}, ` +\n `isCancelled=${(cm as any).isCancelled}, isPaused=${(cm as any).isPaused}`,\n );\n\n logger.info(\n this.isDownloader() ? \"DownloadFile\" : \"UploadFile\",\n `文件 ${this.fileName} 开始重试`,\n {\n fileId: this.fileId,\n fileName: this.fileName,\n status: this.proxy.status,\n completedChunks: cm.completedChunks,\n totalChunks: cm.totalChunks,\n hasFailedChunks,\n allChunksCompleted,\n },\n );\n\n // 🔑 重置取消/重试标志,清除上一次 cancel 的残留状态\n this.isCancel = false;\n this.proxy.isCancel = false;\n this.proxy.isRetry = true;\n this.proxy.percent = 0;\n this.proxy.status = \"UDLoading\";\n\n console.log(`[TransferFile] ✅ 已重置标志, proxyStatus=${this.proxy.status}`);\n\n // 🔑 始终走 start() 路径,统一初始化入口\n // 原因:取消后的异步清理可能残留 failedChunks,若走 retryFailedChunks()\n // 会绕过 doAfterStartReset/doInit/writable 创建 → retry 完全无效。\n // retryAll 能工作正是因为调用时机更早,失败分片还没入队。\n console.log(\n `[TransferFile] 调用 chunkManager.start(), completedChunks=${cm.completedChunks}, totalChunks=${cm.totalChunks}`,\n );\n await (cm as any).start().catch((err: any) => {\n console.error(`[TransferFile] ❌ start() 抛出异常:`, err);\n logger.error(\n this.isDownloader() ? \"DownloadFile\" : \"UploadFile\",\n `文件 ${this.fileName} 重试失败`,\n err,\n );\n this.onError(err);\n });\n console.log(`[TransferFile] start() 完成, proxyStatus=${this.proxy.status}`);\n } catch (syncErr: any) {\n console.error(`[TransferFile] ❌ retry() 同步异常:`, syncErr);\n this.onError(syncErr);\n }\n } else {\n logger.info(\n this.isDownloader() ? \"DownloadFile\" : \"UploadFile\",\n `文件 ${this.fileName} 开始普通重试`,\n );\n // 🔑 重置取消标志,与分片路径保持一致。否则 download() 内保存文件失败时\n // isCancel 仍为 true → onError 被跳过 → status 残留 \"success\" 但 percent 未到 100\n this.isCancel = false;\n this.proxy.isCancel = false;\n this.proxy.isRetry = true;\n this.proxy.percent = 0;\n this.proxy.status = \"UDLoading\";\n // 🔑 cancel 的 finally 清理已将文件移出 activeFiles,\n // 重试时需要重新加入,否则 onSuccess 中统计和 files-complete 事件会错乱\n if (!this.transfer.activeFiles.includes(this as any)) {\n this.transfer.activeFiles.push(this as any);\n }\n\n await this.doRetryTransfer().catch((err: any) => {\n logger.error(\n this.isDownloader() ? \"DownloadFile\" : \"UploadFile\",\n `文件 ${this.fileName} 重试失败`,\n err,\n );\n this.onError(err);\n });\n }\n this.proxy.isRetry = false;\n this.transfer.emit(\"retry\", this.proxy as any);\n }\n\n /**\n * 执行普通传输的重试(模板方法,由子类覆写)\n * - UploadFile: 重试上传 → 调用 upload()\n * - DownloadFile: 重试下载 → 调用 download()\n */\n protected async doRetryTransfer(): Promise<any> {\n // 子类覆写\n }\n\n public onSuccess(res: D) {\n const isDownload = this.isDownloader();\n\n // ✅ 基类统一设置传输成功状态(普通/分片 上传/下载 均走此路径)\n this.proxy.status = \"success\";\n\n this.transfer[\"runHook\"](\"onSuccess\", res, this, this.transfer);\n\n this.transfer.successCallback?.(res, this.proxy);\n\n // 🔑 只在文件确实在 activeFiles 中时才移除,避免 splice(-1, 1) 误删其他文件\n const afIdx = this.transfer.activeFiles.indexOf(this);\n if (afIdx !== -1) {\n this.transfer.activeFiles.splice(afIdx, 1);\n }\n if (!isDownload) {\n (this.transfer as unknown as Uploader<T>)?.remObjectUrls?.(this.url);\n }\n\n // 添加 fileId 到日志参数中,供监控模块提取\n logger.info(\n isDownload ? \"DownloadFile\" : \"UploadFile\",\n `文件传输成功: ${this.fileName}`,\n {\n fileId: this.fileId,\n fileName: this.fileName,\n fileSize: this.File?.size ?? this.size ?? 0,\n },\n );\n\n // ✅ 关键修复:更新全局统计信息(总进度、总大小)\n this.transfer.updateGlobalStats();\n this.transfer.triggerUpdate();\n\n if (!this.transfer.activeFiles.length) {\n this.transfer.emit(\"files-complete\", this.transfer.files);\n console.log(\"所有文件传输完成\");\n\n computeTransferTime(this.transfer.transferTime).end();\n this.transfer.transferTime.startTime = 0;\n\n if (this.sub) {\n (this.sub as any).currentQueue = [];\n (this.sub as any).assignedFiles = new Set<T>();\n (this.sub as any).xhrToFileMap = new WeakMap<XMLHttpRequest, T>();\n (this.sub as any).interceptor.assignedFiles = new Set<T>();\n (this.sub as any).interceptor.restore();\n }\n }\n }\n\n public onError(err: any) {\n const up = this.transfer;\n const isDownload = this.isDownloader();\n if (this.isCancel) {\n logger.warn(\n isDownload ? \"DownloadFile\" : \"UploadFile\",\n `文件传输被取消: ${this.fileName}`,\n {\n fileId: this.fileId,\n fileName: this.fileName,\n fileSize: this.File?.size ?? this.size ?? 0,\n },\n );\n return;\n }\n\n // 添加 fileId 到错误日志中,供监控模块提取\n logger.error(\n isDownload ? \"DownloadFile\" : \"UploadFile\",\n `文件传输失败: ${this.fileName}`,\n {\n fileId: this.fileId,\n fileName: this.fileName,\n fileSize: this.File?.size ?? this.size ?? 0,\n error: err.message || err,\n },\n );\n\n // ✅ 修复:移除不存在的 this.up 和 this.context 引用\n // 错误处理应该由 transfer 实例来处理\n this.status = \"error\";\n\n // 触发插件 onError 钩子(upload/download 共享)\n const context = {\n transfer: up,\n file: this,\n shared: up.pluginSharedData,\n config: (up as any).config,\n };\n up.runHook(\"onError\", err, this, context);\n\n up.emit(\n \"error\",\n new FileUDError(\n isDownload ? ErrorCode.DOWNLOAD_FAILED : ErrorCode.UPLOAD_FAILED,\n err,\n ).toJSON(),\n );\n }\n\n public initInterceptor(): void {\n const instance = this.constructor as any;\n this.sub = instance;\n if (instance.interceptor) return;\n\n instance.interceptor = XHRInterceptor.getInstance<T>(\n this.transfer.constructor.name,\n {\n name: this.transfer.constructor.name,\n\n getGlobalHeaders: (file: any) => {\n const configHeaders =\n (file.constructor.name === \"UploadFile\" ? file.up : file.dl)?.config\n ?.headers || {};\n\n // 🔑 分片下载时,从队列 shift 取出当前 XHR 对应的 Range 头\n // 并发安全:每个 XHR 的 send() 消费一个 entry,不会互相覆盖\n const chunkHeaders =\n file._chunkHeadersQueue?.length > 0\n ? file._chunkHeadersQueue.shift()!\n : {};\n\n return { ...configHeaders, ...chunkHeaders };\n },\n\n onProgress: (file, event) => {\n file.handleProgress(event);\n },\n\n onAbort: (file, xhr) => {\n // 🔑 累加模式:并发分片会产生多个 XHR,file.abort 需能取消所有\n if (!(file as any).__xhrAbortCallbacks) {\n (file as any).__xhrAbortCallbacks = [];\n }\n (file as any).__xhrAbortCallbacks.push(() => {\n if (xhr.readyState !== XMLHttpRequest.DONE) {\n xhr.abort();\n }\n });\n file.abort = () => {\n const callbacks = (file as any).__xhrAbortCallbacks || [];\n callbacks.forEach((fn: () => void) => {\n try {\n fn();\n } catch (_) {}\n });\n (file as any).__xhrAbortCallbacks = [];\n // 注意:不在此设置 isCancel / status,由 TransferFile.cancel() 统一管理\n // 超时触发的 abort 只需要取消 XHR 请求,不应标记文件为已取消(chunkWithRetry 会重试)\n };\n },\n\n getFileQueue: () => instance.currentQueue,\n getFileFromXHR: (xhr) => instance.xhrToFileMap.get(xhr) || null,\n // ✅ 设置 XHR 与文件的映射\n setFileToXHR: (xhr, file) => {\n instance.xhrToFileMap.set(xhr, file);\n },\n markFileAsAssigned: (file) => instance.assignedFiles.add(file),\n\n unmarkFileAsAssigned: (file) => instance.assignedFiles.delete(file),\n },\n );\n }\n\n // ==================== 进度 & 速率计算 ====================\n\n /**\n * 处理传输进度事件(供 XHRInterceptor 回调)\n *\n * 统一流程:\n * 1. 调用子类的 {@link updateLocalProgress} 更新当前文件的本体进度/字节数\n * 2. 更新格式化后的已传输大小\n * 3. 调用子类的 {@link calculateGlobalProgress} 计算全局统计\n * 4. 调用 {@link calculateSpeed} 计算并更新速率\n * 5. 发射 progress 事件\n *\n * @param event - XHR 进度事件对象\n */\n public handleProgress(event: ProgressEvent): void {\n const transfer = this.transfer;\n\n // 1. 子类差异化:更新当前文件进度/字节数\n this.updateLocalProgress(event);\n\n // 2. 更新格式化后的已传输大小\n this.proxy.transferFormatSize = formatFileSize(this.__transferBytes__);\n\n // 3. 子类差异化:计算全局进度\n this.calculateGlobalProgress(event);\n\n // 4. 计算速率\n // 🔑 分片传输由 ChunkManager 独立管控进度和速度,XHR 进度事件的 loadedBytes\n // 只反映单个分片(且 uploadChunkManager 场景下 __transferBytes__ 恒为 0),\n // 若在此计算会覆盖 ChunkManager 写入的正确速度,导致列表行一直显示\"计算中...\"\n if (!this.getChunkManager()) {\n this.calculateSpeed(this.__transferBytes__);\n }\n\n // 5. 发射进度事件\n transfer.emit(\"progress\", transfer.totalPercent);\n\n // 6. 触发插件 onProgress 钩子\n const context = {\n transfer,\n file: this,\n shared: transfer.pluginSharedData,\n config: (transfer as any).config,\n };\n transfer.runHook(\"onProgress\", this.proxy.percent, this, context);\n }\n\n /**\n * 更新当前文件的进度和已传输字节数(模板方法,由子类实现)\n *\n * - **普通传输**:从 event.loaded/event.total 直接计算百分比\n * - **分片传输**:由分片管理器独立维护进度,此处无需额外操作\n *\n * @param event - XHR 进度事件对象\n */\n protected updateLocalProgress(_event: ProgressEvent): void {\n // 子类重写\n }\n\n /**\n * 获取文件对应的分片管理器(模板方法,由子类实现)\n * - UploadFile 返回 uploadChunkManager\n * - DownloadFile 返回 downloadChunkManager\n */\n protected getChunkManager(): ChunkManager | null {\n return null;\n }\n\n /**\n * 获取文件大小(字节数),兼容上传(有 File 对象)和下载(无 File 对象)场景\n */\n public getFileSize(): number {\n return this.File?.size || this.size || 0;\n }\n\n /**\n * 计算全局传输进度(统一实现,子类只需覆写 getChunkManager / getFileSize)\n *\n * 汇总所有活动文件的传输进度,更新 transfer 实例上的全局统计\n *\n * @param _event - XHR 进度事件对象\n */\n protected calculateGlobalProgress(_event: ProgressEvent): void {\n const transfer = this.transfer;\n\n let totalTransferredBytes = 0;\n let currentTotalBytes = 0;\n let totalFilesSize = 0;\n\n transfer.files.forEach((file) => {\n const fileSize = file.getFileSize();\n if (fileSize > 0) {\n totalFilesSize += fileSize;\n }\n\n // 只统计活跃中的文件,避免已完成文件的字节被重复累加\n const isActive = this.getChunkManager()\n ? [\"UDLoading\", \"paused\", \"fail\"].includes(file.status!)\n : file.status === \"UDLoading\" || file.status === \"paused\";\n\n if (isActive) {\n currentTotalBytes += fileSize;\n\n const chunkManager = file.getChunkManager();\n if (chunkManager) {\n // 分片传输:使用 chunkManager 的已传输字节数\n totalTransferredBytes += chunkManager.totalChunkSize;\n } else {\n // 普通传输:使用 __transferBytes__\n totalTransferredBytes += file.__transferBytes__ || 0;\n }\n }\n });\n\n transfer.transferredBytes = totalTransferredBytes;\n transfer.totalBytes = totalFilesSize;\n transfer.totalFormatSize = formatFileSize(totalFilesSize);\n\n // 🔑 总进度 = 所有文件按大小加权的平均百分比(不区分活跃/已完成)\n // 使用与 updateGlobalStats() 一致的加权算法,避免已完成文件被忽略\n // 导致 totalPercent 只反映活跃文件进度\n if (totalFilesSize > 0) {\n const weightedSum = transfer.files.reduce((sum, file) => {\n const fileSize = file.getFileSize();\n return sum + (file.percent || 0) * fileSize;\n }, 0);\n transfer.totalPercent = Math.min(\n 100,\n Math.round(weightedSum / totalFilesSize),\n );\n } else {\n // 未知总大小(如下载无 Content-Length 头):使用当前文件的 percent\n transfer.totalPercent = this.proxy.percent || 0;\n }\n }\n\n /**\n * 计算传输速率(带防抖优化)\n *\n * 采用最小时间间隔采样策略,避免高频进度回调导致数值剧烈抖动。\n * 瞬时速度基于最近两个有效采样点计算,平均速度基于总耗时和总字节数计算。\n *\n * @param loadedBytes - 当前已传输的字节数\n */\n protected calculateSpeed(loadedBytes: number): void {\n const now = Date.now();\n\n // 初始化传输开始时间(首次调用)\n if (this.transferStartTime === 0) {\n this.transferStartTime = now;\n this.lastUpdateTime = now;\n this.lastTransferBytes = loadedBytes;\n return;\n }\n\n // 防抖:最小时间间隔采样(100ms)\n // 避免高频回调导致计算开销过大和数值抖动\n const timeDiff = now - this.lastUpdateTime;\n if (timeDiff < 100) {\n return;\n }\n\n // 计算瞬时速度(bytes/s)\n // 公式:(当前字节 - 上次字节) / 时间差(秒)\n const bytesDiff = loadedBytes - this.lastTransferBytes;\n const currentSpeed = (bytesDiff / timeDiff) * 1000;\n\n // 计算平均速度(bytes/s)\n // 公式:总字节 / 总耗时(秒)\n const totalTime = now - this.transferStartTime;\n const averageSpeed = totalTime > 0 ? (loadedBytes / totalTime) * 1000 : 0;\n\n // 更新速率信息到 Proxy 对象\n // 通过 Proxy.set 陷阱自动触发 Transfer.triggerUpdate()\n // 计算预计剩余时间(基于平均速度)\n const fileSize = this.getFileSize();\n const remainingBytes = Math.max(0, fileSize - loadedBytes);\n let estimatedTimeRemaining = -1;\n let estimatedTimeFormatted = \"计算中...\";\n\n if (averageSpeed > 0 && remainingBytes > 0) {\n estimatedTimeRemaining = Math.ceil(remainingBytes / averageSpeed);\n estimatedTimeFormatted = formatDuration(estimatedTimeRemaining * 1000);\n } else if (remainingBytes <= 0) {\n estimatedTimeRemaining = 0;\n estimatedTimeFormatted = \"即将完成\";\n }\n\n this.proxy.speed = {\n currentSpeed,\n averageSpeed,\n currentSpeedFormatted: formatSpeed(currentSpeed),\n averageSpeedFormatted: formatSpeed(averageSpeed),\n estimatedTimeRemaining,\n estimatedTimeFormatted,\n };\n\n // 更新内部状态,为下次计算做准备\n this.lastUpdateTime = now;\n this.lastTransferBytes = loadedBytes;\n }\n}\n","import Uploader from \"../uploader\";\nimport { formatFileSize } from \"../utils\";\n/**\n * 错误码枚举(所有可能的错误)\n */\nexport enum ErrorCode {\n // ==================== 通用错误 (1000-1999) ====================\n /** 未知错误 */\n UNKNOWN = 1000,\n /** 操作已中止 */\n ABORTED = 1001,\n /** 请求超时 */\n TIMEOUT = 1002,\n /** 网络错误 */\n NETWORK = 1003,\n\n // ==================== 文件验证错误 (2000-2999) ====================\n /** 文件过大 */\n FILE_TOO_LARGE = 2000,\n /** 文件过小 */\n FILE_TOO_SMALL = 2001,\n /** 文件类型无效 */\n INVALID_TYPE = 2002,\n /** 文件数量超限 */\n FILE_LIMIT_EXCEEDED = 2003,\n /** 重复的文件 */\n DUPLICATE_FILE = 2004,\n /** 文件为空 */\n FILE_EMPTY = 2005,\n /** 文件已损坏 */\n FILE_CORRUPTED = 2006,\n /** 文件过于空泛(可能缺少必要内容) */\n FILE_TOO_EMPTY = 2007,\n\n // ==================== 图片验证错误 (2100-2199) ====================\n /** 图片宽度不符合要求 */\n IMAGE_WIDTH_INVALID = 2100,\n /** 图片高度不符合要求 */\n IMAGE_HEIGHT_INVALID = 2101,\n /** 图片宽高比不符合要求 */\n IMAGE_ASPECT_RATIO_INVALID = 2102,\n /** 图片分辨率不符合要求 */\n IMAGE_RESOLUTION_INVALID = 2103,\n /** 图片不是正方形 */\n IMAGE_NOT_SQUARE = 2104,\n /** 不支持动态图片 */\n IMAGE_ANIMATED = 2105,\n\n // ==================== 视频验证错误 (2200-2299) ====================\n /** 视频时长不符合要求 */\n VIDEO_DURATION_INVALID = 2200,\n /** 视频宽度不符合要求 */\n VIDEO_WIDTH_INVALID = 2201,\n /** 视频高度不符合要求 */\n VIDEO_HEIGHT_INVALID = 2202,\n /** 视频比特率不符合要求 */\n VIDEO_BITRATE_INVALID = 2203,\n /** 视频编码格式不支持 */\n VIDEO_CODEC_INVALID = 2204,\n\n // ==================== 上传错误 (3000-3999) ====================\n /** 上传失败 */\n UPLOAD_FAILED = 3000,\n /** 分片上传失败 */\n CHUNK_UPLOAD_FAILED = 3001,\n /** 合并分片失败 */\n MERGE_FAILED = 3002,\n /** 服务器错误 */\n SERVER_ERROR = 3003,\n /** 未授权访问 */\n UNAUTHORIZED = 3004,\n /** 禁止访问 */\n FORBIDDEN = 3005,\n /** 资源不存在 */\n NOT_FOUND = 3006,\n\n // ==================== 下载错误 (4000-4999) ====================\n /** 下载失败 */\n DOWNLOAD_FAILED = 4000,\n /** 分片下载失败 */\n CHUNK_DOWNLOAD_FAILED = 4001,\n\n // ==================== 插件错误 (5000-5999) ====================\n /** 插件错误 */\n PLUGIN_ERROR = 5000,\n /** 插件初始化失败 */\n PLUGIN_INIT_FAILED = 5001,\n /** 插件执行失败 */\n PLUGIN_EXECUTION_FAILED = 5002,\n}\n\n/**\n * 错误级别\n */\nexport enum ErrorLevel {\n INFO = \"info\", // 提示信息,不影响流程\n WARNING = \"warn\", // 警告,但继续执行\n ERROR = \"error\", // 错误,中断当前操作\n CRITICAL = \"critical\", // 致命错误,整个实例不可用\n}\n\n/**\n * 错误上下文(包含现场信息)\n */\nexport interface ErrorContext {\n /** 发生时间 */\n timestamp?: number;\n\n /** 插件名称 */\n plugin?: string;\n\n /** 上传器实例 */\n uploader?: Uploader;\n\n /* \n 参数选项\n */\n options?: Record<string, any>;\n\n /** 文件名 */\n fileName?: string;\n\n /** 文件大小 */\n fileSize?: number;\n\n /** 分片索引 */\n chunkIndex?: number;\n\n /** HTTP 状态码 */\n httpStatus?: number;\n\n /** 重试次数 */\n retryCount?: number;\n\n /** 原始错误 */\n originalError?: any;\n\n /** 自定义字段 */\n [key: string]: any;\n}\n\n/**\n * 错误配置\n */\nexport interface ErrorOptions {\n /** 是否可恢复 */\n recoverable?: boolean;\n\n /** 是否自动重试 */\n retryable?: boolean;\n\n /** 建议操作 */\n suggestion?: string;\n\n /** 是否显示给用户 */\n userVisible?: boolean;\n\n /** 国际化key */\n i18nKey?: string;\n}\nexport interface FileUDErrorJSON {\n name: string;\n code: ErrorCode;\n level: ErrorLevel;\n message: string;\n context: ErrorContext;\n options: ErrorOptions;\n stack?: string;\n}\n\n/**\n * 错误码到中文描述的映射表(静态常量,避免重复创建)\n */\nconst ERROR_CODE_DESCRIPTIONS: Readonly<Record<number, string>> = {\n // 通用错误\n [ErrorCode.UNKNOWN]: \"未知错误\",\n [ErrorCode.ABORTED]: \"操作已中止\",\n [ErrorCode.TIMEOUT]: \"请求超时\",\n [ErrorCode.NETWORK]: \"网络错误\",\n\n // 文件验证错误\n [ErrorCode.FILE_TOO_LARGE]: \"文件过大\",\n [ErrorCode.FILE_TOO_SMALL]: \"文件过小\",\n [ErrorCode.INVALID_TYPE]: \"文件类型无效\",\n [ErrorCode.FILE_LIMIT_EXCEEDED]: \"文件数量超限\",\n [ErrorCode.DUPLICATE_FILE]: \"重复的文件\",\n [ErrorCode.FILE_EMPTY]: \"文件为空\",\n [ErrorCode.FILE_CORRUPTED]: \"文件已损坏\",\n [ErrorCode.FILE_TOO_EMPTY]: \"文件内容不完整\",\n\n // 图片验证错误\n [ErrorCode.IMAGE_WIDTH_INVALID]: \"图片宽度不符合要求\",\n [ErrorCode.IMAGE_HEIGHT_INVALID]: \"图片高度不符合要求\",\n [ErrorCode.IMAGE_ASPECT_RATIO_INVALID]: \"图片宽高比不符合要求\",\n [ErrorCode.IMAGE_RESOLUTION_INVALID]: \"图片分辨率不符合要求\",\n [ErrorCode.IMAGE_NOT_SQUARE]: \"图片不是正方形\",\n [ErrorCode.IMAGE_ANIMATED]: \"不支持动态图片\",\n\n // 视频验证错误\n [ErrorCode.VIDEO_DURATION_INVALID]: \"视频时长不符合要求\",\n [ErrorCode.VIDEO_WIDTH_INVALID]: \"视频宽度不符合要求\",\n [ErrorCode.VIDEO_HEIGHT_INVALID]: \"视频高度不符合要求\",\n [ErrorCode.VIDEO_BITRATE_INVALID]: \"视频比特率不符合要求\",\n [ErrorCode.VIDEO_CODEC_INVALID]: \"视频编码格式不支持\",\n\n // 上传错误\n [ErrorCode.UPLOAD_FAILED]: \"上传失败\",\n [ErrorCode.CHUNK_UPLOAD_FAILED]: \"分片上传失败\",\n [ErrorCode.MERGE_FAILED]: \"合并分片失败\",\n [ErrorCode.SERVER_ERROR]: \"服务器错误\",\n [ErrorCode.UNAUTHORIZED]: \"未授权访问\",\n [ErrorCode.FORBIDDEN]: \"禁止访问\",\n [ErrorCode.NOT_FOUND]: \"资源不存在\",\n\n // 下载错误\n [ErrorCode.DOWNLOAD_FAILED]: \"下载失败\",\n [ErrorCode.CHUNK_DOWNLOAD_FAILED]: \"分片下载失败\",\n\n // 插件错误\n [ErrorCode.PLUGIN_ERROR]: \"插件错误\",\n [ErrorCode.PLUGIN_INIT_FAILED]: \"插件初始化失败\",\n [ErrorCode.PLUGIN_EXECUTION_FAILED]: \"插件执行失败\",\n} as const;\n\n/**\n * 🔥 核心错误类\n */\nexport class FileUDError extends Error {\n /** 错误码 */\n code: ErrorCode;\n\n /** 错误级别 */\n level: ErrorLevel;\n\n /** 错误上下文 */\n readonly context: ErrorContext;\n\n /** 错误选项 */\n readonly options: ErrorOptions;\n\n /** 原始错误 */\n readonly cause?: Error;\n\n /** 内部定时器(用于防抖) */\n private __noticeTimer__: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n code: ErrorCode,\n message: string,\n context: Partial<ErrorContext> = {},\n options: Partial<ErrorOptions> = {},\n cause?: Error,\n ) {\n super(message);\n this.name = \"FileUDError\";\n this.code = code;\n this.level = this.determineLevel(code);\n \n // 使用辅助方法获取文件上下文,减少全局状态耦合\n this.context = {\n timestamp: Date.now(),\n ...this.getCurrentFileContext(),\n ...context,\n };\n \n this.options = {\n recoverable: true,\n retryable: false,\n userVisible: true,\n ...options,\n };\n this.cause = cause;\n\n // 捕获堆栈(V8 引擎特有,非标准 API)\n (Error as any).captureStackTrace?.(this, FileUDError);\n }\n\n /**\n * 根据错误码确定错误级别\n */\n private determineLevel(code: ErrorCode): ErrorLevel {\n if (code < 2000) return ErrorLevel.ERROR;\n if (code < 3000) return ErrorLevel.WARNING;\n if (code < 4000) return ErrorLevel.ERROR;\n return ErrorLevel.CRITICAL;\n }\n\n /**\n * 获取当前上传文件的上下文信息(避免直接访问全局状态)\n * @returns 文件上下文信息\n * @private\n */\n private getCurrentFileContext(): Pick<ErrorContext, \"fileName\" | \"fileSize\"> {\n const uploadFile = Uploader.uploadFile;\n return {\n fileName: uploadFile?.fileName,\n fileSize: uploadFile?.File.size,\n };\n }\n\n /**\n * 设置/更新错误上下文信息\n * @param context - 要设置的上下文信息\n * @returns this (支持链式调用)\n */\n setContext(context: Partial<ErrorContext>): this {\n Object.assign(this.context, context);\n return this;\n }\n\n /**\n * 设置/更新错误码\n * @param code - 新的错误码\n * @returns this (支持链式调用)\n */\n setCode(code: ErrorCode): this {\n this.code = code as any;\n // 重新计算错误级别\n (this as any).level = this.determineLevel(code);\n this.notice();\n return this;\n }\n\n /**\n * 通知错误(防抖处理)\n */\n private notice(): void {\n // 清除之前的定时器\n if (this.__noticeTimer__) {\n clearTimeout(this.__noticeTimer__);\n }\n\n // 设置新的定时器,延迟通知以避免频繁触发\n this.__noticeTimer__ = setTimeout(() => {\n Uploader.onError?.(this.toJSON());\n this.__noticeTimer__ = null;\n }, 0);\n }\n\n /**\n * 获取错误码对应的中文描述\n * @param code - 错误码(可选,不传则使用当前错误码)\n * @returns 中文描述字符串\n */\n getChineseDescription(code?: ErrorCode): string {\n const errorCode = code ?? this.code;\n return ERROR_CODE_DESCRIPTIONS[errorCode as number] || \"未知错误\";\n }\n\n /**\n * 设置/更新错误消息\n * @param message - 新的错误消息\n * @returns this (支持链式调用)\n */\n setMessage(message: string): this {\n this.message = message;\n this.notice();\n return this;\n }\n\n /**\n * 设置/更新错误选项\n * @param options - 新的错误选项\n * @returns this (支持链式调用)\n */\n setOptions(options: Partial<ErrorOptions>): this {\n Object.assign(this.options, options);\n return this;\n }\n\n /**\n * 转换为 JSON\n */\n toJSON(): FileUDErrorJSON {\n return {\n name: this.name,\n code: this.code,\n level: this.level,\n message: this.message,\n context: this.context,\n options: this.options,\n stack: this.stack,\n };\n }\n}\n\n/**\n * 错误工厂(方便创建常见错误)\n */\nexport const Errors = {\n /**\n * 内部辅助方法:创建并发出错误事件\n * @param uploader - 上传器实例\n * @param error - 错误实例\n * @private\n */\n _emitError(uploader: Uploader, error: FileUDError): void {\n uploader.emit(\"error\", error.toJSON());\n },\n\n // 文件相关\n fileTooLarge(this: Uploader, maxSize: number) {\n Errors._emitError(\n this,\n new FileUDError(\n ErrorCode.FILE_TOO_LARGE,\n `文件大小 ${Uploader.uploadFile?.formatSize} 超过限制 ${formatFileSize(maxSize)}`,\n { suggestion: \"请选择更小的文件\", uploader: this },\n ),\n );\n },\n fileTooType(this: Uploader, options: { accept?: any[]; fileName: string }) {\n Errors._emitError(\n this,\n new FileUDError(\n ErrorCode.INVALID_TYPE,\n `文件${options.fileName}类型不支持,允许:${options.accept?.join(\", \")}`,\n {\n accept: options.accept,\n uploader: this,\n },\n { suggestion: \"请选择符合要求的文件类型\" },\n ),\n );\n },\n fileTooLimit(this: Uploader, limit: number) {\n Errors._emitError(\n this,\n new FileUDError(\n ErrorCode.FILE_LIMIT_EXCEEDED,\n \"文件数量超过限制\",\n {\n limit,\n uploader: this,\n },\n { suggestion: \"请选择更少的文件\" },\n ),\n );\n },\n\n // 网络相关\n networkError(this: Uploader, cause?: Error) {\n Errors._emitError(\n this,\n new FileUDError(\n ErrorCode.NETWORK,\n \"网络连接失败,请检查网络后重试\",\n { uploader: this },\n { retryable: true, recoverable: true },\n cause,\n ),\n );\n },\n\n // 上传相关\n uploadFailed(this: Uploader, fileName: string, status?: number) {\n Errors._emitError(\n this,\n new FileUDError(\n ErrorCode.UPLOAD_FAILED,\n `文件 ${fileName} 上传失败${status ? ` (${status})` : \"\"}`,\n { fileName, httpStatus: status, uploader: this },\n { retryable: true },\n ),\n );\n },\n\n // 插件错误\n pluginError(this: Uploader, pluginName: string, cause?: Error) {\n Errors._emitError(\n this,\n new FileUDError(\n ErrorCode.PLUGIN_ERROR,\n `插件 ${pluginName} 执行失败`,\n {\n plugin: pluginName,\n uploader: this,\n },\n { recoverable: false },\n cause,\n ),\n );\n },\n};\n","// core/xhr-interceptor.ts\nimport { checkNetworkStatus, logger } from \"../utils\";\nimport { FileUDError, ErrorCode } from \"../fileUD/errors\";\nimport TransferFile from \"../transfer/TransferFile\";\n\n/**\n * XHR 拦截器配置\n */\nexport interface XHRInterceptorConfig<T> {\n /** 拦截器名称(用于日志) */\n name: string;\n /** 获取文件队列的方法 */\n getFileQueue: () => T[];\n /** 获取全局 headers 的方法 */\n getGlobalHeaders: (file: T) => Record<string, string> | undefined;\n /** 处理进度事件的方法 */\n onProgress: (file: T, event: ProgressEvent) => void;\n /** 设置 abort 方法(可选) */\n setupAbort?: (file: T, xhr: XMLHttpRequest) => void;\n /** 标记文件已分配(可选) */\n markFileAsAssigned?: (file: T) => void;\n /** 从 XHR 获取文件实例的方法 */\n getFileFromXHR?: (xhr: XMLHttpRequest) => T | null;\n /** 设置 XHR 与文件的映射 */\n\n setFileToXHR?: (xhr: XMLHttpRequest, file: T) => void;\n /** 设置 abort 方法 */\n onAbort?: (file: T, xhr: XMLHttpRequest) => void;\n\n /** 取消标记文件 */\n unmarkFileAsAssigned: (file: T) => void;\n}\n\n/**\n * 全局 XHR 拦截器管理器\n */\nexport class XHRInterceptor<T extends TransferFile<T>> {\n private static instances = new Map<string, XHRInterceptor<any>>();\n\n private isInstalled: boolean = false;\n private originalXHR: typeof XMLHttpRequest | null = null;\n private xhrToFileMap = new WeakMap<XMLHttpRequest, T>();\n private assignedFiles = new Set<T>();\n\n constructor(private config: XHRInterceptorConfig<T>) {\n console.log(`[XHRInterceptor] 创建拦截器: ${config.name}`);\n this.config = config;\n }\n\n /**\n * 获取或创建拦截器实例(单例模式)\n */\n static getInstance<T extends TransferFile<T>>(\n name: string,\n config: XHRInterceptorConfig<T>,\n ): XHRInterceptor<T> {\n if (!this.instances.has(name)) {\n this.instances.set(name, new XHRInterceptor(config));\n }\n return this.instances.get(name) as XHRInterceptor<T>;\n }\n\n /**\n * 添加文件到已分配集合\n */\n private markAsAssigned(file: T): void {\n this.assignedFiles.add(file);\n if (this.config.markFileAsAssigned) {\n this.config.markFileAsAssigned(file);\n }\n }\n\n /**\n * 从 XHR 获取关联的文件\n */\n private getFileFromXHR(xhr: XMLHttpRequest): T | null {\n if (this.config.getFileFromXHR) {\n return this.config.getFileFromXHR(xhr);\n }\n return this.xhrToFileMap.get(xhr) || null;\n }\n\n /**\n * 设置 XHR 与文件的映射\n */\n private setFileToXHR(xhr: XMLHttpRequest, file: T): void {\n if (this.config.setFileToXHR) {\n this.config.setFileToXHR(xhr, file);\n } else {\n this.xhrToFileMap.set(xhr, file);\n }\n }\n\n /**\n * 安装拦截器(全局只安装一次)\n */\n install(): void {\n if (this.isInstalled) {\n console.log(`[XHRInterceptor] ${this.config.name} 拦截器已安装,跳过`);\n return;\n }\n\n this.originalXHR = window.XMLHttpRequest;\n const interceptor = this;\n const OriginalXHR = window.XMLHttpRequest;\n\n const XHRProxy = function (this: any) {\n const xhr = new OriginalXHR();\n const headersMap = new Map<string, string>();\n let currentFileInstance: T | null = null;\n\n // 拦截 setRequestHeader\n const originalSetHeader = xhr.setRequestHeader;\n xhr.setRequestHeader = function (name: string, value: string) {\n headersMap.set(name.toLowerCase(), value);\n };\n\n // 拦截 send - 核心逻辑\n const originalSend = xhr.send;\n xhr.send = function (body?: any) {\n // ✅ 1. 先尝试从映射中获取文件实例\n currentFileInstance = interceptor.getFileFromXHR(xhr);\n\n // ✅ 2. 如果还没有,从队列中分配一个未使用的文件\n if (!currentFileInstance) {\n const fileQueue = interceptor.config.getFileQueue();\n for (const file of fileQueue) {\n if (!interceptor.assignedFiles.has(file)) {\n currentFileInstance = file;\n interceptor.setFileToXHR(xhr, currentFileInstance);\n interceptor.markAsAssigned(currentFileInstance);\n break;\n }\n }\n\n // ✅ 分片上传兼容:大文件分片上传时,同一文件会产生多个并发 XHR\n // assignedFiles 会导致已分配的文件被跳过(设计用于多文件防重复匹配)\n // 因此需要回退机制:当队列中所有文件都已分配(典型分片场景),\n // 允许重新匹配队列中的文件,确保每个分片的 XHR 都能关联到文件实例\n if (!currentFileInstance && fileQueue.length > 0) {\n currentFileInstance = fileQueue[0];\n interceptor.setFileToXHR(xhr, currentFileInstance);\n }\n }\n\n // 网络检查\n const networkCheck = checkNetworkStatus();\n if (!networkCheck.online) {\n const error = new FileUDError(\n ErrorCode.NETWORK,\n \"网络连接异常,请检查网络设置后重试\",\n { timestamp: networkCheck.timestamp },\n {\n recoverable: true,\n retryable: true,\n suggestion: \"请检查网络连接后重试\",\n userVisible: true,\n },\n );\n\n logger.error(\n interceptor.config.name,\n `网络检查失败: ${error.message}`,\n );\n\n if (currentFileInstance) {\n interceptor.config.onProgress(currentFileInstance, {\n loaded: 0,\n total: 0,\n lengthComputable: false,\n } as ProgressEvent);\n }\n throw error;\n }\n\n // 添加全局 headers\n if (currentFileInstance) {\n const globalHeaders =\n interceptor.config.getGlobalHeaders(currentFileInstance) || {};\n Object.entries(globalHeaders).forEach(([key, value]) => {\n headersMap.set(key.toLowerCase(), value as string); // 直接覆盖\n });\n }\n headersMap.forEach((value, key) => {\n originalSetHeader.call(xhr, key, value);\n });\n // ✅ 3. 绑定进度回调\n if (currentFileInstance) {\n // 对于上传,监听 upload.onprogress\n if (xhr.upload) {\n xhr.upload.onprogress = function (event: ProgressEvent) {\n interceptor.config.onProgress(currentFileInstance!, event);\n };\n }\n if (currentFileInstance.isDownloader()) {\n // 对于下载,监听 xhr.onprogress\n xhr.onprogress = function (event: ProgressEvent) {\n interceptor.config.onProgress(currentFileInstance!, event);\n };\n }\n } else {\n console.warn(\n `[${interceptor.config.name}] 没有找到关联的文件实例,进度回调可能无法触发`,\n );\n }\n\n // 设置 abort 方法\n if (currentFileInstance && interceptor.config.onAbort) {\n interceptor.config.onAbort(currentFileInstance, xhr);\n }\n\n return originalSend.call(this, body);\n };\n\n return xhr;\n } as any;\n\n XHRProxy.prototype = OriginalXHR.prototype;\n window.XMLHttpRequest = XHRProxy;\n this.isInstalled = true;\n\n console.log(`✅ 全局 XHR 拦截器已安装: ${this.config.name}`);\n }\n\n /**\n * 恢复原始 XHR\n */\n restore(): void {\n if (this.originalXHR) {\n window.XMLHttpRequest = this.originalXHR;\n this.isInstalled = false;\n this.originalXHR = null;\n }\n }\n\n /**\n * 清理文件(文件完成时调用)\n */\n cleanupFile(file: T): void {\n this.assignedFiles.delete(file);\n }\n}\n","import type {\n DownloaderConfig,\n IUDPlugin,\n PluginContext,\n UDFile,\n} from \"../types\";\nimport Transfer from \"../transfer/Transfer\";\nimport {\n mergeObjects,\n formatFileSize,\n logger,\n getFileExtension,\n saveFileHandle,\n loadFileHandle,\n} from \"../utils\";\nimport FileConcurrencyController from \"../concurrency/FileConcurrencyController\";\n\nimport DownloadFile from \"./DownloadFile\";\n\nexport const defaultConfig: DownloaderConfig = {\n action: \"\",\n headers: {},\n timeout: 30000,\n axiosOptions: {\n method: \"get\",\n responseType: \"blob\",\n },\n};\n\n/**\n * 文件下载器类 (Downloader)\n *\n * 继承自 Transfer,专门处理文件下载相关的业务逻辑。\n * 回调属性(successCallback / updateCallback / onInitChunkCallback / OnMergeChunkCallBack /\n * beforeTransferCallback)及其 setter 全部由 Transfer 基类提供,此处不再重复声明。\n */\nexport default class Downloader<T = any> extends Transfer<DownloadFile, T> {\n /** 全局基础配置 */\n public static baseConfig: DownloaderConfig;\n\n /** 当前实例的配置信息 */\n public config: DownloaderConfig | null = null;\n public static instances: Downloader | null = null;\n public static fileIndex: number = 0;\n\n /** 静态默认插件(影响之后创建的所有实例) */\n private static defaultPlugins: IUDPlugin<DownloadFile>[] = [];\n\n // ==================== 回调设置器(仅基类未提供的) ====================\n\n constructor(config?: DownloaderConfig) {\n super();\n try {\n if (!Downloader.instances) {\n this.config = mergeObjects(Downloader.baseConfig, config);\n\n Downloader.instances = this.create(this.config);\n }\n return Downloader.instances!;\n } catch (error: any) {\n throw new Error(`Failed to initialize downloader: ${error.message}`);\n }\n }\n\n /**\n * 动态更新配置\n * @param config 部分配置项\n */\n public updateConfig(config: Partial<DownloaderConfig>) {\n this.config = mergeObjects(this.config!, config);\n }\n\n /**\n * 创建下载器实例(多例模式支持)\n * @param config 下载器配置\n * @returns Downloader 实例\n */\n public create(config?: DownloaderConfig) {\n // 合并配置\n Downloader.baseConfig = Object.assign(defaultConfig, Downloader.baseConfig);\n this.config = { ...Downloader.baseConfig, ...config };\n\n // 初始化状态\n this.resetState();\n\n // 加载默认插件\n this.init();\n\n // 🔑 配置文件级并发\n if (this.config.maxFileConcurrent !== undefined) {\n FileConcurrencyController.getInstance().maxDownloadConcurrent =\n this.config.maxFileConcurrent;\n }\n\n return this;\n }\n\n /**\n * 设置全局默认插件(影响之后创建的所有实例)\n */\n static setDefaultPlugins(plugins: IUDPlugin<DownloadFile>[]): void {\n Downloader.defaultPlugins = plugins;\n }\n\n /**\n * 初始化实例插件列表(继承全局默认插件)\n */\n private init() {\n this.plugins = [...Downloader.defaultPlugins];\n }\n\n // ==================== 流式保存(File System Access API) ====================\n\n /**\n * 🔑 文件名 → FileSystemFileHandle 缓存\n *\n * 用途:避免重复调用 showSaveFilePicker() 截断已存在的文件。\n * Chrome 的 showSaveFilePicker() 在用户选择覆盖同名文件时会直接截断文件为 0 字节,\n * 导致\"秒下\"检测失败。缓存句柄后,二次下载同一文件直接复用句柄。\n *\n * ⚠️ 挂载在 window 上而非 static Map:Vite HMR 会重置模块级静态变量,\n * 但 window 上的属性不受影响,保证开发模式下缓存不丢失。\n * 页面全量刷新后缓存自然清空,功能不受影响(回退到正常下载)。\n */\n private static get fileHandleCache(): Map<string, FileSystemFileHandle> {\n const key = \"__UD_FILE_HANDLE_CACHE__\";\n if (!(window as any)[key]) {\n (window as any)[key] = new Map<string, FileSystemFileHandle>();\n }\n return (window as any)[key];\n }\n\n /**\n * 静态方法:打开系统\"另存为\"对话框,获取流式写入 FileHandle\n *\n * 使用 File System Access API(Chrome 86+, Edge 86+)。\n * 如果 API 不可用,返回 undefined(调用方应回退到 Blob 下载模式)。\n *\n * 🔑 如果当前会话中已下载过同名文件,直接复用缓存的 handle,\n * 避免 showSaveFilePicker() 截断已有文件。\n *\n * @param suggestedName 建议的文件名\n * @returns FileHandle(用户选择后),null(用户取消),undefined(API 不可用)\n */\n public static async pickSaveFile(\n suggestedName?: string,\n ): Promise<FileSystemFileHandle | null | undefined> {\n try {\n if (typeof window === \"undefined\" || !window.showSaveFilePicker) {\n return undefined;\n }\n\n // 🔑 多层缓存策略(按优先级):\n // L1: 内存 Map(window 挂载,同会话最快)\n // L2: IndexedDB(跨页面刷新,持久化 FileSystemFileHandle)\n\n // ---- L1: 内存缓存 ----\n if (suggestedName && Downloader.fileHandleCache.has(suggestedName)) {\n const cachedHandle = Downloader.fileHandleCache.get(suggestedName)!;\n try {\n const diskFile = await cachedHandle.getFile();\n if (diskFile.size > 0) {\n return cachedHandle;\n }\n Downloader.fileHandleCache.delete(suggestedName);\n } catch (e) {\n Downloader.fileHandleCache.delete(suggestedName);\n }\n }\n\n // ---- L2: IndexedDB 持久化句柄(跨页面刷新) ----\n if (suggestedName) {\n try {\n const indexedHandle = await loadFileHandle(suggestedName);\n if (indexedHandle) {\n // 验证句柄仍有效(文件未被手动删除)\n try {\n const diskFile = await indexedHandle.getFile();\n if (diskFile.size > 0) {\n // 🔄 回填到 L1 内存缓存,下次更快\n Downloader.fileHandleCache.set(suggestedName, indexedHandle);\n return indexedHandle;\n }\n } catch (verifyErr) {\n // 句柄验证失败,跳过\n }\n }\n } catch (loadErr) {\n // IndexedDB 查询失败,跳过 L2 缓存\n }\n }\n\n // ---- L3: 无缓存 → 弹出系统\"另存为\"对话框 ----\n const handle = await window.showSaveFilePicker({\n suggestedName,\n });\n\n // 🔑 新选择的句柄 → 回填到 L1(拿到句柄就缓存,不等下载完成)\n // ⚠️ 不在此处写 L2 IndexedDB:此时 showSaveFilePicker 刚截断文件为 0 字节,\n // 写 L2 会导致下次刷新后 loadFileHandle 拿到\"空文件句柄\"。\n // L2 持久化放在 downloadFile.then() 中(下载完成后)。\n if (suggestedName && handle) {\n Downloader.fileHandleCache.set(suggestedName, handle);\n }\n\n return handle;\n } catch (err: any) {\n // 用户取消保存对话框 → DOMException: The user aborted a request\n if (\n err instanceof DOMException &&\n (err.name === \"AbortError\" || err.name === \"UnknownError\")\n ) {\n return null;\n }\n logger.warn(\"Downloader\", \"pickSaveFile 异常\", err);\n return undefined;\n }\n }\n\n /**\n * 添加下载任务并立即开始下载\n * @param file 下载文件配置\n * @param fileHandle 可选,File System Access API 的 FileHandle(流式保存用)\n * @returns DownloadFile 实例\n */\n public downloadFile(\n file: UDFile,\n fileHandle?: FileSystemFileHandle,\n ): DownloadFile<T> {\n // 创建下载文件实例\n const downloadFile = new DownloadFile(\n {\n fileName: file.fileName || file.url,\n status: \"pending\",\n percent: 0,\n url: file.url,\n index: Downloader.fileIndex++,\n extension: getFileExtension(file.fileName),\n formatSize: formatFileSize(file.size),\n size: file.size,\n fileHandle: fileHandle || file.fileHandle,\n },\n this as unknown as Transfer,\n );\n\n // 先添加到文件列表,让 UI 立即可见\n this.files.push(downloadFile);\n this.activeFiles.push(downloadFile);\n this.updateGlobalStats();\n this.triggerUpdate();\n\n // 🔑 构建插件上下文,存到文件上供后续 onProgress / onError 钩子使用\n const pluginContext: PluginContext<DownloadFile> = {\n transfer: this as unknown as Transfer<DownloadFile>,\n shared: this.pluginSharedData,\n config: this.config,\n };\n (downloadFile as any).__pluginContext = pluginContext;\n\n // 🔑 异步执行插件钩子 + 开始下载\n (async () => {\n try {\n // onFileSelect 钩子:插件可修改/拒绝文件\n const selectResults = await this.runHook(\n \"onFileSelect\",\n downloadFile,\n pluginContext,\n );\n if (selectResults.some((r) => r === false)) {\n downloadFile.remove();\n return;\n }\n\n // beforeTransfer 钩子:插件可在下载前拦截\n const beforeResults = await this.runHook(\n \"beforeTransfer\",\n downloadFile,\n pluginContext,\n );\n if (beforeResults.some((r) => r === false)) {\n downloadFile.remove();\n return;\n }\n } catch (err: any) {\n // 插件抛出异常 → 拒绝本次下载\n logger.warn(\"Downloader\", `插件拦截下载: ${downloadFile.fileName}`, err);\n downloadFile.remove();\n return;\n }\n\n // 通过插件检查后,开始下载\n try {\n await downloadFile.start(downloadFile.downloadChunkManager);\n } catch (_) {\n // start 内部已通过 onError 处理错误并触发插件钩子\n }\n\n // 🔑 下载成功后缓存文件句柄(L1 内存 + L2 IndexedDB)\n const fh = downloadFile.fileHandle;\n if (fh && downloadFile.status === \"success\") {\n const name = file.fileName || file.url;\n // L1: 内存缓存(最快,但页面刷新后丢失 )\n Downloader.fileHandleCache.set(name, fh);\n // L2: IndexedDB 持久化(跨页面刷新)\n saveFileHandle(name, fh).catch(() => {});\n }\n\n // 下载完成后触发一次最终更新\n this.updateGlobalStats();\n this.triggerUpdate();\n })();\n\n return downloadFile;\n }\n\n // ==================== 全局批量控制 ====================\n\n /**\n * 提交所有待下载文件(自动调用所有文件 start 方法)\n */\n public submit(): void {\n this.files.forEach((file) => {\n if (file.status === \"pending\" || file.status === \"fail\") {\n file.start(file.downloadChunkManager);\n }\n });\n }\n\n /**\n * 取消所有下载任务\n */\n public cancelAll(): void {\n this.files.forEach((file) => {\n file.cancel();\n });\n }\n\n /**\n * 暂停所有下载任务\n */\n public pauseAll(): void {\n this.files.forEach((file) => {\n const proxyFile = file.proxy || file;\n if (proxyFile.status === \"UDLoading\") {\n file.pause();\n }\n });\n }\n\n /**\n * 恢复所有下载任务\n */\n public async resumeAll(): Promise<void> {\n const resumes: Promise<void>[] = [];\n this.files.forEach((file) => {\n const proxyFile = file.proxy || file;\n if (proxyFile.status === \"paused\") {\n resumes.push(file.resume());\n }\n });\n await Promise.allSettled(resumes);\n }\n\n /**\n * 移除所有下载任务(取消进行中的下载并清空列表)\n */\n public removeAll(): void {\n this.files.forEach((file) => {\n file.cancel();\n });\n this.files = [];\n this.activeFiles = [];\n this.updateGlobalStats();\n this.triggerUpdate();\n }\n\n /**\n * 重试所有失败/取消的下载任务\n */\n public async retryAll(): Promise<void> {\n const retries: Promise<void>[] = [];\n this.files.forEach((file) => {\n const proxyFile = file.proxy || file;\n if ([\"cancelled\", \"fail\", \"error\"].includes(proxyFile.status!)) {\n retries.push(file.retry());\n }\n });\n await Promise.allSettled(retries);\n }\n\n /**\n * 保存 Blob 到本地(触发浏览器下载对话框)\n * @param fileName 文件名\n * @param data Blob 数据或可转为 Blob 的数据\n */\n public static saveBlob(fileName: string, data: any): void {\n // 确保 data 是有效的 Blob 对象\n let blob: Blob;\n if (data instanceof Blob) {\n blob = data;\n } else {\n blob = new Blob([data]);\n }\n\n const blobUrl = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = blobUrl;\n a.download = fileName;\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n\n // ✅ 部分浏览器在异步回调中 a.click() 不会触发下载弹窗,\n // 延迟移除元素并清理 URL,给浏览器足够时间处理\n setTimeout(() => {\n document.body.removeChild(a);\n URL.revokeObjectURL(blobUrl);\n }, 100);\n }\n\n /**\n * 通过 URL 下载并保存文件到本地(触发浏览器下载对话框)\n * @param fileName 文件名\n * @param url 下载链接\n */\n public static async saveFile(fileName: string, url: string): Promise<void> {\n // 去除 query string 和 hash,避免 getFileExtension 提取出带参数的扩展名\n const cleanUrl = url.split(\"?\")[0].split(\"#\")[0];\n const extension = getFileExtension(cleanUrl);\n fileName = extension ? `${fileName}.${extension}` : fileName;\n try {\n const response = await fetch(url);\n const blob = await response.blob();\n Downloader.saveBlob(fileName, blob);\n } catch (error) {\n logger.error(\"Downloader\", `文件保存失败: ${fileName}`, error);\n throw error;\n }\n }\n}\n","import axios from \"axios\";\nimport { IDownloadFile } from \"../types\";\nimport TransferFile from \"../transfer/TransferFile\";\n\nimport {\n computeTransferTime,\n createReactiveDownloadFile,\n isPlainObject,\n logger,\n} from \"../utils\";\nimport ChunkManager from \"../chunkManager\";\nimport DownloadChunkManager from \"./DownloadChunkManager\";\nimport Downloader from \".\";\nimport Transfer from \"../transfer/Transfer\";\nimport { XHRInterceptor } from \"../xhr-intercepto\";\n\n/**\n * 文件下载实例类\n * 负责单个文件的下载逻辑、状态管理、速率计算和生命周期控制\n *\n * @template T - 下载成功后的响应数据类型\n */\nexport default class DownloadFile<T = any> extends TransferFile<\n DownloadFile,\n T\n> {\n /**\n * 分片管理器实例\n * 每个文件拥有独立的 downloadChunkManager,实现并发控制、断点续传和失败隔离\n */\n public downloadChunkManager: DownloadChunkManager | null = null;\n public dl: Downloader<T>;\n\n /**\n * 🔑 文件创建时保存 action 快照,防止切换模式后重试时用到错误的 action\n */\n private _action: NonNullable<DownloadFile[\"dl\"][\"config\"]>[\"action\"] | undefined;\n\n /**\n * 流式保存的 FileHandle(File System Access API)\n * 如果设置了 fileHandle,下载完成后通过流式写入文件,不触发浏览器下载对话框\n */\n public fileHandle: FileSystemFileHandle | null | undefined = null;\n\n /**\n * 分片下载专用 headers 队列(如 Range)\n * 并发分片各自 push,拦截器 shift 取走,避免并发覆盖导致 Range 头错乱\n * 函数 action 下,用户不需要手动处理分片 headers\n */\n public _chunkHeadersQueue: Record<string, string>[] = [];\n\n public static interceptor: XHRInterceptor<DownloadFile>;\n\n /**\n * 全局共享的 WeakMap\n * 用于存储 XHR 实例与文件上下文的映射关系,支持拦截器透明注入\n */\n private static xhrToFileMap = new WeakMap<XMLHttpRequest, DownloadFile>();\n\n /**\n * 全局共享的当前下载文件队列\n * 用于在拦截器中查找匹配的 XHR\n */\n private static currentQueue: DownloadFile[] = [];\n\n /**\n * 跟踪已分配到 XHR 的文件集合\n * 用于按顺序分配 XHR 实例,避免冲突\n */\n private static assignedFiles = new Set<DownloadFile>();\n\n constructor(file: IDownloadFile, transfer: Transfer) {\n super(file, transfer);\n this.dl = transfer as unknown as Downloader<T>;\n // 🔑 文件创建时保存 action 快照,重试/恢复时始终使用创建时的 action\n this._action = this.dl.config?.action;\n this.proxy = createReactiveDownloadFile(this, transfer);\n\n // 存储流式保存的 fileHandle\n if (file.fileHandle) {\n this.fileHandle = file.fileHandle;\n }\n\n // 如果是分片下载(检查 chunkOptions 配置),在构造时就创建 downloadChunkManager\n if (this.dl.config?.chunkOptions) {\n this.downloadChunkManager = new DownloadChunkManager(\n this.dl.config.chunkOptions,\n this,\n );\n this.chunkManager = this.downloadChunkManager;\n\n // 如果回显数据中包含分片信息,初始化状态(基类统一实现)\n if (file.totalChunks !== undefined) {\n this.initChunkManagerFromRestore(file);\n }\n }\n\n this.initInterceptor();\n\n // 添加到队列\n DownloadFile.currentQueue.push(this);\n\n DownloadFile.interceptor.install();\n }\n\n /**\n * 执行普通下载的重试(模板方法覆写)\n */\n protected async doRetryTransfer(): Promise<T> {\n return this.download();\n }\n\n /**\n * 从回显数据初始化 downloadChunkManager 状态\n */\n private async initChunkManagerFromRestore(\n file: IDownloadFile,\n ): Promise<void> {\n if (!this.downloadChunkManager) {\n logger.warn(\n \"DownloadFile\",\n \"initChunkManagerFromRestore: downloadChunkManager 不存在\",\n );\n return;\n }\n\n logger.info(\n \"DownloadFile\",\n `从回显数据初始化分片下载状态: ${this.fileName}`,\n {\n fileId: this.fileId,\n totalChunks: file.totalChunks,\n completedChunks: file.completedChunks,\n chunkIndexes: file.chunkIndexes?.length || 0,\n fileHash: file.fileHash,\n },\n );\n\n if (file.totalChunks !== undefined) {\n this.downloadChunkManager.totalChunks = file.totalChunks;\n }\n\n if (file.completedChunks !== undefined) {\n this.downloadChunkManager.completedChunks = file.completedChunks;\n }\n\n if (file.totalChunks !== undefined) {\n this.downloadChunkManager.chunks = new Array(file.totalChunks).fill(\n false,\n );\n }\n\n if (file.chunkIndexes && file.chunkIndexes.length > 0) {\n file.chunkIndexes.forEach((index) => {\n if (\n this.downloadChunkManager &&\n index >= 0 &&\n index < this.downloadChunkManager.totalChunks\n ) {\n this.downloadChunkManager.chunks[index] = true;\n }\n });\n } else if (\n file.completedChunks !== undefined &&\n this.downloadChunkManager\n ) {\n for (\n let i = 0;\n i < file.completedChunks && i < this.downloadChunkManager.totalChunks;\n i++\n ) {\n this.downloadChunkManager.chunks[i] = true;\n }\n }\n\n if (file.fileHash) {\n (this.downloadChunkManager as any).fileHash = file.fileHash;\n }\n\n if (this.downloadChunkManager.totalChunks > 0) {\n const percent = Math.round(\n (this.downloadChunkManager.completedChunks /\n this.downloadChunkManager.totalChunks) *\n 100,\n );\n this.percent = percent;\n this.proxy.percent = percent;\n }\n\n if (\n this.downloadChunkManager.completedChunks ===\n this.downloadChunkManager.totalChunks &&\n this.downloadChunkManager.totalChunks > 0\n ) {\n // 🔑 全部分片已传输完,但合并尚未开始 — 进度卡在 99%,状态留给 start() 流转\n this.percent = 99;\n this.proxy.percent = 99;\n }\n\n this.transfer.updateGlobalStats();\n this.transfer.triggerUpdate();\n }\n\n /**\n * 执行下载\n */\n public async download(): Promise<T> {\n this.proxy.loading = true;\n // 🔑 重试场景:cancel 的 finally 块已将文件移出 currentQueue,\n // 拦截器需要在队列中找到文件才能匹配 XHR 并绑定进度回调\n // 同时清理 assignedFiles 残留,避免新 XHR 被分配到错误文件\n DownloadFile.interceptor?.cleanupFile?.(this as any);\n if (!DownloadFile.currentQueue.includes(this as any)) {\n DownloadFile.currentQueue.push(this as any);\n }\n return new Promise<T>((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n this.transfer.loading = true;\n\n (async () => {\n try {\n if (!this._action) {\n logger.warn(\"DownloadFile\", \"请设置下载地址\");\n reject(new Error(\"请设置下载地址\"));\n return;\n }\n\n // 记录下载开始时间\n if (!this.downloadChunkManager) {\n computeTransferTime(this.proxy.transferTime).start();\n }\n if (!this.transfer.transferTime.startTime) {\n computeTransferTime(this.transfer.transferTime).start();\n }\n\n let result:\n | boolean\n | undefined\n | null\n | { [key: string]: any };\n if (this.dl.beforeTransferCallback) {\n try {\n result = await this.dl.beforeTransferCallback(this);\n if (!result) {\n reject(new Error(\"beforeTransferCallback 返回 false\"));\n return;\n }\n } catch (error) {\n reject(error);\n return;\n }\n }\n\n this.proxy.status !== \"cancelled\" &&\n (this.proxy.status = \"UDLoading\");\n\n const response = await this._doHttpRequest({\n data: isPlainObject(result) ? result : undefined,\n });\n const res = response.data;\n // 🚀 流式保存:先确保文件成功落盘,再触发 onSuccess → 避免保存失败时\n // status 已是 \"success\" 但 percent 未到 100 的状态不一致\n if (this.fileHandle) {\n await DownloadFile.writeToFileHandle(this.fileHandle, res);\n } else {\n Downloader.saveBlob(this.fileName, res as Blob);\n }\n this.proxy.percent = 100;\n this.onSuccess(res);\n // 🔑 双重保险:绕开 proxy,直接写原始对象上的 status,\n // 确保无论 proxy setter 链路如何,底层值一定正确\n (this as any).status = \"success\";\n\n resolve(res);\n } catch (err: any) {\n if (this.isCancel !== true) {\n this.proxy.status = \"error\";\n }\n this.onError(err);\n reject(err);\n } finally {\n // 记录下载结束时间\n if (!this.downloadChunkManager) {\n computeTransferTime(this.proxy.transferTime).end();\n }\n this.proxy.loading = false;\n this.transfer.loading = false;\n\n // 清理队列和活跃文件列表(防止内存泄漏)\n const queueIdx = DownloadFile.currentQueue.indexOf(this as any);\n if (queueIdx !== -1) {\n DownloadFile.currentQueue.splice(queueIdx, 1);\n }\n DownloadFile.assignedFiles.delete(this as any);\n // 🔑 同步清理拦截器内部的 assignedFiles 集合,\n // 否则重试时文件被标记为\"已分配\"会绕过主匹配逻辑(仅靠 fallback 兜底)\n DownloadFile.interceptor?.cleanupFile(this as any);\n\n // 🔑 兜底移除(取消/失败场景),带 indexOf 保护避免 splice(-1, 1) 误删\n const activeIdx = this.transfer.activeFiles.indexOf(this);\n if (activeIdx !== -1) {\n this.transfer.activeFiles.splice(activeIdx, 1);\n }\n }\n })();\n });\n }\n\n /**\n * 更新当前文件的下载进度和已传输字节数(模板方法)\n *\n * - 普通下载:从 event.loaded/event.total 直接计算百分比和字节数\n * - 分片下载:由 downloadChunkManager 独立维护进度,此处无需额外操作\n *\n * 当响应没有 Content-Length 头(event.total === 0)时:\n * - __transferBytes__ 使用 event.loaded 记录实际已下载字节数\n * - 百分比无法精确计算,使用基于 loaded 的模拟进度(0~99%),完成时跳到 100%\n */\n protected updateLocalProgress(event: ProgressEvent): void {\n if (!this.downloadChunkManager) {\n const fileSize = this.File?.size || this.size || 0;\n\n if (event.lengthComputable && event.total > 0) {\n // 已知总大小:精确计算百分比,但上限卡在 99%\n // 🔑 最后一个 progress 事件 loaded===total 时,响应体可能还在传输中,\n // 此时 status 仍是 \"UDLoading\",若 percent=100 会造成视觉不一致\n // 100% 只在 onSuccess 之后显式设置\n this.proxy.percent = Math.min(\n 99,\n Math.floor((event.loaded * 100) / event.total),\n );\n this.__transferBytes__ = Math.floor(\n fileSize * (this.proxy.percent / 100),\n );\n } else if (event.loaded > 0) {\n // 未知总大小:记录已下载字节数,用模拟进度(避免一直是 0%)\n this.__transferBytes__ = event.loaded;\n // 使用对数衰减模拟进度,越到后面增长越慢,最大到 99%\n this.proxy.percent = Math.min(\n 99,\n Math.floor(Math.log2(event.loaded / 1024 + 1) * 10),\n );\n } else {\n this.__transferBytes__ = 0;\n }\n }\n }\n\n /**\n * 返回下载的分片管理器\n */\n protected getChunkManager(): ChunkManager | null {\n return this.downloadChunkManager;\n }\n\n /**\n * 下载单个分片(Range 请求)\n *\n * - 字符串 action:直接走 axios,Range 头在 overrides.headers 中显式传入\n * - 函数 action:通过 _chunkHeadersQueue 告知拦截器注入 Range 头,\n * 最终由 cfg.action(this) 调用用户的 axios 函数\n *\n * @param start - 分片起始字节偏移\n * @param end - 分片结束字节偏移(含)\n * @param signal - AbortSignal,用于超时/取消\n * @returns 分片的 Blob 数据\n */\n public async downloadChunk(\n start: number,\n end: number,\n signal?: AbortSignal,\n ): Promise<Blob> {\n const rangeHeader = { Range: `bytes=${start}-${end}` };\n\n // 🔑 把本分片的 headers 推入队列,拦截器在 XHR send 时 shift 取出\n // 并发分片各自 push → send 各自 shift,避免单属性被覆盖\n this._chunkHeadersQueue.push(rangeHeader);\n\n try {\n const response = await this._doHttpRequest({\n headers: rangeHeader,\n signal,\n });\n\n return response.data instanceof Blob\n ? response.data\n : new Blob([response.data]);\n } finally {\n // 🔑 清理队列中未被拦截器消费的残留(请求失败/取消等情况)\n const idx = this._chunkHeadersQueue.indexOf(rangeHeader);\n if (idx !== -1) {\n this._chunkHeadersQueue.splice(idx, 1);\n }\n }\n }\n\n /**\n * 统一 HTTP 请求方法(download() 和 downloadChunk() 共用)\n *\n * action 不区分普通/分片,只按类型分发:\n * - 字符串 action → 始终走 axios(分片时 headers 里带 Range,普通时不带)\n * - 函数 action → 始终调 cfg.action(this)(分片时 _chunkHeadersQueue 已 push,拦截器自动注入 Range)\n */\n private async _doHttpRequest(overrides?: {\n headers?: Record<string, string>;\n signal?: AbortSignal;\n data?: any;\n }): Promise<any> {\n const cfg = this.dl.config;\n this.__transferBytes__ = 0;\n if (!cfg) {\n throw new Error(\"下载配置不存在,请检查 DownloaderConfig\");\n }\n\n const method = cfg.axiosOptions?.method || \"get\";\n const headers: Record<string, string> = {\n ...cfg.headers,\n ...overrides?.headers,\n };\n\n // ======== 字符串 action:始终 axios ========\n if (typeof this._action === \"string\") {\n return (cfg.axiosInstance || axios)({\n url: this._action,\n method,\n headers,\n data: overrides?.data,\n responseType: cfg.axiosOptions?.responseType || \"blob\",\n timeout: cfg.timeout,\n signal: overrides?.signal,\n });\n } else if (typeof this._action === \"function\") {\n // 🔑 函数 action:通过 XHR 拦截器机制处理取消,不暴露 signal 给用户\n if (overrides?.signal) {\n overrides.signal.addEventListener(\n \"abort\",\n () => this.abort?.(),\n { once: true },\n );\n }\n const actionResult = this._action(this);\n const res = await Promise.resolve(actionResult);\n // 支持函数返回字符串 URL:核心代码自动发起 axios 请求\n if (typeof res === \"string\") {\n return (cfg.axiosInstance || axios)({\n url: res,\n method,\n headers,\n data: overrides?.data,\n responseType: cfg.axiosOptions?.responseType || \"blob\",\n timeout: cfg.timeout,\n signal: overrides?.signal,\n });\n }\n return { data: res };\n }\n // action 既不是字符串也不是函数 → 配置错误\n throw new Error(\"action 必须是字符串 URL 或函数\");\n }\n\n /**\n * 获取文件大小(下载场景:无 File 对象,fallback 到 size 属性)\n */\n public getFileSize(): number {\n return this.File?.size || this.size || 0;\n }\n\n /**\n * 通过 FileHandle 流式写入 Blob 数据到磁盘\n *\n * 使用 File System Access API,直接写入用户选择的文件位置。\n * 绕过浏览器内存限制,适合大文件下载。\n *\n * @param fileHandle - 文件句柄\n * @param data - 要写入的数据(Blob 或可转为 Blob)\n */\n /**\n * 从下载列表中移除当前文件,取消进行中的下载\n */\n remove() {\n // 取消下载\n if (this.downloadChunkManager) {\n this.downloadChunkManager.cancel();\n } else {\n this.abort?.();\n }\n\n const dl = this.transfer;\n\n // 从文件列表中移除\n dl.files = dl.files.filter((f) => f.fileId !== this.fileId);\n\n // 重新计算全局统计信息\n dl.updateGlobalStats();\n\n dl.triggerUpdate();\n dl.emit(\"remove\", this.proxy as any);\n }\n\n public static async writeToFileHandle(\n fileHandle: FileSystemFileHandle,\n data: Blob | any,\n ): Promise<void> {\n try {\n const writable = await fileHandle.createWritable();\n await writable.write(data instanceof Blob ? data : new Blob([data]));\n await writable.close();\n logger.info(\"DownloadFile\", \"流式写入完成\");\n } catch (err: any) {\n logger.error(\"DownloadFile\", \"流式写入文件失败\", err);\n throw err;\n }\n }\n}\n","import ChunkManager from \"../chunkManager\";\nimport TransferFile from \"../transfer/TransferFile\";\nimport { ChunkOptions } from \"../types\";\nimport DownloadFile from \"./DownloadFile\";\nimport { calculateFileMD5, formatFileSize, logger } from \"../utils\";\nimport Downloader from \".\";\n\nexport default class DownloadChunkManager extends ChunkManager {\n private downloadFile: DownloadFile;\n\n /** 存储每个分片的 Blob 数据(流式写入不可用时的内存兜底) */\n private chunkBlobs: Map<number, Blob> = new Map();\n\n /** 流式写入的 FileHandle(File System Access API) */\n private streamFileHandle: FileSystemFileHandle | null = null;\n\n /** 流式写入的可写流 */\n private writable: FileSystemWritableFileStream | null = null;\n\n /** 待完成写入的 Promise 列表(fire-and-forget,不阻塞 PQueue worker) */\n private pendingWrites: Promise<void>[] = [];\n\n /** 🔑 已确认落盘的分片索引(仅流式模式使用,内存模式不写此 Set) */\n private _diskWrittenChunks: Set<number> = new Set();\n\n /** 🔑 doAfterStartReset() 保存 IndexedDB 时的安全快照。\n * ensureWritableClosed() 的 pending writes 会追加新分片到 _diskWrittenChunks,\n * 若 close() 失败 → abort() → 磁盘数据丢失,需要回滚 IndexedDB 到这个安全状态,\n * 而不是全量删除。已 settle 的 writes 在磁盘上是安全的。 */\n private _safeDiskChunks: Set<number> = new Set();\n\n /** 上次写入 IndexedDB 进度的时间戳(节流用) */\n private _lastProgressSaveTime: number = 0;\n\n // ==================== 构造函数 ====================\n\n constructor(chunkOptions: ChunkOptions, file: TransferFile<any, any>) {\n super(chunkOptions, file);\n this.downloadFile = file as unknown as DownloadFile;\n\n // 🔑 读取下载限速配置\n const dlConfig = (this.downloadFile.dl as any).config as import(\"../types\").DownloaderConfig | null;\n if (dlConfig?.maxDownloadSpeed && dlConfig.maxDownloadSpeed > 0) {\n this.maxSpeed = dlConfig.maxDownloadSpeed;\n }\n }\n\n // ==================== 抽象方法实现 ====================\n\n protected getTag(): string {\n return \"DownloadChunkManager\";\n }\n\n /**\n * 计算文件唯一标识\n *\n * - 若 this.fileHash 已设置(服务端返回的真实 MD5),直接返回\n * - 否则兜底用 fileName-fileSize,仅作为 onInitChunk → check-file 的临时传参\n */\n protected async computeFileIdentifier(): Promise<string> {\n if (this.fileHash && /^[a-f0-9]{32}$/i.test(this.fileHash)) {\n return this.fileHash;\n }\n const fileName = this.downloadFile.fileName || \"download\";\n const fileSize = this.downloadFile.getFileSize();\n return `${fileName}-${fileSize}`;\n }\n\n /**\n * 初始化下载任务(含断点续传 / 秒下检查)\n *\n * 1. 调用 onInitChunkCallback 检查已下载分片(断点续传 / 秒下)\n * 2. 如果用户提供了 fileHandle 直接使用;否则尝试调用 showSaveFilePicker\n * 让用户选择保存位置,实现流式写入磁盘(内存峰值 = 并发数 × 分片大小)。\n * 3. 若 API 不可用或用户取消,回退到内存累积模式。\n */\n protected async doInit(): Promise<any> {\n logger.info(this.getTag(), \"初始化下载任务\", {\n fileName: this.downloadFile.fileName,\n totalChunks: this.totalChunks,\n });\n\n const downloader = this.downloadFile.transfer;\n let initResult: any = null;\n let isResuming = false;\n const isRetry = !!(this.downloadFile.proxy as any).isRetry;\n\n // ========== Step 0: 本地磁盘秒下检查(优先于一切网络请求) ==========\n // 🔑 如果有 fileHandle,直接检查本地磁盘文件是否已完成。\n // 无需依赖服务端或回调,下载器自己看磁盘就行了。\n if (!this.isInstantTransfer) {\n const fileHandle = this.downloadFile.fileHandle || this.streamFileHandle;\n if (fileHandle) {\n try {\n const diskFile = await fileHandle.getFile();\n if (diskFile.size > 0 && diskFile.size === this.downloadFile.getFileSize()) {\n logger.info(this.getTag(), \"⚡ 秒下:本地磁盘文件已完整\", {\n fileName: this.downloadFile.fileName,\n fileSize: diskFile.size,\n });\n this.markInstantDownload();\n return null;\n }\n } catch {\n // 磁盘文件不可访问,走正常下载流程\n }\n }\n }\n\n // ========== Step 1: 服务端回调 → 获取真实 MD5(优先,为 IndexedDB 提供正确 key) ==========\n // 🔑 下载端没有本地 File 对象,无法调用 calculateFileMD5()。\n // 必须先调服务端 check-file 获取真实 MD5,再用它做 IndexedDB key,\n // 这样索引键才能与上传端的 MD5 一致,实现正确的秒下/续传判断。\n if (downloader.onInitChunkCallback) {\n try {\n const tempKey = await this.computeFileIdentifier();\n\n logger.debug(this.getTag(), \"调用 onInitChunk 回调获取服务端哈希\", {\n fileName: this.downloadFile.fileName,\n tempKey,\n });\n\n initResult = await downloader.onInitChunkCallback(\n this.downloadFile as any,\n this.totalChunks,\n tempKey,\n );\n\n if (initResult?.fileHash) {\n // 🔑 验证是否为真实 MD5(32 位 hex),不是则仍然用 computeFileIdentifier 兜底\n if (/^[a-f0-9]{32}$/i.test(initResult.fileHash)) {\n this.fileHash = initResult.fileHash;\n logger.info(this.getTag(), \"已获取服务端真实文件哈希\", {\n fileName: this.downloadFile.fileName,\n fileHash: this.fileHash,\n });\n } else {\n // onInitChunk 返回的 fileHash 不是有效 MD5,将继续用 computeFileIdentifier 兜底\n }\n\n // ✅ 秒下:仅当回调显式标记 isInstantDownload 时生效\n if (initResult.isInstantDownload === true) {\n logger.info(this.getTag(), \"⚡ 秒下(回调标记),文件已在本地\", {\n fileName: this.downloadFile.fileName,\n });\n this.markInstantDownload();\n return initResult;\n }\n }\n } catch (error) {\n // 回调失败不阻塞下载,回退到全新下载\n logger.warn(this.getTag(), \"服务端哈希查询失败,将全新下载\", error);\n }\n }\n\n // 🔑 兜底:如果 onInitChunk 未返回有效 MD5(fileHash 未设置),用computeFileIdentifier 生成\n if (!this.fileHash) {\n this.fileHash = await this.computeFileIdentifier();\n }\n\n // ========== Step 2: IndexedDB 恢复(用 this.fileHash 作为 key) ==========\n // 🔑 saveProgressToCacheSoft 仅当 writable.write() 确认落盘后才保存 IndexedDB\n // (流式模式),或 chunkBlobs 已在内存(内存模式)。若 close() 失败回退 abort(),\n // ensureWritableClosed() 已同步清理 IndexedDB。因此这里恢复的所有进度都是可信的。\n if (this.config.enableFileCache && this.fileHash) {\n try {\n const { loadDownloadProgress } = await import(\"../utils/fileCache\");\n const progress = await loadDownloadProgress(this.fileHash);\n\n if (progress && progress.chunkIndexes.length > 0) {\n logger.info(this.getTag(), \"✅ 从 IndexedDB 恢复下载进度\", {\n fileName: progress.fileName,\n completedChunks: progress.chunkIndexes.length,\n totalChunks: progress.totalChunks,\n });\n\n this.completedChunks = 0;\n progress.chunkIndexes.forEach((index: number) => {\n if (index >= 0 && index < this.totalChunks) {\n this.chunks[index] = true;\n this.completedChunks++;\n this.countedChunks.add(index);\n this.totalChunkSize += this.chunkSize;\n }\n });\n this.updateProgress();\n isResuming = true;\n\n const dl = this.downloadFile.transfer;\n dl.updateGlobalStats();\n dl.triggerUpdate();\n\n // ✅ 秒下:IndexedDB 显示所有分片已下载\n if (this.completedChunks === this.totalChunks) {\n // 🔑 必须验证磁盘文件真实存在且 MD5 一致\n const fileHandle = this.downloadFile.fileHandle || this.streamFileHandle;\n let fileVerified = false;\n let shouldDeleteCache = false; // 只有文件确实损坏才删除 IndexedDB\n\n if (fileHandle) {\n try {\n const diskFile = await fileHandle.getFile();\n const diskSize = diskFile.size;\n const expectedSize = this.downloadFile.getFileSize();\n\n if (diskSize === 0) {\n // 🔑 磁盘文件大小为 0 → 不可能是一个已完成的有效下载\n shouldDeleteCache = true;\n fileVerified = false;\n } else if (diskSize !== expectedSize) {\n fileVerified = false;\n shouldDeleteCache = false;\n } else {\n // 🔑 大小一致 → 进一步校验 MD5 哈希(仅对中小文件,大文件跳过避免卡死 UI)\n if (this.fileHash && /^[a-f0-9]{32}$/i.test(this.fileHash)) {\n const MAX_MD5_SIZE = 50 * 1024 * 1024; // >50MB 跳过 MD5\n if (diskSize <= MAX_MD5_SIZE) {\n try {\n const computedHash = await calculateFileMD5(diskFile);\n fileVerified = computedHash === this.fileHash;\n if (!fileVerified) {\n logger.warn(\n this.getTag(),\n `磁盘文件大小一致但 MD5 不匹配 → 内容已变更, expected=${this.fileHash.slice(0, 8)}..., computed=${computedHash.slice(0, 8)}...`,\n );\n shouldDeleteCache = true; // 文件确实损坏,清理缓存\n }\n } catch {\n // 哈希计算失败,回退到大小校验\n fileVerified = true;\n }\n } else {\n // 大文件仅靠大小校验,避免 MD5 计算阻塞 UI 30-60s\n fileVerified = true;\n }\n } else {\n // 无真实哈希可用,仅靠大小校验\n fileVerified = true;\n }\n }\n } catch (getFileErr) {\n // getFile() 失败:文件被删除 / 权限被撤销\n shouldDeleteCache = true;\n fileVerified = false;\n }\n }\n\n if (fileVerified) {\n this.markInstantDownload();\n return initResult;\n }\n\n // 🔑 内存模式秒下兜底:无 fileHandle 时无法验证本地磁盘文件,\n // 但 IndexedDB 显示全部分片已完成 → 可信秒下\n if (!fileHandle && !shouldDeleteCache) {\n logger.info(this.getTag(), \"⚡ 秒下(内存模式兜底),IndexedDB 全量完成\", {\n fileName: this.downloadFile.fileName,\n });\n this.markInstantDownload();\n return initResult;\n }\n\n // 文件不存在 / 无法验证\n if (shouldDeleteCache) {\n // 只有 MD5 不匹配(文件确实损坏)才清理 IndexedDB\n logger.warn(\n this.getTag(),\n \"IndexedDB 记录显示全部下载完成,但磁盘文件哈希不匹配,将清除缓存并重新下载\",\n { fileHandleExists: !!fileHandle },\n );\n if (this.config.enableFileCache) {\n try {\n const { removeDownloadProgress } =\n await import(\"../utils/fileCache\");\n await removeDownloadProgress(this.fileHash);\n } catch {\n // IndexedDB 清理失败不阻塞\n }\n }\n } else {\n // 新文件句柄 / getFile() 失败 → 保留 IndexedDB 记录\n logger.info(\n this.getTag(),\n \"IndexedDB 记录保留(文件句柄指向新位置或无权限),继续正常下载\",\n { fileHandleExists: !!fileHandle },\n );\n }\n\n // 重置完成状态,继续走正常下载流程\n this.completedChunks = 0;\n this.chunks = new Array(this.totalChunks).fill(false);\n this.countedChunks.clear();\n this.totalChunkSize = 0;\n this.updateProgress();\n isResuming = false;\n\n const dl2 = this.downloadFile.transfer;\n dl2.updateGlobalStats();\n dl2.triggerUpdate();\n // 不 return,继续往下走到正常下载流程\n }\n }\n } catch (error) {\n logger.warn(this.getTag(), \"从 IndexedDB 恢复进度失败\", error);\n }\n }\n\n // ========== Step 3: 服务端断点续传(用已有 chunks 列表恢复) ==========\n // 🔑 重试时跳过服务端断点续传:与 Step 2 同理,doAfterStartReset() 已清空本地分片数据,\n // 服务端返回的 chunks(对下载场景通常是全量列表)会标记分片为\"已完成\"但无实际数据。\n if (!isResuming && initResult && !isRetry) {\n if (\n initResult.chunks &&\n Array.isArray(initResult.chunks) &&\n initResult.chunks.length > 0\n ) {\n this.completedChunks = 0;\n initResult.chunks.forEach((index: number) => {\n if (index >= 0 && index < this.totalChunks) {\n this.chunks[index] = true;\n this.completedChunks++;\n this.countedChunks.add(index);\n this.totalChunkSize += this.chunkSize;\n }\n });\n this.updateProgress();\n isResuming = true;\n\n logger.info(this.getTag(), \"✅ 服务端回调恢复分片\", {\n completedChunks: this.completedChunks,\n totalChunks: this.totalChunks,\n percent: Math.round(\n (this.completedChunks / this.totalChunks) * 100,\n ),\n });\n\n const dl = this.downloadFile.transfer;\n dl.updateGlobalStats();\n dl.triggerUpdate();\n }\n }\n\n // ========== 初始化流式写入或内存模式 ==========\n // 🔑 如果 fileHandle 已存在(首次下载已选择或回显),直接复用\n if (this.downloadFile.fileHandle) {\n this.streamFileHandle = this.downloadFile.fileHandle;\n } else if (\n // 🔑 重试时不弹出文件选择对话框(用户已在首次下载时选择或取消过)\n !(this.downloadFile as any).isRetry &&\n typeof window !== \"undefined\" &&\n typeof (window as any).showSaveFilePicker === \"function\"\n ) {\n try {\n this.streamFileHandle = await (window as any).showSaveFilePicker({\n suggestedName: this.downloadFile.fileName,\n });\n // 🔑 回写到 downloadFile.fileHandle,确保重试时可复用同一句柄\n // (doAfterStartReset() 会清空 streamFileHandle,但 downloadFile.fileHandle 不受影响)\n this.downloadFile.fileHandle = this.streamFileHandle;\n } catch (_e: any) {\n // 用户取消文件选择 → 回退到内存模式\n logger.info(this.getTag(), \"用户取消文件选择,回退到内存模式\");\n }\n }\n\n // 🔑 秒下检查:用户可能通过 showSaveFilePicker 选择了已有文件\n // 此时 IndexedDB 无记录(已在上次完成后清理),但磁盘文件完整\n // 必须在 createWritable 之前检测,否则不含 keepExistingData 会截断文件\n if (this.streamFileHandle && !isResuming) {\n try {\n const diskFile = await this.streamFileHandle.getFile();\n if (diskFile.size === this.downloadFile.getFileSize()) {\n // 🔑 大小一致 → 进一步校验 MD5 哈希,防止同大小不同内容的文件被误判\n let hashMatch = true;\n const expectedHash = this.fileHash || initResult?.fileHash || \"\";\n if (\n expectedHash &&\n /^[a-f0-9]{32}$/i.test(expectedHash)\n ) {\n try {\n const computedHash = await calculateFileMD5(diskFile);\n hashMatch = computedHash === expectedHash;\n if (!hashMatch) {\n logger.warn(\n this.getTag(),\n `showSaveFilePicker 选中文件大小一致但 MD5 不匹配 → 文件内容不同, expected=${expectedHash.slice(0, 8)}..., computed=${computedHash.slice(0, 8)}...`,\n );\n }\n } catch {\n // 哈希计算失败,回退到大小校验\n }\n }\n\n if (hashMatch) {\n logger.info(this.getTag(), \"⚡ 秒下:磁盘文件已通过大小+哈希双验证\", {\n fileName: this.downloadFile.fileName,\n fileSize: diskFile.size,\n });\n this.markInstantDownload();\n return initResult;\n }\n }\n } catch {\n // 文件不存在或无法访问 → 继续正常下载\n }\n }\n\n // 🔑 磁盘断点续传检测:无 IndexedDB 记录(isResuming=false),但磁盘文件有部分数据\n // (例如换浏览器后重新选择同一文件,或 IndexedDB 被清理)。\n // 根据文件大小反算已完成的完整分片数,标记后从断点继续。\n // 必须在 createWritable 之前检测,否则不含 keepExistingData 会截断已有数据。\n if (this.streamFileHandle && !isResuming) {\n try {\n const diskFile = await this.streamFileHandle.getFile();\n const fileSize = this.downloadFile.getFileSize();\n // 完整文件已在上面秒下检查处理(diskFile.size === fileSize),\n // 这里只处理有部分数据但未完成的文件\n if (diskFile.size > 0 && diskFile.size < fileSize) {\n const completedFromDisk = Math.floor(diskFile.size / this.chunkSize);\n if (completedFromDisk > 0 && completedFromDisk < this.totalChunks) {\n logger.info(this.getTag(), \"🔄 从磁盘文件恢复下载进度(无 IndexedDB 记录)\", {\n fileName: this.downloadFile.fileName,\n diskSize: diskFile.size,\n completedChunks: completedFromDisk,\n totalChunks: this.totalChunks,\n });\n\n this.completedChunks = 0;\n for (let i = 0; i < completedFromDisk; i++) {\n this.chunks[i] = true;\n this.completedChunks++;\n this.countedChunks.add(i);\n this.totalChunkSize += this.chunkSize;\n }\n this.updateProgress();\n isResuming = true;\n\n const dl = this.downloadFile.transfer;\n dl.updateGlobalStats();\n dl.triggerUpdate();\n }\n }\n } catch {\n // 磁盘文件不可访问,继续正常下载\n }\n }\n\n // 🔑 磁盘验证:若 IndexedDB 声称有已完成分片(isResuming=true),但磁盘文件\n // 为空或大小为 0,说明上一次 ensureWritableClosed() 的 close() 失败导致\n // abort 重置了文件。此时 IndexedDB 进度不可信,必须清理并全新下载。\n // 防止 createWritable({ keepExistingData: true }) 在空文件上打开 →\n // 已恢复的分片被跳过 → 文件出现空洞损坏。\n if (this.streamFileHandle && isResuming) {\n try {\n const diskFile = await this.streamFileHandle.getFile();\n const minExpectedSize = this.completedChunks * this.chunkSize;\n if (diskFile.size === 0) {\n this.completedChunks = 0;\n this.chunks = new Array(this.totalChunks).fill(false);\n this.countedChunks.clear();\n this.totalChunkSize = 0;\n isResuming = false;\n if (this.config.enableFileCache && this.fileHash) {\n try {\n const { removeDownloadProgress } =\n await import(\"../utils/fileCache\");\n await removeDownloadProgress(this.fileHash);\n } catch {}\n }\n } else if (diskFile.size < minExpectedSize) {\n // 磁盘文件过小,IndexedDB 进度可能落后\n }\n } catch (diskErr: any) {\n }\n }\n\n if (this.streamFileHandle) {\n // 🔑 断点续传时保留已有数据,全新下载时截断文件\n this.writable = await this.streamFileHandle.createWritable(\n isResuming ? { keepExistingData: true } : undefined,\n );\n logger.info(this.getTag(), isResuming ? \"✅ 启用流式写入磁盘(续传模式)\" : \"✅ 启用流式写入磁盘\", {\n fileName: this.downloadFile.fileName,\n keepExistingData: isResuming,\n });\n } else {\n logger.info(this.getTag(), \"⚠️ 流式写入不可用,使用内存模式\");\n }\n\n return initResult || { chunks: [] };\n }\n\n /**\n * 标记为秒下(instant download),设置所有状态为已完成\n */\n private markInstantDownload(): void {\n this.isInstantTransfer = true;\n this.completedChunks = this.totalChunks;\n this.totalChunkSize = this.downloadFile.getFileSize();\n this.countedChunks = new Set(\n Array.from({ length: this.totalChunks }, (_, i) => i),\n );\n this.chunks = new Array(this.totalChunks).fill(true);\n this.downloadFile.proxy.percent = 100;\n this.downloadFile.proxy.status = \"success\";\n this.downloadFile.proxy.speed = {\n currentSpeed: 0,\n averageSpeed: 0,\n currentSpeedFormatted: \"0 B/s\",\n averageSpeedFormatted: \"0 B/s\",\n estimatedTimeRemaining: 0,\n estimatedTimeFormatted: \"已完成\",\n };\n const dl = this.downloadFile.transfer;\n dl.updateGlobalStats();\n dl.triggerUpdate();\n }\n\n /**\n * 下载单个分片(Range 请求)\n */\n protected async doChunkTransfer(\n chunkIndex: number,\n signal?: AbortSignal,\n ): Promise<{ data: Blob; chunkSize: number }> {\n const fileSize = this.downloadFile.getFileSize();\n const chunkSize = this.chunkSize;\n const start = chunkIndex * chunkSize;\n const end = Math.min(start + chunkSize - 1, fileSize - 1);\n\n const blob = await this.downloadFile.downloadChunk(start, end, signal);\n\n return { data: blob, chunkSize: blob.size };\n }\n\n /**\n * 合并所有已下载分片\n *\n * - 流式模式:等待所有 fire-and-forget 写入完成后关闭 writable\n * - 内存模式:将 chunkBlobs Map 中的分片按顺序拼接为单一 Blob\n */\n protected async doMergeChunks(): Promise<Blob | FileSystemFileHandle> {\n const dl = this.downloadFile.transfer as any;\n let result: Blob | FileSystemFileHandle;\n\n try {\n // 🔑 流式模式:等待所有 fire-and-forget 写入完成,再关闭流\n if (this.writable && this.streamFileHandle) {\n try {\n if (this.pendingWrites.length > 0) {\n await Promise.all(this.pendingWrites);\n this.pendingWrites = [];\n }\n await this.writable.close();\n logger.info(this.getTag(), \"✅ 流式写入完成,文件已落盘\", {\n fileName: this.downloadFile.fileName,\n });\n } finally {\n this.writable = null;\n }\n result = this.streamFileHandle;\n } else {\n // 内存模式兜底\n const allChunks: Blob[] = [];\n\n for (let i = 0; i < this.totalChunks; i++) {\n const chunk = this.chunkBlobs.get(i);\n if (chunk) {\n allChunks.push(chunk);\n }\n }\n\n if (allChunks.length === 0) {\n // 🔑 秒下场景:文件已在本地,无需合并分片\n const fh = this.downloadFile.fileHandle || this.streamFileHandle;\n if (fh && this.isInstantTransfer) {\n // 流式/FileHandle 模式:直接返回磁盘文件句柄\n logger.info(this.getTag(), \"⚡ 秒下合并:文件已在本地磁盘,跳过合并\");\n result = fh;\n } else if (this.isInstantTransfer) {\n // 内存模式秒下兜底:无 fileHandle,服务端+IndexedDB 双确认文件已完整\n // 返回空 Blob,doBeforeOnSuccess 会跳过保存(size=0)\n logger.info(this.getTag(), \"⚡ 秒下合并(内存模式):文件已在本地,跳过合并\");\n result = new Blob();\n } else {\n logger.warn(this.getTag(), \"没有可合并的分片\");\n result = new Blob();\n }\n } else {\n result = new Blob(allChunks);\n logger.info(this.getTag(), \"分片合并完成\", {\n fileName: this.downloadFile.fileName,\n totalChunks: allChunks.length,\n size: formatFileSize(result.size),\n });\n }\n }\n\n dl.emit(\"merge-success\", { file: this.downloadFile.proxy });\n return result;\n } catch (error) {\n dl.emit(\"merge-error\", {\n file: this.downloadFile.proxy,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n // ==================== 覆写钩子 ====================\n\n /**\n * 保存每个分片的 Blob 数据 + 自动缓存下载进度到 IndexedDB\n *\n * - 流式模式:fire-and-forget 写入(不 await,PQueue worker 立即继续下载下一个分片)\n * - 内存模式:存储到 chunkBlobs Map 中,待合并时一次性拼接\n *\n * 🔑 IndexedDB 安全性:仅当磁盘写入确认完成后才保存进度。避免 cancel 时\n * write 尚未 flush → close() 未保住数据 → abort() 丢弃 → IndexedDB 却标记\"已完成\"\n * → 重试时跳过这些分片 → 文件出现空洞损坏。\n */\n protected async doSaveChunkResult(chunkIndex: number, data: any): Promise<void> {\n if (!(data instanceof Blob)) return;\n\n // 🔑 流式模式:fire-and-forget 写盘,不阻塞 PQueue worker 获取下一个分片\n if (this.writable) {\n const position = chunkIndex * this.chunkSize;\n const writeP = this.writable\n .write({ type: \"write\", position, data })\n .then(async () => {\n // ✅ 磁盘写入完成 → 标记落盘 → 才允许存 IndexedDB\n this._diskWrittenChunks.add(chunkIndex);\n // 🔑 必须 return IndexedDB 保存的 Promise,让 writeP 链正确等待\n // 否则 ensureWritableClosed() 的 allSettled 不等 IndexedDB 写完就认为完成\n await this.saveProgressToCacheSoft(chunkIndex);\n })\n .catch((err) => {\n console.error(\n `[DownloadChunkManager] ❌ 分片 ${chunkIndex} 写入磁盘失败:`,\n err,\n );\n });\n this.pendingWrites.push(writeP);\n } else {\n // 内存兜底模式:Blob 已在 chunkBlobs Map 中,100% 可恢复\n this.chunkBlobs.set(chunkIndex, data);\n // 内存模式数据不会因 cancel 丢失,直接保存 IndexedDB\n this.saveProgressToCacheSoft(chunkIndex);\n }\n }\n\n /**\n * 将下载进度存入 IndexedDB(节流,最多每 2 秒写一次)\n *\n * 🔑 流式模式:仅保存 _diskWrittenChunks 中已确认落盘的分片,防止\n * cancel→abort 丢弃数据后 IndexedDB 仍标记\"已完成\"→重试跳过→文件损坏。\n * 内存模式:chunkBlobs Map 存于内存,cancel 不丢数据,直接用 this.chunks[]。\n */\n private async saveProgressToCacheSoft(chunkIndex: number): Promise<void> {\n if (!this.config.enableFileCache) return;\n\n // 节流:每 2 秒或最后一个分片时才写入\n const now = Date.now();\n const isLastChunk = this.completedChunks + 1 >= this.totalChunks;\n if (!isLastChunk && now - this._lastProgressSaveTime < 2000) return;\n this._lastProgressSaveTime = now;\n\n try {\n const fileHash = this.fileHash || await this.computeFileIdentifier();\n\n // 🔑 合并 _diskWrittenChunks(本轮写入)+ this.chunks[](从 IndexedDB 恢复的历史完成分片)。\n // 重试后 doInit() 恢复的历史分片被跳过(不再写入磁盘),不在 _diskWrittenChunks 中。\n // 若只存 _diskWrittenChunks 会覆盖 IndexedDB 丢失历史进度 → 下次重试倒退 ❌。\n const chunkIndexes: number[] = [];\n const merged = new Set<number>(this._diskWrittenChunks);\n for (let i = 0; i < this.totalChunks; i++) {\n if (this.chunks[i]) merged.add(i);\n }\n // 确保当前分片也包含在内(可能在 .then() 回调时序中还未标记为 completed)\n merged.add(chunkIndex);\n for (const idx of merged) {\n chunkIndexes.push(idx);\n }\n\n chunkIndexes.sort((a, b) => a - b);\n\n const { saveDownloadProgress } = await import(\"../utils/fileCache\");\n await saveDownloadProgress(\n fileHash,\n this.downloadFile.fileName,\n this.downloadFile.getFileSize(),\n this.totalChunks,\n chunkIndexes,\n );\n } catch (error) {\n logger.warn(this.getTag(), \"保存下载进度失败\", error);\n console.error(\"[DownloadChunkManager] ❌ 保存下载进度失败:\", error);\n }\n }\n\n /**\n * 合并完成后保存文件\n *\n * - 流式模式:文件已在写入流关闭时落盘,无需额外操作\n * - 内存模式:如果有 fileHandle 则流式写入,否则触发浏览器下载对话框\n */\n protected async doBeforeOnSuccess(mergeResult: any): Promise<void> {\n // 流式模式:文件已在磁盘,无需操作\n if (mergeResult instanceof FileSystemFileHandle) {\n return;\n }\n\n // 内存模式:Blob 形式,需要保存\n if (mergeResult instanceof Blob && mergeResult.size > 0) {\n if (this.downloadFile.fileHandle) {\n await DownloadFile.writeToFileHandle(\n this.downloadFile.fileHandle,\n mergeResult,\n );\n } else {\n Downloader.saveBlob(this.downloadFile.fileName, mergeResult);\n }\n }\n\n // 🔑 下载完成后保留 IndexedDB 进度缓存作为\"秒下\"标记\n // 下次下载同一文件时,IndexedDB 显示 92/92 → 验证磁盘文件 → 秒下跳过下载\n // 不在此处清理,由 doInit() 中的磁盘验证逻辑决定是否需要清理(文件损坏 / 被删除时)\n if (this.config.enableFileCache) {\n try {\n const fileHash = this.fileHash || await this.computeFileIdentifier();\n const { saveDownloadProgress } = await import(\"../utils/fileCache\");\n // 保存最终完成状态(全部 chunks 已完成),作为下次秒下的依据\n const allIndexes = Array.from({ length: this.totalChunks }, (_, i) => i);\n await saveDownloadProgress(\n fileHash,\n this.downloadFile.fileName,\n this.downloadFile.getFileSize(),\n this.totalChunks,\n allIndexes,\n );\n logger.debug(this.getTag(), \"已保存下载完成标记到 IndexedDB\");\n } catch (error) {\n logger.warn(this.getTag(), \"保存下载完成标记失败\", error);\n }\n }\n }\n\n /**\n * start() 重置后清理之前的 Blob 缓存和流式写入状态\n *\n * 🔑 在此处将 _diskWrittenChunks 保存到 IndexedDB,但 NOT clear()。\n * clear() 推迟到 ensureWritableClosed() 的最终保存完成后执行:\n * - doAfterStartReset() 保存时,_diskWrittenChunks 含已 settle 的分片\n * - ensureWritableClosed() 等待 pending writes → 新 settle 的分片被\n * .then() 加入 _diskWrittenChunks → 最终保存含完整集合 → 覆盖写入 ✅\n * - 如果 doAfterStartReset() 就 clear(),最终保存只剩几个零碎分片 → 覆盖掉\n * 刚刚保存的完整记录 → 进度倒退 ❌\n */\n protected async doAfterStartReset(): Promise<void> {\n // 🔑 保留已完成分片的数据,重试时避免全部重新下载:\n // - 内存模式:chunkBlobs Map 中已存有已完成分片的 Blob 数据\n // - 流式模式:ensureWritableClosed() 改用 close() 保留磁盘上已写入的分片数据\n // doInit() 从 IndexedDB 恢复进度 → transferWithConcurrency 跳过已完成分片\n // → 只需下载缺失分片,不用从 0% 重新开始。\n if (this.chunkBlobs.size === 0) {\n this.chunkBlobs.clear();\n }\n\n // 🔑 清除上一轮可能残留的安全快照(如果 ensureWritableClosed 没执行到清理)\n this._safeDiskChunks.clear();\n\n // 🔑 合并 _diskWrittenChunks(本轮写入)+ this.chunks[](从 IndexedDB 恢复的历史完成分片)\n // 保存到 IndexedDB,避免覆盖丢失历史进度。\n // 重试时 doInit() 从 IndexedDB 恢复分片 → transferWithConcurrency 跳过它们 →\n // 不再写入磁盘 → 不在 _diskWrittenChunks 中。若只存 _diskWrittenChunks 会覆盖\n // IndexedDB 丢失历史进度 → 下次重试进度倒退 ❌。\n if (this.config.enableFileCache) {\n const allCompleted = new Set<number>(this._diskWrittenChunks);\n for (let i = 0; i < this.totalChunks; i++) {\n if (this.chunks[i]) allCompleted.add(i);\n }\n if (allCompleted.size > 0) {\n try {\n const chunkIndexes = Array.from(allCompleted).sort((a, b) => a - b);\n const fileHash =\n this.fileHash || (await this.computeFileIdentifier());\n const { saveDownloadProgress } = await import(\"../utils/fileCache\");\n await saveDownloadProgress(\n fileHash,\n this.downloadFile.fileName,\n this.downloadFile.getFileSize(),\n this.totalChunks,\n chunkIndexes,\n );\n // 🔑 保存安全快照:这些分片的 write 已 settle,磁盘上数据是安全的。\n // ensureWritableClosed() 等待 pending writes 追加新分片后,\n // 若 close() 失败,用此快照回滚 IndexedDB(而非全量删除)。\n this._safeDiskChunks = new Set(this._diskWrittenChunks);\n } catch (_err) {\n console.warn(\n `[DownloadChunkManager] doAfterStartReset 保存 IndexedDB 失败:`,\n _err,\n );\n }\n } else {\n this._safeDiskChunks.clear();\n }\n }\n\n // 🔑 不在此处清理 _diskWrittenChunks!如果立即 clear(),ensureWritableClosed()\n // 等待 pending writes 时 .then() 回调只能添加新 settle 的零散分片,\n // 最终保存的 IndexedDB 记录会覆盖掉上面刚存的完整记录,只剩少数分片。\n // _diskWrittenChunks 的清理由 ensureWritableClosed() 在最终保存后统一执行。\n // 原因:最终保存会把新增 settle 的分片合并写入 IndexedDB,完整度 >= 上面那次。\n //\n // 旧标记指向的上次 writable 已关闭,数据或在磁盘(close 成功)或已丢失(abort)。\n // 重试的 doInit() 会从 IndexedDB 恢复进度 —— 若 IndexedDB 已被 abort 路径清理,\n // 会从零重新下载,_diskWrittenChunks 也将重新填充。\n\n // 🔑 不在此处关闭 writable —— 它的关闭是异步的,必须 await 才能确保\n // 文件锁已释放。关闭逻辑移到 ensureWritableClosed() 中统一处理。\n this.streamFileHandle = null;\n\n // 🔑 清理分片 headers 队列,避免上一次取消残留的 Range 头干扰\n this.downloadFile._chunkHeadersQueue = [];\n\n // 🔑 重置分片下载相关的状态,避免重试时使用旧数据\n this._lastProgressSaveTime = 0;\n }\n\n /**\n * 异步清理:关闭上次的 writable,释放文件锁。\n *\n * ⚠️ 必须 await 调用,确保 writable 完全关闭、文件锁释放后,\n * doInit() 才能安全调用 createWritable(),否则会因锁冲突抛 InvalidStateError。\n *\n * 🔑 竞态修复:先置 null this.writable,再异步 close。\n * 否则旧 PQueue 残留的 doSaveChunkResult 回调会在 close() 进行中\n * 检查 if (this.writable) → 仍为非 null → writable.write() 写入正在关闭的流\n * → 浏览器抛出 \"Cannot write to a closing writable stream\"。\n *\n * 🔑 数据安全:先等待 pendingWrites 全部 settle → write.then() 中会更新\n * _diskWrittenChunks → 再 close() flush 缓冲区 → 磁盘数据完整。\n * 若 close() 失败退化为 abort(),磁盘数据丢失,必须同步清理 IndexedDB。\n */\n public async ensureWritableClosed(): Promise<void> {\n const oldWritable = this.writable;\n this.writable = null;\n\n if (this.pendingWrites.length > 0) {\n await Promise.allSettled(this.pendingWrites);\n }\n this.pendingWrites = [];\n\n if (oldWritable) {\n try {\n await oldWritable.close();\n\n if (this.config.enableFileCache) {\n const allCompleted = new Set<number>(this._diskWrittenChunks);\n for (let i = 0; i < this.totalChunks; i++) {\n if (this.chunks[i]) allCompleted.add(i);\n }\n if (allCompleted.size > 0) {\n try {\n const fileHash =\n this.fileHash || (await this.computeFileIdentifier());\n const chunkIndexes = Array.from(allCompleted).sort((a, b) => a - b);\n const { saveDownloadProgress } = await import(\n \"../utils/fileCache\"\n );\n await saveDownloadProgress(\n fileHash,\n this.downloadFile.fileName,\n this.downloadFile.getFileSize(),\n this.totalChunks,\n chunkIndexes,\n );\n } catch (_finalSaveErr) {\n console.warn(\n `[DownloadChunkManager] 最终保存进度失败:`,\n _finalSaveErr,\n );\n }\n }\n }\n\n this._diskWrittenChunks.clear();\n this._safeDiskChunks.clear();\n } catch (_closeErr) {\n console.warn(\n `[DownloadChunkManager] ⚠️ close() 失败,回退 abort(), err=`,\n _closeErr,\n );\n try {\n await oldWritable.abort();\n } catch (__) {\n console.warn(`[DownloadChunkManager] writable abort 也失败了:`, __);\n }\n\n // 🔑 abort 路径:合并 _safeDiskChunks + this.chunks[](历史已完成分片)。\n // abort 可能只丢弃未 flush 的本轮数据,历史分片在磁盘上仍然安全。\n // doInit() 的磁盘验证会兜底:若文件真被清空,会清理 IndexedDB 重新下载。\n if (this.config.enableFileCache && this.fileHash) {\n try {\n const allSafe = new Set<number>(this._safeDiskChunks);\n for (let i = 0; i < this.totalChunks; i++) {\n if (this.chunks[i]) allSafe.add(i);\n }\n if (allSafe.size > 0) {\n const safeIndexes = Array.from(allSafe).sort((a, b) => a - b);\n const { saveDownloadProgress } = await import(\n \"../utils/fileCache\"\n );\n await saveDownloadProgress(\n this.fileHash,\n this.downloadFile.fileName,\n this.downloadFile.getFileSize(),\n this.totalChunks,\n safeIndexes,\n );\n } else {\n const { removeDownloadProgress } =\n await import(\"../utils/fileCache\");\n await removeDownloadProgress(this.fileHash);\n }\n } catch (_cacheErr) {\n console.warn(\n `[DownloadChunkManager] abort 路径 IndexedDB 操作失败:`,\n _cacheErr,\n );\n }\n }\n this._safeDiskChunks.clear();\n }\n }\n }\n}\n","import {\n EventName,\n UploaderConfig,\n DownloaderConfig,\n LogConfig,\n} from \"../types\";\nimport Uploader, { defaultConfig } from \"../uploader/index\";\nimport Downloader, {\n defaultConfig as defaultDownloaderConfig,\n} from \"../downloader/index\";\nimport { initLogger, LogLevel, mergeObjects } from \"../utils\";\n\nexport default class FileUD {\n private static uploaders: Map<string, Uploader> = new Map();\n private static downloaders: Map<string, Downloader> = new Map();\n public static startUDLogger(logConfig?: LogConfig) {\n // 初始化日志配置\n\n initLogger({\n enabled: logConfig?.enabled ?? true,\n level: logConfig?.level ?? LogLevel.DEBUG,\n showTimestamp: logConfig?.showTimestamp,\n enableColors: logConfig?.enableColors,\n });\n }\n\n public static createUploader<T = any>(\n name: string,\n config?: UploaderConfig,\n ): Uploader<T> {\n if (!name) {\n throw new Error(\"Uploader name is required\");\n }\n\n // ✅ 如果已经存在同名的 uploader,先销毁旧实例(清理 input 元素)\n if (FileUD.uploaders.has(name)) {\n const oldUploader = FileUD.uploaders.get(name);\n if (oldUploader) {\n // 清空文件列表并重置索引\n oldUploader.clearFiles();\n // 移除旧的 input 元素\n oldUploader.inputHTML?.remove();\n // 从 Map 中删除\n FileUD.uploaders.delete(name);\n }\n }\n\n // 创建新的 uploader 实例\n const newUploader: Uploader<T> = Object.create(Uploader.prototype);\n newUploader.config = mergeObjects(defaultConfig, config || {});\n newUploader.create(newUploader.config);\n newUploader.inputHTML?.setAttribute(\"data-uploader-name\", name);\n FileUD.uploaders.set(name, newUploader);\n\n return newUploader;\n }\n\n /**\n * 创建下载器实例(多例模式)\n * @param name 下载器名称(唯一标识)\n * @param config 下载器配置\n * @returns Downloader 实例\n */\n public static createDownloader<T = any>(\n name: string,\n config?: DownloaderConfig,\n ): Downloader<T> {\n if (!name) {\n throw new Error(\"Downloader name is required\");\n }\n\n // ✅ 如果已经存在同名的 downloader,先销毁旧实例\n if (FileUD.downloaders.has(name)) {\n const oldDownloader = FileUD.downloaders.get(name);\n if (oldDownloader) {\n // 清空文件列表\n oldDownloader.files = [];\n oldDownloader.activeFiles = [];\n // 从 Map 中删除\n FileUD.downloaders.delete(name);\n }\n }\n\n // 使用 Object.create 创建新的 downloader 实例(与 createUploader 保持一致)\n const newDownloader: Downloader<T> = Object.create(Downloader.prototype);\n // 调用 create 方法初始化\n newDownloader.create(config);\n FileUD.downloaders.set(name, newDownloader as unknown as Downloader);\n\n return newDownloader;\n }\n // 销毁所有创建的上传器\n public static destroyUploaders(name?: string) {\n if (name && FileUD.uploaders.has(name)) {\n const uploader = FileUD.uploaders.get(name);\n uploader?.inputHTML?.remove();\n FileUD.uploaders.delete(name);\n } else {\n for (const [key, uploader] of FileUD.uploaders.entries()) {\n uploader.inputHTML?.remove();\n }\n FileUD.uploaders.clear();\n }\n }\n // 获取上传器\n public static getUploaders(name?: string) {\n if (name && FileUD.uploaders.has(name)) {\n return FileUD.uploaders.get(name);\n }\n return FileUD.uploaders;\n }\n\n /**\n * 销毁所有创建的下载器\n * @param name 可选,指定销毁的下载器名称\n */\n public static destroyDownloaders(name?: string) {\n if (name && FileUD.downloaders.has(name)) {\n const downloader = FileUD.downloaders.get(name);\n if (downloader) {\n // 清空文件列表\n downloader.files = [];\n downloader.activeFiles = [];\n }\n FileUD.downloaders.delete(name);\n } else {\n for (const [, downloader] of FileUD.downloaders.entries()) {\n downloader.files = [];\n downloader.activeFiles = [];\n }\n FileUD.downloaders.clear();\n }\n }\n\n /**\n * 获取下载器\n * @param name 可选,指定获取的下载器名称\n * @returns 单个下载器或所有下载器的 Map\n */\n public static getDownloaders(name?: string) {\n if (name && FileUD.downloaders.has(name)) {\n return FileUD.downloaders.get(name);\n }\n return FileUD.downloaders;\n }\n}\n","import { printFileUDBanner } from \"./utils/banner\";\n\nprintFileUDBanner();\n\nexport { default as FileUD } from \"./fileUD\";\nexport { default as Transfer } from \"./transfer/Transfer\";\nexport { default as TransferFile } from \"./transfer/TransferFile\";\nexport { default as DownloadFile } from \"./downloader/DownloadFile\";\nexport { default as UploadFile } from \"./uploader/UploadFile\";\nexport { default as Downloader, defaultConfig as downloaderDefaultConfig } from \"./downloader\";\nexport { default as Uploader, defaultConfig as uploaderDefaultConfig } from \"./uploader\";\nexport type { ErrorOptions, ErrorContext } from \"./fileUD/errors\";\nexport { FileUDError, ErrorCode, ErrorLevel, Errors } from \"./fileUD/errors\";\nexport {\n addLogCollector,\n clearLogCollectors,\n formatFileSize,\n initLogger,\n logger,\n LogLevel,\n setLogLevel,\n validator,\n} from \"./utils\";\nexport type { LogCollectorCallback, LogEntry, LoggerOptions } from \"./utils\";\nexport type * from \"./types\";\n"]}