"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.StorageService = void 0;
const common_1 = require("@nestjs/common");
const node_path_1 = require("node:path");
const constants_1 = require("../constants");
const storage_core_1 = require("../cores/storage.core");
const decorators_1 = require("../decorators");
const enum_1 = require("../enum");
const base_service_1 = require("./base.service");
const misc_1 = require("../utils/misc");
const docsMessage = `Please see https://docs.immich.app/administration/system-integrity#folder-checks for more information.`;
let StorageService = class StorageService extends base_service_1.BaseService {
    detectMediaLocation() {
        const envData = this.configRepository.getEnv();
        if (envData.storage.mediaLocation) {
            return envData.storage.mediaLocation;
        }
        const targets = [];
        const candidates = ['/data', '/usr/src/app/upload'];
        for (const candidate of candidates) {
            const exists = this.storageRepository.existsSync(candidate);
            if (exists) {
                targets.push(candidate);
            }
        }
        if (targets.length === 1) {
            return targets[0];
        }
        return '/usr/src/app/upload';
    }
    async onBootstrap() {
        storage_core_1.StorageCore.setMediaLocation(this.detectMediaLocation());
        await this.databaseRepository.withLock(enum_1.DatabaseLock.SystemFileMounts, async () => {
            const flags = (await this.systemMetadataRepository.get(enum_1.SystemMetadataKey.SystemFlags)) ||
                { mountChecks: {} };
            if (!flags.mountChecks) {
                flags.mountChecks = {};
            }
            let updated = false;
            this.logger.log(`Verifying system mount folder checks, current state: ${JSON.stringify(flags)}`);
            try {
                for (const folder of Object.values(enum_1.StorageFolder)) {
                    if (!flags.mountChecks[folder]) {
                        this.logger.log(`Writing initial mount file for the ${folder} folder`);
                        await this.createMountFile(folder);
                    }
                    await this.verifyReadAccess(folder);
                    await this.verifyWriteAccess(folder);
                    if (!flags.mountChecks[folder]) {
                        flags.mountChecks[folder] = true;
                        updated = true;
                    }
                }
                if (updated) {
                    await this.systemMetadataRepository.set(enum_1.SystemMetadataKey.SystemFlags, flags);
                    this.logger.log('Successfully enabled system mount folders checks');
                }
                this.logger.log('Successfully verified system mount folder checks');
            }
            catch (error) {
                const envData = this.configRepository.getEnv();
                if (envData.storage.ignoreMountCheckErrors) {
                    this.logger.error(error);
                    this.logger.warn('Ignoring mount folder errors');
                }
                else {
                    throw error;
                }
            }
        });
        await this.databaseRepository.withLock(enum_1.DatabaseLock.MediaLocation, async () => {
            const current = storage_core_1.StorageCore.getMediaLocation();
            const samples = await this.assetRepository.getFileSamples();
            const savedValue = await this.systemMetadataRepository.get(enum_1.SystemMetadataKey.MediaLocation);
            if (samples.length > 0) {
                const path = samples[0].path;
                let previous = savedValue?.location || '';
                if (!previous && this.configRepository.getEnv().storage.mediaLocation) {
                    previous = current;
                }
                if (!previous) {
                    previous = path.startsWith('upload/') ? 'upload' : '/usr/src/app/upload';
                }
                if (previous !== current) {
                    this.logger.log(`Media location changed (from=${previous}, to=${current})`);
                    if (!path.startsWith(previous)) {
                        throw new Error(constants_1.ErrorMessages.InconsistentMediaLocation);
                    }
                    this.logger.warn(`Detected a change to media location, performing an automatic migration of file paths from ${previous} to ${current}, this may take awhile`);
                    await this.databaseRepository.migrateFilePaths(previous, current);
                }
            }
            if (savedValue?.location !== current) {
                await this.systemMetadataRepository.set(enum_1.SystemMetadataKey.MediaLocation, { location: current });
            }
        });
    }
    async handleDeleteFiles(job) {
        const { files } = job;
        for (const file of files) {
            if (!file) {
                continue;
            }
            try {
                await this.storageRepository.unlink(file);
            }
            catch (error) {
                this.logger.warn('Unable to remove file from disk', error);
            }
        }
        return enum_1.JobStatus.Success;
    }
    async verifyReadAccess(folder) {
        const { internalPath, externalPath } = this.getMountFilePaths(folder);
        try {
            await this.storageRepository.readFile(internalPath);
        }
        catch (error) {
            this.logger.error(`Failed to read (${internalPath}): ${error}`);
            throw new misc_1.ImmichStartupError(`Failed to read: "${externalPath} (${internalPath}) - ${docsMessage}"`);
        }
    }
    async createMountFile(folder) {
        const { folderPath, internalPath, externalPath } = this.getMountFilePaths(folder);
        try {
            this.storageRepository.mkdirSync(folderPath);
            await this.storageRepository.createFile(internalPath, Buffer.from(`${Date.now()}`));
        }
        catch (error) {
            if (error.code === 'EEXIST') {
                this.logger.warn('Found existing mount file, skipping creation');
                return;
            }
            this.logger.error(`Failed to create ${internalPath}: ${error}`);
            throw new misc_1.ImmichStartupError(`Failed to create "${externalPath} - ${docsMessage}"`);
        }
    }
    async verifyWriteAccess(folder) {
        const { internalPath, externalPath } = this.getMountFilePaths(folder);
        try {
            await this.storageRepository.overwriteFile(internalPath, Buffer.from(`${Date.now()}`));
        }
        catch (error) {
            this.logger.error(`Failed to write ${internalPath}: ${error}`);
            throw new misc_1.ImmichStartupError(`Failed to write "${externalPath} - ${docsMessage}"`);
        }
    }
    getMountFilePaths(folder) {
        const folderPath = storage_core_1.StorageCore.getBaseFolder(folder);
        const internalPath = (0, node_path_1.join)(folderPath, '.immich');
        const externalPath = `<UPLOAD_LOCATION>/${folder}/.immich`;
        return { folderPath, internalPath, externalPath };
    }
};
exports.StorageService = StorageService;
__decorate([
    (0, decorators_1.OnEvent)({ name: 'AppBootstrap', priority: enum_1.BootstrapEventPriority.StorageService }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], StorageService.prototype, "onBootstrap", null);
__decorate([
    (0, decorators_1.OnJob)({ name: enum_1.JobName.FileDelete, queue: enum_1.QueueName.BackgroundTask }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], StorageService.prototype, "handleDeleteFiles", null);
exports.StorageService = StorageService = __decorate([
    (0, common_1.Injectable)()
], StorageService);
//# sourceMappingURL=storage.service.js.map