const Fs = require('fire-fs'); const FsExtra = require('fs-extra'); const Path = require('fire-path'); let vueInst = null; Editor.Panel.extend({ style: Fs.readFileSync(Editor.url('packages://res-remove/pannel/index.css'), 'utf-8'), template: Fs.readFileSync(Editor.url('packages://res-remove/pannel/index.html'), 'utf-8'), $: { logText: "#logTextArea" }, ready() { // 显示日志用. let logCtrl = this.$logText; let logListScrollToBottom = () => { setTimeout(function () { logCtrl.scrollTop = logCtrl.scrollHeight; }, 10); }; vueInst = new window.Vue({ el: this.shadowRoot, created() { Fs.readFile(Editor.url('packages://res-remove/.setting.json'), 'utf-8', (err, res) => { if (err) { return; } const settingObj = res ? JSON.parse(res) : {}; this.settings = settingObj; this.filters = settingObj.filters || ""; this.moveNumber = settingObj.moveNumber || 3; this.moveSwitch = settingObj.moveSwitch || true; this.onConfirmFilter(); }); }, data: { logView: "", checkProgress: 0, fileGroups: {}, savedFiles: {}, depend0s: [], filters: "/res/", settings: {}, taskState: "空闲中", moveSwitch: true, moveNumber: 3, }, methods: { _addLog(str) { let time = new Date(); this.logView += `[${time.toLocaleString()}]: ${str}\n`; logListScrollToBottom(); Editor.log(str); }, _setProgress(p) { this.checkProgress = p; }, _saveSetting() { Fs.writeFile(Editor.url('packages://res-remove/.setting.json'), JSON.stringify(this.settings), 'utf-8'); }, onFilterChange(ev) { this.filters = ev.target.value; }, onMoveNumberChanged(ev) { this.moveNumber = ev.target.value; }, onMoveSwitchChanged(ev) { this.moveSwitch = ev.target.checked; }, onConfirmFilter() { this.settings.filters = this.filters; this.settings.moveNumber = this.moveNumber || 3; this.settings.moveSwitch = this.moveSwitch; this._saveSetting(); Editor.Ipc.sendToMain("res-remove:setting-changed", this.settings); }, onMakCache() { this.fileGroups = {}; this.savedFiles = {}; this.depend0s = []; this.checkProgress = 0; // 开始重建缓存数据. Editor.log("开始重建资源索引数据..."); this.taskState = "正在重建资源索引数据中"; Editor.Ipc.sendToMain("res-remove:make-cache", (error, answer) => { if (error && error.code === 'ETIMEOUT') { //check the error code to confirm a timeout Editor.error('Timeout for ipc message foobar:greeting'); return; } Editor.success("资源索引构建完成!"); this.taskState = "重建索引完成" }); }, async showDumpRes(res) { this.fileGroups = res; }, checkDefaultSaves(res) { // 检测可以默认选中的资源: 包含db://assets/Texture/, db://assets/resources/,且总引用数大于0. const SAVE_DIR = ["db://assets/Texture/", "db://assets/resources/"]; for (let crc in res) { const files = res[crc]; let deps = 0; let saveFile = null; let saveFile2 = null;// 虽然不在texture/下, 但存在引用计数. for (const f of files) { deps += (f.depends || 0) const inTexture = SAVE_DIR.filter(v => { return f.url.startsWith(v) }); if (inTexture.length > 0) { saveFile = f; } else if (f.depends && !saveFile2) { saveFile2 = f; } } if (deps > 0) { if (saveFile) { this.savedFiles[saveFile.uuid] = saveFile; } else if (saveFile2) { this.savedFiles[saveFile2.uuid] = saveFile2; } } } }, updateDepends0(depend0List) { this.depend0s = depend0List; // 存储无引用资源. }, async onConfirmAll() { const files = this.files; const length = files.length; if (length <= 0) { this._addLog("没有发现重复资源,无需处理!!!") return; } this.taskState = "正在替换重复资源中" const p = 100 / length; let count = 0; this.checkProgress = 0; const savedFiles = this.savedFiles; for (const group of files) { const dropped = []; // 如果所有资源都未勾选,则如果有引用时会被忽略删除. const saved = [];// 原则上,每一组相同资源,只应该保留其中一个,若选择了多个,则会用第一个选项进行资源替换,其他勾选项不做处理,也不删除. for (let f of group) { if (savedFiles.hasOwnProperty(f.uuid)) { saved.push(f) } else { dropped.push(f) } } // 查找待移除的uuid资源,将其引用预制体修改掉,saved[0]有可能为空,则都不保留.全部删掉. Editor.Ipc.sendToMain("res-remove:exchange-uuid", saved[0], dropped, (err, resp) => { this.checkProgress += p; count += 1; if (count == length) { Editor.Ipc.sendToMain("res-remove:exchange-uuid-finish", (err) => { this.startRemoveUnused(); }, 30000); } }, 20000); } }, startRemoveUnused() { this._addLog("准备清理无用资源...") this.taskState = "正在清理无引用资源" this.checkProgress = 0; const unusedFiles = this.depend0s.filter(v => { return !this.savedFiles.hasOwnProperty(v.uuid); }).map(v => { return v.url; }); // 移除未引用资源. if (unusedFiles && unusedFiles.length > 0) { Editor.Ipc.sendToMain("res-remove:remove-unused", unusedFiles, (err) => { // 确认所有待清理资源已发送完成. this._addLog("资源清理完成!"); this.taskState = "资源清理完成"; this.onMakCache() }, 30000); } else { this._addLog("未用资源数量为0,无需清理.资源清理完成!"); this.onMakCache() } }, onItemChecked(ele, v) { if (ele.target.checked) { window.Vue.set(this.savedFiles, v.uuid, v); } else { window.Vue.delete(this.savedFiles, v.uuid); } }, onPrintDepends(item) { // 打印显示某资源的依赖资源列表 Editor.Ipc.sendToMain("res-remove:print-depends", item.subMetas[0].uuid); }, onUnusedSelect(ele) { if (ele.target.checked) { // 全选 this.depend0s.forEach(v => { window.Vue.set(this.savedFiles, v.uuid, v); }); } else { this.depend0s.forEach(v => { window.Vue.delete(this.savedFiles, v.uuid); }); } } }, computed: { files: function () { const values = Object.values(this.fileGroups); return values; } } }); }, messages: { 'update-progress'(event, args) { if (args) { vueInst._setProgress(args); } if (event.reply) { event.reply(null, ""); } }, 'addLog'(event, args) { if (args) { vueInst._addLog(args); } if (event.reply) { event.reply(null, ""); } }, 'res-search-finished'(event, fileGroups, depend0s) { Editor.log("资源检索完成"); if (event.reply) { event.reply(null, ""); } if (fileGroups) { vueInst.checkDefaultSaves(fileGroups); vueInst.showDumpRes(fileGroups); } if (depend0s) { vueInst.updateDepends0(depend0s); } } } });