Restore the v2.1.19 application baseline and retain only the focused import preflight summary with duplicate, unavailable, destination, job, and size-limit visibility.
This commit is contained in:
+158
-167
@@ -1,185 +1,176 @@
|
||||
(function initImportPreflight(root, factory) {
|
||||
const api = typeof module === 'object' && module.exports
|
||||
? factory(require('path'), require('./filename-filter'))
|
||||
? factory(require('path'))
|
||||
: factory({
|
||||
normalize: value => String(value).replace(/[\\/]+/g, '/'),
|
||||
basename: value => String(value).split(/[\\/]/).pop() || ''
|
||||
}, root.FilenameFilter);
|
||||
});
|
||||
if (typeof module === 'object' && module.exports) module.exports = api;
|
||||
if (root) root.ImportPreflight = api;
|
||||
})(typeof window !== 'undefined' ? window : globalThis, function createImportPreflight(path, filenameFilter) {
|
||||
const { applyFilenameFilter } = filenameFilter;
|
||||
|
||||
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);
|
||||
})(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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
return path.normalize(text);
|
||||
}
|
||||
|
||||
const filtered = applyFilenameFilter(unique, options.filenameFilter);
|
||||
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(filtered.accepted, concurrency, async entry => {
|
||||
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 {
|
||||
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: '' };
|
||||
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) {
|
||||
return { entry, reason: error && error.code === 'ENOENT' ? 'missing' : 'unreadable' };
|
||||
if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) return { exists: false };
|
||||
return { exists: true, readable: false };
|
||||
} finally {
|
||||
if (fileHandle) {
|
||||
try {
|
||||
await fileHandle.close();
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
});
|
||||
const accepted = [];
|
||||
for (const result of inspected) {
|
||||
if (result.reason) unavailable.push({ ...result.entry, reason: result.reason });
|
||||
else accepted.push(result.entry);
|
||||
}
|
||||
|
||||
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 {
|
||||
candidateCount: input.length,
|
||||
duplicateCount: duplicates.length,
|
||||
filteredCount: filtered.excluded.length,
|
||||
unavailableCount: unavailable.length,
|
||||
acceptedCount: accepted.length,
|
||||
accepted,
|
||||
duplicates,
|
||||
filtered: filtered.excluded,
|
||||
unavailable
|
||||
getEligibleImportHosters,
|
||||
inspectImportEntries,
|
||||
inspectReadableImportPath,
|
||||
isImportPairEligible,
|
||||
summarizeImportPlan
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const sizeLimitedJobCount = accepted.length * selectedHosters.length - jobCount;
|
||||
return {
|
||||
candidateCount: Number(inspection.candidateCount) || 0,
|
||||
duplicateCount: Number(inspection.duplicateCount) || 0,
|
||||
filteredCount: Number(inspection.filteredCount) || 0,
|
||||
unavailableCount: Number(inspection.unavailableCount) || 0,
|
||||
acceptedCount: accepted.length,
|
||||
targetCount: selectedHosters.length,
|
||||
jobCount,
|
||||
sizeLimitedJobCount
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
getEligibleImportHosters,
|
||||
inspectImportEntries,
|
||||
inspectReadableImportPath,
|
||||
isImportPairEligible,
|
||||
summarizeImportPlan
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user