Files
pixiv/backend/services/download-registry.js
T
2025-10-01 19:26:34 +08:00

557 lines
17 KiB
JavaScript

const path = require('path');
const fs = require('fs-extra');
const { defaultLogger } = require('../utils/logger');
// 创建logger实例
const logger = defaultLogger.child('DownloadRegistry');
/**
* 下载记录管理器 - 维护已下载作品的JSON记录
* 用于快速检测作品是否已下载,支持导入导出和多设备同步
*/
class DownloadRegistry {
constructor(dataPath) {
this.dataPath = dataPath;
this.registryPath = path.join(dataPath, 'download_registry.json');
this.registry = {
version: '1.0.5',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
artists: {} // 格式: { artistName: { artworks: [artworkId1, artworkId2, ...] } }
};
this.loaded = false;
}
/**
* 初始化注册表
*/
async init() {
try {
// 确保数据目录存在
await fs.ensureDir(this.dataPath);
// 加载现有注册表
await this.loadRegistry();
logger.info(`下载记录注册表初始化完成,总共包含${Object.keys(this.registry.artists).length}个作者,${this.getTotalArtworkCount()}个工作品`);
} catch (error) {
logger.error('下载记录注册表初始化失败:', error);
throw error;
}
}
/**
* 加载注册表文件
*/
async loadRegistry() {
try {
if (await fs.pathExists(this.registryPath)) {
const data = await fs.readJson(this.registryPath);
// 验证数据格式
if (data && typeof data === 'object' && data.artists) {
this.registry = {
version: data.version || '1.0.0',
created_at: data.created_at || new Date().toISOString(),
updated_at: data.updated_at || new Date().toISOString(),
artists: data.artists || {}
};
} else {
logger.warn('注册表文件格式不正确,使用默认格式');
}
} else {
logger.info('注册表文件不存在,将创建新的注册表');
}
this.loaded = true;
} catch (error) {
logger.error('加载注册表文件失败:', error);
// 使用默认注册表
this.loaded = true;
}
}
/**
* 保存注册表到文件
*/
async saveRegistry() {
try {
this.registry.updated_at = new Date().toISOString();
await fs.writeJson(this.registryPath, this.registry, { spaces: 2 });
logger.debug('注册表已保存到文件', { path: this.registryPath });
} catch (error) {
logger.error('保存注册表失败:', error);
throw error;
}
}
/**
* 添加已下载的作品记录
* @param {string} artistName - 作者名称
* @param {number|string} artworkId - 作品ID
*/
async addArtwork(artistName, artworkId) {
if (!this.loaded) {
await this.loadRegistry();
}
const normalizedArtistName = this.normalizeArtistName(artistName);
const normalizedArtworkId = parseInt(artworkId);
if (!this.registry.artists[normalizedArtistName]) {
this.registry.artists[normalizedArtistName] = {
artworks: []
};
}
// 检查是否已存在
if (!this.registry.artists[normalizedArtistName].artworks.includes(normalizedArtworkId)) {
this.registry.artists[normalizedArtistName].artworks.push(normalizedArtworkId);
this.registry.artists[normalizedArtistName].artworks.sort((a, b) => b - a); // 按ID倒序排列
await this.saveRegistry();
logger.debug('添加作品记录', { artistName: normalizedArtistName, artworkId: normalizedArtworkId });
}
}
/**
* 移除作品记录
* @param {string} artistName - 作者名称
* @param {number|string} artworkId - 作品ID
*/
async removeArtwork(artistName, artworkId) {
if (!this.loaded) {
await this.loadRegistry();
}
const normalizedArtistName = this.normalizeArtistName(artistName);
const normalizedArtworkId = parseInt(artworkId);
logger.debug('开始移除作品记录', {
originalArtistName: artistName,
normalizedArtistName: normalizedArtistName,
artworkId: normalizedArtworkId
});
if (this.registry.artists[normalizedArtistName]) {
const artworks = this.registry.artists[normalizedArtistName].artworks;
const index = artworks.indexOf(normalizedArtworkId);
logger.debug('查找作品在注册表中的位置', {
artistName: normalizedArtistName,
artworkId: normalizedArtworkId,
index: index,
artworks: artworks
});
if (index !== -1) {
artworks.splice(index, 1);
// 如果作者下没有作品了,删除作者记录
if (artworks.length === 0) {
delete this.registry.artists[normalizedArtistName];
logger.info('作者下无作品,删除作者记录', { artistName: normalizedArtistName });
}
await this.saveRegistry();
logger.debug('成功移除作品记录', { artistName: normalizedArtistName, artworkId: normalizedArtworkId });
} else {
logger.warn('作品在注册表中未找到', { artistName: normalizedArtistName, artworkId: normalizedArtworkId });
}
} else {
logger.warn('作者在注册表中未找到', { artistName: normalizedArtistName });
}
}
/**
* 检查作品是否已下载
* @param {number|string} artworkId - 作品ID
* @returns {boolean} 是否已下载
*/
async isArtworkDownloaded(artworkId) {
if (!this.loaded) {
await this.loadRegistry();
}
const normalizedArtworkId = parseInt(artworkId);
// 遍历所有作者查找作品
for (const artistName in this.registry.artists) {
if (this.registry.artists[artistName].artworks.includes(normalizedArtworkId)) {
return true;
}
}
return false;
}
/**
* 获取已下载的作品ID列表
* @returns {number[]} 作品ID数组
*/
async getDownloadedArtworkIds() {
if (!this.loaded) {
await this.loadRegistry();
}
const artworkIds = new Set();
for (const artistName in this.registry.artists) {
for (const artworkId of this.registry.artists[artistName].artworks) {
artworkIds.add(artworkId);
}
}
return Array.from(artworkIds).sort((a, b) => b - a);
}
/**
* 获取指定作者的已下载作品
* @param {string} artistName - 作者名称
* @returns {number[]} 作品ID数组
*/
async getArtistArtworks(artistName) {
if (!this.loaded) {
await this.loadRegistry();
}
const normalizedArtistName = this.normalizeArtistName(artistName);
if (this.registry.artists[normalizedArtistName]) {
return [...this.registry.artists[normalizedArtistName].artworks];
}
return [];
}
/**
* 获取所有已下载的作者列表
* @returns {string[]} 作者名称数组
*/
async getDownloadedArtists() {
if (!this.loaded) {
await this.loadRegistry();
}
return Object.keys(this.registry.artists);
}
/**
* 获取统计信息
*/
async getStats() {
if (!this.loaded) {
await this.loadRegistry();
}
const artists = Object.keys(this.registry.artists);
const totalArtworks = this.getTotalArtworkCount();
return {
artistCount: artists.length,
artworkCount: totalArtworks,
version: this.registry.version,
created_at: this.registry.created_at,
updated_at: this.registry.updated_at
};
}
/**
* 导出注册表数据
* @returns {Object} 注册表数据
*/
async exportRegistry() {
if (!this.loaded) {
await this.loadRegistry();
}
return {
...this.registry,
exported_at: new Date().toISOString()
};
}
/**
* 导入注册表数据(增量导入,不覆盖现有数据)
* @param {Object} importData - 要导入的数据
* @returns {Object} 导入结果统计
*/
async importRegistry(importData) {
if (!this.loaded) {
await this.loadRegistry();
}
if (!importData || !importData.artists) {
throw new Error('导入数据格式不正确');
}
let addedArtists = 0;
let addedArtworks = 0;
let skippedArtworks = 0;
for (const artistName in importData.artists) {
const normalizedArtistName = this.normalizeArtistName(artistName);
const importArtworks = importData.artists[artistName].artworks || [];
if (!this.registry.artists[normalizedArtistName]) {
this.registry.artists[normalizedArtistName] = { artworks: [] };
addedArtists++;
}
const existingArtworks = new Set(this.registry.artists[normalizedArtistName].artworks);
for (const artworkId of importArtworks) {
const normalizedArtworkId = parseInt(artworkId);
if (!existingArtworks.has(normalizedArtworkId)) {
this.registry.artists[normalizedArtistName].artworks.push(normalizedArtworkId);
addedArtworks++;
} else {
skippedArtworks++;
}
}
// 排序
this.registry.artists[normalizedArtistName].artworks.sort((a, b) => b - a);
}
await this.saveRegistry();
const result = {
addedArtists,
addedArtworks,
skippedArtworks,
totalArtists: Object.keys(this.registry.artists).length,
totalArtworks: this.getTotalArtworkCount()
};
logger.info(`注册表导入完成,导入了 ${result.addedArtists} 个作者,${result.addedArtworks} 个作品,跳过了 ${result.skippedArtworks} 个重复作品。当前总计:${result.totalArtists} 个作者,${result.totalArtworks} 个作品`);
return result;
}
/**
* 从文件系统扫描并重建注册表
* @param {Object} fileManager - 文件管理器实例
* @returns {Object} 扫描结果统计
*/
async rebuildFromFileSystem(fileManager) {
try {
logger.info('开始从文件系统扫描并添加新作品到注册表...');
if (!this.loaded) {
await this.loadRegistry();
}
let scannedArtists = 0;
let scannedArtworks = 0;
let addedArtworks = 0;
let skippedArtworks = 0;
const downloadPath = await fileManager.getDownloadPath();
logger.debug(`扫描下载路径: ${downloadPath}`);
const artists = await fileManager.listDirectory(downloadPath);
logger.debug(`找到 ${artists.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; // 跳过有问题的作品目录
}
}
}
} catch (error) {
logger.debug(`处理作者目录 ${artist} 时出错:`, error);
continue; // 跳过有问题的作者目录
}
}
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;
}
}
/**
* 清理注册表(移除不存在的记录)
* @param {Object} fileManager - 文件管理器实例
* @returns {Object} 清理结果统计
*/
async cleanupRegistry(fileManager) {
try {
if (!this.loaded) {
await this.loadRegistry();
}
logger.info('开始清理注册表...');
let removedArtists = 0;
let removedArtworks = 0;
const downloadPath = await fileManager.getDownloadPath();
for (const artistName in this.registry.artists) {
const artworks = [...this.registry.artists[artistName].artworks];
let validArtworks = [];
for (const artworkId of artworks) {
// 检查作品目录是否存在
let found = false;
try {
const artistPath = path.join(downloadPath, artistName);
if (await fileManager.directoryExists(artistPath)) {
const artworkEntries = await fileManager.listDirectory(artistPath);
for (const entry of artworkEntries) {
const match = entry.match(/^(\d+)_(.+)$/);
if (match && parseInt(match[1]) === artworkId) {
const artworkPath = path.join(artistPath, entry);
const infoPath = path.join(artworkPath, 'artwork_info.json');
// 检查信息文件是否存在
if (await fs.pathExists(infoPath)) {
found = true;
break;
}
}
}
}
} catch (error) {
logger.debug(`检查作品 ${artworkId} 时出错:`, error);
}
if (found) {
validArtworks.push(artworkId);
} else {
removedArtworks++;
logger.debug(`移除无效作品记录: ${artistName} - ${artworkId}`);
}
}
if (validArtworks.length > 0) {
this.registry.artists[artistName].artworks = validArtworks;
} else {
delete this.registry.artists[artistName];
removedArtists++;
logger.debug(`移除空作者记录: ${artistName}`);
}
}
await this.saveRegistry();
const result = {
removedArtists,
removedArtworks,
remainingArtists: Object.keys(this.registry.artists).length,
remainingArtworks: this.getTotalArtworkCount()
};
logger.info('注册表清理完成', result);
return result;
} catch (error) {
logger.error('注册表清理失败:', error);
throw error;
}
}
/**
* 标准化作者名称(处理特殊字符)
*/
normalizeArtistName(artistName) {
if (!artistName || typeof artistName !== 'string') {
return 'Unknown Artist';
}
return artistName.trim();
}
/**
* 获取总作品数量
*/
getTotalArtworkCount() {
let total = 0;
for (const artistName in this.registry.artists) {
total += this.registry.artists[artistName].artworks.length;
}
return total;
}
/**
* 获取注册表文件路径
*/
getRegistryPath() {
return this.registryPath;
}
}
module.exports = DownloadRegistry;