UNPKG

@theia/filesystem

Version:
385 lines • 18.7 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2018 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** Object.defineProperty(exports, "__esModule", { value: true }); exports.FileResourceResolver = exports.FileResource = exports.FileResourceVersion = void 0; const tslib_1 = require("tslib"); const inversify_1 = require("@theia/core/shared/inversify"); const resource_1 = require("@theia/core/lib/common/resource"); const disposable_1 = require("@theia/core/lib/common/disposable"); const event_1 = require("@theia/core/lib/common/event"); const files_1 = require("../common/files"); const file_service_1 = require("./file-service"); const dialogs_1 = require("@theia/core/lib/browser/dialogs"); const label_provider_1 = require("@theia/core/lib/browser/label-provider"); const filesystem_preferences_1 = require("../common/filesystem-preferences"); const frontend_application_state_1 = require("@theia/core/lib/browser/frontend-application-state"); const core_1 = require("@theia/core"); const async_mutex_1 = require("async-mutex"); var FileResourceVersion; (function (FileResourceVersion) { function is(version) { return !!version && 'encoding' in version && 'mtime' in version && 'etag' in version; } FileResourceVersion.is = is; })(FileResourceVersion || (exports.FileResourceVersion = FileResourceVersion = {})); class FileResource { get version() { return this._version; } get encoding() { var _a; return (_a = this._version) === null || _a === void 0 ? void 0 : _a.encoding; } get readOnly() { return this.options.readOnly; } constructor(uri, fileService, options) { this.uri = uri; this.fileService = fileService; this.options = options; this.acceptTextOnly = true; this.toDispose = new disposable_1.DisposableCollection(); this.onDidChangeContentsEmitter = new event_1.Emitter(); this.onDidChangeContents = this.onDidChangeContentsEmitter.event; this.onDidChangeReadOnlyEmitter = new event_1.Emitter(); this.onDidChangeReadOnly = this.onDidChangeReadOnlyEmitter.event; this.writingLock = new async_mutex_1.Mutex(); this.doWrite = async (content, options) => { const version = (options === null || options === void 0 ? void 0 : options.version) || this._version; const current = FileResourceVersion.is(version) ? version : undefined; const etag = current === null || current === void 0 ? void 0 : current.etag; const releaseLock = await this.writingLock.acquire(); try { const stat = await this.fileService.write(this.uri, content, { encoding: options === null || options === void 0 ? void 0 : options.encoding, overwriteEncoding: options === null || options === void 0 ? void 0 : options.overwriteEncoding, etag, mtime: current === null || current === void 0 ? void 0 : current.mtime }); this._version = { etag: stat.etag, mtime: stat.mtime, encoding: stat.encoding }; } catch (e) { if (e instanceof files_1.FileOperationError && e.fileOperationResult === 3 /* FileOperationResult.FILE_MODIFIED_SINCE */) { if (etag !== files_1.ETAG_DISABLED && await this.shouldOverwrite()) { return this.doWrite(content, { ...options, version: { stat: { ...current, etag: files_1.ETAG_DISABLED } } }); } const { message, stack } = e; throw resource_1.ResourceError.OutOfSync({ message, stack, data: { uri: this.uri } }); } throw e; } finally { releaseLock(); } }; this.doSaveContentChanges = async (changes, options) => { const version = (options === null || options === void 0 ? void 0 : options.version) || this._version; const current = FileResourceVersion.is(version) ? version : undefined; if (!current) { throw resource_1.ResourceError.NotFound({ message: 'has not been read yet', data: { uri: this.uri } }); } const etag = current === null || current === void 0 ? void 0 : current.etag; const releaseLock = await this.writingLock.acquire(); try { const stat = await this.fileService.update(this.uri, changes, { readEncoding: current.encoding, encoding: options === null || options === void 0 ? void 0 : options.encoding, overwriteEncoding: options === null || options === void 0 ? void 0 : options.overwriteEncoding, etag, mtime: current === null || current === void 0 ? void 0 : current.mtime }); this._version = { etag: stat.etag, mtime: stat.mtime, encoding: stat.encoding }; } catch (e) { if (e instanceof files_1.FileOperationError && e.fileOperationResult === 1 /* FileOperationResult.FILE_NOT_FOUND */) { const { message, stack } = e; throw resource_1.ResourceError.NotFound({ message, stack, data: { uri: this.uri } }); } if (e instanceof files_1.FileOperationError && e.fileOperationResult === 3 /* FileOperationResult.FILE_MODIFIED_SINCE */) { const { message, stack } = e; throw resource_1.ResourceError.OutOfSync({ message, stack, data: { uri: this.uri } }); } throw e; } finally { releaseLock(); } }; this.toDispose.push(this.onDidChangeContentsEmitter); this.toDispose.push(this.onDidChangeReadOnlyEmitter); this.toDispose.push(this.fileService.onDidFilesChange(event => { if (event.contains(this.uri)) { this.sync(); } })); this.toDispose.push(this.fileService.onDidRunOperation(e => { if ((e.isOperation(1 /* FileOperation.DELETE */) || e.isOperation(2 /* FileOperation.MOVE */)) && e.resource.isEqualOrParent(this.uri)) { this.sync(); } })); try { this.toDispose.push(this.fileService.watch(this.uri)); } catch (e) { console.error(e); } this.updateSavingContentChanges(); this.toDispose.push(this.fileService.onDidChangeFileSystemProviderCapabilities(async (e) => { if (e.scheme === this.uri.scheme) { this.updateReadOnly(); } })); this.toDispose.push(this.fileService.onDidChangeFileSystemProviderReadOnlyMessage(async (e) => { if (e.scheme === this.uri.scheme) { this.updateReadOnly(); } })); } async updateReadOnly() { const oldReadOnly = this.options.readOnly; const readOnlyMessage = this.fileService.getReadOnlyMessage(this.uri); if (readOnlyMessage) { this.options.readOnly = readOnlyMessage; } else { this.options.readOnly = this.fileService.hasCapability(this.uri, 2048 /* FileSystemProviderCapabilities.Readonly */); } if (this.options.readOnly !== oldReadOnly) { this.updateSavingContentChanges(); this.onDidChangeReadOnlyEmitter.fire(this.options.readOnly); } } dispose() { this.toDispose.dispose(); } async readContents(options) { var _a, _b; try { const encoding = (options === null || options === void 0 ? void 0 : options.encoding) || ((_a = this.version) === null || _a === void 0 ? void 0 : _a.encoding); const stat = await this.fileService.read(this.uri, { encoding, etag: files_1.ETAG_DISABLED, acceptTextOnly: this.acceptTextOnly, limits: this.limits }); this._version = { encoding: stat.encoding, etag: stat.etag, mtime: stat.mtime }; return stat.value; } catch (e) { if (e instanceof file_service_1.TextFileOperationError && e.textFileOperationResult === 0 /* TextFileOperationResult.FILE_IS_BINARY */) { if (await this.shouldOpenAsText(core_1.nls.localize('theia/filesystem/fileResource/binaryTitle', 'The file is either binary or uses an unsupported text encoding.'))) { this.acceptTextOnly = false; return this.readContents(options); } } else if (e instanceof files_1.FileOperationError && e.fileOperationResult === 7 /* FileOperationResult.FILE_TOO_LARGE */) { const stat = await this.fileService.resolve(this.uri, { resolveMetadata: true }); const maxFileSize = filesystem_preferences_1.GENERAL_MAX_FILE_SIZE_MB * 1024 * 1024; if (((_b = this.limits) === null || _b === void 0 ? void 0 : _b.size) !== maxFileSize && await this.shouldOpenAsText(core_1.nls.localize('theia/filesystem/fileResource/largeFileTitle', 'The file is too large ({0}).', files_1.BinarySize.formatSize(stat.size)))) { this.limits = { size: maxFileSize }; return this.readContents(options); } } else if (e instanceof files_1.FileOperationError && e.fileOperationResult === 1 /* FileOperationResult.FILE_NOT_FOUND */) { this._version = undefined; const { message, stack } = e; throw resource_1.ResourceError.NotFound({ message, stack, data: { uri: this.uri } }); } throw e; } } async readStream(options) { var _a, _b; try { const encoding = (options === null || options === void 0 ? void 0 : options.encoding) || ((_a = this.version) === null || _a === void 0 ? void 0 : _a.encoding); const stat = await this.fileService.readStream(this.uri, { encoding, etag: files_1.ETAG_DISABLED, acceptTextOnly: this.acceptTextOnly, limits: this.limits }); this._version = { encoding: stat.encoding, etag: stat.etag, mtime: stat.mtime }; return stat.value; } catch (e) { if (e instanceof file_service_1.TextFileOperationError && e.textFileOperationResult === 0 /* TextFileOperationResult.FILE_IS_BINARY */) { if (await this.shouldOpenAsText(core_1.nls.localize('theia/filesystem/fileResource/binaryTitle', 'The file is either binary or uses an unsupported text encoding.'))) { this.acceptTextOnly = false; return this.readStream(options); } } else if (e instanceof files_1.FileOperationError && e.fileOperationResult === 7 /* FileOperationResult.FILE_TOO_LARGE */) { const stat = await this.fileService.resolve(this.uri, { resolveMetadata: true }); const maxFileSize = filesystem_preferences_1.GENERAL_MAX_FILE_SIZE_MB * 1024 * 1024; if (((_b = this.limits) === null || _b === void 0 ? void 0 : _b.size) !== maxFileSize && await this.shouldOpenAsText(core_1.nls.localize('theia/filesystem/fileResource/largeFileTitle', 'The file is too large ({0}).', files_1.BinarySize.formatSize(stat.size)))) { this.limits = { size: maxFileSize }; return this.readStream(options); } } else if (e instanceof files_1.FileOperationError && e.fileOperationResult === 1 /* FileOperationResult.FILE_NOT_FOUND */) { this._version = undefined; const { message, stack } = e; throw resource_1.ResourceError.NotFound({ message, stack, data: { uri: this.uri } }); } throw e; } } updateSavingContentChanges() { if (this.readOnly) { delete this.saveContentChanges; delete this.saveContents; delete this.saveStream; } else { this.saveContents = this.doWrite; this.saveStream = this.doWrite; if (this.fileService.hasCapability(this.uri, 33554432 /* FileSystemProviderCapabilities.Update */)) { this.saveContentChanges = this.doSaveContentChanges; } } } async guessEncoding() { // TODO limit size const content = await this.fileService.read(this.uri, { autoGuessEncoding: true }); return content.encoding; } async sync() { if (await this.isInSync()) { return; } this.onDidChangeContentsEmitter.fire(undefined); } async isInSync() { try { await this.writingLock.waitForUnlock(); const stat = await this.fileService.resolve(this.uri, { resolveMetadata: true }); return !!this.version && this.version.mtime >= stat.mtime; } catch { return !this.version; } } async shouldOverwrite() { return this.options.shouldOverwrite(); } async shouldOpenAsText(error) { return this.options.shouldOpenAsText(error); } } exports.FileResource = FileResource; let FileResourceResolver = class FileResourceResolver { constructor() { /** This resolver interacts with the VSCode plugin system in a way that can cause delays. Most other resource resolvers fail immediately, so this one should be tried late. */ this.priority = -10; } async resolve(uri) { var _a; let stat; try { stat = await this.fileService.resolve(uri); } catch (e) { if (!(e instanceof files_1.FileOperationError && e.fileOperationResult === 1 /* FileOperationResult.FILE_NOT_FOUND */)) { throw e; } } if (stat && stat.isDirectory) { throw new Error('The given uri is a directory: ' + this.labelProvider.getLongName(uri)); } const readOnlyMessage = this.fileService.getReadOnlyMessage(uri); const isFileSystemReadOnly = this.fileService.hasCapability(uri, 2048 /* FileSystemProviderCapabilities.Readonly */); const readOnly = readOnlyMessage !== null && readOnlyMessage !== void 0 ? readOnlyMessage : (isFileSystemReadOnly ? isFileSystemReadOnly : ((_a = stat === null || stat === void 0 ? void 0 : stat.isReadonly) !== null && _a !== void 0 ? _a : false)); return new FileResource(uri, this.fileService, { readOnly: readOnly, shouldOverwrite: () => this.shouldOverwrite(uri), shouldOpenAsText: error => this.shouldOpenAsText(uri, error) }); } async shouldOverwrite(uri) { const dialog = new dialogs_1.ConfirmDialog({ title: core_1.nls.localize('theia/filesystem/fileResource/overwriteTitle', "The file '{0}' has been changed on the file system.", this.labelProvider.getName(uri)), msg: core_1.nls.localize('theia/fileSystem/fileResource/overWriteBody', "Do you want to overwrite the changes made to '{0}' on the file system?", this.labelProvider.getLongName(uri)), ok: dialogs_1.Dialog.YES, cancel: dialogs_1.Dialog.NO, }); return !!await dialog.open(); } async shouldOpenAsText(uri, error) { switch (this.applicationState.state) { case 'init': case 'started_contributions': case 'attached_shell': return true; // We're restoring state - assume that we should open files that were previously open. default: { const dialog = new dialogs_1.ConfirmDialog({ title: error, msg: core_1.nls.localize('theia/filesystem/fileResource/binaryFileQuery', "Opening it might take some time and might make the IDE unresponsive. Do you want to open '{0}' anyway?", this.labelProvider.getLongName(uri)), ok: dialogs_1.Dialog.YES, cancel: dialogs_1.Dialog.NO, }); return !!await dialog.open(); } } } }; exports.FileResourceResolver = FileResourceResolver; tslib_1.__decorate([ (0, inversify_1.inject)(file_service_1.FileService), tslib_1.__metadata("design:type", file_service_1.FileService) ], FileResourceResolver.prototype, "fileService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(label_provider_1.LabelProvider), tslib_1.__metadata("design:type", label_provider_1.LabelProvider) ], FileResourceResolver.prototype, "labelProvider", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(frontend_application_state_1.FrontendApplicationStateService), tslib_1.__metadata("design:type", frontend_application_state_1.FrontendApplicationStateService) ], FileResourceResolver.prototype, "applicationState", void 0); exports.FileResourceResolver = FileResourceResolver = tslib_1.__decorate([ (0, inversify_1.injectable)() ], FileResourceResolver); //# sourceMappingURL=file-resource.js.map