Skip to content

Commit

Permalink
add basic extension support #753
Browse files Browse the repository at this point in the history
  • Loading branch information
bpatrik committed Oct 29, 2023
1 parent 237e61c commit 538593e
Show file tree
Hide file tree
Showing 22 changed files with 315 additions and 29 deletions.
6 changes: 3 additions & 3 deletions benchmark/BenchmarkRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class BenchmarkRunner {
const bm = new Benchmark('List directory', req,
async (): Promise<void> => {
await ObjectManagers.reset();
await ObjectManagers.InitSQLManagers();
await ObjectManagers.InitManagers();
}, null,
async (): Promise<void> => {
Config.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
Expand All @@ -135,7 +135,7 @@ export class BenchmarkRunner {
async bmListPersons(): Promise<BenchmarkResult[]> {
const bm = new Benchmark('Listing Faces', Utils.clone(this.requestTemplate), async (): Promise<void> => {
await ObjectManagers.reset();
await ObjectManagers.InitSQLManagers();
await ObjectManagers.InitManagers();
}, null,
async (): Promise<void> => {
Config.Indexing.reIndexingSensitivity = ReIndexingSensitivity.low;
Expand Down Expand Up @@ -289,7 +289,7 @@ export class BenchmarkRunner {
await fs.promises.rm(ProjectPath.DBFolder, {recursive: true, force: true});
Config.Database.type = DatabaseType.sqlite;
Config.Jobs.scheduled = [];
await ObjectManagers.InitSQLManagers();
await ObjectManagers.InitManagers();
};

private async setupDB(): Promise<void> {
Expand Down
4 changes: 3 additions & 1 deletion src/backend/ProjectPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import * as path from 'path';
import * as fs from 'fs';
import {Config} from '../common/config/private/Config';

class ProjectPathClass {
export class ProjectPathClass {
public Root: string;
public ImageFolder: string;
public TempFolder: string;
public TranscodedFolder: string;
public FacesFolder: string;
public FrontendFolder: string;
public ExtensionFolder: string;
public DBFolder: string;

constructor() {
Expand All @@ -35,6 +36,7 @@ class ProjectPathClass {
this.TranscodedFolder = path.join(this.TempFolder, 'tc');
this.FacesFolder = path.join(this.TempFolder, 'f');
this.DBFolder = this.getAbsolutePath(Config.Database.dbFolder);
this.ExtensionFolder = path.join(this.Root, 'extension');

// create thumbnail folder if not exist
if (!fs.existsSync(this.TempFolder)) {
Expand Down
2 changes: 1 addition & 1 deletion src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ if ((process.argv || []).includes('--run-diagnostics')) {
process.exit(0);
});
} else {
new Server();
Server.getInstance();
}
48 changes: 39 additions & 9 deletions src/backend/model/ObjectManagers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {AlbumManager} from './database/AlbumManager';
import {PersonManager} from './database/PersonManager';
import {SharingManager} from './database/SharingManager';
import {IObjectManager} from './database/IObjectManager';
import {ExtensionManager} from './extension/ExtensionManager';

const LOG_TAG = '[ObjectManagers]';

Expand All @@ -32,6 +33,7 @@ export class ObjectManagers {
private jobManager: JobManager;
private locationManager: LocationManager;
private albumManager: AlbumManager;
private extensionManager: ExtensionManager;

constructor() {
this.managers = [];
Expand Down Expand Up @@ -169,6 +171,18 @@ export class ObjectManagers {
this.managers.push(this.jobManager as IObjectManager);
}

get ExtensionManager(): ExtensionManager {
return this.extensionManager;
}

set ExtensionManager(value: ExtensionManager) {
if (this.extensionManager) {
this.managers.splice(this.managers.indexOf(this.extensionManager as IObjectManager), 1);
}
this.extensionManager = value;
this.managers.push(this.extensionManager as IObjectManager);
}

public static getInstance(): ObjectManagers {
if (this.instance === null) {
this.instance = new ObjectManagers();
Expand All @@ -179,27 +193,33 @@ export class ObjectManagers {
public static async reset(): Promise<void> {
Logger.silly(LOG_TAG, 'Object manager reset begin');
if (
ObjectManagers.getInstance().IndexingManager &&
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress
ObjectManagers.getInstance().IndexingManager &&
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress
) {
await ObjectManagers.getInstance().IndexingManager.SavingReady;
}
if (ObjectManagers.getInstance().JobManager) {
ObjectManagers.getInstance().JobManager.stopSchedules();
for (const manager of ObjectManagers.getInstance().managers) {
if (manager === ObjectManagers.getInstance().versionManager) {
continue;
}
if (manager.cleanUp) {
await manager.cleanUp();
}
}

await SQLConnection.close();
this.instance = null;
Logger.debug(LOG_TAG, 'Object manager reset');
Logger.debug(LOG_TAG, 'Object manager reset done');
}

public static async InitSQLManagers(): Promise<void> {
public static async InitManagers(): Promise<void> {
await ObjectManagers.reset();
await SQLConnection.init();
this.initManagers();
await this.initManagers();
Logger.debug(LOG_TAG, 'SQL DB inited');
}

private static initManagers(): void {
private static async initManagers(): Promise<void> {
ObjectManagers.getInstance().AlbumManager = new AlbumManager();
ObjectManagers.getInstance().GalleryManager = new GalleryManager();
ObjectManagers.getInstance().IndexingManager = new IndexingManager();
Expand All @@ -211,10 +231,20 @@ export class ObjectManagers {
ObjectManagers.getInstance().VersionManager = new VersionManager();
ObjectManagers.getInstance().JobManager = new JobManager();
ObjectManagers.getInstance().LocationManager = new LocationManager();
ObjectManagers.getInstance().ExtensionManager = new ExtensionManager();

for (const manager of ObjectManagers.getInstance().managers) {
if (manager === ObjectManagers.getInstance().versionManager) {
continue;
}
if (manager.init) {
await manager.init();
}
}
}

public async onDataChange(
changedDir: ParentDirectoryDTO = null
changedDir: ParentDirectoryDTO = null
): Promise<void> {
await this.VersionManager.onNewDataVersion();

Expand Down
2 changes: 2 additions & 0 deletions src/backend/model/database/IObjectManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ import {ParentDirectoryDTO} from '../../../common/entities/DirectoryDTO';

export interface IObjectManager {
onNewDataVersion?: (changedDir?: ParentDirectoryDTO) => Promise<void>;
cleanUp?: () => Promise<void>;
init?: () => Promise<void>;
}
27 changes: 27 additions & 0 deletions src/backend/model/extension/ExtensionDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {IExtensionEvent, IExtensionEvents} from './IExtension';
import {ObjectManagers} from '../ObjectManagers';
import {ExtensionEvent} from './ExtensionEvent';

export const ExtensionDecorator = <I extends [], O>(fn: (ee: IExtensionEvents) => IExtensionEvent<I, O>) => {
return (
target: unknown,
propertyName: string,
descriptor: PropertyDescriptor
) => {
const targetMethod = descriptor.value;
descriptor.value = async function(...args: I) {
const event = fn(ObjectManagers.getInstance().ExtensionManager.events) as ExtensionEvent<I, O>;
const eventObj = {stopPropagation: false};
const input = await event.triggerBefore({inputs: args}, eventObj);

// skip the rest of the execution if the before handler asked for stop propagation
if (eventObj.stopPropagation) {
return input as O;
}
const out = await targetMethod.apply(this, args);
return await event.triggerAfter(out);
};

return descriptor;
};
};
57 changes: 57 additions & 0 deletions src/backend/model/extension/ExtensionEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {IExtensionAfterEventHandler, IExtensionBeforeEventHandler, IExtensionEvent} from './IExtension';

export class ExtensionEvent<I, O> implements IExtensionEvent<I, O> {
protected beforeHandlers: IExtensionBeforeEventHandler<I, O>[] = [];
protected afterHandlers: IExtensionAfterEventHandler<O>[] = [];

public before(handler: IExtensionBeforeEventHandler<I, O>): void {
if (typeof handler !== 'function') {
throw new Error('ExtensionEvent::before: Handler is not a function');
}
this.beforeHandlers.push(handler);
}

public after(handler: IExtensionAfterEventHandler<O>): void {
if (typeof handler !== 'function') {
throw new Error('ExtensionEvent::after: Handler is not a function');
}
this.afterHandlers.push(handler);
}

public offAfter(handler: IExtensionAfterEventHandler<O>): void {
this.afterHandlers = this.afterHandlers.filter((h) => h !== handler);
}

public offBefore(handler: IExtensionBeforeEventHandler<I, O>): void {
this.beforeHandlers = this.beforeHandlers.filter((h) => h !== handler);
}


public async triggerBefore(input: { inputs: I }, event: { stopPropagation: boolean }): Promise<{ inputs: I } | O> {
let pipe: { inputs: I } | O = input;
if (this.beforeHandlers && this.beforeHandlers.length > 0) {
const s = this.beforeHandlers.slice(0);
for (let i = 0; i < s.length; ++i) {
if (event.stopPropagation) {
break;
}
pipe = await s[i](pipe as { inputs: I }, event);
}
}
return pipe;
}

public async triggerAfter(output: O): Promise<O> {
if (this.afterHandlers && this.afterHandlers.length > 0) {
const s = this.afterHandlers.slice(0);
for (let i = 0; i < s.length; ++i) {
output = await s[i](output);
}
}
return output;
}

}



89 changes: 89 additions & 0 deletions src/backend/model/extension/ExtensionManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {ProjectPath} from '../../ProjectPath';
import {Config} from '../../../common/config/private/Config';
import * as fs from 'fs';
import * as path from 'path';
import {IObjectManager} from '../database/IObjectManager';
import {Logger} from '../../Logger';
import {IExtensionEvents, IExtensionObject, IServerExtension} from './IExtension';
import {ObjectManagers} from '../ObjectManagers';
import {Server} from '../../server';
import {ExtensionEvent} from './ExtensionEvent';

const LOG_TAG = '[ExtensionManager]';

export class ExtensionManager implements IObjectManager {

events: IExtensionEvents = {
gallery: {
MetadataLoader: {
loadPhotoMetadata: new ExtensionEvent()
}
}
};

public async init() {
this.loadExtensionsList();
await this.initExtensions();
}

public loadExtensionsList() {
if (!fs.existsSync(ProjectPath.ExtensionFolder)) {
return;
}
Config.Extensions.list = fs
.readdirSync(ProjectPath.ExtensionFolder)
.filter((f): boolean =>
fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory()
);
Config.Extensions.list.sort();
}

private async callServerFN(fn: (ext: IServerExtension, extName: string) => Promise<void>) {
for (let i = 0; i < Config.Extensions.list.length; ++i) {
const extName = Config.Extensions.list[i];
const extPath = path.join(ProjectPath.ExtensionFolder, extName);
const serverExt = path.join(extPath, 'server.js');
if (!fs.existsSync(serverExt)) {
continue;
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ext = require(serverExt);
await fn(ext, extName);
}
}

private createExtensionObject(): IExtensionObject {
return {
app: {
objectManagers: ObjectManagers.getInstance(),
config: Config,
paths: ProjectPath,
expressApp: Server.getInstance().app
},
events: null
};
}

private async initExtensions() {
await this.callServerFN(async (ext, extName) => {
if (typeof ext?.init === 'function') {
Logger.debug(LOG_TAG, 'Running Init on extension:' + extName);
await ext?.init(this.createExtensionObject());
}
});
}

private async cleanUpExtensions() {
await this.callServerFN(async (ext, extName) => {
if (typeof ext?.cleanUp === 'function') {
Logger.debug(LOG_TAG, 'Running Init on extension:' + extName);
await ext?.cleanUp();
}
});
}


public async cleanUp() {
await this.cleanUpExtensions();
}
}
46 changes: 46 additions & 0 deletions src/backend/model/extension/IExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as express from 'express';
import {PrivateConfigClass} from '../../../common/config/private/Config';
import {ObjectManagers} from '../ObjectManagers';
import {ProjectPathClass} from '../../ProjectPath';
import {ExtensionEvent} from './ExtensionEvent';


export type IExtensionBeforeEventHandler<I, O> = (input: { inputs: I }, event: { stopPropagation: boolean }) => Promise<{ inputs: I } | O>;
export type IExtensionAfterEventHandler<O> = (output: O) => Promise<O>;


export interface IExtensionEvent<I, O> {
before: (handler: IExtensionBeforeEventHandler<I, O>) => void;
after: (handler: IExtensionAfterEventHandler<O>) => void
}

export interface IExtensionEvents {
gallery: {
// indexing: IExtensionEvent<any, any>;
// scanningDirectory: IExtensionEvent<any, any>;
MetadataLoader: {
loadPhotoMetadata: IExtensionEvent<any, any>
}

//listingDirectory: IExtensionEvent<any, any>;
//searching: IExtensionEvent<any, any>;
};
}

export interface IExtensionApp {
expressApp: express.Express;
config: PrivateConfigClass;
objectManagers: ObjectManagers;
paths: ProjectPathClass;
}

export interface IExtensionObject {
app: IExtensionApp;
events: IExtensionEvents;
}

export interface IServerExtension {
init(app: IExtensionObject): Promise<void>;

cleanUp?: () => Promise<void>;
}
Loading

0 comments on commit 538593e

Please sign in to comment.