Files
SpineViewer/SpineThumbnailGenerateServer/js/compound-compat.js
T

198 lines
5.7 KiB
JavaScript

// 复合后缀兼容修改:只生成计划并持久化原值;真正修改 Eagle 字段由插件在用户确认后执行。
(function () {
'use strict';
const fs = require('fs');
const path = require('path');
const STORE_VERSION = 1;
function fullName(name, ext) {
return String(name || '') + (ext ? '.' + ext : '');
}
/** 返回需要展示给用户的字段变更;不需要修改或被安全条件阻断时返回 null / blocked。 */
function planChange(item, eagleItem, splitCompoundName) {
const sourceName = String((item && item.entryName) || '');
const currentName = String((eagleItem && eagleItem.name) || '');
const currentExt = String((eagleItem && eagleItem.ext) || '');
const currentFileName = fullName(currentName, currentExt);
if (!sourceName || currentFileName !== sourceName) {
return {
blocked: true,
reason: 'sourceMismatch',
sourceName,
currentName,
currentExt,
currentFileName,
};
}
const parts = splitCompoundName(sourceName);
if (!parts || (currentName === parts.name && currentExt === parts.ext)) return null;
if (fullName(parts.name, parts.ext) !== sourceName) {
return {
blocked: true,
reason: 'sourceMismatch',
sourceName,
currentName,
currentExt,
currentFileName,
};
}
return {
sourceName,
currentName,
currentExt,
currentFileName,
newName: parts.name,
newExt: parts.ext,
newFileName: fullName(parts.name, parts.ext),
};
}
function emptyStore() {
return { version: STORE_VERSION, entries: {} };
}
function readStore(file) {
try {
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
if (!data || data.version !== STORE_VERSION || !data.entries || typeof data.entries !== 'object') {
throw new Error('备份文件格式不正确');
}
return data;
} catch (error) {
if (error && error.code === 'ENOENT') return emptyStore();
throw error;
}
}
function writeStore(file, data) {
fs.mkdirSync(path.dirname(file), { recursive: true });
const temp = file + '.' + process.pid + '.tmp';
fs.writeFileSync(temp, JSON.stringify(data, null, 2) + '\n', 'utf8');
fs.renameSync(temp, file);
}
function createStore(userDataPath) {
const file = path.join(userDataPath, 'SpineThumbnailGenerateServer', 'compound-name-backups.json');
return {
file,
list() {
return Object.values(readStore(file).entries)
.sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0));
},
remember(change) {
const data = readStore(file);
if (!data.entries[change.itemId]) {
data.entries[change.itemId] = {
itemId: change.itemId,
sourceName: change.sourceName,
folderPath: change.folderPath || '',
originalName: change.currentName,
originalExt: change.currentExt,
appliedName: change.newName,
appliedExt: change.newExt,
updatedAt: Date.now(),
};
writeStore(file, data);
}
return data.entries[change.itemId];
},
remove(itemId) {
const data = readStore(file);
if (!data.entries[itemId]) return;
delete data.entries[itemId];
writeStore(file, data);
},
};
}
/**
* 为恢复对话框生成可审计列表,并标出哪些条目允许恢复。
* 用户后来亲自改过的字段不会被覆盖。
*/
async function inspectBackups(store, getItem, sourceExists) {
const entries = store.list();
const rows = [];
for (const entry of entries) {
let item = null;
try {
item = await getItem(entry.itemId);
if (!item) {
let sourcePresent = false;
try {
sourcePresent = sourceExists ? await sourceExists(entry) : false;
} catch (error) { /* 检查失败时保守按缺失处理 */ }
if (sourcePresent) {
rows.push({
entry,
item: null,
currentName: entry.appliedName,
currentExt: entry.appliedExt,
status: 'pluginApplied',
});
} else {
rows.push({ entry, item: null, status: 'missing' });
}
continue;
}
const currentName = String(item.name || '');
const currentExt = String(item.ext || '');
if (currentName === entry.originalName && currentExt === entry.originalExt) {
rows.push({ entry, item, currentName, currentExt, status: 'alreadyOriginal' });
continue;
}
if (currentName !== entry.appliedName || currentExt !== entry.appliedExt) {
rows.push({ entry, item, currentName, currentExt, status: 'changed' });
continue;
}
rows.push({ entry, item, currentName, currentExt, status: 'ready' });
} catch (error) {
rows.push({ entry, item, status: 'error' });
}
}
return rows;
}
async function restoreBackups(store, selectedItemIds, getItem, saveItem) {
const selected = new Set(selectedItemIds || []);
const entries = store.list().filter((entry) => selected.has(entry.itemId));
const result = { total: entries.length, success: 0, skipped: 0, failed: 0 };
for (const entry of entries) {
let item = null;
try {
item = await getItem(entry.itemId);
if (!item) {
result.skipped += 1;
continue;
}
const currentName = String(item.name || '');
const currentExt = String(item.ext || '');
if (currentName === entry.originalName && currentExt === entry.originalExt) {
store.remove(entry.itemId);
result.success += 1;
continue;
}
if (currentName !== entry.appliedName || currentExt !== entry.appliedExt) {
result.skipped += 1;
continue;
}
item.name = entry.originalName;
item.ext = entry.originalExt;
await saveItem(item);
store.remove(entry.itemId);
result.success += 1;
} catch (error) {
result.failed += 1;
}
}
return result;
}
const api = { fullName, planChange, createStore, inspectBackups, restoreBackups, STORE_VERSION };
if (typeof window !== 'undefined') window.SpineThumbCompoundCompat = api;
else if (typeof module !== 'undefined' && module.exports) module.exports = api;
})();