mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 01:27:11 +00:00
feat(update): implement automatic retry for GitHub raw file downloads
- Added functionality to retry downloads from GitHub on encountering rate limit and server error status codes (429, 502, 503, 504) with exponential backoff. - Updated the changelog to reflect the new panel updater feature that enhances locale sync reliability during large updates. - Included tests for the new retry logic to ensure proper handling of retryable status codes.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
### Added
|
||||
- **Web Remote — File Transfer drop zone:** left panel shows a drag-and-drop upload box (click to pick files, multi-file supported). Uploads go to the folder currently open on the remote side; progress appears in the Transfers column.
|
||||
- **Web Remote — RustDesk file transfer parity:** 128 KB blocks, zstd upload compression, resume via `offset_blk` / `transferred_size`, overwrite confirmation dialog (Skip / Overwrite / apply to all), remote context menu (rename, delete, new folder), hidden-files toggle, and Resume in the transfer queue after errors.
|
||||
- **Panel updater:** GitHub raw file downloads retry automatically on HTTP 429/502/503/504 (exponential backoff), reducing failed locale syncs during large updates.
|
||||
|
||||
### Changed
|
||||
- _(none yet)_
|
||||
|
||||
@@ -349,9 +349,17 @@ function ghGet(urlPath, { bypassCache = false } = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Download raw file content from GitHub (binary-safe).
|
||||
* Download raw file content from GitHub (binary-safe), with retry on rate limits.
|
||||
*/
|
||||
function ghDownloadFile(owner, repo, ref, filePath) {
|
||||
function isRetryableDownloadStatus(statusCode) {
|
||||
return statusCode === 429 || statusCode === 502 || statusCode === 503 || statusCode === 504;
|
||||
}
|
||||
|
||||
function getDownloadRetryDelayMs(attempt) {
|
||||
return Math.min(1000 * Math.pow(2, Math.max(0, attempt - 1)), 15000);
|
||||
}
|
||||
|
||||
function ghDownloadFileOnce(owner, repo, ref, filePath) {
|
||||
const url = `https://raw.githubusercontent.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${encodeURIComponent(ref)}/${filePath}`;
|
||||
return new Promise((resolve, reject) => {
|
||||
const headers = { 'User-Agent': USER_AGENT };
|
||||
@@ -376,6 +384,30 @@ function ghDownloadFile(owner, repo, ref, filePath) {
|
||||
});
|
||||
}
|
||||
|
||||
async function ghDownloadFile(owner, repo, ref, filePath, opts = {}) {
|
||||
const maxAttempts = Number(opts.maxAttempts) > 0 ? Number(opts.maxAttempts) : 4;
|
||||
let lastErr;
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
return await ghDownloadFileOnce(owner, repo, ref, filePath);
|
||||
} catch (err) {
|
||||
lastErr = err;
|
||||
const match = /Download failed \((\d+)\)/.exec(err.message || '');
|
||||
const statusCode = match ? Number(match[1]) : 0;
|
||||
if (!isRetryableDownloadStatus(statusCode) || attempt >= maxAttempts) {
|
||||
throw err;
|
||||
}
|
||||
const delayMs = getDownloadRetryDelayMs(attempt);
|
||||
console.warn(
|
||||
`[UPDATE] Download retry ${attempt}/${maxAttempts} for ${filePath}`
|
||||
+ ` after ${delayMs}ms (${err.message})`
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, delayMs));
|
||||
}
|
||||
}
|
||||
throw lastErr;
|
||||
}
|
||||
|
||||
// ======================== Docker image deployment ========================
|
||||
|
||||
function shasMatch(a, b) {
|
||||
@@ -3256,6 +3288,8 @@ module.exports = {
|
||||
isNonCriticalUpdateFailure,
|
||||
GITHUB_COMPARE_FILE_LIMIT,
|
||||
isCompareLikelyTruncated,
|
||||
isRetryableDownloadStatus,
|
||||
getDownloadRetryDelayMs,
|
||||
resolveConsoleRequire,
|
||||
collectConsoleRequiredFiles,
|
||||
isResolvedByIndexModule,
|
||||
|
||||
@@ -4,6 +4,8 @@ const { createConsoleDeployGraph } = require('../lib/consoleDeployGraph');
|
||||
const {
|
||||
GITHUB_COMPARE_FILE_LIMIT,
|
||||
isCompareLikelyTruncated,
|
||||
isRetryableDownloadStatus,
|
||||
getDownloadRetryDelayMs,
|
||||
} = require('../services/updateService');
|
||||
|
||||
describe('updateService console sync helpers', () => {
|
||||
@@ -50,4 +52,13 @@ describe('updateService console sync helpers', () => {
|
||||
expect(required.has('routes/auth.routes.js')).toBe(true);
|
||||
expect(required.has('services/serverAttestation.js')).toBe(true);
|
||||
});
|
||||
|
||||
test('retries GitHub raw downloads on rate limit status codes', () => {
|
||||
expect(isRetryableDownloadStatus(429)).toBe(true);
|
||||
expect(isRetryableDownloadStatus(503)).toBe(true);
|
||||
expect(isRetryableDownloadStatus(404)).toBe(false);
|
||||
expect(getDownloadRetryDelayMs(1)).toBe(1000);
|
||||
expect(getDownloadRetryDelayMs(2)).toBe(2000);
|
||||
expect(getDownloadRetryDelayMs(6)).toBe(15000);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user