Verify update artifacts with exact metadata and SHA-512, require host-confirmed upload completion before cleanup, harden credentials and backups, improve queue recovery and skipped-state reporting, expand Windows path coverage, and add CI packaging checks.
This commit is contained in:
+48
-3
@@ -23,16 +23,18 @@
|
||||
if (!file || !Array.isArray(file.results)) continue;
|
||||
for (const r of file.results) {
|
||||
if (!r || !r.hoster) continue;
|
||||
const bucket = out[r.hoster] || (out[r.hoster] = { ok: 0, fail: 0, total: 0 });
|
||||
const bucket = out[r.hoster] || (out[r.hoster] = { ok: 0, fail: 0, skipped: 0, total: 0 });
|
||||
bucket.total++;
|
||||
if (r.status === 'done') bucket.ok++;
|
||||
else if (r.status === 'skipped') bucket.skipped++;
|
||||
else bucket.fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const h of Object.keys(out)) {
|
||||
const b = out[h];
|
||||
b.rate = b.total > 0 ? b.ok / b.total : null;
|
||||
const attempted = b.ok + b.fail;
|
||||
b.rate = attempted > 0 ? b.ok / attempted : null;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -61,7 +63,7 @@
|
||||
for (const f of batchSummary.files) {
|
||||
if (!f || !Array.isArray(f.results)) continue;
|
||||
for (const r of f.results) {
|
||||
if (!r || r.status === 'done') continue;
|
||||
if (!r || r.status === 'done' || r.status === 'skipped') continue;
|
||||
const cat = classifyErrorCategory(r.error);
|
||||
buckets[cat].push({
|
||||
fileName: f.name || f.fileName || '',
|
||||
@@ -74,6 +76,48 @@
|
||||
return buckets;
|
||||
}
|
||||
|
||||
function mergeSkippedIntoSummary(summary, skippedJobs) {
|
||||
const source = summary && typeof summary === 'object' ? summary : {};
|
||||
const merged = {
|
||||
...source,
|
||||
files: Array.isArray(source.files)
|
||||
? source.files.map(file => ({ ...file, results: Array.isArray(file.results) ? [...file.results] : [] }))
|
||||
: []
|
||||
};
|
||||
const existingJobIds = new Set();
|
||||
const filesByName = new Map();
|
||||
for (const file of merged.files) {
|
||||
filesByName.set(String(file.name || file.fileName || ''), file);
|
||||
for (const result of file.results) {
|
||||
if (result?.jobId) existingJobIds.add(result.jobId);
|
||||
}
|
||||
}
|
||||
let added = 0;
|
||||
for (const skipped of Array.isArray(skippedJobs) ? skippedJobs : []) {
|
||||
if (!skipped || (skipped.jobId && existingJobIds.has(skipped.jobId))) continue;
|
||||
const fileName = String(skipped.fileName || skipped.file || '').split(/[\\/]/).pop() || '';
|
||||
let file = filesByName.get(fileName);
|
||||
if (!file) {
|
||||
file = { name: fileName, size: Number(skipped.size) || 0, results: [] };
|
||||
merged.files.push(file);
|
||||
filesByName.set(fileName, file);
|
||||
}
|
||||
file.results.push({
|
||||
jobId: skipped.jobId || null,
|
||||
hoster: skipped.hoster || '',
|
||||
status: 'skipped',
|
||||
error: skipped.reason || 'Übersprungen'
|
||||
});
|
||||
if (skipped.jobId) existingJobIds.add(skipped.jobId);
|
||||
added++;
|
||||
}
|
||||
merged.total = (Number(source.total) || 0) + added;
|
||||
merged.succeeded = Number(source.succeeded) || 0;
|
||||
merged.failed = Number(source.failed) || 0;
|
||||
merged.skipped = (Number(source.skipped) || 0) + added;
|
||||
return merged;
|
||||
}
|
||||
|
||||
const RETRYABLE_CATEGORIES = new Set(['hoster-transient', 'network', 'unknown']);
|
||||
function isRetryableCategory(cat) {
|
||||
return RETRYABLE_CATEGORIES.has(cat);
|
||||
@@ -128,6 +172,7 @@
|
||||
summarizePerHoster,
|
||||
classifyErrorCategory,
|
||||
summarizeBatchErrors,
|
||||
mergeSkippedIntoSummary,
|
||||
isRetryableCategory,
|
||||
RETRYABLE_CATEGORIES,
|
||||
CATEGORY_LABELS,
|
||||
|
||||
Reference in New Issue
Block a user