86 lines
3.5 KiB
JavaScript
86 lines
3.5 KiB
JavaScript
'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;
|
|
});
|