feat: Add VPS storage system and complete integration
🎯 Overview: Implemented complete VPS-based storage system allowing the iOS app to download and store manga chapters on the VPS for ad-free offline reading. 📦 Backend Changes: - Added storage.js service for managing chapter downloads (270 lines) - Updated server.js with 6 new storage endpoints: - POST /api/download - Download chapters to VPS - GET /api/storage/chapters/:mangaSlug - List downloaded chapters - GET /api/storage/chapter/:mangaSlug/:chapterNumber - Check download status - GET /api/storage/image/:mangaSlug/:chapterNumber/:pageIndex - Serve images - DELETE /api/storage/chapter/:mangaSlug/:chapterNumber - Delete chapters - GET /api/storage/stats - Get storage statistics - Fixed scraper.js Puppeteer compatibility issues (waitForTimeout, networkidle0) - Added comprehensive test suite: - test-vps-flow.js (13 tests - 100% pass rate) - test-concurrent-downloads.js (10 tests for parallel operations) - run-tests.sh automation script 📱 iOS App Changes: - Created APIConfig.swift with VPS connection settings - Created VPSAPIClient.swift service (727 lines) for backend communication - Updated MangaDetailView.swift with VPS download integration: - Cloud icon for VPS-available chapters - Upload button to download chapters to VPS - Progress indicators for active downloads - Bulk download options (last 10 or all chapters) - Updated ReaderView.swift to load images from VPS first - Progressive enhancement: app works without VPS, enhances when available ✅ Tests: - All 13 VPS flow tests passing (100%) - Tests verify: scraping, downloading, storage, serving, deletion, stats - Chapter 789 download test: 21 images, 4.68 MB - Concurrent download tests verify no race conditions 🔧 Configuration: - VPS URL: https://gitea.cbcren.online:3001 - Storage location: /home/ren/ios/MangaReader/storage/ - Static file serving: /storage path 📚 Documentation: - Added VPS_INTEGRATION_SUMMARY.md - Complete feature overview - Added CHANGES.md - Detailed code changes reference - Added TEST_README.md, TEST_QUICK_START.md, TEST_SUMMARY.md - Added APIConfig README with usage examples 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -28,8 +28,8 @@ async function getRenderedHTML(url, waitFor = 3000) {
|
||||
|
||||
// Navigate to the URL and wait for network to be idle
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle0',
|
||||
timeout: 30000
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45000
|
||||
});
|
||||
|
||||
// Additional wait to ensure JavaScript content is loaded
|
||||
@@ -63,12 +63,12 @@ export async function getMangaChapters(mangaSlug) {
|
||||
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
|
||||
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle0',
|
||||
timeout: 30000
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45000
|
||||
});
|
||||
|
||||
// Wait for content to load
|
||||
await page.waitForTimeout(3000);
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// Extract chapters using page.evaluate
|
||||
const chapters = await page.evaluate(() => {
|
||||
@@ -136,12 +136,12 @@ export async function getChapterImages(chapterSlug) {
|
||||
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
|
||||
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle0',
|
||||
timeout: 30000
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45000
|
||||
});
|
||||
|
||||
// Wait for images to load
|
||||
await page.waitForTimeout(3000);
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// Extract image URLs
|
||||
const images = await page.evaluate(() => {
|
||||
@@ -214,11 +214,11 @@ export async function getMangaInfo(mangaSlug) {
|
||||
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
|
||||
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle0',
|
||||
timeout: 30000
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45000
|
||||
});
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Extract manga information
|
||||
const mangaInfo = await page.evaluate(() => {
|
||||
@@ -315,11 +315,11 @@ export async function getPopularMangas() {
|
||||
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
|
||||
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle0',
|
||||
timeout: 30000
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45000
|
||||
});
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// Extract manga list
|
||||
const mangas = await page.evaluate(() => {
|
||||
|
||||
Reference in New Issue
Block a user