diff --git a/backend/routes/download.js b/backend/routes/download.js index d9de9ed..925ecdc 100644 --- a/backend/routes/download.js +++ b/backend/routes/download.js @@ -972,6 +972,28 @@ router.get('/stats', async (req, res) => { } }); +/** + * 获取下载注册表统计信息 + * GET /api/download/registry/stats + */ +router.get('/registry/stats', async (req, res) => { + try { + const downloadService = req.backend.getDownloadService(); + const stats = await downloadService.downloadRegistry.getStats(); + + res.json({ + success: true, + data: stats + }); + } catch (error) { + logger.error('获取下载注册表统计信息失败:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + /** * 导出下载注册表 * GET /api/download/registry/export @@ -1032,14 +1054,63 @@ router.post('/registry/rebuild', async (req, res) => { try { const downloadService = req.backend.getDownloadService(); const fileManager = downloadService.fileManager; - const result = await downloadService.downloadRegistry.rebuildFromFileSystem(fileManager); + // 生成任务ID + const taskId = `registry-rebuild-${Date.now()}`; + + // 立即返回任务ID,不等待完成 res.json({ success: true, - data: result + data: { + taskId, + status: 'started', + message: '注册表重建任务已启动' + } }); + + // 异步执行重建任务 + setImmediate(async () => { + try { + // 设置任务状态为进行中 + global.registryRebuildTasks = global.registryRebuildTasks || new Map(); + global.registryRebuildTasks.set(taskId, { + status: 'running', + startTime: Date.now(), + progress: { + scannedArtists: 0, + scannedArtworks: 0, + addedArtworks: 0, + skippedArtworks: 0, + currentArtist: null + } + }); + + const result = await downloadService.downloadRegistry.rebuildFromFileSystem(fileManager, taskId); + + // 更新任务状态为完成 + global.registryRebuildTasks.set(taskId, { + status: 'completed', + startTime: global.registryRebuildTasks.get(taskId).startTime, + endTime: Date.now(), + result: result + }); + + logger.info(`注册表重建任务完成: ${taskId}`, result); + } catch (error) { + logger.error(`注册表重建任务失败: ${taskId}`, error); + + // 更新任务状态为失败 + global.registryRebuildTasks.set(taskId, { + status: 'failed', + startTime: global.registryRebuildTasks.get(taskId).startTime, + endTime: Date.now(), + error: error.message + }); + } + }); + } catch (error) { - logger.error('重建下载注册表失败:', error); + logger.error('启动注册表重建任务失败:', error); res.status(500).json({ success: false, error: error.message @@ -1048,20 +1119,29 @@ router.post('/registry/rebuild', async (req, res) => { }); /** - * 获取下载注册表统计信息 - * GET /api/download/registry/stats + * 获取注册表重建任务状态 + * GET /api/download/registry/rebuild/status/:taskId */ -router.get('/registry/stats', async (req, res) => { +router.get('/registry/rebuild/status/:taskId', async (req, res) => { try { - const downloadService = req.backend.getDownloadService(); - const stats = await downloadService.downloadRegistry.getStats(); + const { taskId } = req.params; + + global.registryRebuildTasks = global.registryRebuildTasks || new Map(); + const task = global.registryRebuildTasks.get(taskId); + + if (!task) { + return res.status(404).json({ + success: false, + error: '任务不存在' + }); + } res.json({ success: true, - data: stats + data: task }); } catch (error) { - logger.error('获取下载注册表统计信息失败:', error); + logger.error('获取注册表重建任务状态失败:', error); res.status(500).json({ success: false, error: error.message @@ -1070,21 +1150,40 @@ router.get('/registry/stats', async (req, res) => { }); /** - * 清理下载注册表 - * POST /api/download/registry/cleanup + * 取消注册表重建任务 + * DELETE /api/download/registry/rebuild/:taskId */ -router.post('/registry/cleanup', async (req, res) => { +router.delete('/registry/rebuild/:taskId', async (req, res) => { try { - const downloadService = req.backend.getDownloadService(); - const fileManager = downloadService.fileManager; - const result = await downloadService.downloadRegistry.cleanupRegistry(fileManager); + const { taskId } = req.params; + + global.registryRebuildTasks = global.registryRebuildTasks || new Map(); + const task = global.registryRebuildTasks.get(taskId); + + if (!task) { + return res.status(404).json({ + success: false, + error: '任务不存在' + }); + } + + if (task.status === 'running') { + // 标记任务为已取消 + global.registryRebuildTasks.set(taskId, { + ...task, + status: 'cancelled', + endTime: Date.now() + }); + } res.json({ success: true, - data: result + data: { + message: '任务已取消' + } }); } catch (error) { - logger.error('清理下载注册表失败:', error); + logger.error('取消注册表重建任务失败:', error); res.status(500).json({ success: false, error: error.message diff --git a/backend/services/download-registry.js b/backend/services/download-registry.js index 96691ca..2ba3b1d 100644 --- a/backend/services/download-registry.js +++ b/backend/services/download-registry.js @@ -329,118 +329,95 @@ class DownloadRegistry { } /** - * 从文件系统扫描并重建注册表 - * @param {Object} fileManager - 文件管理器实例 - * @returns {Object} 扫描结果统计 + * 从文件系统重建注册表 + * @param {FileManager} fileManager + * @param {string} taskId - 任务ID,用于更新进度 + * @returns {Promise<{scannedArtists: number, scannedArtworks: number, addedArtworks: number, skippedArtworks: number}>} */ - async rebuildFromFileSystem(fileManager) { - try { - logger.info('开始从文件系统扫描并添加新作品到注册表...'); - - if (!this.loaded) { - await this.loadRegistry(); - } - - let scannedArtists = 0; - let scannedArtworks = 0; - let addedArtworks = 0; - let skippedArtworks = 0; + async rebuildFromFileSystem(fileManager, taskId = null) { + logger.info('开始从文件系统重建下载注册表...'); + + const stats = { + scannedArtists: 0, + scannedArtworks: 0, + addedArtworks: 0, + skippedArtworks: 0 + }; - const downloadPath = await fileManager.getDownloadPath(); - logger.debug(`扫描下载路径: ${downloadPath}`); - - const artists = await fileManager.listDirectory(downloadPath); - logger.debug(`找到 ${artists.length} 个作者目录`); + // 获取所有艺术家目录 + const artistDirs = await fileManager.getArtistDirectories(); + logger.info(`发现 ${artistDirs.length} 个艺术家目录`); - for (const artist of artists) { - try { - const artistPath = path.join(downloadPath, artist); - const artistStat = await fileManager.getFileInfo(artistPath); - - if (artistStat.exists && artistStat.isDirectory) { - scannedArtists++; - logger.debug(`扫描作者: ${artist}`); - - const artworks = await fileManager.listDirectory(artistPath); - - for (const artwork of artworks) { - try { - const artworkPath = path.join(artistPath, artwork); - const artworkStat = await fileManager.getFileInfo(artworkPath); - - if (artworkStat.exists && artworkStat.isDirectory) { - scannedArtworks++; - - // 检查是否是作品目录(包含数字ID) - const artworkMatch = artwork.match(/^(\d+)_(.+)$/); - if (artworkMatch) { - const artworkId = parseInt(artworkMatch[1]); - - // 检查作品是否已经在注册表中 - const isAlreadyRegistered = await this.isArtworkDownloaded(artworkId); - if (isAlreadyRegistered) { - skippedArtworks++; - continue; // 跳过已注册的作品 - } - - // 检查作品信息文件和图片文件 - const infoPath = path.join(artworkPath, 'artwork_info.json'); - let artworkInfo; - try { - const infoContent = await fs.readFile(infoPath, 'utf8'); - artworkInfo = JSON.parse(infoContent); - } catch (error) { - logger.debug(`读取作品信息文件失败: ${infoPath}`, error); - continue; // 跳过没有信息文件的目录 - } - - // 检查是否有图片文件 - const files = await fileManager.listDirectory(artworkPath); - const imageFiles = files.filter(file => /\.(jpg|jpeg|png|gif|webp)$/i.test(file)); - - if (imageFiles.length > 0) { - // 检查图片数量是否与artwork_info.json中记录的一致 - const expectedImageCount = artworkInfo.page_count || 1; - if (imageFiles.length >= expectedImageCount) { - // 添加到注册表(只添加新的) - await this.addArtwork(artist, artworkId); - addedArtworks++; - logger.debug(`添加作品到注册表: ${artist} - ${artworkId}`); - } else { - logger.debug(`作品图片数量不足: ${artist} - ${artworkId}, 期望: ${expectedImageCount}, 实际: ${imageFiles.length}`); - } - } else { - logger.debug(`作品目录无图片文件: ${artworkPath}`); - } - } - } - } catch (error) { - logger.debug(`处理作品目录 ${artwork} 时出错:`, error); - continue; // 跳过有问题的作品目录 - } + // 更新进度的辅助函数 + const updateProgress = (currentArtist = null) => { + if (taskId && global.registryRebuildTasks) { + const task = global.registryRebuildTasks.get(taskId); + if (task && task.status === 'running') { + global.registryRebuildTasks.set(taskId, { + ...task, + progress: { + ...stats, + currentArtist } - } - } catch (error) { - logger.debug(`处理作者目录 ${artist} 时出错:`, error); - continue; // 跳过有问题的作者目录 + }); + } + // 检查是否被取消 + if (task && task.status === 'cancelled') { + throw new Error('任务已被取消'); } } + }; - const result = { - scannedArtists, - scannedArtworks, - addedArtworks, - skippedArtworks, - totalRegisteredArtists: Object.keys(this.registry.artists).length, - totalRegisteredArtworks: this.getTotalArtworkCount() - }; - - logger.info('注册表扫描完成', result); - return result; - } catch (error) { - logger.error('注册表扫描失败:', error); - throw error; + for (const artistDir of artistDirs) { + try { + stats.scannedArtists++; + updateProgress(artistDir); + + logger.info(`扫描艺术家目录: ${artistDir}`); + + // 获取艺术家目录下的所有作品目录 + const artworkDirs = await fileManager.getArtworkDirectories(artistDir); + + for (const artworkDir of artworkDirs) { + try { + stats.scannedArtworks++; + updateProgress(artistDir); + + // 检查作品是否已在注册表中 + const isRegistered = await this.isArtworkRegistered(artistDir, artworkDir); + + if (!isRegistered) { + // 获取作品信息并添加到注册表 + const artworkInfo = await fileManager.getArtworkInfo(artistDir, artworkDir); + if (artworkInfo) { + await this.addArtwork(artistDir, artworkDir, artworkInfo); + stats.addedArtworks++; + logger.debug(`添加作品到注册表: ${artistDir}/${artworkDir}`); + } + } else { + stats.skippedArtworks++; + } + + // 每处理10个作品更新一次进度 + if (stats.scannedArtworks % 10 === 0) { + updateProgress(artistDir); + } + + } catch (error) { + logger.warn(`处理作品目录失败 ${artistDir}/${artworkDir}:`, error.message); + } + } + + } catch (error) { + logger.warn(`处理艺术家目录失败 ${artistDir}:`, error.message); + } } + + // 最终更新进度 + updateProgress(null); + + logger.info('从文件系统重建下载注册表完成', stats); + return stats; } /** diff --git a/backend/services/file-manager.js b/backend/services/file-manager.js index 9b40d54..2255035 100644 --- a/backend/services/file-manager.js +++ b/backend/services/file-manager.js @@ -639,6 +639,64 @@ class FileManager { } } + /** + * 获取所有艺术家目录 + */ + async getArtistDirectories() { + try { + const downloadPath = await this.getDownloadPath(); + if (!await this.directoryExists(downloadPath)) { + return []; + } + + const items = await this.listDirectory(downloadPath); + const artistDirs = []; + + for (const item of items) { + const itemPath = path.join(downloadPath, item); + const stat = await fs.stat(itemPath); + if (stat.isDirectory()) { + artistDirs.push(item); + } + } + + return artistDirs; + } catch (error) { + logger.error('获取艺术家目录失败:', error); + return []; + } + } + + /** + * 获取指定艺术家目录下的所有作品目录 + */ + async getArtworkDirectories(artistName) { + try { + const downloadPath = await this.getDownloadPath(); + const artistPath = path.join(downloadPath, artistName); + + if (!await this.directoryExists(artistPath)) { + return []; + } + + const items = await this.listDirectory(artistPath); + const artworkDirs = []; + + for (const item of items) { + const itemPath = path.join(artistPath, item); + const stat = await fs.stat(itemPath); + if (stat.isDirectory()) { + artworkDirs.push(item); + } + } + + return artworkDirs; + } catch (error) { + logger.error(`获取艺术家 ${artistName} 的作品目录失败:`, error); + return []; + } + } + /** * 检查目录是否存在 */ diff --git a/ui/src/App.vue b/ui/src/App.vue index 8c262d0..c5740cc 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -1,6 +1,6 @@