UNPKG

andrade-soulseek-downloader

Version:

Simple, safe Soulseek download library with built-in rate limiting to prevent bans

136 lines 5.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DownloadRacingService = void 0; /** * Service for managing concurrent download racing. * Implements intelligent parallel downloading with automatic cancellation. */ class DownloadRacingService { logger; constructor(logger) { this.logger = logger; } /** * Groups tracks by equivalent quality for racing. * @param tracks - Sorted tracks (best quality first) * @param maxConcurrent - Maximum concurrent downloads per race * @returns Groups of tracks for racing */ groupTracksForRacing(tracks, maxConcurrent = 3) { if (tracks.length === 0) return []; const groups = []; let currentGroup = []; let currentScore = tracks[0].calculateQualityScore(); // Tolerance for considering tracks "equivalent" (within 5 points) const scoreTolerance = 5; for (const track of tracks) { const trackScore = track.calculateQualityScore(); // If score is significantly different or group is full, start new group if (Math.abs(trackScore - currentScore) > scoreTolerance || currentGroup.length >= maxConcurrent) { if (currentGroup.length > 0) { groups.push([...currentGroup]); } currentGroup = [track]; currentScore = trackScore; } else { // Only add if it's a different user (avoid racing same user) const isDifferentUser = !currentGroup.some(t => t.getUser() === track.getUser()); if (isDifferentUser) { currentGroup.push(track); } } } // Add the last group if (currentGroup.length > 0) { groups.push(currentGroup); } return groups; } /** * Races multiple downloads and returns the first successful one. * @param tracks - Tracks to race (should be from same quality group) * @param downloadFn - Function to download a single track * @returns Promise that resolves with first successful download */ async raceDownloads(tracks, downloadFn) { if (tracks.length === 0) return null; if (tracks.length === 1) { // Single track, no racing needed const result = await downloadFn(tracks[0]); return result ? { result, winner: tracks[0] } : null; } this.logger.info(`🏁 Racing ${tracks.length} downloads from different users`); // Create promises for each download with metadata const racePromises = tracks.map(async (track, index) => { try { this.logger.debug(`Starting race #${index + 1}: ${track.getUser()}`); const result = await downloadFn(track); if (result) { this.logger.success(`🏆 Race winner #${index + 1}: ${track.getUser()}`); return { result, winner: track, index }; } this.logger.warning(`❌ Race #${index + 1} failed: ${track.getUser()}`); return null; } catch (error) { this.logger.error(`💥 Race #${index + 1} error: ${error instanceof Error ? error.message : String(error)}`); return null; } }); try { // Race all downloads - first successful one wins const results = await Promise.allSettled(racePromises); // Find the first successful result for (const result of results) { if (result.status === 'fulfilled' && result.value) { const { result: downloadResult, winner } = result.value; this.logger.info(`🎉 Download race completed, winner: ${winner.getUser()}`); return { result: downloadResult, winner }; } } this.logger.warning(`🚫 All ${tracks.length} downloads in race failed`); return null; } catch (error) { this.logger.error(`💥 Download race error: ${error instanceof Error ? error.message : String(error)}`); return null; } } /** * Selects best tracks for racing, ensuring diversity of users. * @param tracks - All available tracks * @param maxPerRace - Maximum tracks per race * @returns Tracks optimized for racing */ selectTracksForRacing(tracks, maxPerRace = 3) { const groups = this.groupTracksForRacing(tracks, maxPerRace); // Flatten groups but limit total attempts to reasonable number const selected = []; const maxTotalAttempts = 15; // Maximum total tracks to attempt for (const group of groups) { selected.push(...group); if (selected.length >= maxTotalAttempts) { break; } } return selected.slice(0, maxTotalAttempts); } /** * Logs racing strategy information. * @param groups - Track groups for racing */ logRacingStrategy(groups) { this.logger.info(`📊 Racing strategy: ${groups.length} quality groups`); groups.forEach((group, index) => { const users = group.map(t => t.getUser()).join(', '); const avgScore = Math.round(group.reduce((sum, t) => sum + t.calculateQualityScore(), 0) / group.length); this.logger.debug(` Group ${index + 1}: ${group.length} tracks (score ~${avgScore}) - Users: ${users}`); }); } } exports.DownloadRacingService = DownloadRacingService; //# sourceMappingURL=download-racing-service.js.map