UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

388 lines (386 loc) 17.4 kB
"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 { 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) { 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) { 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; } _addExistingFolder(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._clearExistingFolder(this); this._parentFolder = newParentFolder; this._name = newFolderName; newParentFolder._addExistingFolder(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"]; 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"]; 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) { 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((fileName) => { let filePath = this.fullPath; if (!filePath.endsWith(NodeStorage_1.default.platformFolderDelimiter)) { filePath += NodeStorage_1.default.platformFolderDelimiter; } filePath += fileName; try { const stat = fs.statSync(filePath); if (stat.isDirectory()) { this.ensureFolder(fileName); } else if (stat.isFile() && StorageUtilities_1.default.isUsableFile(filePath)) { const file = this.ensureFile(fileName); 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