Fix duplicate VOE and Vidmoly recovery claims
Share account-scoped recovery claims with login uploader instances and serialize same-title recovery windows. Reserve direct and recovered file codes consistently, reject reused identities, and cover concurrent singleton recovery plus distinct-code and parallel-success cases.
This commit is contained in:
+29
-13
@@ -14,8 +14,9 @@ const RESULT_POLL_DELAY_MS = 2000;
|
||||
* XFileSharing-based upload for Vidmoly (login + form upload)
|
||||
*/
|
||||
class VidmolyUploader {
|
||||
constructor() {
|
||||
constructor(recoveryClaim = null) {
|
||||
this.cookies = new Map();
|
||||
this.recoveryClaim = recoveryClaim;
|
||||
}
|
||||
|
||||
_cookieHeader() {
|
||||
@@ -352,6 +353,7 @@ class VidmolyUploader {
|
||||
try {
|
||||
return this._parseUploadResult(resultHtml);
|
||||
} catch (primaryErr) {
|
||||
if (primaryErr && primaryErr.remoteIdentityClaimed === true) throw primaryErr;
|
||||
if (baselineCodes) {
|
||||
const fallback = await this._resolveUploadedFileFromVmApi(fileName, baselineCodes, signal);
|
||||
if (fallback) return fallback;
|
||||
@@ -372,9 +374,21 @@ class VidmolyUploader {
|
||||
.replace(/[^a-z0-9]+/g, '');
|
||||
}
|
||||
|
||||
_buildUrlsFromCode(fileCode) {
|
||||
_buildUrlsFromCode(fileCode, phase = 'upload-result') {
|
||||
const code = String(fileCode || '').trim();
|
||||
if (!code) return null;
|
||||
if (this.recoveryClaim
|
||||
&& typeof this.recoveryClaim.reserve === 'function'
|
||||
&& !this.recoveryClaim.reserve(code)) {
|
||||
const error = createTransportError('Vidmoly Upload-Ergebnis ist bereits einem anderen Upload zugeordnet', {
|
||||
phase,
|
||||
endpoint: BASE_URL,
|
||||
retryable: true,
|
||||
hosterTransient: true
|
||||
});
|
||||
error.remoteIdentityClaimed = true;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
download_url: `${BASE_URL}/w/${code}`,
|
||||
@@ -473,14 +487,18 @@ class VidmolyUploader {
|
||||
|
||||
const withCode = files.filter((f) => f && typeof f.file_code === 'string' && f.file_code.trim());
|
||||
const newFiles = withCode.filter((f) => !baselineCodes.has(f.file_code.trim()));
|
||||
const matches = newFiles.filter((file) => {
|
||||
const title = this._normalizeTitle(file.full_title || file.title_txt || '');
|
||||
return expectedTitle && title === expectedTitle;
|
||||
});
|
||||
const matches = newFiles
|
||||
.filter((file) => {
|
||||
const title = this._normalizeTitle(file.full_title || file.title_txt || '');
|
||||
return expectedTitle && title === expectedTitle;
|
||||
})
|
||||
.filter((file) => !this.recoveryClaim
|
||||
|| typeof this.recoveryClaim.has !== 'function'
|
||||
|| !this.recoveryClaim.has(file.file_code.trim()));
|
||||
|
||||
if (matches.length > 1) return null;
|
||||
if (matches.length === 1) {
|
||||
return this._buildUrlsFromCode(matches[0].file_code);
|
||||
return this._buildUrlsFromCode(matches[0].file_code, 'recovery-poll');
|
||||
}
|
||||
|
||||
if (attempt < RESULT_POLL_ATTEMPTS - 1) {
|
||||
@@ -577,12 +595,10 @@ class VidmolyUploader {
|
||||
if (codeInPage) file_code = codeInPage[1];
|
||||
}
|
||||
|
||||
// Build URLs from file_code
|
||||
if (file_code && !download_url) {
|
||||
download_url = `${BASE_URL}/w/${file_code}`;
|
||||
}
|
||||
if (file_code && !embed_url) {
|
||||
embed_url = `${BASE_URL}/embed-${file_code}.html`;
|
||||
if (file_code) {
|
||||
const urls = this._buildUrlsFromCode(file_code);
|
||||
if (!download_url) download_url = urls.download_url;
|
||||
if (!embed_url) embed_url = urls.embed_url;
|
||||
}
|
||||
|
||||
if (!download_url && !file_code) {
|
||||
|
||||
Reference in New Issue
Block a user