@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
388 lines (386 loc) • 17.4 kB
JavaScript
"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