mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-02-26 14:08:49 +00:00
189 lines
7.3 KiB
TypeScript
189 lines
7.3 KiB
TypeScript
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
|
import type { LOG_LEVEL } from "../lib/src/common/types";
|
|
import type { LiveSyncCore } from "../main";
|
|
import { unique } from "octagonal-wheels/collection";
|
|
import type { IObsidianModule } from "./AbstractObsidianModule.ts";
|
|
import type {
|
|
ICoreModuleBase,
|
|
AllInjectableProps,
|
|
AllExecuteProps,
|
|
EveryExecuteProps,
|
|
AnyExecuteProps,
|
|
ICoreModule,
|
|
} from "./ModuleTypes";
|
|
|
|
function isOverridableKey(key: string): key is keyof ICoreModuleBase {
|
|
return key.startsWith("$");
|
|
}
|
|
|
|
function isInjectableKey(key: string): key is keyof AllInjectableProps {
|
|
return key.startsWith("$$");
|
|
}
|
|
|
|
function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
|
|
return key.startsWith("$all");
|
|
}
|
|
function isEveryExecuteKey(key: string): key is keyof EveryExecuteProps {
|
|
return key.startsWith("$every");
|
|
}
|
|
function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
|
|
return key.startsWith("$any");
|
|
}
|
|
/**
|
|
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
|
* Please refer to the module's source code to understand the function.
|
|
* $$ : Completely overridden functions.
|
|
* $all : Process all modules and return all results.
|
|
* $every : Process all modules until the first failure.
|
|
* $any : Process all modules until the first success.
|
|
* $ : Other interceptive points. You should manually assign the module
|
|
* All of above performed on injectModules function.
|
|
*/
|
|
export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
|
|
const allKeys = unique([
|
|
...Object.keys(Object.getOwnPropertyDescriptors(target)),
|
|
...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target))),
|
|
]).filter((e) => e.startsWith("$")) as (keyof ICoreModule)[];
|
|
const moduleMap = new Map<string, IObsidianModule[]>();
|
|
for (const module of modules) {
|
|
for (const key of allKeys) {
|
|
if (isOverridableKey(key)) {
|
|
if (key in module) {
|
|
const list = moduleMap.get(key) || [];
|
|
if (typeof module[key] === "function") {
|
|
module[key] = module[key].bind(module) as any;
|
|
}
|
|
list.push(module);
|
|
moduleMap.set(key, list);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Logger(`Injecting modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
|
for (const key of allKeys) {
|
|
const modules = moduleMap.get(key) || [];
|
|
if (isInjectableKey(key)) {
|
|
if (modules.length == 0) {
|
|
throw new Error(`No module injected for ${key}. This is a fatal error.`);
|
|
}
|
|
target[key] = modules[0][key]! as any;
|
|
Logger(`[${modules[0].constructor.name}]: Injected ${key} `, LOG_LEVEL_VERBOSE);
|
|
} else if (isAllExecuteKey(key)) {
|
|
const modules = moduleMap.get(key) || [];
|
|
target[key] = async (...args: any) => {
|
|
for (const module of modules) {
|
|
try {
|
|
//@ts-ignore
|
|
await module[key]!(...args);
|
|
} catch (ex) {
|
|
Logger(`[${module.constructor.name}]: All handler for ${key} failed`, LOG_LEVEL_VERBOSE);
|
|
Logger(ex, LOG_LEVEL_VERBOSE);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
for (const module of modules) {
|
|
Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
|
|
}
|
|
} else if (isEveryExecuteKey(key)) {
|
|
target[key] = async (...args: any) => {
|
|
for (const module of modules) {
|
|
try {
|
|
//@ts-ignore:2556
|
|
const ret = await module[key]!(...args);
|
|
if (ret !== undefined && !ret) {
|
|
// Failed then return that falsy value.
|
|
return ret;
|
|
}
|
|
} catch (ex) {
|
|
Logger(`[${module.constructor.name}]: Every handler for ${key} failed`);
|
|
Logger(ex, LOG_LEVEL_VERBOSE);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
for (const module of modules) {
|
|
Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
|
|
}
|
|
} else if (isAnyExecuteKey(key)) {
|
|
//@ts-ignore
|
|
target[key] = async (...args: any[]) => {
|
|
for (const module of modules) {
|
|
try {
|
|
//@ts-ignore:2556
|
|
const ret = await module[key](...args);
|
|
// If truly value returned, then return that value.
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
} catch (ex) {
|
|
Logger(`[${module.constructor.name}]: Any handler for ${key} failed`);
|
|
Logger(ex, LOG_LEVEL_VERBOSE);
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
for (const module of modules) {
|
|
Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
|
|
}
|
|
} else {
|
|
Logger(`No injected handler for ${key} `, LOG_LEVEL_VERBOSE);
|
|
}
|
|
}
|
|
Logger(`Injected modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
|
return true;
|
|
}
|
|
|
|
export abstract class AbstractModule {
|
|
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
|
if (typeof msg === "string" && level !== LOG_LEVEL_NOTICE) {
|
|
msg = `[${this.constructor.name}]\u{200A} ${msg}`;
|
|
}
|
|
// console.log(msg);
|
|
Logger(msg, level, key);
|
|
};
|
|
|
|
get localDatabase() {
|
|
return this.core.localDatabase;
|
|
}
|
|
get settings() {
|
|
return this.core.settings;
|
|
}
|
|
set settings(value) {
|
|
this.core.settings = value;
|
|
}
|
|
|
|
constructor(public core: LiveSyncCore) {
|
|
Logger(`[${this.constructor.name}] Loaded`, LOG_LEVEL_VERBOSE);
|
|
}
|
|
saveSettings = this.core.saveSettings.bind(this.core);
|
|
|
|
// abstract $everyTest(): Promise<boolean>;
|
|
addTestResult(key: string, value: boolean, summary?: string, message?: string) {
|
|
this.core.$$addTestResult(`${this.constructor.name}`, key, value, summary, message);
|
|
}
|
|
testDone(result: boolean = true) {
|
|
return Promise.resolve(result);
|
|
}
|
|
testFail(message: string) {
|
|
this._log(message, LOG_LEVEL_NOTICE);
|
|
return this.testDone(false);
|
|
}
|
|
|
|
async _test(key: string, process: () => Promise<any>) {
|
|
this._log(`Testing ${key}`, LOG_LEVEL_VERBOSE);
|
|
try {
|
|
const ret = await process();
|
|
if (ret !== true) {
|
|
this.addTestResult(key, false, ret.toString());
|
|
return this.testFail(`${key} failed: ${ret}`);
|
|
}
|
|
this.addTestResult(key, true, "");
|
|
} catch (ex: any) {
|
|
this.addTestResult(key, false, "Failed by Exception", ex.toString());
|
|
return this.testFail(`${key} failed: ${ex}`);
|
|
}
|
|
return this.testDone();
|
|
}
|
|
}
|