修复暂停时没有中断的问题
This commit is contained in:
@@ -16,6 +16,9 @@ class DownloadExecutor {
|
|||||||
this.progressManager = progressManager;
|
this.progressManager = progressManager;
|
||||||
this.historyManager = historyManager;
|
this.historyManager = historyManager;
|
||||||
this.downloadService = downloadService; // 添加下载服务引用
|
this.downloadService = downloadService; // 添加下载服务引用
|
||||||
|
|
||||||
|
// 存储每个任务的中断控制器
|
||||||
|
this.abortControllers = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +26,10 @@ class DownloadExecutor {
|
|||||||
*/
|
*/
|
||||||
async executeArtworkDownload(task, images, size, artworkDir, artwork) {
|
async executeArtworkDownload(task, images, size, artworkDir, artwork) {
|
||||||
try {
|
try {
|
||||||
|
// 为这个任务创建中断控制器
|
||||||
|
const abortController = new AbortController();
|
||||||
|
this.abortControllers.set(task.id, abortController);
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|
||||||
for (let index = 0; index < images.length; index++) {
|
for (let index = 0; index < images.length; index++) {
|
||||||
@@ -33,6 +40,8 @@ class DownloadExecutor {
|
|||||||
// 检查是否应该暂停
|
// 检查是否应该暂停
|
||||||
if (this.shouldPause(task.id)) {
|
if (this.shouldPause(task.id)) {
|
||||||
logger.info('任务已暂停,停止下载:', task.id);
|
logger.info('任务已暂停,停止下载:', task.id);
|
||||||
|
// 中断当前下载
|
||||||
|
abortController.abort();
|
||||||
// 确保任务状态为暂停
|
// 确保任务状态为暂停
|
||||||
task.status = 'paused';
|
task.status = 'paused';
|
||||||
await this.taskManager.saveTasks();
|
await this.taskManager.saveTasks();
|
||||||
@@ -100,8 +109,8 @@ class DownloadExecutor {
|
|||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
await this.fileManager.ensureDirectory(path.dirname(filePath));
|
await this.fileManager.ensureDirectory(path.dirname(filePath));
|
||||||
|
|
||||||
// 下载文件并等待完成
|
// 下载文件并等待完成,传入中断控制器
|
||||||
await this.fileManager.downloadFile(imageUrl, filePath);
|
await this.fileManager.downloadFile(imageUrl, filePath, abortController);
|
||||||
|
|
||||||
// 验证下载的文件完整性,传入期望的MIME类型
|
// 验证下载的文件完整性,传入期望的MIME类型
|
||||||
const expectedMimeType = this.getMimeTypeFromUrl(imageUrl);
|
const expectedMimeType = this.getMimeTypeFromUrl(imageUrl);
|
||||||
@@ -232,6 +241,9 @@ class DownloadExecutor {
|
|||||||
task.end_time = new Date();
|
task.end_time = new Date();
|
||||||
await this.taskManager.saveTasks();
|
await this.taskManager.saveTasks();
|
||||||
this.progressManager.notifyProgressUpdate(task.id, task);
|
this.progressManager.notifyProgressUpdate(task.id, task);
|
||||||
|
} finally {
|
||||||
|
// 清理中断控制器
|
||||||
|
this.abortControllers.delete(task.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,6 +624,19 @@ class DownloadExecutor {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中断指定任务的下载
|
||||||
|
* @param {string} taskId - 任务ID
|
||||||
|
*/
|
||||||
|
abortTask(taskId) {
|
||||||
|
const abortController = this.abortControllers.get(taskId);
|
||||||
|
if (abortController) {
|
||||||
|
logger.info('中断任务下载', { taskId });
|
||||||
|
abortController.abort();
|
||||||
|
this.abortControllers.delete(taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查任务是否应该暂停
|
* 检查任务是否应该暂停
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -239,6 +239,9 @@ class DownloadService {
|
|||||||
// 更新任务状态为取消中,防止并发操作
|
// 更新任务状态为取消中,防止并发操作
|
||||||
await this.taskManager.updateTask(taskId, { status: 'cancelling' });
|
await this.taskManager.updateTask(taskId, { status: 'cancelling' });
|
||||||
|
|
||||||
|
// 立即中断正在进行的下载
|
||||||
|
this.downloadExecutor.abortTask(taskId);
|
||||||
|
|
||||||
// 清理未完成的文件
|
// 清理未完成的文件
|
||||||
await this.cleanupIncompleteFiles(task);
|
await this.cleanupIncompleteFiles(task);
|
||||||
|
|
||||||
@@ -288,6 +291,9 @@ class DownloadService {
|
|||||||
// 更新任务状态为暂停中,防止并发操作
|
// 更新任务状态为暂停中,防止并发操作
|
||||||
await this.taskManager.updateTask(taskId, { status: 'pausing' });
|
await this.taskManager.updateTask(taskId, { status: 'pausing' });
|
||||||
|
|
||||||
|
// 立即中断正在进行的下载
|
||||||
|
this.downloadExecutor.abortTask(taskId);
|
||||||
|
|
||||||
// 清理未完成的文件
|
// 清理未完成的文件
|
||||||
await this.cleanupIncompleteFiles(task);
|
await this.cleanupIncompleteFiles(task);
|
||||||
|
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ class FileManager {
|
|||||||
/**
|
/**
|
||||||
* 简单的文件下载方法
|
* 简单的文件下载方法
|
||||||
*/
|
*/
|
||||||
async downloadFile(url, filePath) {
|
async downloadFile(url, filePath, abortController = null) {
|
||||||
const downloadConfig = await this.getDownloadConfig();
|
const downloadConfig = await this.getDownloadConfig();
|
||||||
const maxRetries = downloadConfig.retryAttempts;
|
const maxRetries = downloadConfig.retryAttempts;
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
@@ -272,6 +272,11 @@ class FileManager {
|
|||||||
let response = null;
|
let response = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 检查是否已被中断
|
||||||
|
if (abortController && abortController.signal.aborted) {
|
||||||
|
throw new Error('下载已被中断');
|
||||||
|
}
|
||||||
|
|
||||||
// 使用增强的文件工具类确保目录存在
|
// 使用增强的文件工具类确保目录存在
|
||||||
const dirPath = path.dirname(filePath);
|
const dirPath = path.dirname(filePath);
|
||||||
const dirCreated = await FileUtils.safeEnsureDirEnhanced(dirPath);
|
const dirCreated = await FileUtils.safeEnsureDirEnhanced(dirPath);
|
||||||
@@ -288,6 +293,11 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 再次检查是否已被中断
|
||||||
|
if (abortController && abortController.signal.aborted) {
|
||||||
|
throw new Error('下载已被中断');
|
||||||
|
}
|
||||||
|
|
||||||
response = await axios({
|
response = await axios({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: url,
|
url: url,
|
||||||
@@ -296,7 +306,8 @@ class FileManager {
|
|||||||
'Referer': 'https://www.pixiv.net/',
|
'Referer': 'https://www.pixiv.net/',
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||||
},
|
},
|
||||||
timeout: downloadConfig.timeout
|
timeout: downloadConfig.timeout,
|
||||||
|
signal: abortController ? abortController.signal : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用增强的写入流创建方法
|
// 使用增强的写入流创建方法
|
||||||
@@ -315,6 +326,24 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听中断信号
|
||||||
|
if (abortController) {
|
||||||
|
abortController.signal.addEventListener('abort', () => {
|
||||||
|
if (isResolved) return;
|
||||||
|
isResolved = true;
|
||||||
|
|
||||||
|
logger.info(`下载被中断: ${filePath}`);
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
// 删除未完成的文件
|
||||||
|
this.safeDeleteFile(filePath).catch(error => {
|
||||||
|
logger.warn('删除被中断的文件失败', { filePath, error: error.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
reject(new Error('下载被中断'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
writer.on('finish', async () => {
|
writer.on('finish', async () => {
|
||||||
if (isResolved) return;
|
if (isResolved) return;
|
||||||
isResolved = true;
|
isResolved = true;
|
||||||
@@ -378,6 +407,9 @@ class FileManager {
|
|||||||
// 清理超时定时器
|
// 清理超时定时器
|
||||||
writer.on('finish', () => clearTimeout(timeout));
|
writer.on('finish', () => clearTimeout(timeout));
|
||||||
writer.on('error', () => clearTimeout(timeout));
|
writer.on('error', () => clearTimeout(timeout));
|
||||||
|
if (abortController) {
|
||||||
|
abortController.signal.addEventListener('abort', () => clearTimeout(timeout));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -391,7 +423,25 @@ class FileManager {
|
|||||||
|
|
||||||
lastError = error;
|
lastError = error;
|
||||||
|
|
||||||
// 处理文件系统错误
|
// 首先检查是否是中断错误,如果是则直接抛出,不重试
|
||||||
|
if (error.message === '下载已被中断' ||
|
||||||
|
error.code === 'ERR_CANCELED' ||
|
||||||
|
error.name === 'AbortError' ||
|
||||||
|
(error.message && error.message.includes('canceled'))) {
|
||||||
|
|
||||||
|
logger.error(`下载文件失败 (尝试 ${attempt}/${maxRetries}): ${filePath}`, {
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
url,
|
||||||
|
retryable: false,
|
||||||
|
attempt,
|
||||||
|
reason: 'download_interrupted'
|
||||||
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理其他文件系统错误
|
||||||
const errorResult = ErrorHandler.handleFileSystemError(error, filePath, 'download');
|
const errorResult = ErrorHandler.handleFileSystemError(error, filePath, 'download');
|
||||||
|
|
||||||
logger.error(`下载文件失败 (尝试 ${attempt}/${maxRetries}): ${filePath}`, {
|
logger.error(`下载文件失败 (尝试 ${attempt}/${maxRetries}): ${filePath}`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user