"use strict";
/*
 * @adonisjs/drive
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FakeDriver = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const etag_1 = __importDefault(require("etag"));
const memfs_1 = require("memfs");
const path_1 = require("path");
const utils_1 = require("../utils");
const LocalFileServer_1 = require("../LocalFileServer");
const Exceptions_1 = require("../Exceptions");
const DirectoryListing_1 = require("../DirectoryListing");
const PathPrefixer_1 = require("../PathPrefixer");
/**
 * Memory driver is mainly used for testing
 */
class FakeDriver {
    constructor(disk, config, router) {
        this.disk = disk;
        this.config = config;
        this.router = router;
        /**
         * Reference to the underlying adapter. Which is memfs
         */
        this.adapter = new memfs_1.Volume();
        /**
         * Name of the driver
         */
        this.name = 'fake';
        /**
         * Path prefixer used for prefixing paths with disk root
         * It doesn't play any role but we will try to construct same path as faked driver is using
         * We use the root path if it is available for driver and also add prefix if provided
         */
        this.prefixer = PathPrefixer_1.PathPrefixer.fromPath(this.config.root || '/').withPrefix(this.config.prefix || '');
        /**
         * Rely on the config for visibility or fallback to private
         */
        this.visibility = this.config.visibility || 'private';
    }
    /**
     * Make absolute path to a given location
     */
    makePath(location) {
        return this.prefixer.prefixPath(location);
    }
    /**
     * Creates the directory recursively with in the memory
     */
    ensureDir(location) {
        return new Promise((resolve, reject) => {
            this.adapter.mkdirp((0, path_1.dirname)(location), (error) => {
                if (error) {
                    reject(error);
                }
                else {
                    resolve();
                }
            });
        });
    }
    /**
     * Returns the file contents as a buffer. The buffer return
     * value allows you to self choose the encoding when
     * converting the buffer to a string.
     */
    async get(location) {
        return new Promise((resolve, reject) => {
            this.adapter.readFile(this.makePath(location), (error, data) => {
                if (error) {
                    reject(Exceptions_1.CannotReadFileException.invoke(location, error));
                }
                else {
                    resolve(data);
                }
            });
        });
    }
    /**
     * Returns the file contents as a stream
     */
    async getStream(location) {
        return this.adapter.createReadStream(this.makePath(location));
    }
    /**
     * A boolean to find if the location path exists or not
     */
    exists(location) {
        return new Promise((resolve) => {
            this.adapter.exists(this.makePath(location), (exists) => {
                resolve(exists);
            });
        });
    }
    /**
     * Not supported
     */
    async getVisibility() {
        return this.visibility;
    }
    /**
     * Returns the file stats
     */
    async getStats(location) {
        return new Promise((resolve, reject) => {
            this.adapter.stat(this.makePath(location), (error, stats) => {
                if (error) {
                    reject(Exceptions_1.CannotGetMetaDataException.invoke(location, 'stats', error));
                }
                else {
                    resolve({
                        modified: stats.mtime,
                        size: stats.size,
                        isFile: stats.isFile(),
                        etag: (0, etag_1.default)(stats),
                    });
                }
            });
        });
    }
    /**
     * Returns a signed URL for a given location path
     */
    async getSignedUrl(location, options) {
        const { expiresIn, ...qs } = options || {};
        return this.router.makeSignedUrl('/__drive_fake', {
            disk: this.disk,
            [LocalFileServer_1.LocalFileServer.filePathParamName]: [this.prefixer.normalizePath(location)],
        }, {
            expiresIn,
            qs,
            disableRouteLookup: true,
        });
    }
    /**
     * Returns a URL for a given location path
     */
    async getUrl(location) {
        return this.router.makeUrl('/__drive_fake', {
            disk: this.disk,
            [LocalFileServer_1.LocalFileServer.filePathParamName]: [this.prefixer.normalizePath(location)],
        }, {
            disableRouteLookup: true,
        });
    }
    /**
     * Write string|buffer contents to a destination. The missing
     * intermediate directories will be created (if required).
     */
    async put(location, contents) {
        const absolutePath = this.makePath(location);
        await this.ensureDir(absolutePath);
        return new Promise((resolve, reject) => {
            this.adapter.writeFile(absolutePath, contents, (error) => {
                if (error) {
                    reject(Exceptions_1.CannotWriteFileException.invoke(location, error));
                }
                else {
                    resolve();
                }
            });
        });
    }
    /**
     * Write a stream to a destination. The missing intermediate
     * directories will be created (if required).
     */
    async putStream(location, contents) {
        const absolutePath = this.makePath(location);
        try {
            await this.ensureDir(absolutePath);
            const writeStream = this.adapter.createWriteStream(absolutePath);
            /**
             * If streaming is interrupted, then the destination file will be
             * created with partial or empty contents.
             *
             * Earlier we are cleaning up the empty file, which addresses one
             * use case (no pre-existing file was there).
             *
             * However, in case there was already a file, it will be then emptied
             * out. So basically there is no way to get the original contents
             * back unless we read the existing content in buffer, but then
             * we don't know how large the file is.
             */
            await (0, utils_1.pipelinePromise)(contents, writeStream);
        }
        catch (error) {
            throw Exceptions_1.CannotWriteFileException.invoke(location, error);
        }
    }
    /**
     * Not supported
     */
    async setVisibility() {
        return;
    }
    /**
     * Remove a given location path
     */
    async delete(location) {
        if (!(await this.exists(location))) {
            return;
        }
        return new Promise((resolve, reject) => {
            this.adapter.unlink(this.makePath(location), (error) => {
                if (error) {
                    reject(Exceptions_1.CannotDeleteFileException.invoke(location, error));
                }
                else {
                    resolve();
                }
            });
        });
    }
    /**
     * Copy a given location path from the source to the desination.
     * The missing intermediate directories will be created (if required)
     */
    async copy(source, destination) {
        const desintationAbsolutePath = this.makePath(destination);
        await this.ensureDir(desintationAbsolutePath);
        return new Promise((resolve, reject) => {
            this.adapter.copyFile(this.makePath(source), desintationAbsolutePath, (error) => {
                if (error) {
                    reject(Exceptions_1.CannotCopyFileException.invoke(source, destination, error));
                }
                else {
                    resolve();
                }
            });
        });
    }
    /**
     * Move a given location path from the source to the desination.
     * The missing intermediate directories will be created (if required)
     */
    async move(source, destination) {
        const sourceAbsolutePath = this.makePath(source);
        const desintationAbsolutePath = this.makePath(destination);
        await this.ensureDir(desintationAbsolutePath);
        return new Promise((resolve, reject) => {
            this.adapter.copyFile(sourceAbsolutePath, desintationAbsolutePath, (error) => {
                if (error) {
                    reject(Exceptions_1.CannotMoveFileException.invoke(source, destination, error));
                }
                else {
                    resolve();
                }
            });
        }).then(() => this.delete(source));
    }
    /**
     * Return a listing directory iterator for given location.
     */
    list(location) {
        const fullPath = this.makePath(location);
        return new DirectoryListing_1.DirectoryListing(this, async function* () {
            try {
                const dir = (await this.adapter.promises.readdir(fullPath, {
                    withFileTypes: true,
                }));
                const prefixer = this.prefixer.withStrippedPrefix(fullPath);
                for (const dirent of dir) {
                    yield {
                        location: prefixer.prefixPath(dirent.name),
                        isFile: dirent.isFile(),
                        original: dirent,
                    };
                }
            }
            catch (error) {
                throw Exceptions_1.CannotListDirectoryException.invoke(location, error);
            }
        });
    }
}
exports.FakeDriver = FakeDriver;
