Clean up service manager - move all services to manager
This commit is contained in:
+29
-11
@@ -1,18 +1,18 @@
|
|||||||
import ServiceInterface from "../interfaces/ServiceInterface";
|
|
||||||
|
|
||||||
type Constructor = new (...args:any[]) => any;
|
type Constructor = new (...args:any[]) => any;
|
||||||
|
|
||||||
const serviceNameSymbol = Symbol("symbol.serviceName");
|
const serviceNameSymbol = Symbol("symbol.serviceName");
|
||||||
|
|
||||||
export abstract class BaseService implements ServiceInterface {
|
export abstract class BaseService {
|
||||||
private startPromise: Promise<void> | null = null;
|
private startPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
if (!this.startPromise) {
|
if (!this.startPromise) {
|
||||||
await ServiceManager.get().startDependencies(
|
// @ts-ignore Retrieve service name from subclass metadata. (set by @service decorator)
|
||||||
// @ts-ignore
|
const serviceName = this.constructor[Symbol.metadata]?.[serviceNameSymbol];
|
||||||
this.constructor[Symbol.metadata][serviceNameSymbol]
|
|
||||||
);
|
// If the service is properly labeled, use it's serviceName to ensure all dependent services have been started first.
|
||||||
|
if (serviceName) await ServiceManager.get().startDependencies(serviceName);
|
||||||
|
|
||||||
|
// Start the service by running the (subclass provided) init() function.
|
||||||
this.startPromise = new Promise<void>(async (resolve) => {
|
this.startPromise = new Promise<void>(async (resolve) => {
|
||||||
resolve(await this.init());
|
resolve(await this.init());
|
||||||
})
|
})
|
||||||
@@ -20,16 +20,24 @@ export abstract class BaseService implements ServiceInterface {
|
|||||||
return this.startPromise;
|
return this.startPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract init(): Promise<void>;
|
/**
|
||||||
|
* Abstract method to provide functionality to "start" the service
|
||||||
|
*/
|
||||||
|
protected abstract init(): Promise<void>;
|
||||||
|
|
||||||
async stop(): Promise<void> {
|
async stop(): Promise<void> {
|
||||||
|
// Make sure we are not in the middle of starting the service
|
||||||
await this.startPromise;
|
await this.startPromise;
|
||||||
|
|
||||||
|
// Call (subclass provided) destroy method
|
||||||
await this.destroy();
|
await this.destroy();
|
||||||
this.startPromise = null;
|
this.startPromise = null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract destroy(): Promise<void>;
|
/**
|
||||||
|
* Abstract method to provide functionality to "stop" the service
|
||||||
|
*/
|
||||||
|
protected abstract destroy(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceContainer = {
|
type ServiceContainer = {
|
||||||
@@ -80,6 +88,9 @@ export class ServiceManager {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start all decorated (@service) services that derive from the BaseService class
|
||||||
|
*/
|
||||||
async start() {
|
async start() {
|
||||||
// Start all constructed services
|
// Start all constructed services
|
||||||
for (const service of this.services.values()) {
|
for (const service of this.services.values()) {
|
||||||
@@ -140,6 +151,13 @@ export class ServiceManager {
|
|||||||
|
|
||||||
return service.service as T;
|
return service.service as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replaceService(name: string, service: any) {
|
||||||
|
if (!this.services.has(name)) {
|
||||||
|
const serviceContainer = this.services.get(name) as ServiceContainer;
|
||||||
|
serviceContainer.service = service;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function service(propertyName: string) {
|
export function service(propertyName: string) {
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export default interface ServiceInterface {
|
|
||||||
start(): Promise<void>;
|
|
||||||
init(): Promise<void>;
|
|
||||||
stop(): Promise<void>;
|
|
||||||
destroy(): Promise<void>;
|
|
||||||
}
|
|
||||||
+19
-15
@@ -1,22 +1,26 @@
|
|||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import { once } from 'node:events';
|
import { once } from 'node:events';
|
||||||
|
import {service} from "../core/service";
|
||||||
|
|
||||||
export async function runCommand(command: string, args: string[]) {
|
@service('cliService')
|
||||||
const cmd = spawn(command, args);
|
export class CliService {
|
||||||
const stdout = [] as string[];
|
async runCommand(command: string, args: string[]) {
|
||||||
const stderr = [] as string[];
|
const cmd = spawn(command, args);
|
||||||
|
const stdout = [] as string[];
|
||||||
|
const stderr = [] as string[];
|
||||||
|
|
||||||
cmd.stdout?.on('data', (data: Buffer) => {
|
cmd.stdout?.on('data', (data: Buffer) => {
|
||||||
stdout.push(data.toString('utf-8'));
|
stdout.push(data.toString('utf-8'));
|
||||||
});
|
});
|
||||||
|
|
||||||
cmd.stderr?.on('data', (data: Buffer) => {
|
cmd.stderr?.on('data', (data: Buffer) => {
|
||||||
stderr.push(data.toString('utf-8'));
|
stderr.push(data.toString('utf-8'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const [code] = await once(cmd, 'close');
|
const [code] = await once(cmd, 'close');
|
||||||
return {
|
return {
|
||||||
stdout: stdout.join(''),
|
stdout: stdout.join(''),
|
||||||
stderr: stderr.join(''),
|
stderr: stderr.join(''),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,21 @@ export default class DatabaseService extends BaseService {
|
|||||||
protected db: Database.Database;
|
protected db: Database.Database;
|
||||||
|
|
||||||
@inject('configService') protected accessor config!: ConfigService;
|
@inject('configService') protected accessor config!: ConfigService;
|
||||||
|
protected migrations: Migration[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.db = new Database('database/core.db');
|
this.db = new Database('database/core.db');
|
||||||
this.db.pragma('journal_mode = WAL');
|
this.db.pragma('journal_mode = WAL');
|
||||||
|
this.migrations = this.config.migrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
const migrations = this.config.migrations;
|
|
||||||
|
|
||||||
this.assertMigrationTable();
|
this.assertMigrationTable();
|
||||||
|
|
||||||
// Turn array of migrations into dictionary
|
// Turn array of migrations into dictionary
|
||||||
const migrationDict = {} as {[key: string]: Migration};
|
const migrationDict = {} as {[key: string]: Migration};
|
||||||
for (const migration of migrations) {
|
for (const migration of this.migrations) {
|
||||||
migrationDict[migration.name] = migration;
|
migrationDict[migration.name] = migration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import {runCommand} from "./CliService";
|
import {inject, service} from "../core/service";
|
||||||
import {service} from "../core/service";
|
import {CliService} from "./CliService";
|
||||||
|
|
||||||
@service('ffmpegService')
|
@service('ffmpegService')
|
||||||
export default class FFMpegService {
|
export default class FFMpegService {
|
||||||
static async checkFile(filename: string) {
|
@inject('cliService') protected accessor cliService!: CliService;
|
||||||
const file = await runCommand('ffprobe', [
|
|
||||||
|
async checkFile(filename: string) {
|
||||||
|
const file = await this.cliService.runCommand('ffprobe', [
|
||||||
'-v',
|
'-v',
|
||||||
'quiet',
|
'quiet',
|
||||||
'-print_format',
|
'-print_format',
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import {Dirent} from "node:fs";
|
|||||||
import {fileTypeFromFile} from "file-type";
|
import {fileTypeFromFile} from "file-type";
|
||||||
import FFMpegService from "../services/FFMpegService";
|
import FFMpegService from "../services/FFMpegService";
|
||||||
import TaskInterface from "../interfaces/TaskInterface";
|
import TaskInterface from "../interfaces/TaskInterface";
|
||||||
|
import {inject} from "../core/service";
|
||||||
|
|
||||||
export default class ScanFoldersTask implements TaskInterface {
|
export default class ScanFoldersTask implements TaskInterface {
|
||||||
private hasRun: boolean = false;
|
private hasRun: boolean = false;
|
||||||
|
@inject('ffmpegService') protected accessor ffmpegService!: FFMpegService;
|
||||||
|
|
||||||
constructor(protected mount: string) {
|
constructor(protected mount: string) {}
|
||||||
}
|
|
||||||
|
|
||||||
shouldRun(): boolean {
|
shouldRun(): boolean {
|
||||||
return !this.hasRun;
|
return !this.hasRun;
|
||||||
@@ -37,7 +38,7 @@ export default class ScanFoldersTask implements TaskInterface {
|
|||||||
console.log('file', pathname);
|
console.log('file', pathname);
|
||||||
const type = await fileTypeFromFile(pathname);
|
const type = await fileTypeFromFile(pathname);
|
||||||
if (type === undefined || type.mime.startsWith('audio')) {
|
if (type === undefined || type.mime.startsWith('audio')) {
|
||||||
console.log(await FFMpegService.checkFile(pathname));
|
console.log(await this.ffmpegService.checkFile(pathname));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user