"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const kysely_1 = require("kysely");
const nest_commander_1 = require("nest-commander");
const node_child_process_1 = require("node:child_process");
const node_path_1 = require("node:path");
const node_worker_threads_1 = require("node:worker_threads");
const app_module_1 = require("./app.module");
const enum_1 = require("./enum");
const config_repository_1 = require("./repositories/config.repository");
const system_metadata_repository_1 = require("./repositories/system-metadata.repository");
const database_1 = require("./utils/database");
class Workers {
    workers = {};
    restarting = false;
    async bootstrap() {
        const isMaintenanceMode = await this.isMaintenanceMode();
        const { workers } = new config_repository_1.ConfigRepository().getEnv();
        if (isMaintenanceMode) {
            this.startWorker(enum_1.ImmichWorker.Maintenance);
        }
        else {
            await this.waitForFreeLock();
            for (const worker of workers) {
                this.startWorker(worker);
            }
        }
    }
    async isMaintenanceMode() {
        const { database } = new config_repository_1.ConfigRepository().getEnv();
        const { log: _, ...kyselyConfig } = (0, database_1.getKyselyConfig)(database.config);
        const kysely = new kysely_1.Kysely(kyselyConfig);
        const systemMetadataRepository = new system_metadata_repository_1.SystemMetadataRepository(kysely);
        try {
            const value = await systemMetadataRepository.get(enum_1.SystemMetadataKey.MaintenanceMode);
            return value?.isMaintenanceMode || false;
        }
        catch (error) {
            if (error.code === '42P01') {
                return false;
            }
            throw error;
        }
        finally {
            await kysely.destroy();
        }
    }
    async waitForFreeLock() {
        const { database } = new config_repository_1.ConfigRepository().getEnv();
        const kysely = new kysely_1.Kysely((0, database_1.getKyselyConfig)(database.config));
        let isLocked = false;
        while (!isLocked) {
            isLocked = await kysely.connection().execute(async (conn) => {
                const { rows } = await (0, kysely_1.sql) `SELECT pg_try_advisory_lock(${enum_1.DatabaseLock.MaintenanceOperation})`.execute(conn);
                const isLocked = rows[0].pg_try_advisory_lock;
                if (isLocked) {
                    await (0, kysely_1.sql) `SELECT pg_advisory_unlock(${enum_1.DatabaseLock.MaintenanceOperation})`.execute(conn);
                }
                return isLocked;
            });
            await new Promise((resolve) => setTimeout(resolve, 1000));
        }
        await kysely.destroy();
    }
    startWorker(name) {
        console.log(`Starting ${name} worker`);
        const basePath = (0, node_path_1.dirname)(__filename);
        const workerFile = (0, node_path_1.join)(basePath, 'workers', `${name}.js`);
        let anyWorker;
        let kill;
        if (name === enum_1.ImmichWorker.Api) {
            const worker = (0, node_child_process_1.fork)(workerFile, [], {
                execArgv: process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)),
            });
            kill = (signal) => void worker.kill(signal);
            anyWorker = worker;
        }
        else {
            const worker = new node_worker_threads_1.Worker(workerFile);
            kill = async () => void (await worker.terminate());
            anyWorker = worker;
        }
        anyWorker.on('error', (error) => this.onError(name, error));
        anyWorker.on('exit', (exitCode) => this.onExit(name, exitCode));
        this.workers[name] = { kill };
    }
    onError(name, error) {
        console.error(`${name} worker error: ${error}, stack: ${error.stack}`);
    }
    onExit(name, exitCode) {
        if (exitCode === enum_1.ExitCode.AppRestart || this.restarting) {
            this.restarting = true;
            console.info(`${name} worker shutdown for restart`);
            delete this.workers[name];
            if (Object.keys(this.workers).length === 0) {
                void this.bootstrap();
                this.restarting = false;
            }
            return;
        }
        delete this.workers[name];
        if (exitCode !== 0) {
            console.error(`${name} worker exited with code ${exitCode}`);
            if (Object.hasOwn(this.workers, enum_1.ImmichWorker.Api) && name !== enum_1.ImmichWorker.Api) {
                console.error('Killing api process');
                void this.workers[enum_1.ImmichWorker.Api].kill('SIGTERM');
            }
        }
        process.exit(exitCode);
    }
}
function main() {
    const immichApp = process.argv[2];
    if (immichApp) {
        process.argv.splice(2, 1);
    }
    if (immichApp === 'immich-admin') {
        process.title = 'immich_admin_cli';
        process.env.IMMICH_LOG_LEVEL = enum_1.LogLevel.Warn;
        return nest_commander_1.CommandFactory.run(app_module_1.ImmichAdminModule);
    }
    if (immichApp === 'immich' || immichApp === 'microservices') {
        console.error(`Using "start.sh ${immichApp}" has been deprecated. See https://github.com/immich-app/immich/releases/tag/v1.118.0 for more information.`);
        process.exit(1);
    }
    if (immichApp) {
        console.error(`Unknown command: "${immichApp}"`);
        process.exit(1);
    }
    process.title = 'immich';
    void new Workers().bootstrap();
}
void main();
//# sourceMappingURL=main.js.map