"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);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var StorageRepository_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageRepository = void 0;
const common_1 = require("@nestjs/common");
const archiver_1 = __importDefault(require("archiver"));
const chokidar_1 = __importDefault(require("chokidar"));
const fast_glob_1 = require("fast-glob");
const node_fs_1 = require("node:fs");
const promises_1 = __importDefault(require("node:fs/promises"));
const node_path_1 = __importDefault(require("node:path"));
const node_zlib_1 = require("node:zlib");
const logging_repository_1 = require("./logging.repository");
const mime_types_1 = require("../utils/mime-types");
let StorageRepository = StorageRepository_1 = class StorageRepository {
    logger;
    constructor(logger) {
        this.logger = logger;
        this.logger.setContext(StorageRepository_1.name);
    }
    realpath(filepath) {
        return promises_1.default.realpath(filepath);
    }
    readdir(folder) {
        return promises_1.default.readdir(folder);
    }
    readdirWithTypes(folder) {
        return promises_1.default.readdir(folder, { withFileTypes: true });
    }
    copyFile(source, target) {
        return promises_1.default.copyFile(source, target);
    }
    stat(filepath) {
        return promises_1.default.stat(filepath);
    }
    createFile(filepath, buffer) {
        return promises_1.default.writeFile(filepath, buffer, { flag: 'wx' });
    }
    createWriteStream(filepath) {
        return (0, node_fs_1.createWriteStream)(filepath, { flags: 'w', flush: true });
    }
    createOrOverwriteFile(filepath, buffer) {
        return promises_1.default.writeFile(filepath, buffer, { flag: 'w' });
    }
    overwriteFile(filepath, buffer) {
        return promises_1.default.writeFile(filepath, buffer, { flag: 'r+' });
    }
    rename(source, target) {
        return promises_1.default.rename(source, target);
    }
    utimes(filepath, atime, mtime) {
        return promises_1.default.utimes(filepath, atime, mtime);
    }
    createZipStream() {
        const archive = (0, archiver_1.default)('zip', { store: true });
        const addFile = (input, filename) => {
            archive.file(input, { name: filename, mode: 0o644 });
        };
        const finalize = () => archive.finalize();
        return { stream: archive, addFile, finalize };
    }
    createGzip() {
        return (0, node_zlib_1.createGzip)();
    }
    createGunzip() {
        return (0, node_zlib_1.createGunzip)();
    }
    createPlainReadStream(filepath) {
        return (0, node_fs_1.createReadStream)(filepath);
    }
    async createReadStream(filepath, mimeType) {
        const { size } = await promises_1.default.stat(filepath);
        await promises_1.default.access(filepath, node_fs_1.constants.R_OK);
        return {
            stream: (0, node_fs_1.createReadStream)(filepath),
            length: size,
            type: mimeType || undefined,
        };
    }
    async readFile(filepath, options) {
        if (options) {
            const file = await promises_1.default.open(filepath);
            try {
                const { buffer } = await file.read(options);
                return buffer;
            }
            finally {
                await file.close();
            }
        }
        return promises_1.default.readFile(filepath);
    }
    async readJsonFile(filepath) {
        const file = await promises_1.default.readFile(filepath, 'utf8');
        return JSON.parse(file);
    }
    async checkFileExists(filepath, mode = node_fs_1.constants.F_OK) {
        try {
            await promises_1.default.access(filepath, mode);
            return true;
        }
        catch {
            return false;
        }
    }
    async unlink(file) {
        try {
            await promises_1.default.unlink(file);
        }
        catch (error) {
            if (error?.code === 'ENOENT') {
                this.logger.warn(`File ${file} does not exist.`);
            }
            else {
                throw error;
            }
        }
    }
    async unlinkDir(folder, options) {
        await promises_1.default.rm(folder, { ...options, maxRetries: 5, retryDelay: 100 });
    }
    async removeEmptyDirs(directory, self = false) {
        const stats = await promises_1.default.lstat(directory);
        if (!stats.isDirectory()) {
            return;
        }
        const files = await promises_1.default.readdir(directory);
        await Promise.all(files.map((file) => this.removeEmptyDirs(node_path_1.default.join(directory, file), true)));
        if (self) {
            const updated = await promises_1.default.readdir(directory);
            if (updated.length === 0) {
                try {
                    await promises_1.default.rmdir(directory);
                }
                catch (error) {
                    if (error.code !== 'ENOTEMPTY') {
                        this.logger.warn(`Attempted to remove directory, but failed: ${error}`);
                    }
                }
            }
        }
    }
    mkdirSync(filepath) {
        if (!(0, node_fs_1.existsSync)(filepath)) {
            (0, node_fs_1.mkdirSync)(filepath, { recursive: true });
        }
    }
    existsSync(filepath) {
        return (0, node_fs_1.existsSync)(filepath);
    }
    async checkDiskUsage(folder) {
        const stats = await promises_1.default.statfs(folder);
        return {
            available: stats.bavail * stats.bsize,
            free: stats.bfree * stats.bsize,
            total: stats.blocks * stats.bsize,
        };
    }
    crawl(crawlOptions) {
        const { pathsToCrawl, exclusionPatterns, includeHidden } = crawlOptions;
        if (pathsToCrawl.length === 0) {
            return Promise.resolve([]);
        }
        const globbedPaths = pathsToCrawl.map((path) => this.asGlob(path));
        return (0, fast_glob_1.glob)(globbedPaths, {
            absolute: true,
            caseSensitiveMatch: false,
            onlyFiles: true,
            dot: includeHidden,
            ignore: exclusionPatterns,
        });
    }
    async *walk(walkOptions) {
        const { pathsToCrawl, exclusionPatterns, includeHidden } = walkOptions;
        if (pathsToCrawl.length === 0) {
            async function* emptyGenerator() { }
            return emptyGenerator();
        }
        const globbedPaths = pathsToCrawl.map((path) => this.asGlob(path));
        const stream = (0, fast_glob_1.globStream)(globbedPaths, {
            absolute: true,
            caseSensitiveMatch: false,
            onlyFiles: true,
            dot: includeHidden,
            ignore: exclusionPatterns,
        });
        let batch = [];
        for await (const value of stream) {
            batch.push(value.toString());
            if (batch.length === walkOptions.take) {
                yield batch;
                batch = [];
            }
        }
        if (batch.length > 0) {
            yield batch;
        }
    }
    watch(paths, options, events) {
        const watcher = chokidar_1.default.watch(paths, options);
        watcher.on('ready', () => events.onReady?.());
        watcher.on('add', (path) => events.onAdd?.(path));
        watcher.on('change', (path) => events.onChange?.(path));
        watcher.on('unlink', (path) => events.onUnlink?.(path));
        watcher.on('error', (error) => events.onError?.(error));
        return () => watcher.close();
    }
    watchDir = node_fs_1.watch;
    asGlob(pathToCrawl) {
        const escapedPath = (0, fast_glob_1.escapePath)(pathToCrawl).replaceAll('"', '["]').replaceAll("'", "[']").replaceAll('`', '[`]');
        const extensions = `*{${mime_types_1.mimeTypes.getSupportedFileExtensions().join(',')}}`;
        return `${escapedPath}/**/${extensions}`;
    }
};
exports.StorageRepository = StorageRepository;
exports.StorageRepository = StorageRepository = StorageRepository_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [logging_repository_1.LoggingRepository])
], StorageRepository);
//# sourceMappingURL=storage.repository.js.map