Start Date: October 2, 2025
Estimated Time: 9-12 hours (6-8 hours Part A + 3-4 hours Part B)
Status: Planning → Implementation
Complete the parallel processing system by:
Goal: Replace sequential download logic with parallel DownloadManager
Files to Modify:
scripts/app.js - handleDownloadVideos() methodImplementation Steps:
Import and Initialize DownloadManager
// At top of app.js
this.downloadManager = null; // Will be set via IPC
// In init()
if (window.electronAPI) {
// DownloadManager is in main process, access via IPC
}
Update handleDownloadVideos() Method
async handleDownloadVideos() {
const videos = this.getDownloadableVideos();
if (videos.length === 0) return;
// Use download manager instead of sequential loop
for (const video of videos) {
try {
// Add to download queue via IPC
await window.electronAPI.queueDownload({
videoId: video.id,
url: video.url,
quality: video.quality,
format: video.format,
savePath: this.state.config.savePath,
cookieFile: this.state.config.cookieFile,
priority: video.priority || 0
});
// Update UI to show queued status
this.state.updateVideo(video.id, { status: 'queued' });
} catch (error) {
console.error(`Failed to queue video ${video.id}:`, error);
}
}
}
Set Up Download Event Listeners
setupDownloadEventListeners() {
// Listen for download started
window.electronAPI.onDownloadStarted?.((data) => {
this.state.updateVideo(data.videoId, {
status: 'downloading',
progress: 0
});
});
// Listen for download progress (already exists)
window.IPCManager.onDownloadProgress('app', (progressData) => {
this.handleDownloadProgress(progressData);
});
// Listen for download completed
window.electronAPI.onDownloadCompleted?.((data) => {
this.state.updateVideo(data.videoId, {
status: 'completed',
progress: 100,
filename: data.filename
});
this.showDownloadNotification(data.video, 'success');
});
// Listen for download failed
window.electronAPI.onDownloadFailed?.((data) => {
this.state.updateVideo(data.videoId, {
status: 'error',
error: data.error
});
this.showDownloadNotification(data.video, 'error', data.error);
});
}
Files to Modify:
src/preload.js - Add new IPC methodssrc/main.js - Add download manager IPC handlerspreload.js Additions:
// Queue management
queueDownload: (options) => ipcRenderer.invoke('queue-download', options),
cancelDownload: (videoId) => ipcRenderer.invoke('cancel-download', videoId),
pauseDownload: (videoId) => ipcRenderer.invoke('pause-download', videoId),
resumeDownload: (videoId) => ipcRenderer.invoke('resume-download', videoId),
getQueueStatus: () => ipcRenderer.invoke('get-queue-status'),
// Event listeners
onDownloadStarted: (callback) => {
ipcRenderer.on('download-started', (event, data) => callback(data));
return () => ipcRenderer.removeListener('download-started', callback);
},
onDownloadCompleted: (callback) => {
ipcRenderer.on('download-completed', (event, data) => callback(data));
return () => ipcRenderer.removeListener('download-completed', callback);
},
onDownloadFailed: (callback) => {
ipcRenderer.on('download-failed', (event, data) => callback(data));
return () => ipcRenderer.removeListener('download-failed', callback);
}
main.js Additions:
// Queue download handler
ipcMain.handle('queue-download', async (event, options) => {
try {
const downloadId = await downloadManager.addDownload(options);
return { success: true, downloadId };
} catch (error) {
console.error('Error queuing download:', error);
return { success: false, error: error.message };
}
});
// Pause download handler
ipcMain.handle('pause-download', async (event, videoId) => {
try {
const paused = downloadManager.pauseDownload(videoId);
return { success: paused };
} catch (error) {
return { success: false, error: error.message };
}
});
// Resume download handler
ipcMain.handle('resume-download', async (event, videoId) => {
try {
const resumed = downloadManager.resumeDownload(videoId);
return { success: resumed };
} catch (error) {
return { success: false, error: error.message };
}
});
// Get queue status handler
ipcMain.handle('get-queue-status', async (event) => {
try {
const status = downloadManager.getQueueStatus();
return { success: true, status };
} catch (error) {
return { success: false, error: error.message };
}
});
// Set up download manager events
downloadManager.on('downloadStarted', (data) => {
mainWindow?.webContents.send('download-started', data);
});
downloadManager.on('downloadCompleted', (data) => {
mainWindow?.webContents.send('download-completed', data);
performanceMonitor.recordDownload(data);
});
downloadManager.on('downloadFailed', (data) => {
mainWindow?.webContents.send('download-failed', data);
});
Files to Modify:
scripts/app.js - updateQueuePanel() methodEnhanced updateQueuePanel():
async updateQueuePanel() {
if (!window.electronAPI || !window.electronAPI.getDownloadStats) return;
try {
// Get download manager stats
const result = await window.electronAPI.getDownloadStats();
if (result && result.success && result.stats) {
const stats = result.stats;
// Update counts
document.getElementById('activeCount').textContent = stats.active || 0;
document.getElementById('queuedCount').textContent = stats.queued || 0;
// Update max concurrent from settings
const maxConcurrent = this.state.config.maxConcurrent || stats.maxConcurrent || 4;
document.getElementById('maxConcurrentDisplay').textContent = maxConcurrent;
}
// Get queue status for detailed info
const queueResult = await window.electronAPI.getQueueStatus?.();
if (queueResult && queueResult.success) {
this.updateActiveDownloadsList(queueResult.status);
}
} catch (error) {
console.error('Failed to update queue panel:', error);
}
}
updateActiveDownloadsList(queueStatus) {
// Update individual video progress bars
queueStatus.active?.forEach(download => {
const video = this.state.getVideo(download.videoId);
if (video) {
this.state.updateVideo(video.id, {
status: 'downloading',
progress: download.progress,
downloadSpeed: download.speed,
eta: download.eta
});
}
});
}
Files to Modify:
scripts/app.js - getStatusText() and updateVideoElement()index.html - Update video item templateUpdate Video Status Display:
getStatusText(video) {
switch (video.status) {
case 'downloading':
const speed = video.downloadSpeed ? ` (${this.formatSpeed(video.downloadSpeed)})` : '';
const eta = video.eta ? ` - ${this.formatETA(video.eta)}` : '';
return `Downloading ${video.progress || 0}%${speed}${eta}`;
case 'queued':
return 'Queued';
case 'converting':
return `Converting ${video.progress || 0}%`;
case 'completed':
return 'Completed';
case 'error':
return 'Error';
case 'ready':
default:
return 'Ready';
}
}
formatSpeed(bytesPerSecond) {
if (bytesPerSecond < 1024) return `${bytesPerSecond} B/s`;
if (bytesPerSecond < 1024 * 1024) return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
return `${(bytesPerSecond / 1024 / 1024).toFixed(1)} MB/s`;
}
formatETA(seconds) {
if (seconds < 60) return `${Math.round(seconds)}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
}
Files to Modify:
scripts/app.js - Add pause/resume/cancel handlersAdd Control Buttons to Video Items:
// In createVideoElement(), add control buttons
const controlsHTML = video.status === 'downloading' || video.status === 'queued' ? `
<div class="flex items-center gap-1">
${video.status === 'downloading' ? `
<button class="pause-download-btn p-1 rounded hover:bg-[#45556c]"
aria-label="Pause download" title="Pause">
⏸️
</button>
` : ''}
${video.status === 'queued' ? `
<button class="resume-download-btn p-1 rounded hover:bg-[#45556c]"
aria-label="Start download" title="Start">
▶️
</button>
` : ''}
<button class="cancel-download-btn p-1 rounded hover:bg-red-600"
aria-label="Cancel download" title="Cancel">
⏹️
</button>
</div>
` : '';
Add Event Handlers:
handleVideoListClick(event) {
const target = event.target;
const videoItem = target.closest('.video-item');
if (!videoItem) return;
const videoId = videoItem.dataset.videoId;
// Pause download
if (target.closest('.pause-download-btn')) {
this.handlePauseDownload(videoId);
return;
}
// Resume download
if (target.closest('.resume-download-btn')) {
this.handleResumeDownload(videoId);
return;
}
// Cancel download
if (target.closest('.cancel-download-btn')) {
this.handleCancelDownload(videoId);
return;
}
// ... existing handlers
}
async handlePauseDownload(videoId) {
try {
const result = await window.electronAPI.pauseDownload(videoId);
if (result.success) {
this.state.updateVideo(videoId, { status: 'paused' });
this.updateStatusMessage('Download paused');
}
} catch (error) {
this.showError(`Failed to pause download: ${error.message}`);
}
}
async handleResumeDownload(videoId) {
try {
const result = await window.electronAPI.resumeDownload(videoId);
if (result.success) {
this.state.updateVideo(videoId, { status: 'downloading' });
this.updateStatusMessage('Download resumed');
}
} catch (error) {
this.showError(`Failed to resume download: ${error.message}`);
}
}
async handleCancelDownload(videoId) {
try {
const result = await window.electronAPI.cancelDownload(videoId);
if (result.success) {
this.state.updateVideo(videoId, {
status: 'ready',
progress: 0,
error: 'Cancelled by user'
});
this.updateStatusMessage('Download cancelled');
}
} catch (error) {
this.showError(`Failed to cancel download: ${error.message}`);
}
}
File to Create:
tests/performance-benchmark.test.jsBenchmark Tests:
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { spawn } from 'child_process'
import fs from 'fs/promises'
import path from 'path'
describe('Performance Benchmarks', () => {
const testVideos = [
'https://www.youtube.com/watch?v=dQw4w9WgXcQ', // Short video
'https://www.youtube.com/watch?v=jNQXAC9IVRw', // Medium video
// Add more test URLs
];
it('should benchmark sequential downloads', async () => {
const startTime = Date.now();
// Download videos sequentially
const duration = Date.now() - startTime;
console.log(`Sequential: ${duration}ms`);
}, 300000); // 5 min timeout
it('should benchmark parallel downloads (2 concurrent)', async () => {
const startTime = Date.now();
// Download with maxConcurrent=2
const duration = Date.now() - startTime;
console.log(`Parallel (2): ${duration}ms`);
}, 300000);
it('should benchmark parallel downloads (4 concurrent)', async () => {
const startTime = Date.now();
// Download with maxConcurrent=4
const duration = Date.now() - startTime;
console.log(`Parallel (4): ${duration}ms`);
}, 300000);
it('should measure CPU usage during downloads', async () => {
// Track CPU usage throughout download
});
it('should measure memory usage during downloads', async () => {
// Track memory usage
});
it('should measure GPU utilization', async () => {
// Track GPU usage during conversion
});
});
File to Create:
scripts/utils/performance-reporter.js
class PerformanceReporter {
constructor() {
this.benchmarks = [];
}
addBenchmark(name, duration, cpuAvg, memoryPeak, gpuUsed) {
this.benchmarks.push({
name,
duration,
cpuAvg,
memoryPeak,
gpuUsed,
timestamp: new Date()
});
}
generateReport() {
return {
summary: this.getSummary(),
detailed: this.benchmarks,
recommendations: this.getRecommendations()
};
}
getSummary() {
// Calculate averages and best performers
}
getRecommendations() {
// Provide optimization recommendations
}
exportToFile(filepath) {
// Export as JSON or Markdown
}
}
Files to Modify:
scripts/models/AppState.js - Adjust defaultssrc/download-manager.js - Tune concurrencyPotential Optimizations:
Total Estimated Time: 9-12 hours
Ready to begin implementation! 🚀