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