资源组更新缩略图

This commit is contained in:
tianmo committed 2026-09-17 18:40:29 +08:00
1 parent 05c4fc4b90
commit 2a5fe0ce1f
27 files changed
+562 -161

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { applyThumbnailTargets, orderCurrentTargetLast } = require('../js/spine-group-thumbnails');
(async () => {
const targets = ['json', 'skel', 'atlas'].map((id) => ({ sourcePath: '/library/' + id }));
assert.deepEqual(
orderCurrentTargetLast(targets, '/library/json').map((target) => target.sourcePath),
['/library/skel', '/library/atlas', '/library/json'],
'the preview-owning item must be processed last because Eagle tears down its viewer after updating it'
);
const applied = new Set();
const inputPaths = [];
const result = await applyThumbnailTargets({
targets,
initialSkipped: 1,
settleDelayMs: 0,
cleanupDelayMs: 0,
rendered: { buffer: Buffer.from('thumbnail'), extension: '.webp' },
getItem: async (sourcePath) => ({ id: sourcePath.split('/').pop(), data: { customThumbnail: false } }),
setThumbnail: async (id, thumbnailPath) => {
assert(fs.existsSync(thumbnailPath));
assert.equal(fs.readFileSync(thumbnailPath, 'utf8'), 'thumbnail');
inputPaths.push(thumbnailPath);
// Simulate Eagle consuming the supplied file. Reusing one file would make
// every call after the first fail, which was the original regression.
fs.unlinkSync(thumbnailPath);
applied.add(id);
return true;
},
waitForApplied: async (sourcePath) => applied.has(sourcePath.split('/').pop()),
});
assert.deepEqual(result, { success: 3, skipped: 1, failed: 0 });
assert.equal(new Set(inputPaths).size, 3);
const overwritten = await applyThumbnailTargets({
targets: [{ sourcePath: '/library/custom', allowOverwrite: true }],
settleDelayMs: 0,
cleanupDelayMs: 0,
rendered: { buffer: Buffer.from('replacement'), extension: '.webp' },
getItem: async () => ({ id: 'custom', data: { customThumbnail: true } }),
setThumbnail: async (_id, thumbnailPath) => { assert(fs.existsSync(thumbnailPath)); return true; },
waitForApplied: async () => true,
});
assert.deepEqual(overwritten, { success: 1, skipped: 0, failed: 0 });
const deferredReads = [];
const deferred = await applyThumbnailTargets({
targets,
settleDelayMs: 15,
cleanupDelayMs: 25,
rendered: { buffer: Buffer.from('deferred-thumbnail'), extension: '.webp' },
getItem: async (sourcePath) => ({ id: sourcePath.split('/').pop(), data: { customThumbnail: false } }),
setThumbnail: async (id, thumbnailPath) => {
setTimeout(() => {
deferredReads.push({ id, content: fs.readFileSync(thumbnailPath, 'utf8') });
}, 10);
return true;
},
// Simulate the Web API exposing customThumbnail before its background file
// copy has completed.
waitForApplied: async () => true,
});
assert.deepEqual(deferred, { success: 3, skipped: 0, failed: 0 });
assert.deepEqual(deferredReads.map((entry) => entry.id), ['json', 'skel', 'atlas']);
assert(deferredReads.every((entry) => entry.content === 'deferred-thumbnail'));
let rejectedCalls = 0;
const retried = await applyThumbnailTargets({
targets: [{ sourcePath: '/library/retry' }],
settleDelayMs: 0,
cleanupDelayMs: 0,
rendered: { buffer: Buffer.from('retry-thumbnail'), extension: '.png' },
getItem: async () => ({ id: 'retry', data: { customThumbnail: false } }),
setThumbnail: async () => ++rejectedCalls === 1 ? false : true,
waitForApplied: async () => true,
});
assert.equal(rejectedCalls, 2);
assert.deepEqual(retried, { success: 1, skipped: 0, failed: 0 });
console.log('PASS: first batch applies every selected item using an independent temporary file');
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
+5 -1
View File
@@ -72,7 +72,11 @@ try {
const viewerSource = fs.readFileSync(path.join(__dirname, '../viewer/spine.html'), 'utf8');
assert.match(viewerSource, /currentItem\.data\.customThumbnail === true && !window\.confirm/);
assert.match(viewerSource, /activeAssets\.skeletonPath[\s\S]*activeAssets\.atlasPath[\s\S]*Object\.values\(activeAssets\.texturePaths/);
assert.match(viewerSource, /fresh\.data\.customThumbnail === true\) \{ skipped\+\+; continue; \}[\s\S]*setEagleCustomThumbnail\(fresh\.id/);
assert.match(viewerSource, /activeAssets\.skeletonCandidatePaths[\s\S]*activeAssets\.atlasCandidatePaths[\s\S]*utils\.normalizeStem/);
assert.match(viewerSource, /selected: item\.data\.customThumbnail !== true && !utils\.isTextureFile\(sourcePath\)/);
const groupThumbnailSource = fs.readFileSync(path.join(__dirname, '../js/spine-group-thumbnails.js'), 'utf8');
assert.match(groupThumbnailSource, /fresh\.data\.customThumbnail === true && target\.allowOverwrite !== true\) \{ skipped\+\+; continue; \}[\s\S]*options\.setThumbnail\(fresh\.id/);
assert.match(groupThumbnailSource, /itemTempPath = path\.join\(os\.tmpdir\(\), 'spineviewer-group-' \+ fresh\.id[\s\S]*options\.setThumbnail\(fresh\.id, itemTempPath\)/);
assert.match(viewerSource, /groupThumbBtn[\s\S]*thumbBtn/);
assert.match(viewerSource, /path\.join\(os\.tmpdir\(\), 'spineviewer-current-'/);
assert.doesNotMatch(viewerSource, /fs\.writeFileSync\(outPath/);