const fs = require('fs').promises const path = require('path') const { promisify } = require('util') const { exec } = require('child_process') const ConfigManager = require('../config/config-manager') const execAsync = promisify(exec) class RepositoryService { constructor() { // 初始化配置管理器 this.configManager = new ConfigManager() this.config = null } // 获取当前工作目录(基于配置) getCurrentBaseDir() { if (this.config && this.config.downloadDir) { // 如果是相对路径,转换为绝对路径 return path.isAbsolute(this.config.downloadDir) ? this.config.downloadDir : path.resolve(process.cwd(), this.config.downloadDir) } // 默认返回项目根目录下的downloads文件夹 return path.resolve(process.cwd(), 'downloads') } // 初始化仓库 async initialize() { try { // 初始化配置管理器 await this.configManager.initialize() // 加载配置 await this.loadConfig() // 确保下载目录存在 const currentBaseDir = this.getCurrentBaseDir() await fs.mkdir(currentBaseDir, { recursive: true }) return { success: true, message: '仓库初始化成功' } } catch (error) { throw new Error(`仓库初始化失败: ${error.message}`) } } // 加载配置 async loadConfig() { try { this.config = await this.configManager.readConfig() } catch (error) { console.error('加载配置失败:', error) // 如果加载失败,使用默认配置 this.config = await this.configManager.readConfig() } } // 保存配置 async saveConfig() { try { await this.configManager.saveConfig(this.config) } catch (error) { throw new Error(`保存配置失败: ${error.message}`) } } // 获取仓库配置 async getConfig() { await this.loadConfig() return this.config } // 更新仓库配置 async updateConfig(newConfig) { this.config = { ...this.config, ...newConfig } await this.saveConfig() return { success: true, message: '配置更新成功' } } // 重置仓库配置为默认值 async resetConfig() { try { this.config = await this.configManager.resetToDefault() return { success: true, message: '配置已重置为默认值' } } catch (error) { throw new Error(`重置配置失败: ${error.message}`) } } // 获取仓库统计信息 async getStats() { try { const stats = await this.scanRepository() return { totalArtworks: stats.artworks.length, totalArtists: stats.artists.length, totalSize: stats.totalSize, diskUsage: await this.getDiskUsage(), lastScan: new Date().toISOString() } } catch (error) { throw new Error(`获取统计信息失败: ${error.message}`) } } // 扫描仓库 async scanRepository() { const artworks = [] const artists = new Set() let totalSize = 0 try { // 确保配置已加载 await this.loadConfig() // 使用当前配置的目录 const currentBaseDir = this.getCurrentBaseDir() // 扫描作者目录 const artistEntries = await fs.readdir(currentBaseDir, { withFileTypes: true }) for (const artistEntry of artistEntries) { if (!artistEntry.isDirectory()) continue // 跳过配置文件和隐藏文件 if (artistEntry.name.startsWith('.') || artistEntry.name === '.repository-config.json') { continue } const artistName = artistEntry.name const artistPath = path.join(currentBaseDir, artistName) // 扫描作者下的作品目录 const artworkEntries = await fs.readdir(artistPath, { withFileTypes: true }) for (const artworkEntry of artworkEntries) { if (!artworkEntry.isDirectory()) continue const fullPath = path.join(artistPath, artworkEntry.name) // 检查是否是作品目录(包含数字ID) const artworkMatch = artworkEntry.name.match(/^(\d+)_(.+)$/) if (artworkMatch) { const artworkId = artworkMatch[1] const title = artworkMatch[2] // 扫描作品文件 const files = await this.scanArtworkFiles(fullPath) if (files.length > 0) { artworks.push({ id: artworkId, title: title, artist: artistName, artistPath: artistPath, path: fullPath, files: files, size: files.reduce((sum, file) => sum + file.size, 0), createdAt: await this.getFileCreationTime(fullPath) }) artists.add(artistName) totalSize += files.reduce((sum, file) => sum + file.size, 0) } } } } return { artworks, artists: Array.from(artists), totalSize } } catch (error) { throw new Error(`扫描仓库失败: ${error.message}`) } } // 扫描作品文件 async scanArtworkFiles(artworkPath) { try { // 确保配置已加载 await this.loadConfig() const files = [] const entries = await fs.readdir(artworkPath, { withFileTypes: true }) for (const entry of entries) { if (entry.isFile()) { const filePath = path.join(artworkPath, entry.name) const ext = path.extname(entry.name).toLowerCase() if (this.config.allowedExtensions.includes(ext)) { const stats = await fs.stat(filePath) const currentBaseDir = this.getCurrentBaseDir() files.push({ name: entry.name, path: path.relative(currentBaseDir, filePath), size: stats.size, extension: ext, modifiedAt: stats.mtime }) } } } return files } catch (error) { return [] } } // 获取文件创建时间 async getFileCreationTime(filePath) { try { const stats = await fs.stat(filePath) return stats.birthtime } catch (error) { return new Date() } } // 获取磁盘使用情况 async getDiskUsage() { try { const currentBaseDir = this.getCurrentBaseDir() const stats = await fs.statfs(currentBaseDir) const total = stats.blocks * stats.bsize const free = stats.bavail * stats.bsize const used = total - free return { total, used, free, usagePercent: Math.round((used / total) * 100) } } catch (error) { return { total: 0, used: 0, free: 0, usagePercent: 0 } } } // 获取作者作品 async getArtworksByArtist(artistName, offset = 0, limit = 50) { try { const stats = await this.scanRepository() const artistArtworks = stats.artworks.filter(artwork => artwork.artist.toLowerCase() === artistName.toLowerCase() ) return { artworks: artistArtworks.slice(offset, offset + limit), total: artistArtworks.length, offset, limit } } catch (error) { throw new Error(`获取作者作品失败: ${error.message}`) } } // 按作品ID查找 async findArtworkById(artworkId) { try { const stats = await this.scanRepository() return stats.artworks.find(artwork => artwork.id === artworkId) } catch (error) { throw new Error(`查找作品失败: ${error.message}`) } } // 检查作品是否已下载 async isArtworkDownloaded(artworkId) { try { // 确保配置已加载 await this.loadConfig() // 使用当前配置的目录 const currentBaseDir = this.getCurrentBaseDir() // 扫描所有作者目录 const artistEntries = await fs.readdir(currentBaseDir, { withFileTypes: true }) for (const artistEntry of artistEntries) { if (!artistEntry.isDirectory()) continue // 跳过配置文件和隐藏文件 if (artistEntry.name.startsWith('.') || artistEntry.name === '.repository-config.json') { continue } const artistPath = path.join(currentBaseDir, artistEntry.name) // 扫描作者下的作品目录 const artworkEntries = await fs.readdir(artistPath, { withFileTypes: true }) for (const artworkEntry of artworkEntries) { if (!artworkEntry.isDirectory()) continue // 检查是否是目标作品目录(包含数字ID) const artworkMatch = artworkEntry.name.match(/^(\d+)_(.+)$/) if (artworkMatch && artworkMatch[1] === artworkId.toString()) { // 检查作品目录中是否有图片文件 const artworkPath = path.join(artistPath, artworkEntry.name) const files = await this.scanArtworkFiles(artworkPath) return files.length > 0 } } } return false } catch (error) { console.error('检查作品下载状态失败:', error) return false } } // 检查目录是否存在 async checkDirectoryExists(dirPath) { try { // 如果是相对路径,转换为绝对路径 const fullPath = path.isAbsolute(dirPath) ? dirPath : path.resolve(process.cwd(), dirPath) const stats = await fs.stat(fullPath) return stats.isDirectory() } catch (error) { return false } } // 搜索作品 async searchArtworks(query, offset = 0, limit = 50) { try { const stats = await this.scanRepository() const filtered = stats.artworks.filter(artwork => artwork.title.toLowerCase().includes(query.toLowerCase()) || artwork.artist.toLowerCase().includes(query.toLowerCase()) || artwork.id.includes(query) ) return { artworks: filtered.slice(offset, offset + limit), total: filtered.length, offset, limit } } catch (error) { throw new Error(`搜索作品失败: ${error.message}`) } } // 获取所有作者列表 async getArtists(offset = 0, limit = 50) { try { const stats = await this.scanRepository() const artists = stats.artists.slice(offset, offset + limit) // 获取每个作者的统计信息 const artistsWithStats = artists.map(artistName => { const artistArtworks = stats.artworks.filter(artwork => artwork.artist === artistName) const totalSize = artistArtworks.reduce((sum, artwork) => sum + artwork.size, 0) return { name: artistName, artworkCount: artistArtworks.length, totalSize, lastUpdated: artistArtworks.length > 0 ? Math.max(...artistArtworks.map(a => new Date(a.createdAt).getTime())) : null } }) return { artists: artistsWithStats, total: stats.artists.length, offset, limit } } catch (error) { throw new Error(`获取作者列表失败: ${error.message}`) } } // 自动迁移旧项目 async migrateOldProjects(sourceDir) { try { // 确保配置已加载 await this.loadConfig() const currentBaseDir = this.getCurrentBaseDir() const result = { success: true, message: '迁移完成', log: [], totalMigrated: 0 } // 确保目标目录存在 await fs.mkdir(currentBaseDir, { recursive: true }) // 扫描源目录 const sourceEntries = await fs.readdir(sourceDir, { withFileTypes: true }) for (const entry of sourceEntries) { if (!entry.isDirectory()) continue const oldDirPath = path.join(sourceDir, entry.name) const newDirPath = path.join(currentBaseDir, entry.name) // 检查是否已存在 try { await fs.access(newDirPath) result.log.push({ id: entry.name, title: entry.name, status: 'skipped', reason: '目录已存在' }) continue } catch (error) { // 目录不存在,可以迁移 } try { // 直接移动整个目录 await fs.rename(oldDirPath, newDirPath) result.log.push({ id: entry.name, title: entry.name, status: 'success' }) result.totalMigrated++ } catch (error) { result.log.push({ id: entry.name, title: entry.name, status: 'error', reason: error.message }) } } return result } catch (error) { throw new Error(`迁移失败: ${error.message}`) } } // 从旧目录迁移到新目录 async migrateFromOldToNew(oldDir, newDir) { try { const result = { success: true, message: '迁移完成', log: [], totalMigrated: 0 } // 检查旧目录是否存在 try { await fs.access(oldDir) } catch (error) { return { ...result, message: '旧目录不存在,无需迁移' } } // 确保新目录存在 await fs.mkdir(newDir, { recursive: true }) // 扫描旧目录 const oldEntries = await fs.readdir(oldDir, { withFileTypes: true }) for (const entry of oldEntries) { if (!entry.isDirectory()) continue const oldEntryPath = path.join(oldDir, entry.name) const newEntryPath = path.join(newDir, entry.name) // 检查是否已存在 try { await fs.access(newEntryPath) result.log.push({ id: entry.name, title: entry.name, status: 'skipped', reason: '目录已存在' }) continue } catch (error) { // 目录不存在,可以迁移 } try { // 直接移动整个目录 await fs.rename(oldEntryPath, newEntryPath) result.log.push({ id: entry.name, title: entry.name, status: 'success' }) result.totalMigrated++ } catch (error) { result.log.push({ id: entry.name, title: entry.name, status: 'error', reason: error.message }) } } return result } catch (error) { throw new Error(`迁移失败: ${error.message}`) } } // 复制目录 async copyDirectory(source, target) { try { await fs.mkdir(target, { recursive: true }) const entries = await fs.readdir(source, { withFileTypes: true }) for (const entry of entries) { const sourcePath = path.join(source, entry.name) const targetPath = path.join(target, entry.name) if (entry.isDirectory()) { await this.copyDirectory(sourcePath, targetPath) } else { await fs.copyFile(sourcePath, targetPath) } } } catch (error) { throw new Error(`复制目录失败: ${error.message}`) } } // 删除作品 async deleteArtwork(artworkId) { try { const artwork = await this.findArtworkById(artworkId) if (!artwork) { throw new Error('作品不存在') } await fs.rm(artwork.path, { recursive: true, force: true }) // 检查作者目录是否为空,如果为空则删除 const artistDir = artwork.artistPath const artistArtworks = await this.getArtworksByArtist(artwork.artist) if (artistArtworks.artworks.length === 0) { await fs.rmdir(artistDir) } return { success: true, message: '作品删除成功' } } catch (error) { throw new Error(`删除作品失败: ${error.message}`) } } // 获取文件预览URL async getFilePreviewUrl(filePath) { // 这里可以返回一个代理URL,用于前端预览 const relativePath = path.relative(this.baseDir, filePath) return `/api/repository/preview?path=${encodeURIComponent(relativePath)}` } } module.exports = RepositoryService