更新下载进度,修复不能重新下载的问题
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const ConfigManager = require('../config/config-manager');
|
||||
|
||||
/**
|
||||
* 文件管理器 - 负责文件下载、检查和目录管理
|
||||
*/
|
||||
class FileManager {
|
||||
constructor() {
|
||||
this.configManager = new ConfigManager();
|
||||
|
||||
// 下载配置
|
||||
this.downloadConfig = {
|
||||
timeout: 300000, // 5分钟超时
|
||||
chunkSize: 1024 * 1024, // 1MB块大小
|
||||
retryAttempts: 3, // 重试次数
|
||||
retryDelay: 2000, // 重试延迟
|
||||
concurrentDownloads: 3 // 并发下载数
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前下载路径
|
||||
*/
|
||||
async getDownloadPath() {
|
||||
try {
|
||||
const config = await this.configManager.readConfig();
|
||||
const downloadDir = config.downloadDir || './downloads';
|
||||
|
||||
// 如果是相对路径,转换为绝对路径
|
||||
return path.isAbsolute(downloadDir)
|
||||
? downloadDir
|
||||
: path.resolve(process.cwd(), downloadDir);
|
||||
} catch (error) {
|
||||
console.error('获取下载路径失败:', error);
|
||||
// 返回默认路径
|
||||
return path.resolve(process.cwd(), 'downloads');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件MD5
|
||||
*/
|
||||
async calculateFileMD5(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('md5');
|
||||
const stream = fs.createReadStream(filePath);
|
||||
|
||||
stream.on('data', (data) => {
|
||||
hash.update(data);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
resolve(hash.digest('hex'));
|
||||
});
|
||||
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件完整性
|
||||
*/
|
||||
async checkFileIntegrity(filePath, expectedSize = null) {
|
||||
try {
|
||||
if (!await fs.pathExists(filePath)) {
|
||||
return { valid: false, reason: '文件不存在' };
|
||||
}
|
||||
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
// 检查文件大小
|
||||
if (expectedSize && stats.size !== expectedSize) {
|
||||
return { valid: false, reason: '文件大小不匹配', actual: stats.size, expected: expectedSize };
|
||||
}
|
||||
|
||||
// 检查文件是否为空
|
||||
if (stats.size === 0) {
|
||||
return { valid: false, reason: '文件为空' };
|
||||
}
|
||||
|
||||
return { valid: true, size: stats.size };
|
||||
} catch (error) {
|
||||
return { valid: false, reason: '检查文件失败', error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单的文件下载方法
|
||||
*/
|
||||
async downloadFile(url, filePath) {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
responseType: 'stream',
|
||||
headers: {
|
||||
'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'
|
||||
},
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
const writer = fs.createWriteStream(filePath);
|
||||
response.data.pipe(writer);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
writer.on('finish', resolve);
|
||||
writer.on('error', (error) => {
|
||||
// 下载失败时删除文件
|
||||
fs.unlink(filePath, () => {});
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
getFileExtension(url) {
|
||||
const match = url.match(/\.([a-zA-Z0-9]+)(\?|$)/);
|
||||
return match ? `.${match[1]}` : '.jpg';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目录大小
|
||||
*/
|
||||
async getDirectorySize(dirPath) {
|
||||
try {
|
||||
const files = await fs.readdir(dirPath);
|
||||
let totalSize = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const stat = await fs.stat(filePath);
|
||||
if (stat.isFile()) {
|
||||
totalSize += stat.size;
|
||||
}
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建安全的目录名
|
||||
*/
|
||||
createSafeDirectoryName(name) {
|
||||
return name.replace(/[<>:"/\\|?*]/g, '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
async ensureDirectory(dirPath) {
|
||||
await fs.ensureDir(dirPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除目录
|
||||
*/
|
||||
async removeDirectory(dirPath) {
|
||||
if (await fs.pathExists(dirPath)) {
|
||||
await fs.remove(dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查目录是否存在
|
||||
*/
|
||||
async directoryExists(dirPath) {
|
||||
return await fs.pathExists(dirPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出目录内容
|
||||
*/
|
||||
async listDirectory(dirPath) {
|
||||
try {
|
||||
return await fs.readdir(dirPath);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件
|
||||
*/
|
||||
async copyFile(src, dest) {
|
||||
await fs.copy(src, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件
|
||||
*/
|
||||
async moveFile(src, dest) {
|
||||
await fs.move(src, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*/
|
||||
async deleteFile(filePath) {
|
||||
if (await fs.pathExists(filePath)) {
|
||||
await fs.unlink(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*/
|
||||
async fileExists(filePath) {
|
||||
return await fs.pathExists(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*/
|
||||
async getFileInfo(filePath) {
|
||||
try {
|
||||
const stats = await fs.stat(filePath);
|
||||
return {
|
||||
exists: true,
|
||||
size: stats.size,
|
||||
created: stats.birthtime,
|
||||
modified: stats.mtime,
|
||||
isFile: stats.isFile(),
|
||||
isDirectory: stats.isDirectory()
|
||||
};
|
||||
} catch (error) {
|
||||
return { exists: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileManager;
|
||||
Reference in New Issue
Block a user