const fs = require('fs').promises; const path = require('path'); const helpers = require('../utils/helpers'); const ColaMangaScraper = require('../scrapers/ColaMangaScraper'); const MangaPoster = require('../posters/MangaPoster'); const config = require('../config/config'); /** * Main class for downloading manga */ class MangaDownloader { constructor() { this.outputDir = config.baseDir; this.maxConcurrentManga = config.getDownloadSetting('maxConcurrentDownloads'); this.mangaPoster = new MangaPoster(); } /** * Initialize the downloader * @returns {Promise} */ async init_colamanga_scraper() { this.mangaUrls = await helpers.readJsonFile(config.mangaUrlsPath) || {}; this.scraper = new ColaMangaScraper(); await this.scraper.init(); } /** * Download a single manga with memory management * @param {string} mangaName - Manga name * @param {string} mangaUrl - Manga URL * @returns {Promise} */ async downloadManga(mangaName, mangaUrl) { console.log(`Getting Manga: ${mangaName} info`); try { const mangaDetails = await this.scraper.getMangaInfo(mangaUrl); // // Save manga info // const infoPath = path.join(this.outputDir, 'info.json'); // await fs.writeFile(infoPath, JSON.stringify(mangaDetails, null, 2)); // Load manga info // const mangaDetails = await helpers.readJsonFile(path.join(this.outputDir, 'info.json')); const mangaDir = path.join(this.outputDir, mangaName); await helpers.ensureDirectory(mangaDir); // Save cover image if (mangaDetails.coverPic && await helpers.fileExists(path.join(mangaDir, 'cover.jpg')) == false) { const coverPath = path.join(mangaDir, 'cover.jpg'); await fs.writeFile(coverPath, mangaDetails.coverPic); mangaDetails.coverPic = null; } const { chapters, coverPic, genres, ...mangaInfo } = mangaDetails; let response = await this.mangaPoster.getMangaId(mangaDetails.mangaName); if (response.success == true) { mangaDetails.mangaId = response.mangaId; } else { mangaDetails.mangaId = helpers.generateId(); this.mangaPoster.insertMangaInfo({mangaId: mangaDetails.mangaId, ...mangaInfo}); this.mangaPoster.insertMangaGenres(mangaDetails.mangaId, genres); } // Download chapters in sequence to manage memory for (const chapter of chapters) { if (await helpers.isDirectoryNotEmpty(path.join(mangaDir, chapter.chapterName))) { console.log(`Skipping Manga: ${mangaDetails.mangaName}, Chapter: ${chapter.order} - ${chapter.chapterName} as it already exists`); continue; } console.log(`Downloading Manga: ${mangaDetails.mangaName}, Chapter: ${chapter.order} - ${chapter.chapterName}`); const chapterDir = path.join(mangaDir, chapter.chapterName); await helpers.ensureDirectory(chapterDir); await this.scraper.downloadChapterPics(chapter, chapterDir); await this.mangaPoster.insertMangaChapter({ mangaId: mangaDetails.mangaId, chapterName: chapter.chapterName, chapterOrder: chapter.order }); await this.scraper.cleanup(); // Add smart delay between chapter downloads const delay = helpers.getSmartDelay(); console.log(`Waiting ${helpers.formatDelay(delay)} before next chapter...`); await new Promise(resolve => setTimeout(resolve, delay)); } console.log(`Completed downloading: ${mangaName}`); } catch (error) { throw new Error(`Failed to download manga ${mangaName}: ${error.message}`); } } /** * Run the downloader with memory management * @returns {Promise} Download results */ async run() { const results = { successful: [], failed: [] }; try { // Process manga sequentially to manage memory for (const [mangaName, mangaUrl] of Object.entries(this.mangaUrls)) { try { await this.downloadManga(mangaName, mangaUrl); results.successful.push(mangaName); // Clean up after each manga await this.scraper.cleanup(); // Force garbage collection if (global.gc) { global.gc(); } } catch (error) { results.failed.push({ name: mangaName, error: error.message }); } } } finally { await this.scraper.close(); } return results; } } module.exports = MangaDownloader;