Files
pixiv/backend/services/download-executor.js
T

290 lines
9.7 KiB
JavaScript

const fs = require('fs-extra');
const path = require('path');
/**
* 下载执行器 - 负责具体的下载逻辑执行
*/
class DownloadExecutor {
constructor(fileManager, taskManager, progressManager, historyManager) {
this.fileManager = fileManager;
this.taskManager = taskManager;
this.progressManager = progressManager;
this.historyManager = historyManager;
}
/**
* 执行单个作品下载
*/
async executeArtworkDownload(task, images, size, artworkDir, artwork) {
try {
// 检查哪些文件已经存在(断点续传)
const existingFiles = new Set();
if (await this.fileManager.directoryExists(artworkDir)) {
const files = await this.fileManager.listDirectory(artworkDir);
for (const file of files) {
if (/\.(jpg|jpeg|png|gif|webp)$/i.test(file)) {
existingFiles.add(file);
}
}
}
// 逐个下载图片,实时更新进度
const results = [];
for (let index = 0; index < images.length; index++) {
if (task.status === 'cancelled') {
break;
}
const image = images[index];
const imageUrl = image[size] || image.original;
const fileName = `${artwork.title || 'Untitled'}_${artwork.id}_${index + 1}${this.fileManager.getFileExtension(imageUrl)}`;
const filePath = path.join(artworkDir, fileName);
// 如果文件已存在,跳过下载
if (existingFiles.has(fileName)) {
task.completed_files++;
task.progress = Math.round((task.completed_files / task.total_files) * 100);
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
results.push({ success: true, file: fileName, skipped: true });
continue;
}
try {
await this.fileManager.downloadFile(imageUrl, filePath);
task.completed_files++;
task.progress = Math.round((task.completed_files / task.total_files) * 100);
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
results.push({ success: true, file: fileName });
} catch (error) {
task.failed_files++;
console.error(`下载图片失败 ${index + 1}:`, error.message);
this.progressManager.notifyProgressUpdate(task.id, task);
results.push({ success: false, error: error.message });
}
}
// 保存作品信息
const infoPath = path.join(artworkDir, 'artwork_info.json');
await fs.writeJson(infoPath, artwork, { spaces: 2 });
// 更新任务状态
task.status = task.failed_files === 0 ? 'completed' : 'partial';
task.end_time = new Date();
task.progress = 100;
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
// 添加到历史记录
const historyItem = {
id: task.id,
type: 'artwork',
artwork_id: task.artwork_id,
artist_name: task.artist_name,
artwork_title: task.artwork_title,
download_path: artworkDir,
total_files: task.total_files,
completed_files: task.completed_files,
failed_files: task.failed_files,
start_time: task.start_time,
end_time: task.end_time,
status: task.status
};
await this.historyManager.addHistoryItem(historyItem);
console.log('下载完成,历史记录已保存:', {
taskId: task.id,
historyLength: this.historyManager.history.length,
tasksCount: this.taskManager.tasks.size
});
} catch (error) {
console.error('异步下载执行失败:', error);
task.status = 'failed';
task.error = error.message;
task.end_time = new Date();
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
}
}
/**
* 执行批量下载
*/
async executeBatchDownload(task, artworkIds, options) {
const { concurrent = 3, size = 'original', quality = 'high', format = 'auto' } = options;
try {
const results = [];
// 分批下载
for (let i = 0; i < task.filtered_ids.length; i += concurrent) {
if (task.status === 'cancelled') {
break;
}
const batch = task.filtered_ids.slice(i, i + concurrent);
const batchPromises = batch.map(async (artworkId) => {
try {
// 这里需要调用主下载服务的方法,暂时返回模拟结果
task.completed++;
const result = { artwork_id: artworkId, success: true };
results.push(result);
return result;
} catch (error) {
task.failed++;
const result = { artwork_id: artworkId, success: false, error: error.message };
results.push(result);
return result;
}
});
await Promise.all(batchPromises);
task.progress = Math.round((task.completed / task.total) * 100);
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
// 添加延迟避免请求过于频繁
if (i + concurrent < task.filtered_ids.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// 更新任务状态
task.status = task.failed === 0 ? 'completed' : 'partial';
task.end_time = new Date();
task.results = results;
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
} catch (error) {
task.status = 'failed';
task.error = error.message;
task.end_time = new Date();
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
}
}
/**
* 执行作者作品下载
*/
async executeArtistDownload(task, newArtworks, options) {
const { maxConcurrent = 3, size = 'original', quality = 'high', format = 'auto' } = options;
try {
const results = [];
// 分批下载作品
for (let i = 0; i < newArtworks.length; i += maxConcurrent) {
if (task.status === 'cancelled') {
break;
}
const batch = newArtworks.slice(i, i + maxConcurrent);
const batchPromises = batch.map(async (artwork) => {
try {
// 这里需要调用主下载服务的方法,暂时返回模拟结果
task.completed++;
const result = { artwork_id: artwork.id, success: true };
results.push(result);
return result;
} catch (error) {
task.failed++;
const result = { artwork_id: artwork.id, success: false, error: error.message };
results.push(result);
return result;
}
});
await Promise.all(batchPromises);
task.progress = Math.round((task.completed / task.total) * 100);
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
// 添加延迟避免请求过于频繁
if (i + maxConcurrent < newArtworks.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// 更新任务状态
task.status = task.failed === 0 ? 'completed' : 'partial';
task.end_time = new Date();
task.results = results;
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
} catch (error) {
task.status = 'failed';
task.error = error.message;
task.end_time = new Date();
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
}
}
/**
* 执行排行榜作品下载
*/
async executeRankingDownload(task, newArtworks, options) {
const { maxConcurrent = 3, size = 'original', quality = 'high', format = 'auto' } = options;
try {
const results = [];
// 分批下载作品
for (let i = 0; i < newArtworks.length; i += maxConcurrent) {
if (task.status === 'cancelled') {
break;
}
const batch = newArtworks.slice(i, i + maxConcurrent);
const batchPromises = batch.map(async (artwork) => {
try {
// 这里需要调用主下载服务的方法,暂时返回模拟结果
task.completed++;
const result = { artwork_id: artwork.id, success: true };
results.push(result);
return result;
} catch (error) {
task.failed++;
const result = { artwork_id: artwork.id, success: false, error: error.message };
results.push(result);
return result;
}
});
await Promise.all(batchPromises);
task.progress = Math.round((task.completed / task.total) * 100);
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
// 添加延迟避免请求过于频繁
if (i + maxConcurrent < newArtworks.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// 更新任务状态
task.status = task.failed === 0 ? 'completed' : 'partial';
task.end_time = new Date();
task.results = results;
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
} catch (error) {
task.status = 'failed';
task.error = error.message;
task.end_time = new Date();
await this.taskManager.saveTasks();
this.progressManager.notifyProgressUpdate(task.id, task);
}
}
}
module.exports = DownloadExecutor;