"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
const IFolder_1 = require("../storage/IFolder");
const NodeFile_1 = require("./NodeFile");
const NodeStorage_1 = require("./NodeStorage");
const StorageUtilities_1 = require("../storage/StorageUtilities");
const FolderBase_1 = require("../storage/FolderBase");
const Log_1 = require("./../core/Log");
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
class NodeFolder extends FolderBase_1.default {
    _name;
    _path;
    folders;
    files;
    _storage;
    _parentFolder;
    get storage() {
        return this._storage;
    }
    get parentFolder() {
        return this._parentFolder;
    }
    get name() {
        return this._name;
    }
    get fullPath() {
        let path = this._path;
        if (!path.endsWith(NodeStorage_1.default.platformFolderDelimiter)) {
            path += NodeStorage_1.default.platformFolderDelimiter;
        }
        return path + this.name;
    }
    constructor(storage, parentFolder, parentPath, folderName) {
        super();
        this._storage = storage;
        this._parentFolder = parentFolder;
        this._path = parentPath;
        this._name = folderName;
        this.folders = {};
        this.files = {};
    }
    ensureFile(name) {
        Log_1.default.assert(name.indexOf("/") < 0, "Unexpected to find / in file name: " + name);
        const nameCanon = StorageUtilities_1.default.canonicalizeName(name);
        let candFile = this.files[nameCanon];
        if (candFile == null) {
            candFile = new NodeFile_1.default(this, name);
            this.files[nameCanon] = candFile;
        }
        return candFile;
    }
    _removeFile(file) {
        const nameCanon = StorageUtilities_1.default.canonicalizeName(file.name);
        const candFile = this.files[nameCanon];
        Log_1.default.assert(candFile === file, "Files don't match.");
        this.files[nameCanon] = undefined;
        this.storage.notifyFileRemoved(this.storageRelativePath + file.name);
    }
    _addExistingFile(file) {
        const nameCanon = StorageUtilities_1.default.canonicalizeName(file.name);
        this.files[nameCanon] = file;
    }
    async deleteThisFolder() {
        if (this.storage.readOnly) {
            throw new Error("Deletion of this folder " + this.fullPath + " is not supported in read only mode.");
        }
        let absPath = path.resolve(this.fullPath);
        if (StorageUtilities_1.default.isPathRiskyForDelete(absPath)) {
            throw new Error("Deletion of this folder " + absPath + " is not supported because it seems too basic.");
        }
        let isSuccess = true;
        if (fs.existsSync(this.fullPath)) {
            try {
                fs.rmdirSync(this.fullPath, {
                    recursive: true,
                });
            }
            catch (e) {
                isSuccess = false;
            }
        }
        this.removeMeFromParent();
        return isSuccess;
    }
    async deleteAllFolderContents() {
        if (this.storage.readOnly) {
            throw new Error("Deletion of folder contents at " + this.fullPath + " is not supported in read only mode.");
        }
        return await this.recursiveDeleteContentsOfThisFolder();
    }
    ensureFolder(name) {
        Log_1.default.assert(name.indexOf("/") < 0, "Unexpected to find / in folder name: " + name);
        const nameCanon = StorageUtilities_1.default.canonicalizeName(name);
        let candFolder = this.folders[nameCanon];
        if (!candFolder) {
            candFolder = new NodeFolder(this._storage, this, this.fullPath, name);
            this.folders[nameCanon] = candFolder;
        }
        return candFolder;
    }
    _addExistingFolderToParent(folder) {
        const nameCanon = StorageUtilities_1.default.canonicalizeName(folder.name);
        this.folders[nameCanon] = folder;
    }
    async moveTo(newStorageRelativePath) {
        const oldFullPath = this.fullPath;
        const newFolderPath = StorageUtilities_1.default.getFolderPath(newStorageRelativePath);
        const newFolderName = StorageUtilities_1.default.getLeafName(newStorageRelativePath);
        if (newFolderName.length < 2) {
            throw new Error("New path is not correct.");
        }
        if (this.isSameFolder(newStorageRelativePath)) {
            return false;
        }
        if (this._parentFolder !== null) {
            const newParentFolder = await this._parentFolder.storage.ensureFolderFromStorageRelativePath(newFolderPath);
            if (newParentFolder.folders[newFolderName] !== undefined) {
                throw new Error("Could not move folder; folder exists at specified path: " + newStorageRelativePath);
            }
            this._parentFolder._removeExistingFolderFromParent(this);
            this._parentFolder = newParentFolder;
            this._name = newFolderName;
            newParentFolder._addExistingFolderToParent(this);
        }
        this._name = newFolderName;
        const newFullPath = this.fullPath;
        Log_1.default.verbose("Renaming folder from: " + oldFullPath + " to " + newFullPath);
        fs.renameSync(oldFullPath, newFullPath);
        return true;
    }
    async exists() {
        return fs.existsSync(this.fullPath);
    }
    async ensureExists() {
        const exists = fs.existsSync(this.fullPath);
        if (!exists) {
            // Log.message("Creating folder '" + this.fullPath + "'");
            fs.mkdirSync(this.fullPath, { recursive: true });
        }
        return true;
    }
    async generateFileListings(listings) {
        await this.load(true);
        if (!listings) {
            listings = {};
        }
        if (this.files["files.json"] !== undefined) {
            const file = this.files["files.json"];
            if (!file.isContentLoaded) {
                await file.loadContent(false);
            }
            const obj = StorageUtilities_1.default.getJsonObject(file);
            if (obj && obj.files) {
                for (const fileInfo of obj.files) {
                    if (fileInfo.hash && fileInfo.size && fileInfo.path && fileInfo.sourcePath === undefined) {
                        const pathHash = this.generatePathHash(fileInfo);
                        if (!listings[pathHash]) {
                            const relativePath = this.storageRelativePath;
                            if (relativePath) {
                                listings[pathHash] = {
                                    path: fileInfo.path,
                                    size: fileInfo.size,
                                    hash: fileInfo.hash,
                                    sourcePath: StorageUtilities_1.default.canonicalizePath(relativePath + fileInfo.path),
                                };
                            }
                        }
                    }
                }
            }
        }
        else {
            for (const dirName in this.folders) {
                const folder = this.folders[dirName];
                if (folder && !folder.errorStatus) {
                    await folder.generateFileListings(listings);
                }
            }
        }
        return listings;
    }
    generatePathHash(fileInfo) {
        let pathHash = StorageUtilities_1.default.getBaseFromName(fileInfo.path).toLowerCase();
        if (fileInfo.size) {
            pathHash += "|" + fileInfo.size;
        }
        if (fileInfo.hash) {
            pathHash += "|" + fileInfo.hash;
        }
        return pathHash;
    }
    async copyContentsTo(destRootPath, fileInclusionList, addFilesToInclusionList, listings, destStorageRelativePath, copyPath) {
        await this.load(true);
        destRootPath = NodeStorage_1.default.ensureEndsWithDelimiter(destRootPath);
        let dirPath = NodeStorage_1.default.ensureEndsWithDelimiter(destRootPath);
        if (copyPath) {
            dirPath += copyPath;
        }
        const targetFolderExists = fs.existsSync(dirPath);
        if (!targetFolderExists) {
            fs.mkdirSync(dirPath, { recursive: true });
        }
        for (const fileName in this.files) {
            const file = this.files[fileName];
            let filePath = fileName;
            if (copyPath) {
                filePath = copyPath + filePath;
            }
            filePath = StorageUtilities_1.default.canonicalizePath(filePath);
            let targetFileSize;
            if (fileInclusionList) {
                for (const filePathAndSize of fileInclusionList) {
                    if (StorageUtilities_1.default.canonicalizePath(filePathAndSize.path) === filePath) {
                        targetFileSize = filePathAndSize;
                    }
                }
            }
            if (file) {
                await file.loadContent(true);
                if (file.content) {
                    const encoding = StorageUtilities_1.default.getEncodingByFileName(file.name);
                    if (!targetFileSize) {
                        targetFileSize = {
                            size: file.content.length,
                            path: filePath,
                        };
                        if (addFilesToInclusionList === true) {
                            fileInclusionList.push(targetFileSize);
                        }
                    }
                    if (encoding === StorageUtilities_1.EncodingType.ByteBuffer) {
                        let arrData = file.content;
                        if (targetFileSize &&
                            arrData instanceof Buffer &&
                            targetFileSize.size &&
                            arrData.length > targetFileSize.size) {
                            Log_1.default.verbose("Making truncated buffer copy of " + file.fullPath + " to size " + targetFileSize.size);
                            arrData = arrData.subarray(0, targetFileSize.size);
                        }
                        else if (targetFileSize &&
                            arrData instanceof Uint8Array &&
                            targetFileSize.size &&
                            arrData.length > targetFileSize.size) {
                            Log_1.default.verbose("Making truncated array copy of " + file.fullPath + " to size " + targetFileSize.size);
                            arrData = arrData.subarray(0, targetFileSize.size);
                        }
                        const hash = crypto.createHash("MD5");
                        hash.update(arrData);
                        targetFileSize.hash = hash.digest("base64");
                        const pathHash = this.generatePathHash(targetFileSize);
                        let usingSourcePath = false;
                        if (listings) {
                            const fileListing = listings[pathHash];
                            if (fileListing && fileListing.sourcePath) {
                                targetFileSize.sourcePath = fileListing.sourcePath;
                                usingSourcePath = true;
                            }
                        }
                        if (!usingSourcePath) {
                            fs.writeFileSync(dirPath + file.name, arrData);
                            if (listings) {
                                listings[pathHash] = {
                                    size: targetFileSize.size,
                                    hash: targetFileSize.hash,
                                    path: targetFileSize.path,
                                    sourcePath: StorageUtilities_1.default.canonicalizePath(destStorageRelativePath + targetFileSize.path),
                                };
                            }
                        }
                    }
                    else {
                        const hash = crypto.createHash("MD5");
                        hash.update(file.content);
                        targetFileSize.hash = hash.digest("base64");
                        const pathHash = this.generatePathHash(targetFileSize);
                        let usingSourcePath = false;
                        if (listings) {
                            const fileListing = listings[pathHash];
                            if (fileListing && fileListing.sourcePath) {
                                targetFileSize.sourcePath = fileListing.sourcePath;
                                usingSourcePath = true;
                            }
                        }
                        if (!usingSourcePath) {
                            fs.writeFileSync(dirPath + file.name, file.content, { encoding: "utf8" });
                            if (listings) {
                                listings[pathHash] = {
                                    size: targetFileSize.size,
                                    hash: targetFileSize.hash,
                                    path: targetFileSize.path,
                                    sourcePath: StorageUtilities_1.default.canonicalizePath(destStorageRelativePath + targetFileSize.path),
                                };
                            }
                        }
                    }
                }
            }
        }
        if (copyPath === undefined) {
            copyPath = "";
        }
        for (const folderName in this.folders) {
            const nf = this.folders[folderName];
            if (nf && !nf.errorStatus) {
                await nf.copyContentsTo(destRootPath, fileInclusionList, addFilesToInclusionList, listings, destStorageRelativePath, copyPath + folderName + NodeStorage_1.default.platformFolderDelimiter);
            }
        }
    }
    async saveFilesList(pathDescriptor, inclusionList) {
        const obj = { path: pathDescriptor, files: inclusionList };
        fs.writeFileSync(NodeStorage_1.default.ensureEndsWithDelimiter(this.fullPath) + "files.json", JSON.stringify(obj, null, 2), {
            encoding: "utf8",
        });
    }
    async copyContentsOut(destFolder) {
        await this.load(true);
        if (this.files["files.json"] !== undefined) {
            const file = this.files["files.json"];
            if (!file.isContentLoaded) {
                await file.loadContent(false);
            }
            const obj = StorageUtilities_1.default.getJsonObject(file);
            if (obj && obj.files) {
                for (const fileInfo of obj.files) {
                    if (fileInfo.hash && fileInfo.size && fileInfo.path) {
                        let file;
                        if (fileInfo.sourcePath) {
                            file = await this.storage.rootFolder.getFileFromRelativePath(StorageUtilities_1.default.ensureStartsWithDelimiter(fileInfo.sourcePath));
                        }
                        else {
                            file = await this.getFileFromRelativePath(StorageUtilities_1.default.ensureStartsWithDelimiter(fileInfo.path));
                        }
                        if (file) {
                            if (!file.isContentLoaded) {
                                await file.loadContent();
                            }
                            if (file.content !== null) {
                                const targetFile = await destFolder.ensureFileFromRelativePath(StorageUtilities_1.default.ensureStartsWithDelimiter(fileInfo.path));
                                if (targetFile) {
                                    // Log.verbose("Copying file '" + file.fullPath + "' out to '" + targetFile.fullPath + "'");
                                    targetFile.setContent(file.content);
                                }
                            }
                        }
                        else {
                            Log_1.default.debug("Could not find expected backup file '" + fileInfo.path + "' in " + this.fullPath);
                        }
                    }
                }
            }
        }
        else {
            Log_1.default.debug("Could not find files.json in " + this.fullPath);
        }
        await destFolder.saveAll();
    }
    async createFile(name) {
        return this.ensureFile(name);
    }
    async load(force) {
        if (this.lastLoadedOrSaved != null && !force) {
            return this.lastLoadedOrSaved;
        }
        // Log.debug("Reading details on folder '" + this.fullPath + "'");
        if (fs.existsSync(this.fullPath)) {
            const results = fs.readdirSync(this.fullPath);
            results.forEach((fileOrFolderName) => {
                let filePath = this.fullPath;
                if (!filePath.endsWith(NodeStorage_1.default.platformFolderDelimiter)) {
                    filePath += NodeStorage_1.default.platformFolderDelimiter;
                }
                filePath += fileOrFolderName;
                try {
                    const stat = fs.statSync(filePath);
                    if (stat.isDirectory() && !StorageUtilities_1.default.isIgnorableFolder(fileOrFolderName)) {
                        this.ensureFolder(fileOrFolderName);
                    }
                    else if (stat.isFile() && StorageUtilities_1.default.isUsableFile(filePath)) {
                        const file = this.ensureFile(fileOrFolderName);
                        if (stat.mtime) {
                            file.modifiedAtLoad = new Date(stat.mtime);
                        }
                    }
                }
                catch (e) {
                    this.errorStatus = IFolder_1.FolderErrorStatus.unreadable;
                    Log_1.default.error("Error opening folder '" + filePath + "': " + e.toString());
                }
            });
        }
        this.updateLastLoadedOrSaved();
        return this.lastLoadedOrSaved;
    }
}
exports.default = NodeFolder;

//# sourceMappingURL=../maps/local/NodeFolder.js.map
