"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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalDriver = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const etag_1 = __importDefault(require("etag"));
const fsExtra = __importStar(require("fs-extra"));
const path_1 = require("path");
const DirectoryListing_1 = require("../DirectoryListing");
const utils_1 = require("../utils");
const LocalFileServer_1 = require("../LocalFileServer");
const PathPrefixer_1 = require("../PathPrefixer");
const Exceptions_1 = require("../Exceptions");
/**
 * Local driver interacts with the local file system
 */
class LocalDriver {
    constructor(diskName, config, router) {
        this.diskName = diskName;
        this.config = config;
        this.router = router;
        this.routeName = LocalFileServer_1.LocalFileServer.makeRouteName(this.diskName);
        /**
         * Reference to the underlying adapter. Which is
         * fs-extra
         */
        this.adapter = fsExtra;
        /**
         * Name of the driver
         */
        this.name = 'local';
        /**
         * Path prefixer used for prefixing paths with disk root
         */
        this.prefixer = PathPrefixer_1.PathPrefixer.fromPath(this.config.root);
    }
    /**
     * Make absolute path to a given location
     */
    makePath(location) {
        return this.prefixer.prefixPath(location);
    }
    /**
     * 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) {
        try {
            return await this.adapter.readFile(this.makePath(location));
        }
        catch (error) {
            throw Exceptions_1.CannotReadFileException.invoke(location, error);
        }
    }
    /**
     * Returns the file contents as a stream
     */
    async getStream(location) {
        try {
            return this.adapter.createReadStream(this.makePath(location));
        }
        catch (error) {
            throw Exceptions_1.CannotReadFileException.invoke(location, error);
        }
    }
    /**
     * A boolean to find if the location path exists or not
     */
    async exists(location) {
        try {
            return await this.adapter.pathExists(this.makePath(location));
        }
        catch (error) {
            throw Exceptions_1.CannotGetMetaDataException.invoke(location, 'exists', error);
        }
    }
    /**
     * Not supported
     */
    async getVisibility(_) {
        return this.config.visibility;
    }
    /**
     * Returns the file stats
     */
    async getStats(location) {
        try {
            const stats = await this.adapter.stat(this.makePath(location));
            return {
                modified: stats.mtime,
                size: stats.size,
                isFile: stats.isFile(),
                etag: (0, etag_1.default)(stats),
            };
        }
        catch (error) {
            throw Exceptions_1.CannotGetMetaDataException.invoke(location, 'stats', error);
        }
    }
    /**
     * Returns a signed URL for a given location path
     */
    async getSignedUrl(location, options) {
        if (!this.config.serveFiles) {
            throw Exceptions_1.CannotGenerateUrlException.invoke(location, this.diskName);
        }
        const { expiresIn, ...qs } = options || {};
        return this.router.makeSignedUrl(this.routeName, { [LocalFileServer_1.LocalFileServer.filePathParamName]: [this.prefixer.normalizePath(location)] }, {
            expiresIn,
            qs,
        });
    }
    /**
     * Returns a URL for a given location path
     */
    async getUrl(location) {
        if (!this.config.serveFiles) {
            throw Exceptions_1.CannotGenerateUrlException.invoke(location, this.diskName);
        }
        return this.router.makeUrl(this.routeName, {
            [LocalFileServer_1.LocalFileServer.filePathParamName]: [this.prefixer.normalizePath(location)],
        });
    }
    /**
     * Write string|buffer contents to a destination. The missing
     * intermediate directories will be created (if required).
     */
    async put(location, contents) {
        try {
            await this.adapter.outputFile(this.makePath(location), contents);
        }
        catch (error) {
            throw Exceptions_1.CannotWriteFileException.invoke(location, error);
        }
    }
    /**
     * Write a stream to a destination. The missing intermediate
     * directories will be created (if required).
     */
    async putStream(location, contents) {
        const absolutePath = this.makePath(location);
        const dir = (0, path_1.dirname)(absolutePath);
        await this.adapter.ensureDir(dir);
        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.
         */
        try {
            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) {
        try {
            await this.adapter.remove(this.makePath(location));
        }
        catch (error) {
            throw Exceptions_1.CannotDeleteFileException.invoke(location, error);
        }
    }
    /**
     * Copy a given location path from the source to the desination.
     * The missing intermediate directories will be created (if required)
     */
    async copy(source, destination) {
        try {
            await this.adapter.copy(this.makePath(source), this.makePath(destination), {
                overwrite: true,
            });
        }
        catch (error) {
            throw Exceptions_1.CannotCopyFileException.invoke(source, destination, error);
        }
    }
    /**
     * Move a given location path from the source to the desination.
     * The missing intermediate directories will be created (if required)
     */
    async move(source, destination) {
        try {
            await this.adapter.move(this.makePath(source), this.makePath(destination), {
                overwrite: true,
            });
        }
        catch (error) {
            throw Exceptions_1.CannotMoveFileException.invoke(source, destination, error);
        }
    }
    /**
     * 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.opendir(fullPath);
                const prefixer = this.prefixer.withStrippedPrefix(fullPath);
                for await (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.LocalDriver = LocalDriver;
