added auto-retry in frontend
This commit is contained in:
@@ -67,7 +67,7 @@ class DownloadQueue {
|
|||||||
if (!entry || entry.hasEnded) return;
|
if (!entry || entry.hasEnded) return;
|
||||||
|
|
||||||
entry.intervalId = setInterval(async () => {
|
entry.intervalId = setInterval(async () => {
|
||||||
// Note: use the current prgFile value stored in the entry to build the log element id.
|
// Use the current prgFile value stored in the entry to build the log element id.
|
||||||
const logElement = document.getElementById(`log-${entry.uniqueId}-${entry.prgFile}`);
|
const logElement = document.getElementById(`log-${entry.uniqueId}-${entry.prgFile}`);
|
||||||
if (entry.hasEnded) {
|
if (entry.hasEnded) {
|
||||||
clearInterval(entry.intervalId);
|
clearInterval(entry.intervalId);
|
||||||
@@ -140,7 +140,9 @@ class DownloadQueue {
|
|||||||
lastUpdated: Date.now(),
|
lastUpdated: Date.now(),
|
||||||
hasEnded: false,
|
hasEnded: false,
|
||||||
intervalId: null,
|
intervalId: null,
|
||||||
uniqueId: queueId
|
uniqueId: queueId,
|
||||||
|
retryCount: 0, // <== Initialize retry counter
|
||||||
|
autoRetryInterval: null // <== To store the countdown interval ID for auto retry
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +210,9 @@ class DownloadQueue {
|
|||||||
const entry = this.downloadQueue[queueId];
|
const entry = this.downloadQueue[queueId];
|
||||||
if (entry) {
|
if (entry) {
|
||||||
clearInterval(entry.intervalId);
|
clearInterval(entry.intervalId);
|
||||||
|
if (entry.autoRetryInterval) {
|
||||||
|
clearInterval(entry.autoRetryInterval);
|
||||||
|
}
|
||||||
entry.element.remove();
|
entry.element.remove();
|
||||||
delete this.downloadQueue[queueId];
|
delete this.downloadQueue[queueId];
|
||||||
fetch(`/api/prgs/delete/${encodeURIComponent(entry.prgFile)}`, { method: 'DELETE' })
|
fetch(`/api/prgs/delete/${encodeURIComponent(entry.prgFile)}`, { method: 'DELETE' })
|
||||||
@@ -297,7 +302,7 @@ class DownloadQueue {
|
|||||||
return `Finished ${data.type}`;
|
return `Finished ${data.type}`;
|
||||||
|
|
||||||
case 'retrying':
|
case 'retrying':
|
||||||
return `Track "${data.song}" by ${data.artist}" failed, retrying (${data.retry_count}/10) in ${data.seconds_left}s`;
|
return `Track "${data.song}" by ${data.artist}" failed, retrying (${data.retry_count}/3) in ${data.seconds_left}s`;
|
||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
return `Error: ${data.message || 'Unknown error'}`;
|
return `Error: ${data.message || 'Unknown error'}`;
|
||||||
@@ -321,7 +326,7 @@ class DownloadQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* New Methods to Handle Terminal State and Inactivity */
|
/* New Methods to Handle Terminal State, Inactivity and Auto-Retry */
|
||||||
|
|
||||||
handleTerminalState(entry, queueId, progress) {
|
handleTerminalState(entry, queueId, progress) {
|
||||||
// Mark the entry as ended and clear its monitoring interval.
|
// Mark the entry as ended and clear its monitoring interval.
|
||||||
@@ -330,7 +335,6 @@ class DownloadQueue {
|
|||||||
const logElement = document.getElementById(`log-${entry.uniqueId}-${entry.prgFile}`);
|
const logElement = document.getElementById(`log-${entry.uniqueId}-${entry.prgFile}`);
|
||||||
if (!logElement) return;
|
if (!logElement) return;
|
||||||
|
|
||||||
// If the terminal state is an error, hide the cancel button and add error buttons.
|
|
||||||
if (progress.status === 'error') {
|
if (progress.status === 'error') {
|
||||||
// Hide the cancel button.
|
// Hide the cancel button.
|
||||||
const cancelBtn = entry.element.querySelector('.cancel-btn');
|
const cancelBtn = entry.element.querySelector('.cancel-btn');
|
||||||
@@ -338,6 +342,7 @@ class DownloadQueue {
|
|||||||
cancelBtn.style.display = 'none';
|
cancelBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display error message with retry buttons.
|
||||||
logElement.innerHTML = `
|
logElement.innerHTML = `
|
||||||
<div class="error-message">${this.getStatusMessage(progress)}</div>
|
<div class="error-message">${this.getStatusMessage(progress)}</div>
|
||||||
<div class="error-buttons">
|
<div class="error-buttons">
|
||||||
@@ -345,47 +350,49 @@ class DownloadQueue {
|
|||||||
<button class="retry-btn" title="Retry">Retry</button>
|
<button class="retry-btn" title="Retry">Retry</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Close (X) button: immediately remove the queue entry.
|
// Close (X) button: immediately remove the queue entry.
|
||||||
logElement.querySelector('.close-error-btn').addEventListener('click', () => {
|
logElement.querySelector('.close-error-btn').addEventListener('click', () => {
|
||||||
|
// If an auto-retry countdown is running, clear it.
|
||||||
|
if (entry.autoRetryInterval) {
|
||||||
|
clearInterval(entry.autoRetryInterval);
|
||||||
|
entry.autoRetryInterval = null;
|
||||||
|
}
|
||||||
this.cleanupEntry(queueId);
|
this.cleanupEntry(queueId);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Retry button: re-send the original API request.
|
// Manual Retry button: cancel the auto-retry timer (if running) and retry immediately.
|
||||||
logElement.querySelector('.retry-btn').addEventListener('click', async () => {
|
logElement.querySelector('.retry-btn').addEventListener('click', async () => {
|
||||||
logElement.textContent = 'Retrying download...';
|
if (entry.autoRetryInterval) {
|
||||||
if (!entry.requestUrl) {
|
clearInterval(entry.autoRetryInterval);
|
||||||
logElement.textContent = 'Retry not available: missing original request information.';
|
entry.autoRetryInterval = null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const retryResponse = await fetch(entry.requestUrl);
|
|
||||||
const retryData = await retryResponse.json();
|
|
||||||
if (retryData.prg_file) {
|
|
||||||
// Delete the failed prg file before updating to the new one.
|
|
||||||
const oldPrgFile = entry.prgFile;
|
|
||||||
await fetch(`/api/prgs/delete/${encodeURIComponent(oldPrgFile)}`, { method: 'DELETE' });
|
|
||||||
|
|
||||||
// Update the log element's id to reflect the new prg_file.
|
|
||||||
const logEl = entry.element.querySelector('.log');
|
|
||||||
logEl.id = `log-${entry.uniqueId}-${retryData.prg_file}`;
|
|
||||||
|
|
||||||
// Update the entry with the new prg_file and reset its state.
|
|
||||||
entry.prgFile = retryData.prg_file;
|
|
||||||
entry.lastStatus = null;
|
|
||||||
entry.hasEnded = false;
|
|
||||||
entry.lastUpdated = Date.now();
|
|
||||||
logEl.textContent = 'Retry initiated...';
|
|
||||||
|
|
||||||
// Restart monitoring using the new prg_file.
|
|
||||||
this.startEntryMonitoring(queueId);
|
|
||||||
} else {
|
|
||||||
logElement.textContent = 'Retry failed: invalid response from server';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logElement.textContent = 'Retry failed: ' + error.message;
|
|
||||||
}
|
}
|
||||||
|
this.retryDownload(queueId, logElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Auto-Retry Logic ---
|
||||||
|
// Only auto-retry if we have a requestUrl.
|
||||||
|
if (entry.requestUrl) {
|
||||||
|
const maxRetries = 10;
|
||||||
|
if (entry.retryCount < maxRetries) {
|
||||||
|
const autoRetryDelay = 300; // seconds (5 minutes)
|
||||||
|
let secondsLeft = autoRetryDelay;
|
||||||
|
|
||||||
|
// Start a countdown that updates the error message every second.
|
||||||
|
entry.autoRetryInterval = setInterval(() => {
|
||||||
|
secondsLeft--;
|
||||||
|
const errorMsgEl = logElement.querySelector('.error-message');
|
||||||
|
if (errorMsgEl) {
|
||||||
|
errorMsgEl.textContent = `Error: ${progress.message || 'Unknown error'}. Retrying in ${secondsLeft} seconds... (attempt ${entry.retryCount + 1}/${maxRetries})`;
|
||||||
|
}
|
||||||
|
if (secondsLeft <= 0) {
|
||||||
|
clearInterval(entry.autoRetryInterval);
|
||||||
|
entry.autoRetryInterval = null;
|
||||||
|
this.retryDownload(queueId, logElement);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Do not automatically clean up if an error occurred.
|
// Do not automatically clean up if an error occurred.
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -396,17 +403,59 @@ class DownloadQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleInactivity(entry, queueId, logElement) {
|
handleInactivity(entry, queueId, logElement) {
|
||||||
// If no update in 10 seconds, treat as an error.
|
// If no update in 5 minutes (300,000ms), treat as an error.
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - entry.lastUpdated > 300000) {
|
if (now - entry.lastUpdated > 300000) {
|
||||||
const progress = { status: 'error', message: 'Inactivity timeout' };
|
const progress = { status: 'error', message: 'Inactivity timeout' };
|
||||||
this.handleTerminalState(entry, queueId, progress);
|
this.handleTerminalState(entry, queueId, progress);
|
||||||
} else {
|
} else {
|
||||||
if (logElement) {
|
if (logElement) {
|
||||||
logElement.textContent = this.getStatusMessage(entry.lastStatus)
|
logElement.textContent = this.getStatusMessage(entry.lastStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retryDownload() handles both manual and automatic retries.
|
||||||
|
*/
|
||||||
|
async retryDownload(queueId, logElement) {
|
||||||
|
const entry = this.downloadQueue[queueId];
|
||||||
|
if (!entry) return;
|
||||||
|
|
||||||
|
logElement.textContent = 'Retrying download...';
|
||||||
|
if (!entry.requestUrl) {
|
||||||
|
logElement.textContent = 'Retry not available: missing original request information.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const retryResponse = await fetch(entry.requestUrl);
|
||||||
|
const retryData = await retryResponse.json();
|
||||||
|
if (retryData.prg_file) {
|
||||||
|
// Delete the failed prg file before updating to the new one.
|
||||||
|
const oldPrgFile = entry.prgFile;
|
||||||
|
await fetch(`/api/prgs/delete/${encodeURIComponent(oldPrgFile)}`, { method: 'DELETE' });
|
||||||
|
|
||||||
|
// Update the log element's id to reflect the new prg_file.
|
||||||
|
const logEl = entry.element.querySelector('.log');
|
||||||
|
logEl.id = `log-${entry.uniqueId}-${retryData.prg_file}`;
|
||||||
|
|
||||||
|
// Update the entry with the new prg_file and reset its state.
|
||||||
|
entry.prgFile = retryData.prg_file;
|
||||||
|
entry.lastStatus = null;
|
||||||
|
entry.hasEnded = false;
|
||||||
|
entry.lastUpdated = Date.now();
|
||||||
|
entry.retryCount = (entry.retryCount || 0) + 1;
|
||||||
|
logEl.textContent = 'Retry initiated...';
|
||||||
|
|
||||||
|
// Restart monitoring using the new prg_file.
|
||||||
|
this.startEntryMonitoring(queueId);
|
||||||
|
} else {
|
||||||
|
logElement.textContent = 'Retry failed: invalid response from server';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logElement.textContent = 'Retry failed: ' + error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton instance
|
// Singleton instance
|
||||||
|
|||||||
Reference in New Issue
Block a user