This commit is contained in:
+6
-2
@@ -25,6 +25,7 @@ Multi-Hoster-Upload ist eine Electron-Desktopanwendung für Windows, die große
|
||||
- Backup-Importe wenden Sprache und automatischen Account-Check sofort an. Alle Konfigurationsfelder werden übertragen; Warteschlange, Upload-Wiederherstellungsstatus, letzter Dateiauswahlordner, Automatik-Telemetrie und Pausenzustand bleiben bewusst geräte- beziehungsweise laufzeitgebunden.
|
||||
- Ein nicht vorhandener Ordnerüberwachungspfad bleibt nach dem Import sichtbar gespeichert, die Überwachung wird aber deaktiviert und der Nutzer erhält eine Warnung. Ein nicht vorhandener Log-Ordner wird ebenfalls gemeldet, ohne den konfigurierten Pfad still zu löschen.
|
||||
- Upload-Status-Badges und ihre Textlabels sind nicht markierbar; kopierbare Fehlerdetails, Logs und Eingabefelder behalten ihre Textauswahl.
|
||||
- VOE-Fehler mit der Meldung `Maximum storage space of the account used up.` gelten als temporärer Accountfehler. Die Retry-Schleife bricht auch nach einem bereits erfolgten Account-Wechsel sofort ab und setzt die Fallback-Kette Account für Account fort, bis ein Upload gelingt oder kein weiterer Account verfügbar ist.
|
||||
- Version `2.1.43` ist als GitHub- und Forgejo-Release veröffentlicht; Backup-API `2.0.4` blieb bei diesem reinen Oberflächen-Release unverändert aktiv.
|
||||
- Der eingebaute Updater liest Releases und Binärdateien von Forgejo; GitHub liefert ergänzend die öffentlichen Release Notes. Ein Release ist deshalb erst vollständig, wenn die vier Assets auch im Forgejo-Release vorhanden sind.
|
||||
- Forgejo bewahrt Leerzeichen in Asset-Namen, GitHub normalisiert sie zu Punkten. Das Forgejo-`latest.yml` und der Release-Plan verwenden Namen wie `Multi-Hoster-Upload Setup 2.1.43.exe`; das GitHub-Manifest muss auf den dort tatsächlich veröffentlichten Punktnamen zeigen.
|
||||
@@ -57,15 +58,18 @@ npm audit --omit=dev
|
||||
## Offene nächste Schritte
|
||||
|
||||
- Keine offenen Schritte für Release `v2.1.43`; Rollback-Ziel ist Anwendungsversion `2.1.42`.
|
||||
- Der nach `v2.1.43` behobene VOE-Account-Rotationsfehler ist vollständig getestet, aber noch nicht als neue Anwendungsversion veröffentlicht.
|
||||
- Bei Bedarf einen Arbeitsweg ohne `&` im absoluten Pfad verwenden oder die npm-Aufrufe weiterhin direkt ausführen.
|
||||
|
||||
## Zuletzt verifiziert
|
||||
|
||||
Stand: 02.09.2026
|
||||
Stand: 07.09.2026
|
||||
|
||||
- Lint: erfolgreich, 0 Warnungen und 0 Fehler.
|
||||
- Haupttests: 805 erfolgreich, 0 fehlgeschlagen.
|
||||
- Haupttests: 807 erfolgreich, 0 fehlgeschlagen.
|
||||
- Backup-API-Tests: 17 erfolgreich, 0 fehlgeschlagen.
|
||||
- Der Regressionstest für die VOE-Fallback-Kette bestätigt bei deaktivierter normaler Rotation genau einen Versuch auf jedem vollen Account und anschließend den erfolgreichen Wechsel auf den vierten Account.
|
||||
- Das Support-Bundle vom 07.09.2026 bestätigt als Ursache der gemeldeten Datei: Wechsel vom Primäraccount auf `Fallback #1`, dort vier unnötige Versuche, anschließend `skip-account-pause` und Abbruch mit `override-same-as-current` statt Weiterschaltung.
|
||||
- Der vollständige opt-in UI-Smoke bestätigte zusätzlich, dass Upload-Status-Badges und deren Labels nicht markierbar sind; die 16 bekannten themenfremden Abweichungen blieben unverändert.
|
||||
- Produktionsabhängigkeiten: `npm audit --omit=dev` meldet 0 Schwachstellen.
|
||||
- Release-Commit `5311cdf` und der annotierte Tag `v2.1.43` wurden auf GitHub und Forgejo mit identischen Commit-Hashes verifiziert.
|
||||
|
||||
@@ -42,6 +42,7 @@ function classifyAccountFailure(error) {
|
||||
const cooldownPatterns = [
|
||||
/\b429\b|rate[- ]?limit|too many requests/i,
|
||||
/quota|not enough (disk )?(space|storage)|insufficient (disk )?space/i,
|
||||
/maximum storage space of the account (?:used up|reached|exceeded)/i,
|
||||
/disk (space )?full|storage (exhausted|full|voll|limit)|account (full|voll)/i,
|
||||
/session (expired|abgelaufen)|CSRF[- ]?Token nicht gefunden|not logged in/i,
|
||||
/Keine Session erhalten|Session konnte nicht verifiziert werden/i,
|
||||
|
||||
@@ -230,8 +230,8 @@ class UploadManager extends EventEmitter {
|
||||
_shouldSkipRetryOnAccountError(err) {
|
||||
if (!err) return false;
|
||||
if (err.transientNetwork === true) return false;
|
||||
// Explicit account-level flag from hoster parsers — highest priority.
|
||||
if (err.accountError === true) return true;
|
||||
if (classifyAccountFailure(err) !== 'none') return true;
|
||||
if (!err.message) return false;
|
||||
const m = String(err.message);
|
||||
const PATTERNS = [
|
||||
@@ -1109,6 +1109,13 @@ class UploadManager extends EventEmitter {
|
||||
if (this._isFileRejectedError(err)) break;
|
||||
if (this._isHosterTransientError(err)) break;
|
||||
if (this._isTransientNetworkError(err)) break;
|
||||
if (this._shouldSkipRetryOnAccountError(err)) {
|
||||
this._rotLog('fast-fail', {
|
||||
jobId, hoster: task.hoster, fileName, accountId: task.accountId,
|
||||
attempt, error: err && err.message ? err.message : String(err), rotationRetry: true
|
||||
});
|
||||
break;
|
||||
}
|
||||
if (attempt >= maxAttempts) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ test('account failure classification cools down quota and rate-limit errors with
|
||||
const quota = new Error('not enough disk space on your account');
|
||||
quota.accountError = true;
|
||||
assert.equal(classifyAccountFailure(quota), 'cooldown');
|
||||
assert.equal(classifyAccountFailure(new Error('Maximum storage space of the account used up.')), 'cooldown');
|
||||
assert.equal(classifyAccountFailure(new Error('HTTP 429 Too Many Requests')), 'cooldown');
|
||||
const transient = new Error('HTTP 503 Service Unavailable');
|
||||
transient.transientNetwork = true;
|
||||
|
||||
@@ -1111,6 +1111,13 @@ describe('UploadManager', () => {
|
||||
'must not match generic "lehnte Datei ab" as file-rejected');
|
||||
});
|
||||
|
||||
it('classifies VOE storage exhaustion by message alone', () => {
|
||||
const mgr = new UploadManager({});
|
||||
const err = new Error('Maximum storage space of the account used up.');
|
||||
assert.equal(mgr._shouldSkipRetryOnAccountError(err), true);
|
||||
assert.equal(mgr._isFileRejectedError(err), false);
|
||||
});
|
||||
|
||||
it('keeps true file rejections as file-rejected', () => {
|
||||
const mgr = new UploadManager({});
|
||||
const err = new Error('Byse lehnte Datei ab: Duplicate');
|
||||
@@ -1169,6 +1176,45 @@ describe('UploadManager', () => {
|
||||
assert.deepEqual(successes, [{ hoster: 'byse.sx', accountId: 'fallback' }]);
|
||||
});
|
||||
|
||||
it('advances through every VOE fallback after storage exhaustion', async () => {
|
||||
const accounts = [
|
||||
{ id: 'primary', apiKey: 'key-1' },
|
||||
{ id: 'fallback-1', apiKey: 'key-2' },
|
||||
{ id: 'fallback-2', apiKey: 'key-3' },
|
||||
{ id: 'fallback-3', apiKey: 'key-4' }
|
||||
];
|
||||
const attempts = [];
|
||||
mockUploadFile.mock.mockImplementation(async (hoster, filePath, apiKey, onProgress) => {
|
||||
attempts.push(apiKey);
|
||||
if (apiKey !== 'key-4') throw new Error('Maximum storage space of the account used up.');
|
||||
if (onProgress) onProgress(fakeFileSize, fakeFileSize);
|
||||
return { download_url: 'https://voe.sx/final', embed_url: null, file_code: 'final' };
|
||||
});
|
||||
const mgr = new UploadManager({ 'voe.sx': { retries: 3, parallelCount: 1, rotateAccounts: false } });
|
||||
mgr._sleep = async () => {};
|
||||
const paused = [];
|
||||
const completed = [];
|
||||
mgr.on('account-paused', event => paused.push(event));
|
||||
mgr.on('progress', event => {
|
||||
if (event.status === 'done') completed.push(event.accountId);
|
||||
});
|
||||
mgr.on('account-failed', ({ hoster, accountId }) => {
|
||||
const failedIndex = accounts.findIndex(account => account.id === accountId);
|
||||
const fallback = accounts[failedIndex + 1];
|
||||
if (fallback) mgr.switchAccount(hoster, fallback);
|
||||
});
|
||||
|
||||
await mgr.startBatch([
|
||||
{ file: '/test/voe-storage-full.mkv', hoster: 'voe.sx', accountId: 'primary', apiKey: 'key-1' }
|
||||
], {
|
||||
primeOverrides: [['voe.sx', accounts[1]]]
|
||||
});
|
||||
|
||||
assert.deepEqual(attempts, ['key-1', 'key-2', 'key-3', 'key-4']);
|
||||
assert.deepEqual(paused.map(event => event.accountId), ['primary', 'fallback-1', 'fallback-2']);
|
||||
assert.deepEqual(completed, ['fallback-3']);
|
||||
});
|
||||
|
||||
it('emits a manual account pause for credential failures', async () => {
|
||||
mockUploadFile.mock.mockImplementation(async () => {
|
||||
throw new Error('VOE Login fehlgeschlagen: Falscher Username oder Passwort');
|
||||
|
||||
Reference in New Issue
Block a user