UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

314 lines (313 loc) 12.9 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WeRecentlyChangedItThresholdMs = exports.MaxRecentVersionsToConsider = void 0; const IFile_1 = require("./IFile"); const ste_events_1 = require("ste-events"); const StorageUtilities_1 = __importDefault(require("./StorageUtilities")); exports.MaxRecentVersionsToConsider = 100; exports.WeRecentlyChangedItThresholdMs = 50; class StorageBase { isContentUpdated = false; readOnly = false; scanForChangesPhase = 0; static slashFolderDelimiter = "/"; priorVersions = []; currentVersionId; containerFile; #onFileAdded = new ste_events_1.EventDispatcher(); #onFileRemoved = new ste_events_1.EventDispatcher(); #onFileContentsUpdated = new ste_events_1.EventDispatcher(); #onFolderMoved = new ste_events_1.EventDispatcher(); #onFolderAdded = new ste_events_1.EventDispatcher(); #onFolderRemoved = new ste_events_1.EventDispatcher(); #storagePath; available; errorStatus; errorMessage; channelId; get folderDelimiter() { return StorageBase.slashFolderDelimiter; } get storagePath() { return this.#storagePath; } set storagePath(newStoragePath) { this.#storagePath = newStoragePath; } resetContentUpdated() { this.isContentUpdated = false; } get onFileAdded() { return this.#onFileAdded.asEvent(); } get onFileRemoved() { return this.#onFileRemoved.asEvent(); } get onFileContentsUpdated() { return this.#onFileContentsUpdated.asEvent(); } get onFolderMoved() { return this.#onFolderMoved.asEvent(); } get onFolderAdded() { return this.#onFolderAdded.asEvent(); } get onFolderRemoved() { return this.#onFolderRemoved.asEvent(); } async ensureFolderFromStorageRelativePath(path) { if (path.startsWith("/" + this.rootFolder.name + "/")) { path = path.substring(this.rootFolder.name.length + 1); } return this.rootFolder.ensureFolderFromRelativePath(path); } notifyFileAdded(file) { this.#onFileAdded.dispatch(this, file); } notifyFolderAdded(folder) { this.#onFolderAdded.dispatch(this, folder); } notifyFolderRemoved(folder) { this.#onFolderRemoved.dispatch(this, folder.name); } notifyFileContentsUpdated(fileEvent) { this.isContentUpdated = true; this.#onFileContentsUpdated.dispatch(this, fileEvent); } notifyFolderMoved(folderMove) { this.isContentUpdated = true; this.#onFolderMoved.dispatch(this, folderMove); } notifyFileRemoved(fileName) { this.#onFileRemoved.dispatch(this, fileName); } async incrementalScanForChanges() { const folders = this.getFolderList(); this.scanForChangesPhase++; this.scanForChangesPhase %= folders.length; const folderToScan = folders[this.scanForChangesPhase]; await folderToScan.scanForChanges(); for (const fileKey in folderToScan.files) { const file = folderToScan.files[fileKey]; if (file) { await file.scanForChanges(); } } } async scanForChanges() { const folders = this.getFolderList(); for (const folder of folders) { await folder.scanForChanges(); for (const fileKey in folder.files) { const file = folder.files[fileKey]; if (file) { await file.scanForChanges(); } } } } async notifyPathWasUpdatedExternal(path) { const dtNow = new Date().getTime(); path = StorageUtilities_1.default.canonicalizePath(path); for (let i = 0; i < this.priorVersions.length && i < exports.MaxRecentVersionsToConsider; i++) { const pv = this.priorVersions[i]; if (pv.versionTime && StorageUtilities_1.default.canonicalizePath(pv.file.fullPath) === StorageUtilities_1.default.canonicalizePath(path)) { if (Math.abs(pv.versionTime.getTime() - dtNow) < exports.WeRecentlyChangedItThresholdMs) { // we updated the file very recently, so ignore this change, "it's probably us" return; } } } if (path.startsWith(this.rootFolder.fullPath)) { path = StorageUtilities_1.default.ensureStartsWithDelimiter(path.substring(this.rootFolder.fullPath.length)); const file = await this.rootFolder.getFileFromRelativePath(path); if (file) { await file.scanForChanges(); } } } /** * Called when a new file is detected externally (e.g., by fs.watch in Electron). * Creates the file object in the folder tree and fires the onFileAdded event. * * @param path The full path to the newly added file */ async notifyPathWasAddedExternal(path) { path = StorageUtilities_1.default.canonicalizePath(path); if (path.startsWith(this.rootFolder.fullPath)) { const relativePath = StorageUtilities_1.default.ensureStartsWithDelimiter(path.substring(this.rootFolder.fullPath.length)); // Get the parent folder path and filename const lastDelim = relativePath.lastIndexOf("/"); if (lastDelim < 0) { return; // No valid path structure } const folderPath = relativePath.substring(0, lastDelim); const fileName = relativePath.substring(lastDelim + 1); if (!fileName) { // This is a folder being added, not a file const folder = await this.rootFolder.ensureFolderFromRelativePath(relativePath); if (folder) { this.notifyFolderAdded(folder); } return; } // Ensure the parent folder exists in our tree const parentFolder = await this.rootFolder.ensureFolderFromRelativePath(folderPath || "/"); if (parentFolder) { // Create the file in the folder tree const file = parentFolder.ensureFile(fileName); // Fire the event so subscribers (like Project) can react this.notifyFileAdded(file); } } } /** * Called when a file is deleted externally (e.g., by fs.watch in Electron). * Removes the file from the folder tree and fires the onFileRemoved event. * * @param path The full path to the removed file */ async notifyPathWasRemovedExternal(path) { path = StorageUtilities_1.default.canonicalizePath(path); if (path.startsWith(this.rootFolder.fullPath)) { const relativePath = StorageUtilities_1.default.ensureStartsWithDelimiter(path.substring(this.rootFolder.fullPath.length)); // Get the parent folder path and filename const lastDelim = relativePath.lastIndexOf("/"); if (lastDelim < 0) { return; } const folderPath = relativePath.substring(0, lastDelim); const fileName = relativePath.substring(lastDelim + 1); if (!fileName) { // This might be a folder being removed // Try to find and remove it from the parent const parentPath = folderPath.substring(0, folderPath.lastIndexOf("/")) || "/"; const parentFolder = await this.rootFolder.getFolderFromRelativePath(parentPath); const removedFolderName = folderPath.substring(folderPath.lastIndexOf("/") + 1); if (parentFolder && removedFolderName && parentFolder.folders[removedFolderName]) { const removedFolder = parentFolder.folders[removedFolderName]; if (removedFolder) { this.notifyFolderRemoved(removedFolder); } delete parentFolder.folders[removedFolderName]; } return; } // Try to find the parent folder const parentFolder = await this.rootFolder.getFolderFromRelativePath(folderPath || "/"); if (parentFolder) { // Check if the file exists in our tree const file = parentFolder.files[fileName]; if (file) { // Fire the event so subscribers can react this.notifyFileRemoved(file.fullPath); // Remove from the folder's files collection delete parentFolder.files[fileName]; } } } } getFolderList() { const folders = []; this._addFolders(this.rootFolder, folders); return folders; } _addFolders(folder, folderList) { folderList.push(folder); for (const folderKey in folder.folders) { const childFolder = folder.folders[folderKey]; if (childFolder) { this._addFolders(childFolder, folderList); } } } setToVersion(versionId) { let startIndex = this.priorVersions.length - 1; if (this.currentVersionId) { for (let i = 0; i < this.priorVersions.length; i++) { if (this.priorVersions[i].id === this.currentVersionId) { startIndex = i; } } } let updateType = IFile_1.FileUpdateType.versionRestoration; if (this.currentVersionId === undefined) { updateType = IFile_1.FileUpdateType.versionRestorationRetainCurrent; } for (let i = 0; i < this.priorVersions.length; i++) { if (this.priorVersions[i].id === versionId) { // rewind to i if (startIndex > i) { for (let v = startIndex - 1; v >= i; v--) { const content = this.priorVersions[v].content; if (content !== null) { this.priorVersions[v].file.setContent(content, updateType); } } } else if (startIndex < i) { // fast forward to i for (let v = startIndex + 1; v <= i; v++) { const content = this.priorVersions[v].content; if (content !== null) { this.priorVersions[v].file.setContent(content, updateType); } } } this.currentVersionId = versionId; return; } } } trimAfterVersion(versionId) { for (let i = 0; i < this.priorVersions.length; i++) { if (this.priorVersions[i].id === versionId) { let versionsToRemove = this.priorVersions.slice(i + 1); this.priorVersions = this.priorVersions.slice(0, i + 1); for (let v = 0; v < versionsToRemove.length; v++) { let versionToRemove = versionsToRemove[v]; versionToRemove.file.priorVersions = versionToRemove.file.priorVersions.filter((fv) => fv.id !== versionToRemove.id); } break; } } } addVersion(versionContent, updateType) { if (updateType === IFile_1.FileUpdateType.versionRestoration || updateType === IFile_1.FileUpdateType.versionlessEdit) { return; } // we have a new organic change in, so clear out any redo history if (this.currentVersionId) { this.trimAfterVersion(this.currentVersionId); this.currentVersionId = undefined; } this.priorVersions.push(versionContent); this.priorVersions = StorageUtilities_1.default.coalesceVersions(this.priorVersions); versionContent.file.priorVersions.push(versionContent); } joinPath(pathA, pathB) { let fullPath = pathA; if (!fullPath.endsWith(StorageBase.slashFolderDelimiter)) { fullPath += StorageBase.slashFolderDelimiter; } fullPath += pathB; return fullPath; } static getParentFolderPath(path) { const lastDelim = path.lastIndexOf(StorageBase.slashFolderDelimiter); if (lastDelim < 0) { return path; } return path.substring(0, lastDelim); } getUsesPollingBasedUpdating() { return false; } } exports.default = StorageBase;