@xuanqikai/one-click-upload
Version:
A CLI tool for one-click file upload to cloud storage services (OSS, TOS)
227 lines • 7.72 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_RETRY_CONFIG = void 0;
exports.retryOperation = retryOperation;
exports.sleep = sleep;
exports.createProgressAggregator = createProgressAggregator;
exports.validateUploadOptions = validateUploadOptions;
exports.calculateUploadStats = calculateUploadStats;
exports.shouldUploadFile = shouldUploadFile;
exports.generateUploadSummary = generateUploadSummary;
const path = __importStar(require("path"));
/**
* 默认重试配置
*/
exports.DEFAULT_RETRY_CONFIG = {
maxRetries: 3,
baseDelay: 1000, // 1秒
maxDelay: 30000, // 30秒
backoffFactor: 2
};
/**
* 执行带重试的操作
*/
async function retryOperation(operation, config = {}) {
const retryConfig = { ...exports.DEFAULT_RETRY_CONFIG, ...config };
let lastError;
for (let attempt = 1; attempt <= retryConfig.maxRetries; attempt++) {
try {
return await operation();
}
catch (error) {
lastError = error;
if (attempt === retryConfig.maxRetries) {
throw lastError;
}
// 计算延迟时间(指数退避)
const delay = Math.min(retryConfig.baseDelay * Math.pow(retryConfig.backoffFactor, attempt - 1), retryConfig.maxDelay);
console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await sleep(delay);
}
}
throw lastError;
}
/**
* 睡眠函数
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 创建进度聚合器(用于批量上传)
*/
function createProgressAggregator(totalFiles, onProgress) {
const fileProgresses = new Array(totalFiles).fill({ loaded: 0, total: 0 });
return (fileIndex, fileProgress) => {
if (!onProgress)
return;
fileProgresses[fileIndex] = { loaded: fileProgress.loaded, total: fileProgress.total };
// 计算总进度
const totalLoaded = fileProgresses.reduce((sum, p) => sum + p.loaded, 0);
const totalSize = fileProgresses.reduce((sum, p) => sum + p.total, 0);
const overallProgress = {
loaded: totalLoaded,
total: totalSize,
percentage: totalSize > 0 ? Math.round((totalLoaded / totalSize) * 100) : 0,
fileName: `${fileIndex + 1}/${totalFiles} - ${fileProgress.fileName}`
};
onProgress(overallProgress);
};
}
/**
* 验证上传选项
*/
function validateUploadOptions(options) {
const errors = [];
if (!options.localPath || !options.localPath.trim()) {
errors.push('Local path is required');
}
if (!options.remotePath || !options.remotePath.trim()) {
errors.push('Remote path is required');
}
if (options.chunkSize && options.chunkSize <= 0) {
errors.push('Chunk size must be greater than 0');
}
if (options.maxRetries && options.maxRetries < 0) {
errors.push('Max retries must be non-negative');
}
return {
valid: errors.length === 0,
errors
};
}
/**
* 计算上传统计信息
*/
function calculateUploadStats(results) {
const totalFiles = results.length;
const successCount = results.filter(r => r.success).length;
const failedCount = totalFiles - successCount;
const totalSize = results.reduce((sum, r) => sum + (r.size || 0), 0);
const successRate = totalFiles > 0 ? (successCount / totalFiles) * 100 : 0;
return {
totalFiles,
successCount,
failedCount,
totalSize,
successRate
};
}
/**
* 过滤文件(排除隐藏文件、临时文件等)
*/
function shouldUploadFile(filePath, options) {
const fileName = path.basename(filePath);
const opts = { includeHidden: false, excludePatterns: [], ...options };
// 排除隐藏文件
if (!opts.includeHidden && fileName.startsWith('.')) {
return false;
}
// 排除临时文件
const tempExtensions = ['.tmp', '.temp', '.swp', '.bak'];
if (tempExtensions.some(ext => fileName.endsWith(ext))) {
return false;
}
// 排除系统文件
const systemFiles = ['Thumbs.db', 'Desktop.ini', '.DS_Store'];
if (systemFiles.includes(fileName)) {
return false;
}
// 自定义排除模式
if (opts.excludePatterns) {
for (const pattern of opts.excludePatterns) {
const regex = new RegExp(pattern);
if (regex.test(fileName) || regex.test(filePath)) {
return false;
}
}
}
return true;
}
/**
* 生成上传摘要报告
*/
function generateUploadSummary(result) {
const { totalFiles, successCount, failedCount, totalSize, duration } = result;
const successRate = totalFiles > 0 ? ((successCount / totalFiles) * 100).toFixed(1) : '0';
let summary = `Upload Summary:\n`;
summary += `─────────────────────────────────────────\n`;
summary += `Total files: ${totalFiles}\n`;
summary += `Successful: ${successCount} (${successRate}%)\n`;
summary += `Failed: ${failedCount}\n`;
summary += `Total size: ${formatBytes(totalSize)}\n`;
summary += `Duration: ${formatDuration(duration)}\n`;
if (failedCount > 0) {
summary += `\nFailed files:\n`;
result.results
.filter(r => !r.success)
.forEach(r => {
summary += ` ✗ ${r.fileName}: ${r.error}\n`;
});
}
return summary;
}
/**
* 格式化字节数
*/
function formatBytes(bytes) {
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
/**
* 格式化持续时间
*/
function formatDuration(milliseconds) {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
}
else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
}
else {
return `${seconds}s`;
}
}
//# sourceMappingURL=uploadUtils.js.map