mirror of
https://github.com/markuryy/shark.git
synced 2025-12-12 19:51:01 +00:00
fix(dl): add retry logic for network errors and rate limits
This commit is contained in:
@@ -139,8 +139,16 @@ export class DeezerAPI {
|
|||||||
|
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
const errorStr = JSON.stringify(resultJson.error);
|
const errorStr = JSON.stringify(resultJson.error);
|
||||||
|
const errorObj = resultJson.error as any;
|
||||||
console.error(`[ERROR] API returned error for ${method}:`, errorStr);
|
console.error(`[ERROR] API returned error for ${method}:`, errorStr);
|
||||||
|
|
||||||
|
// Handle rate limiting (error codes 4 and 700) - wait 5 seconds and retry
|
||||||
|
if (errorObj.code && [4, 700].includes(errorObj.code)) {
|
||||||
|
console.log(`[DEBUG] Rate limited (code ${errorObj.code}), waiting 5s before retry...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
return this.apiCall(method, args, params, retryCount);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle invalid token - retry with new token (max 2 retries)
|
// Handle invalid token - retry with new token (max 2 retries)
|
||||||
if ((errorStr.includes('invalid api token') || errorStr.includes('Invalid CSRF token')) && retryCount < 2) {
|
if ((errorStr.includes('invalid api token') || errorStr.includes('Invalid CSRF token')) && retryCount < 2) {
|
||||||
console.log(`[DEBUG] Invalid token, fetching new token... (retry ${retryCount + 1}/2)`);
|
console.log(`[DEBUG] Invalid token, fetching new token... (retry ${retryCount + 1}/2)`);
|
||||||
@@ -167,8 +175,17 @@ export class DeezerAPI {
|
|||||||
|
|
||||||
console.log(`[DEBUG] Returning results for ${method}`);
|
console.log(`[DEBUG] Returning results for ${method}`);
|
||||||
return resultJson.results;
|
return resultJson.results;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('[ERROR] deezer.gw', method, args, error);
|
console.error('[ERROR] deezer.gw', method, args, error);
|
||||||
|
|
||||||
|
// Retry on network errors (connection issues, timeouts, etc.)
|
||||||
|
const networkErrors = ['ECONNABORTED', 'ECONNREFUSED', 'ECONNRESET', 'ENETRESET', 'ETIMEDOUT'];
|
||||||
|
if (error.code && networkErrors.includes(error.code)) {
|
||||||
|
console.log(`[DEBUG] Network error (${error.code}), waiting 2s before retry...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
return this.apiCall(method, args, params, retryCount);
|
||||||
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,7 +289,7 @@ export class DeezerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get track download URL
|
// Get track download URL
|
||||||
async getTrackDownloadUrl(trackToken: string, format: string, licenseToken: string): Promise<string | null> {
|
async getTrackDownloadUrl(trackToken: string, format: string, licenseToken: string, retryCount: number = 0): Promise<string | null> {
|
||||||
console.log('[DEBUG] Getting track download URL...', { trackToken, format, licenseToken });
|
console.log('[DEBUG] Getting track download URL...', { trackToken, format, licenseToken });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -316,9 +333,18 @@ export class DeezerAPI {
|
|||||||
|
|
||||||
console.error('[ERROR] No download URL in response:', result);
|
console.error('[ERROR] No download URL in response:', result);
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('[ERROR] Failed to get track download URL:', error);
|
console.error('[ERROR] Failed to get track download URL:', error);
|
||||||
throw error; // Re-throw to let caller handle it
|
|
||||||
|
// Retry on network errors
|
||||||
|
const networkErrors = ['ECONNABORTED', 'ECONNREFUSED', 'ECONNRESET', 'ENETRESET', 'ETIMEDOUT'];
|
||||||
|
if (error.code && networkErrors.includes(error.code)) {
|
||||||
|
console.log(`[DEBUG] Network error (${error.code}) getting download URL, waiting 2s before retry...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
return this.getTrackDownloadUrl(trackToken, format, licenseToken, retryCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ export async function downloadTrack(
|
|||||||
downloadURL: string,
|
downloadURL: string,
|
||||||
musicFolder: string,
|
musicFolder: string,
|
||||||
format: string,
|
format: string,
|
||||||
onProgress?: ProgressCallback
|
onProgress?: ProgressCallback,
|
||||||
|
retryCount: number = 0
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Generate paths
|
// Generate paths
|
||||||
const paths = generateTrackPath(track, musicFolder, format, false);
|
const paths = generateTrackPath(track, musicFolder, format, false);
|
||||||
@@ -50,14 +51,20 @@ export async function downloadTrack(
|
|||||||
console.log('Temp path:', paths.tempPath);
|
console.log('Temp path:', paths.tempPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch the track with streaming
|
// Fetch the track with timeout
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout
|
||||||
|
|
||||||
const response = await fetch(downloadURL, {
|
const response = await fetch(downloadURL, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'
|
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'
|
||||||
}
|
},
|
||||||
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -100,7 +107,7 @@ export async function downloadTrack(
|
|||||||
console.log('Download complete!');
|
console.log('Download complete!');
|
||||||
return finalPath;
|
return finalPath;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
// Clean up temp file on error
|
// Clean up temp file on error
|
||||||
try {
|
try {
|
||||||
if (await exists(paths.tempPath)) {
|
if (await exists(paths.tempPath)) {
|
||||||
@@ -110,6 +117,18 @@ export async function downloadTrack(
|
|||||||
console.error('Error cleaning up temp file:', cleanupError);
|
console.error('Error cleaning up temp file:', cleanupError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retry on network errors or timeout (max 3 retries)
|
||||||
|
const networkErrors = ['ECONNABORTED', 'ECONNREFUSED', 'ECONNRESET', 'ENETRESET', 'ETIMEDOUT'];
|
||||||
|
const isNetworkError = error.code && networkErrors.includes(error.code);
|
||||||
|
const isTimeout = error.name === 'AbortError';
|
||||||
|
|
||||||
|
if ((isNetworkError || isTimeout) && retryCount < 3) {
|
||||||
|
const errorType = isTimeout ? 'timeout' : error.code;
|
||||||
|
console.log(`[DEBUG] Download ${errorType}, waiting 2s before retry (${retryCount + 1}/3)...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
return downloadTrack(track, downloadURL, musicFolder, format, onProgress, retryCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user