Files
Multi-Hoster-Upload/lib/import-preflight.js
T
Sucukdeluxe d7c9f287e4
CI / verify (push) Has been cancelled
release: restore v2.1.19 baseline for v2.1.24
Restore the v2.1.19 application baseline and retain only the focused import preflight summary with duplicate, unavailable, destination, job, and size-limit visibility.
2026-08-17 04:25:22 +02:00

177 lines
6.7 KiB
JavaScript

(function initImportPreflight(root, factory) {
const api = typeof module === 'object' && module.exports
? factory(require('path'))
: factory({
normalize: value => String(value).replace(/[\\/]+/g, '/'),
basename: value => String(value).split(/[\\/]/).pop() || ''
});
if (typeof module === 'object' && module.exports) module.exports = api;
if (root) root.ImportPreflight = api;
})(typeof window !== 'undefined' ? window : globalThis, function createImportPreflight(path) {
function normalizePathValue(value) {
let text = String(value ?? '').trim();
if (!text) return '';
const uncNamespace = text.match(/^[\\/]{2}\?[\\/]UNC[\\/]/i);
if (uncNamespace) text = `\\\\${text.slice(uncNamespace[0].length)}`;
else {
const driveNamespace = text.match(/^[\\/]{2}\?[\\/](?=[A-Za-z]:[\\/])/);
if (driveNamespace) text = text.slice(driveNamespace[0].length);
}
return path.normalize(text);
}
function normalizeEntry(value) {
const source = value && typeof value === 'object' ? value : {};
const filePath = normalizePathValue(typeof value === 'string' ? value : source.path);
const sourceName = typeof value === 'string' ? '' : String(source.name ?? '').trim();
return {
path: filePath,
name: sourceName || path.basename(filePath),
size: Number.isFinite(Number(source.size)) ? Number(source.size) : null
};
}
function createPathKey(value, caseInsensitive) {
const normalized = normalizePathValue(value);
return caseInsensitive ? normalized.toLocaleLowerCase('en-US') : normalized;
}
async function mapWithConcurrency(items, concurrency, operation) {
const results = new Array(items.length);
let cursor = 0;
async function worker() {
while (cursor < items.length) {
const index = cursor++;
results[index] = await operation(items[index], index);
}
}
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, worker));
return results;
}
function unavailableReason(result) {
if (!result || result.exists === false) return 'missing';
if (result.readable === false) return 'unreadable';
const size = Number(result.size);
if (!Number.isFinite(size) || size <= 0) return 'empty';
return '';
}
async function inspectReadableImportPath(filePath, openPath) {
let fileHandle = null;
try {
fileHandle = await openPath(filePath, 'r');
const fileStat = await fileHandle.stat();
if (!fileStat.isFile()) return { exists: true, readable: false, size: fileStat.size };
return { exists: true, readable: true, size: fileStat.size };
} catch (error) {
if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) return { exists: false };
return { exists: true, readable: false };
} finally {
if (fileHandle) {
try {
await fileHandle.close();
} catch {}
}
}
}
async function inspectImportEntries(entries, options = {}) {
const input = Array.isArray(entries) ? entries : [];
const caseInsensitive = options.caseInsensitive ?? (typeof process === 'object' ? process.platform === 'win32' : true);
const existing = new Set((Array.isArray(options.existingPaths) ? options.existingPaths : [])
.map(value => createPathKey(value && typeof value === 'object' ? value.path : value, caseInsensitive))
.filter(Boolean));
const duplicates = [];
const unavailable = [];
const unique = [];
for (const value of input) {
const entry = normalizeEntry(value);
if (!entry.path) {
unavailable.push({ ...entry, reason: 'missing' });
continue;
}
const key = createPathKey(entry.path, caseInsensitive);
if (existing.has(key)) {
duplicates.push(entry);
continue;
}
existing.add(key);
unique.push(entry);
}
const concurrency = Math.max(1, Math.min(32, Math.trunc(Number(options.concurrency)) || 8));
const inspectPath = typeof options.inspectPath === 'function'
? options.inspectPath
: async (_entryPath, entry) => ({ exists: true, readable: true, size: entry.size });
const inspected = await mapWithConcurrency(unique, concurrency, async entry => {
try {
const result = await inspectPath(entry.path, entry);
const reason = unavailableReason(result);
if (reason) return { entry: { ...entry, size: Number(result?.size) || 0 }, reason };
return { entry: { ...entry, size: Number(result.size) }, reason: '' };
} catch (error) {
return { entry, reason: error && error.code === 'ENOENT' ? 'missing' : 'unreadable' };
}
});
const accepted = [];
for (const result of inspected) {
if (result.reason) unavailable.push({ ...result.entry, reason: result.reason });
else accepted.push(result.entry);
}
return {
candidateCount: input.length,
duplicateCount: duplicates.length,
unavailableCount: unavailable.length,
acceptedCount: accepted.length,
accepted,
duplicates,
unavailable
};
}
function normalizeSelectedHosters(values) {
return Array.from(new Set((Array.isArray(values) ? values : [])
.map(value => String(value ?? '').trim())
.filter(Boolean)));
}
function isImportPairEligible(file, hoster, hosterSettings = {}) {
const maxSizeMb = Number(hosterSettings?.[hoster]?.maxSizeMb);
return !(maxSizeMb > 0 && Number(file?.size) > maxSizeMb * 1024 * 1024);
}
function getEligibleImportHosters(file, selectedHosters, hosterSettings = {}) {
return normalizeSelectedHosters(selectedHosters)
.filter(hoster => isImportPairEligible(file, hoster, hosterSettings));
}
function summarizeImportPlan(input = {}) {
const inspection = input.inspection && typeof input.inspection === 'object' ? input.inspection : {};
const accepted = Array.isArray(inspection.accepted) ? inspection.accepted : [];
const selectedHosters = normalizeSelectedHosters(input.selectedHosters);
const settings = input.hosterSettings && typeof input.hosterSettings === 'object' ? input.hosterSettings : {};
let jobCount = 0;
for (const file of accepted) jobCount += getEligibleImportHosters(file, selectedHosters, settings).length;
return {
candidateCount: Number(inspection.candidateCount) || 0,
duplicateCount: Number(inspection.duplicateCount) || 0,
unavailableCount: Number(inspection.unavailableCount) || 0,
acceptedCount: accepted.length,
targetCount: selectedHosters.length,
jobCount,
sizeLimitedJobCount: accepted.length * selectedHosters.length - jobCount
};
}
return {
getEligibleImportHosters,
inspectImportEntries,
inspectReadableImportPath,
isImportPairEligible,
summarizeImportPlan
};
});