149 lines
6.0 KiB
JavaScript
149 lines
6.0 KiB
JavaScript
import Utilities from "./utilities";
|
|
import Settings from "./settingsmanager";
|
|
|
|
const path = require("path");
|
|
const fs = require("fs");
|
|
const Module = require("module").Module;
|
|
Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
|
|
class MetaError extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = "MetaError";
|
|
}
|
|
}
|
|
|
|
class ContentError extends Error {
|
|
constructor(name, filename, message, error) {
|
|
super(message);
|
|
this.name = name;
|
|
this.file = filename;
|
|
this.error = error;
|
|
}
|
|
}
|
|
|
|
export default new class ContentManager {
|
|
|
|
get fileExtension() {return "";}
|
|
get extension() {return "";}
|
|
get contentFolder() {return "";}
|
|
get collection() {return "settings";}
|
|
get category() {return "content";}
|
|
get id() {return "autoReload";}
|
|
getContentModification(content) {return content;}
|
|
|
|
constructor() {
|
|
this.timeCache = {};
|
|
this.content = [];
|
|
this.originalRequire = Module._extensions[this.fileExtension];
|
|
Module._extensions[this.fileExtension] = this.getContentRequire();
|
|
Settings.on(this.collection, this.category, this.id, (enabled) => {
|
|
if (enabled) this.watchContent();
|
|
else this.unwatchContent();
|
|
});
|
|
if (Settings.get(this.collection, this.category, this.id)) this.watchContent();
|
|
}
|
|
|
|
watchContent() {
|
|
if (this.watcher) return;
|
|
this.watcher = fs.watch(this.contentFolder, {persistent: false}, async (eventType, filename) => {
|
|
if (!eventType || !filename || !filename.endsWith(this.extension)) return;
|
|
await new Promise(r => setTimeout(r, 50));
|
|
try {fs.statSync(path.resolve(this.contentFolder, filename));}
|
|
catch (err) {
|
|
if (err.code !== "ENOENT") return;
|
|
delete this.timeCache[filename];
|
|
this.unloadContent(filename);
|
|
}
|
|
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile()) return;
|
|
const stats = fs.statSync(path.resolve(this.contentFolder, filename));
|
|
if (!stats || !stats.mtime || !stats.mtime.getTime()) return;
|
|
if (typeof(stats.mtime.getTime()) !== "number") return;
|
|
if (this.timeCache[filename] == stats.mtime.getTime()) return;
|
|
this.timeCache[filename] = stats.mtime.getTime();
|
|
if (eventType == "rename") this.loadContent(filename);
|
|
if (eventType == "change") this.reloadContent(filename);
|
|
});
|
|
}
|
|
|
|
unwatchContent() {
|
|
if (!this.watcher) return;
|
|
this.watcher.close();
|
|
delete this.watcher;
|
|
}
|
|
|
|
extractMeta(content) {
|
|
const meta = content.split("\n")[0];
|
|
const rawMeta = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
|
|
if (meta.indexOf("META") < 0) throw new MetaError("META was not found.");
|
|
if (!Utilities.testJSON(rawMeta)) throw new MetaError("META could not be parsed.");
|
|
|
|
const parsed = JSON.parse(rawMeta);
|
|
if (!parsed.name) throw new MetaError("META missing name data.");
|
|
return parsed;
|
|
}
|
|
|
|
getContentRequire() {
|
|
const self = this;
|
|
const baseFolder = this.contentFolder;
|
|
const originalRequire = this.originalRequire;
|
|
return function(module, filename) {
|
|
const possiblePath = path.resolve(baseFolder, path.basename(filename));
|
|
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
|
|
let content = fs.readFileSync(filename, "utf8");
|
|
content = Utilities.stripBOM(content);
|
|
const meta = self.extractMeta(content);
|
|
meta.filename = path.basename(filename);
|
|
content = self.getContentModification(content, meta);
|
|
module._compile(content, filename);
|
|
};
|
|
}
|
|
|
|
// Subclasses should use the return (if not ContentError) and push to this.contentList
|
|
loadContent(filename, type) {
|
|
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
|
|
try {__non_webpack_require__(path.resolve(this.contentFolder, filename));}
|
|
catch (error) {return new ContentError(filename, filename, "Could not be compiled.", {message: error.message, stack: error.stack});}
|
|
return __non_webpack_require__(path.resolve(this.contentFolder, filename));
|
|
}
|
|
|
|
unloadContent(idOrFile) {
|
|
if (typeof(filename) === "undefined") return;
|
|
const content = this.content.find(c => c.id == idOrFile || c.filename == idOrFile);
|
|
if (!content) return false;
|
|
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(this.contentFolder, content.file))];
|
|
this.content.splice(this.content.indexOf(content), 1);
|
|
return true;
|
|
}
|
|
|
|
isLoaded(idOrFile) {
|
|
const content = this.content.find(c => c.id == idOrFile || c.filename == idOrFile);
|
|
if (!content) return false;
|
|
return true;
|
|
}
|
|
|
|
reloadContent(filename, type) {
|
|
const didUnload = this.unloadContent(filename, type);
|
|
if (!didUnload) return didUnload;
|
|
return this.loadContent(filename, type);
|
|
}
|
|
|
|
loadNewContent() {
|
|
const files = fs.readdirSync(this.contentFolder);
|
|
const removed = this.content.filter(t => !files.includes(t.filename)).map(c => c.id);
|
|
const added = files.filter(f => !this.content.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.contentFolder, f)).isFile());
|
|
return {added, removed};
|
|
}
|
|
|
|
loadAllContent(type) {
|
|
const errors = [];
|
|
const files = fs.readdirSync(this.contentFolder);
|
|
|
|
for (const filename of files) {
|
|
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile() || !filename.endsWith(this.extension)) continue;
|
|
const content = this.loadContent(filename, type);
|
|
if (content instanceof ContentError) errors.push(content);
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
}; |