parallel-file-uploader
Version:
高性能并行文件上传工具,支持大文件分片上传、断点续传、Web Worker多线程处理
268 lines • 8.68 kB
JavaScript
/**
* 队列持久化管理器
* 将上传队列状态保存到localStorage,支持浏览器刷新后恢复
*/
export class QueuePersistence {
constructor(enabled = false, storageKey) {
this.enabled = false;
this.storageKey = 'parallel-uploader-queue';
this.chunkStorageKey = 'parallel-uploader-chunks';
this.enabled = enabled;
if (storageKey) {
this.storageKey = storageKey;
this.chunkStorageKey = `${storageKey}-chunks`;
}
}
/**
* 启用/禁用持久化
*/
setEnabled(enabled) {
this.enabled = enabled;
if (!enabled) {
this.clearAll();
}
}
/**
* 检查是否启用
*/
isEnabled() {
return this.enabled;
}
/**
* 检查localStorage是否可用
*/
isStorageAvailable() {
if (!this.enabled)
return false;
try {
const testKey = '__storage_test__';
localStorage.setItem(testKey, 'test');
localStorage.removeItem(testKey);
return true;
}
catch (e) {
return false;
}
}
/**
* 保存文件队列状态
*/
saveQueue(files) {
if (!this.isStorageAvailable())
return;
try {
const persistedFiles = files.map(file => ({
fileId: file.fileId,
fileName: file.fileName,
fileSize: file.fileSize,
uploadedSize: file.uploadedSize,
progress: file.progress,
status: file.status,
mimeType: file.mimeType,
lastUpdated: file.lastUpdated,
totalChunks: file.totalChunks,
uploadInfo: file.uploadInfo
}));
localStorage.setItem(this.storageKey, JSON.stringify(persistedFiles));
}
catch (error) {
console.warn('Failed to save queue to localStorage:', error);
}
}
/**
* 保存分片状态
*/
saveChunkStatus(fileId, uploadedChunks, pendingChunks) {
if (!this.isStorageAvailable())
return;
try {
const chunkStatus = {
uploaded: Array.from(uploadedChunks),
pending: Array.from(pendingChunks),
timestamp: Date.now()
};
const allChunkStatus = this.loadAllChunkStatus();
allChunkStatus[fileId] = chunkStatus;
localStorage.setItem(this.chunkStorageKey, JSON.stringify(allChunkStatus));
}
catch (error) {
console.warn('Failed to save chunk status to localStorage:', error);
}
}
/**
* 加载文件队列状态
*/
loadQueue() {
if (!this.isStorageAvailable())
return [];
try {
const stored = localStorage.getItem(this.storageKey);
if (!stored)
return [];
const parsed = JSON.parse(stored);
if (!Array.isArray(parsed))
return [];
// 过滤掉过期的记录(超过24小时)
const now = Date.now();
const maxAge = 24 * 60 * 60 * 1000; // 24小时
return parsed.filter((file) => {
if (!file.lastUpdated)
return false;
return (now - file.lastUpdated) <= maxAge;
});
}
catch (error) {
console.warn('Failed to load queue from localStorage:', error);
return [];
}
}
/**
* 加载分片状态
*/
loadChunkStatus(fileId) {
if (!this.isStorageAvailable())
return null;
try {
const allChunkStatus = this.loadAllChunkStatus();
const chunkStatus = allChunkStatus[fileId];
if (!chunkStatus)
return null;
// 检查是否过期(超过24小时)
const now = Date.now();
const maxAge = 24 * 60 * 60 * 1000;
if (chunkStatus.timestamp && (now - chunkStatus.timestamp) > maxAge) {
this.removeChunkStatus(fileId);
return null;
}
return {
uploaded: chunkStatus.uploaded || [],
pending: chunkStatus.pending || []
};
}
catch (error) {
console.warn('Failed to load chunk status from localStorage:', error);
return null;
}
}
/**
* 加载所有分片状态
*/
loadAllChunkStatus() {
try {
const stored = localStorage.getItem(this.chunkStorageKey);
if (!stored)
return {};
const parsed = JSON.parse(stored);
return typeof parsed === 'object' && parsed !== null ? parsed : {};
}
catch (error) {
return {};
}
}
/**
* 移除指定文件的记录
*/
removeFile(fileId) {
if (!this.isStorageAvailable())
return;
try {
// 移除队列中的文件
const queue = this.loadQueue();
const filteredQueue = queue.filter(file => file.fileId !== fileId);
localStorage.setItem(this.storageKey, JSON.stringify(filteredQueue));
// 移除分片状态
this.removeChunkStatus(fileId);
}
catch (error) {
console.warn('Failed to remove file from localStorage:', error);
}
}
/**
* 移除分片状态
*/
removeChunkStatus(fileId) {
try {
const allChunkStatus = this.loadAllChunkStatus();
delete allChunkStatus[fileId];
localStorage.setItem(this.chunkStorageKey, JSON.stringify(allChunkStatus));
}
catch (error) {
console.warn('Failed to remove chunk status from localStorage:', error);
}
}
/**
* 清除所有持久化数据
*/
clearAll() {
if (!this.isStorageAvailable())
return;
try {
localStorage.removeItem(this.storageKey);
localStorage.removeItem(this.chunkStorageKey);
}
catch (error) {
console.warn('Failed to clear localStorage:', error);
}
}
/**
* 清理过期数据
*/
cleanupExpiredData() {
if (!this.isStorageAvailable())
return;
try {
// 清理过期的队列数据
const queue = this.loadQueue(); // loadQueue 已经过滤了过期数据
localStorage.setItem(this.storageKey, JSON.stringify(queue));
// 清理过期的分片数据
const allChunkStatus = this.loadAllChunkStatus();
const now = Date.now();
const maxAge = 24 * 60 * 60 * 1000;
const cleanedChunkStatus = {};
for (const [fileId, status] of Object.entries(allChunkStatus)) {
if (status.timestamp && (now - status.timestamp) <= maxAge) {
cleanedChunkStatus[fileId] = status;
}
}
localStorage.setItem(this.chunkStorageKey, JSON.stringify(cleanedChunkStatus));
}
catch (error) {
console.warn('Failed to cleanup expired data:', error);
}
}
/**
* 获取存储使用情况
*/
getStorageInfo() {
if (!this.isStorageAvailable()) {
return { queueSize: 0, chunkSize: 0, totalSize: 0, estimatedQuota: 0 };
}
try {
const queueData = localStorage.getItem(this.storageKey) || '';
const chunkData = localStorage.getItem(this.chunkStorageKey) || '';
const queueSize = new Blob([queueData]).size;
const chunkSize = new Blob([chunkData]).size;
// 估算localStorage总使用量
let totalSize = 0;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key) {
const value = localStorage.getItem(key) || '';
totalSize += new Blob([key + value]).size;
}
}
// 估算localStorage配额(通常为5-10MB)
const estimatedQuota = 5 * 1024 * 1024; // 5MB
return {
queueSize,
chunkSize,
totalSize,
estimatedQuota
};
}
catch (error) {
return { queueSize: 0, chunkSize: 0, totalSize: 0, estimatedQuota: 0 };
}
}
}
//# sourceMappingURL=QueuePersistence.js.map