"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueueService = void 0;
const common_1 = require("@nestjs/common");
const decorators_1 = require("../decorators");
const queue_legacy_dto_1 = require("../dtos/queue-legacy.dto");
const enum_1 = require("../enum");
const base_service_1 = require("./base.service");
const misc_1 = require("../utils/misc");
const asNightlyTasksCron = (config) => {
    const [hours, minutes] = config.nightlyTasks.startTime.split(':').map(Number);
    return `${minutes} ${hours} * * *`;
};
let QueueService = class QueueService extends base_service_1.BaseService {
    services = [];
    nightlyJobsLock = false;
    async onConfigInit({ newConfig: config }) {
        if (this.worker === enum_1.ImmichWorker.Microservices) {
            this.updateConcurrency(config);
            return;
        }
        this.nightlyJobsLock = await this.databaseRepository.tryLock(enum_1.DatabaseLock.NightlyJobs);
        if (this.nightlyJobsLock) {
            const cronExpression = asNightlyTasksCron(config);
            this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`);
            this.cronRepository.create({
                name: enum_1.CronJob.NightlyJobs,
                expression: cronExpression,
                start: true,
                onTick: () => (0, misc_1.handlePromiseError)(this.handleNightlyJobs(), this.logger),
            });
        }
    }
    onConfigUpdate({ newConfig: config }) {
        if (this.worker === enum_1.ImmichWorker.Microservices) {
            this.updateConcurrency(config);
            return;
        }
        if (this.nightlyJobsLock) {
            const cronExpression = asNightlyTasksCron(config);
            this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`);
            this.cronRepository.update({ name: enum_1.CronJob.NightlyJobs, expression: cronExpression, start: true });
        }
    }
    onBootstrap() {
        this.jobRepository.setup(this.services);
        if (this.worker === enum_1.ImmichWorker.Microservices) {
            this.jobRepository.startWorkers();
        }
        else if (this.worker === enum_1.ImmichWorker.Api) {
            this.jobRepository.watchWorkers();
        }
    }
    onShutdown() {
        this.jobRepository.teardown();
    }
    updateConcurrency(config) {
        this.logger.debug(`Updating queue concurrency settings`);
        for (const queueName of Object.values(enum_1.QueueName)) {
            let concurrency = 1;
            if (this.isConcurrentQueue(queueName)) {
                concurrency = config.job[queueName].concurrency;
            }
            this.logger.debug(`Setting ${queueName} concurrency to ${concurrency}`);
            this.jobRepository.setConcurrency(queueName, concurrency);
        }
    }
    setServices(services) {
        this.services = services;
    }
    async runCommandLegacy(name, dto) {
        this.logger.debug(`Handling command: queue=${name},command=${dto.command},force=${dto.force}`);
        switch (dto.command) {
            case enum_1.QueueCommand.Start: {
                await this.start(name, dto);
                break;
            }
            case enum_1.QueueCommand.Pause: {
                await this.jobRepository.pause(name);
                break;
            }
            case enum_1.QueueCommand.Resume: {
                await this.jobRepository.resume(name);
                break;
            }
            case enum_1.QueueCommand.Empty: {
                await this.jobRepository.empty(name);
                break;
            }
            case enum_1.QueueCommand.ClearFailed: {
                const failedJobs = await this.jobRepository.clear(name, enum_1.QueueCleanType.Failed);
                this.logger.debug(`Cleared failed jobs: ${failedJobs}`);
                break;
            }
        }
        const response = await this.getByName(name);
        return (0, queue_legacy_dto_1.mapQueueLegacy)(response);
    }
    async getAll(_auth) {
        return Promise.all(Object.values(enum_1.QueueName).map((name) => this.getByName(name)));
    }
    async getAllLegacy(auth) {
        const responses = await this.getAll(auth);
        return (0, queue_legacy_dto_1.mapQueuesLegacy)(responses);
    }
    get(auth, name) {
        return this.getByName(name);
    }
    async update(auth, name, dto) {
        if (dto.isPaused === true) {
            if (name === enum_1.QueueName.BackgroundTask) {
                throw new common_1.BadRequestException(`The BackgroundTask queue cannot be paused`);
            }
            await this.jobRepository.pause(name);
        }
        if (dto.isPaused === false) {
            await this.jobRepository.resume(name);
        }
        return this.getByName(name);
    }
    searchJobs(auth, name, dto) {
        return this.jobRepository.searchJobs(name, dto);
    }
    async emptyQueue(auth, name, dto) {
        await this.jobRepository.empty(name);
        if (dto.failed) {
            await this.jobRepository.clear(name, enum_1.QueueCleanType.Failed);
        }
    }
    async getByName(name) {
        const [statistics, isPaused] = await Promise.all([
            this.jobRepository.getJobCounts(name),
            this.jobRepository.isPaused(name),
        ]);
        return { name, isPaused, statistics };
    }
    async start(name, { force }) {
        const isActive = await this.jobRepository.isActive(name);
        if (isActive) {
            throw new common_1.BadRequestException(`Job is already running`);
        }
        await this.eventRepository.emit('QueueStart', { name });
        switch (name) {
            case enum_1.QueueName.VideoConversion: {
                return this.jobRepository.queue({ name: enum_1.JobName.AssetEncodeVideoQueueAll, data: { force } });
            }
            case enum_1.QueueName.StorageTemplateMigration: {
                return this.jobRepository.queue({ name: enum_1.JobName.StorageTemplateMigration });
            }
            case enum_1.QueueName.Migration: {
                return this.jobRepository.queue({ name: enum_1.JobName.FileMigrationQueueAll });
            }
            case enum_1.QueueName.SmartSearch: {
                return this.jobRepository.queue({ name: enum_1.JobName.SmartSearchQueueAll, data: { force } });
            }
            case enum_1.QueueName.DuplicateDetection: {
                return this.jobRepository.queue({ name: enum_1.JobName.AssetDetectDuplicatesQueueAll, data: { force } });
            }
            case enum_1.QueueName.MetadataExtraction: {
                return this.jobRepository.queue({ name: enum_1.JobName.AssetExtractMetadataQueueAll, data: { force } });
            }
            case enum_1.QueueName.Sidecar: {
                return this.jobRepository.queue({ name: enum_1.JobName.SidecarQueueAll, data: { force } });
            }
            case enum_1.QueueName.ThumbnailGeneration: {
                return this.jobRepository.queue({ name: enum_1.JobName.AssetGenerateThumbnailsQueueAll, data: { force } });
            }
            case enum_1.QueueName.FaceDetection: {
                return this.jobRepository.queue({ name: enum_1.JobName.AssetDetectFacesQueueAll, data: { force } });
            }
            case enum_1.QueueName.FacialRecognition: {
                return this.jobRepository.queue({ name: enum_1.JobName.FacialRecognitionQueueAll, data: { force } });
            }
            case enum_1.QueueName.Library: {
                return this.jobRepository.queue({ name: enum_1.JobName.LibraryScanQueueAll, data: { force } });
            }
            case enum_1.QueueName.BackupDatabase: {
                return this.jobRepository.queue({ name: enum_1.JobName.DatabaseBackup, data: { force } });
            }
            case enum_1.QueueName.Ocr: {
                return this.jobRepository.queue({ name: enum_1.JobName.OcrQueueAll, data: { force } });
            }
            default: {
                throw new common_1.BadRequestException(`Invalid job name: ${name}`);
            }
        }
    }
    isConcurrentQueue(name) {
        return ![
            enum_1.QueueName.FacialRecognition,
            enum_1.QueueName.StorageTemplateMigration,
            enum_1.QueueName.DuplicateDetection,
            enum_1.QueueName.BackupDatabase,
        ].includes(name);
    }
    async handleNightlyJobs() {
        const config = await this.getConfig({ withCache: false });
        const jobs = [];
        if (config.nightlyTasks.databaseCleanup) {
            jobs.push({ name: enum_1.JobName.AssetDeleteCheck }, { name: enum_1.JobName.UserDeleteCheck }, { name: enum_1.JobName.PersonCleanup }, { name: enum_1.JobName.MemoryCleanup }, { name: enum_1.JobName.SessionCleanup }, { name: enum_1.JobName.HlsSessionCleanup }, { name: enum_1.JobName.AuditTableCleanup });
        }
        if (config.nightlyTasks.generateMemories) {
            jobs.push({ name: enum_1.JobName.MemoryGenerate });
        }
        if (config.nightlyTasks.syncQuotaUsage) {
            jobs.push({ name: enum_1.JobName.UserSyncUsage });
        }
        if (config.nightlyTasks.missingThumbnails) {
            jobs.push({ name: enum_1.JobName.AssetGenerateThumbnailsQueueAll, data: { force: false } });
        }
        if (config.nightlyTasks.clusterNewFaces) {
            jobs.push({ name: enum_1.JobName.FacialRecognitionQueueAll, data: { force: false, nightly: true } });
        }
        await this.jobRepository.queueAll(jobs);
    }
};
exports.QueueService = QueueService;
__decorate([
    (0, decorators_1.OnEvent)({ name: 'ConfigInit' }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], QueueService.prototype, "onConfigInit", null);
__decorate([
    (0, decorators_1.OnEvent)({ name: 'ConfigUpdate', server: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], QueueService.prototype, "onConfigUpdate", null);
__decorate([
    (0, decorators_1.OnEvent)({ name: 'AppBootstrap', priority: enum_1.BootstrapEventPriority.JobService }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], QueueService.prototype, "onBootstrap", null);
__decorate([
    (0, decorators_1.OnEvent)({ name: 'AppShutdown' }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], QueueService.prototype, "onShutdown", null);
exports.QueueService = QueueService = __decorate([
    (0, common_1.Injectable)()
], QueueService);
//# sourceMappingURL=queue.service.js.map