andrade-soulseek-downloader
Version:
Simple, safe Soulseek download library with built-in rate limiting to prevent bans
136 lines • 5.79 kB
JavaScript
;
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