更新
This commit is contained in:
1 parent
9635428f78
commit
ef01cdd6ee
6 files changed
+172
-66
No files matched your search
@@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const crypto = require('crypto');
|
||||
const { spawn } = require('child_process');
|
||||
const utils = require('./spine-utils');
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
function enqueue(src) {
|
||||
const library = utils.findLibraryRoot(src);
|
||||
if (!library) return; // Standalone tests/previews do not need Eagle refresh.
|
||||
const key = crypto.createHash('sha256').update(__dirname + library).digest('hex');
|
||||
const directory = path.join(os.tmpdir(), 'spineviewer-reconcile', key);
|
||||
fs.mkdirSync(directory, { recursive: true });
|
||||
const job = path.join(directory, crypto.createHash('sha256').update(src).digest('hex') + '.json');
|
||||
const temp = job + '.' + crypto.randomBytes(8).toString('hex') + '.tmp';
|
||||
fs.writeFileSync(temp, JSON.stringify({ src, deadline: Date.now() + 120000 }));
|
||||
fs.renameSync(temp, job);
|
||||
// Electron's Node mode runs the local worker without opening another Eagle.
|
||||
const child = spawn(process.execPath, [__filename, directory], {
|
||||
detached: true, stdio: 'ignore', env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }
|
||||
});
|
||||
child.on('error', error => {
|
||||
try { fs.appendFileSync(path.join(__dirname, '../thumbnails.log'), '[worker] ' + String(error) + '\n'); } catch (_) {}
|
||||
});
|
||||
child.unref();
|
||||
}
|
||||
|
||||
async function run(directory) {
|
||||
const lock = path.join(directory, 'worker.lock');
|
||||
let fd;
|
||||
try { fd = fs.openSync(lock, 'wx'); }
|
||||
catch (error) {
|
||||
if (error.code !== 'EEXIST') throw error;
|
||||
let pid;
|
||||
try { pid = Number(fs.readFileSync(lock, 'utf8')); } catch (_) { return; }
|
||||
if (!pid) return; // Another worker is still writing its lock.
|
||||
try { process.kill(pid, 0); return; } catch (e) { if (e.code !== 'ESRCH') return; }
|
||||
try { fs.unlinkSync(lock); } catch (_) { return; }
|
||||
return run(directory);
|
||||
}
|
||||
fs.writeFileSync(fd, String(process.pid)); fs.closeSync(fd);
|
||||
const snapshots = new Map();
|
||||
const nextChecks = new Map();
|
||||
const thumbnail = require('../thumbnail/spine');
|
||||
try {
|
||||
let idle = 0;
|
||||
while (idle < 2) {
|
||||
await sleep(1000); // Allow the importing callback/window to finish.
|
||||
const jobs = fs.readdirSync(directory).filter(name => name.endsWith('.json'));
|
||||
if (!jobs.length) { idle++; continue; }
|
||||
idle = 0;
|
||||
for (const name of jobs) {
|
||||
const file = path.join(directory, name);
|
||||
let raw;
|
||||
try {
|
||||
raw = fs.readFileSync(file, 'utf8');
|
||||
const job = JSON.parse(raw);
|
||||
if (Date.now() < (nextChecks.get(name) || 0)) continue;
|
||||
const remaining = job.deadline - Date.now();
|
||||
nextChecks.set(name, Date.now() + (remaining > 110000 ? 1000 : remaining > 80000 ? 2500 : 5000));
|
||||
const result = Date.now() > job.deadline ? { complete: true }
|
||||
: await thumbnail.reconcileOnce(job.src, snapshots.get(name));
|
||||
snapshots.set(name, result.signature);
|
||||
if (result.complete && fs.readFileSync(file, 'utf8') === raw) {
|
||||
fs.unlinkSync(file); snapshots.delete(name); nextChecks.delete(name);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') fs.appendFileSync(path.join(__dirname, '../thumbnails.log'), '[worker] ' + String(error) + '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fs.unlinkSync(lock);
|
||||
// Cover an enqueue racing the final idle check and lock release.
|
||||
if (fs.readdirSync(directory).some(name => name.endsWith('.json'))) await run(directory);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { enqueue, run };
|
||||
if (require.main === module) run(process.argv[2]).catch(error => {
|
||||
try { fs.appendFileSync(path.join(__dirname, '../thumbnails.log'), '[worker] ' + String(error) + '\n'); } catch (_) {}
|
||||
process.exitCode = 1;
|
||||
});
|
||||
+3
-3
@@ -359,14 +359,14 @@ function collectSpineFiles(roots, validFolders) {
|
||||
* 很短的缓存,下一轮轮询仍会重新扫描,因此既降低大文件夹导入开销,也不会漏掉
|
||||
* 正在落盘的新文件。
|
||||
*/
|
||||
function collectSpineFilesCached(filePath, roots) {
|
||||
function collectSpineFilesCached(filePath, roots, refresh) {
|
||||
const libRoot = findLibraryRoot(filePath);
|
||||
// Outside an Eagle library, sibling asset folders are independent search
|
||||
// scopes. Sharing their parent as a cache key can return another skeleton.
|
||||
const key = libRoot || JSON.stringify([...roots].sort());
|
||||
const now = Date.now();
|
||||
const cached = fileIndexCache.get(key);
|
||||
if (cached && now - cached.time < 1500) return cached.files;
|
||||
if (!refresh && cached && now - cached.time < 1500) return cached.files;
|
||||
const files = collectSpineFiles(roots, null);
|
||||
fileIndexCache.set(key, { time: now, files });
|
||||
return files;
|
||||
@@ -434,7 +434,7 @@ function discoverAssets(filePath, options) {
|
||||
const allowedImportDelta = allowCrossBatch ? maxCrossBatchDeltaMs : 30000;
|
||||
let files = collectSpineFiles(roots, validFolders);
|
||||
if (strictStem) {
|
||||
const allFiles = collectSpineFilesCached(filePath, roots);
|
||||
const allFiles = collectSpineFilesCached(filePath, roots, options.refreshIndex);
|
||||
if (allowCrossBatch && validFolders && validFolders.size) {
|
||||
files = allFiles.filter((f) => foldersOfItem(f.fullPath).some((id) => validFolders.has(id)));
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user