|  | /* | 
|  | * Copyright (C) 2012 Google Inc. All rights reserved. | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions are | 
|  | * met: | 
|  | * | 
|  | *     * Redistributions of source code must retain the above copyright | 
|  | * notice, this list of conditions and the following disclaimer. | 
|  | *     * Redistributions in binary form must reproduce the above | 
|  | * copyright notice, this list of conditions and the following disclaimer | 
|  | * in the documentation and/or other materials provided with the | 
|  | * distribution. | 
|  | *     * Neither the name of Google Inc. nor the names of its | 
|  | * contributors may be used to endorse or promote products derived from | 
|  | * this software without specific prior written permission. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
|  | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
|  | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
|  | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
|  | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @unrestricted | 
|  | */ | 
|  | export default class IsolatedFileSystemManager extends Common.Object { | 
|  | constructor() { | 
|  | super(); | 
|  |  | 
|  | /** @type {!Map<string, !Persistence.PlatformFileSystem>} */ | 
|  | this._fileSystems = new Map(); | 
|  | /** @type {!Map<number, function(!Array.<string>)>} */ | 
|  | this._callbacks = new Map(); | 
|  | /** @type {!Map<number, !Common.Progress>} */ | 
|  | this._progresses = new Map(); | 
|  |  | 
|  | Host.InspectorFrontendHost.events.addEventListener( | 
|  | Host.InspectorFrontendHostAPI.Events.FileSystemRemoved, this._onFileSystemRemoved, this); | 
|  | Host.InspectorFrontendHost.events.addEventListener( | 
|  | Host.InspectorFrontendHostAPI.Events.FileSystemAdded, this._onFileSystemAdded, this); | 
|  | Host.InspectorFrontendHost.events.addEventListener( | 
|  | Host.InspectorFrontendHostAPI.Events.FileSystemFilesChangedAddedRemoved, this._onFileSystemFilesChanged, this); | 
|  | Host.InspectorFrontendHost.events.addEventListener( | 
|  | Host.InspectorFrontendHostAPI.Events.IndexingTotalWorkCalculated, this._onIndexingTotalWorkCalculated, this); | 
|  | Host.InspectorFrontendHost.events.addEventListener( | 
|  | Host.InspectorFrontendHostAPI.Events.IndexingWorked, this._onIndexingWorked, this); | 
|  | Host.InspectorFrontendHost.events.addEventListener( | 
|  | Host.InspectorFrontendHostAPI.Events.IndexingDone, this._onIndexingDone, this); | 
|  | Host.InspectorFrontendHost.events.addEventListener( | 
|  | Host.InspectorFrontendHostAPI.Events.SearchCompleted, this._onSearchCompleted, this); | 
|  |  | 
|  | this._initExcludePatterSetting(); | 
|  |  | 
|  | /** @type {?function(?Persistence.IsolatedFileSystem)} */ | 
|  | this._fileSystemRequestResolve = null; | 
|  | this._fileSystemsLoadedPromise = this._requestFileSystems(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Promise<!Array<!Persistence.IsolatedFileSystem>>} | 
|  | */ | 
|  | _requestFileSystems() { | 
|  | let fulfill; | 
|  | const promise = new Promise(f => fulfill = f); | 
|  | Host.InspectorFrontendHost.events.addEventListener( | 
|  | Host.InspectorFrontendHostAPI.Events.FileSystemsLoaded, onFileSystemsLoaded, this); | 
|  | Host.InspectorFrontendHost.requestFileSystems(); | 
|  | return promise; | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | * @this {IsolatedFileSystemManager} | 
|  | */ | 
|  | function onFileSystemsLoaded(event) { | 
|  | const fileSystems = /** @type {!Array.<!Persistence.IsolatedFileSystemManager.FileSystem>} */ (event.data); | 
|  | const promises = []; | 
|  | for (let i = 0; i < fileSystems.length; ++i) { | 
|  | promises.push(this._innerAddFileSystem(fileSystems[i], false)); | 
|  | } | 
|  | Promise.all(promises).then(onFileSystemsAdded); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Array<?Persistence.IsolatedFileSystem>} fileSystems | 
|  | */ | 
|  | function onFileSystemsAdded(fileSystems) { | 
|  | fulfill(fileSystems.filter(fs => !!fs)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string=} type | 
|  | * @return {!Promise<?Persistence.IsolatedFileSystem>} | 
|  | */ | 
|  | addFileSystem(type) { | 
|  | return new Promise(resolve => { | 
|  | this._fileSystemRequestResolve = resolve; | 
|  | Host.InspectorFrontendHost.addFileSystem(type || ''); | 
|  | }); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Persistence.PlatformFileSystem} fileSystem | 
|  | */ | 
|  | removeFileSystem(fileSystem) { | 
|  | Host.InspectorFrontendHost.removeFileSystem(fileSystem.embedderPath()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Promise<!Array<!Persistence.IsolatedFileSystem>>} | 
|  | */ | 
|  | waitForFileSystems() { | 
|  | return this._fileSystemsLoadedPromise; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Persistence.IsolatedFileSystemManager.FileSystem} fileSystem | 
|  | * @param {boolean} dispatchEvent | 
|  | * @return {!Promise<?Persistence.IsolatedFileSystem>} | 
|  | */ | 
|  | _innerAddFileSystem(fileSystem, dispatchEvent) { | 
|  | const embedderPath = fileSystem.fileSystemPath; | 
|  | const fileSystemURL = Common.ParsedURL.platformPathToURL(fileSystem.fileSystemPath); | 
|  | const promise = Persistence.IsolatedFileSystem.create( | 
|  | this, fileSystemURL, embedderPath, fileSystem.type, fileSystem.fileSystemName, fileSystem.rootURL); | 
|  | return promise.then(storeFileSystem.bind(this)); | 
|  |  | 
|  | /** | 
|  | * @param {?Persistence.PlatformFileSystem} fileSystem | 
|  | * @this {IsolatedFileSystemManager} | 
|  | */ | 
|  | function storeFileSystem(fileSystem) { | 
|  | if (!fileSystem) { | 
|  | return null; | 
|  | } | 
|  | this._fileSystems.set(fileSystemURL, fileSystem); | 
|  | if (dispatchEvent) { | 
|  | this.dispatchEventToListeners(Events.FileSystemAdded, fileSystem); | 
|  | } | 
|  | return fileSystem; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} fileSystemURL | 
|  | * @param {!Persistence.PlatformFileSystem} fileSystem | 
|  | */ | 
|  | addPlatformFileSystem(fileSystemURL, fileSystem) { | 
|  | this._fileSystems.set(fileSystemURL, fileSystem); | 
|  | this.dispatchEventToListeners(Events.FileSystemAdded, fileSystem); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | */ | 
|  | async _onFileSystemAdded(event) { | 
|  | const errorMessage = /** @type {string} */ (event.data['errorMessage']); | 
|  | let fileSystem = /** @type {?Persistence.IsolatedFileSystemManager.FileSystem} */ (event.data['fileSystem']); | 
|  | if (errorMessage) { | 
|  | Common.console.error(Common.UIString('Unable to add filesystem: %s', errorMessage)); | 
|  | if (!this._fileSystemRequestResolve) { | 
|  | return; | 
|  | } | 
|  | this._fileSystemRequestResolve.call(null, null); | 
|  | this._fileSystemRequestResolve = null; | 
|  | } else if (fileSystem) { | 
|  | fileSystem = await this._innerAddFileSystem(fileSystem, true); | 
|  | if (this._fileSystemRequestResolve) { | 
|  | this._fileSystemRequestResolve.call(null, fileSystem); | 
|  | this._fileSystemRequestResolve = null; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | */ | 
|  | _onFileSystemRemoved(event) { | 
|  | const embedderPath = /** @type {string} */ (event.data); | 
|  | const fileSystemPath = Common.ParsedURL.platformPathToURL(embedderPath); | 
|  | const isolatedFileSystem = this._fileSystems.get(fileSystemPath); | 
|  | if (!isolatedFileSystem) { | 
|  | return; | 
|  | } | 
|  | this._fileSystems.delete(fileSystemPath); | 
|  | isolatedFileSystem.fileSystemRemoved(); | 
|  | this.dispatchEventToListeners(Events.FileSystemRemoved, isolatedFileSystem); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | */ | 
|  | _onFileSystemFilesChanged(event) { | 
|  | const urlPaths = { | 
|  | changed: groupFilePathsIntoFileSystemPaths.call(this, event.data.changed), | 
|  | added: groupFilePathsIntoFileSystemPaths.call(this, event.data.added), | 
|  | removed: groupFilePathsIntoFileSystemPaths.call(this, event.data.removed) | 
|  | }; | 
|  |  | 
|  | this.dispatchEventToListeners(Events.FileSystemFilesChanged, urlPaths); | 
|  |  | 
|  | /** | 
|  | * @param {!Array<string>} embedderPaths | 
|  | * @return {!Platform.Multimap<string, string>} | 
|  | * @this {IsolatedFileSystemManager} | 
|  | */ | 
|  | function groupFilePathsIntoFileSystemPaths(embedderPaths) { | 
|  | const paths = new Platform.Multimap(); | 
|  | for (const embedderPath of embedderPaths) { | 
|  | const filePath = Common.ParsedURL.platformPathToURL(embedderPath); | 
|  | for (const fileSystemPath of this._fileSystems.keys()) { | 
|  | if (this._fileSystems.get(fileSystemPath).isFileExcluded(embedderPath)) { | 
|  | continue; | 
|  | } | 
|  | const pathPrefix = fileSystemPath.endsWith('/') ? fileSystemPath : fileSystemPath + '/'; | 
|  | if (!filePath.startsWith(pathPrefix)) { | 
|  | continue; | 
|  | } | 
|  | paths.set(fileSystemPath, filePath); | 
|  | } | 
|  | } | 
|  | return paths; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Array<!Persistence.IsolatedFileSystem>} | 
|  | */ | 
|  | fileSystems() { | 
|  | return this._fileSystems.valuesArray(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} fileSystemPath | 
|  | * @return {?Persistence.PlatformFileSystem} | 
|  | */ | 
|  | fileSystem(fileSystemPath) { | 
|  | return this._fileSystems.get(fileSystemPath) || null; | 
|  | } | 
|  |  | 
|  | _initExcludePatterSetting() { | 
|  | const defaultCommonExcludedFolders = [ | 
|  | '/node_modules/', '/bower_components/', '/\\.devtools', '/\\.git/', '/\\.sass-cache/', '/\\.hg/', '/\\.idea/', | 
|  | '/\\.svn/', '/\\.cache/', '/\\.project/' | 
|  | ]; | 
|  | const defaultWinExcludedFolders = ['/Thumbs.db$', '/ehthumbs.db$', '/Desktop.ini$', '/\\$RECYCLE.BIN/']; | 
|  | const defaultMacExcludedFolders = [ | 
|  | '/\\.DS_Store$', '/\\.Trashes$', '/\\.Spotlight-V100$', '/\\.AppleDouble$', '/\\.LSOverride$', '/Icon$', | 
|  | '/\\._.*$' | 
|  | ]; | 
|  | const defaultLinuxExcludedFolders = ['/.*~$']; | 
|  | let defaultExcludedFolders = defaultCommonExcludedFolders; | 
|  | if (Host.isWin()) { | 
|  | defaultExcludedFolders = defaultExcludedFolders.concat(defaultWinExcludedFolders); | 
|  | } else if (Host.isMac()) { | 
|  | defaultExcludedFolders = defaultExcludedFolders.concat(defaultMacExcludedFolders); | 
|  | } else { | 
|  | defaultExcludedFolders = defaultExcludedFolders.concat(defaultLinuxExcludedFolders); | 
|  | } | 
|  | const defaultExcludedFoldersPattern = defaultExcludedFolders.join('|'); | 
|  | this._workspaceFolderExcludePatternSetting = Common.settings.createRegExpSetting( | 
|  | 'workspaceFolderExcludePattern', defaultExcludedFoldersPattern, Host.isWin() ? 'i' : ''); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Common.Setting} | 
|  | */ | 
|  | workspaceFolderExcludePatternSetting() { | 
|  | return this._workspaceFolderExcludePatternSetting; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {function(!Array.<string>)} callback | 
|  | * @return {number} | 
|  | */ | 
|  | registerCallback(callback) { | 
|  | const requestId = ++_lastRequestId; | 
|  | this._callbacks.set(requestId, callback); | 
|  | return requestId; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Progress} progress | 
|  | * @return {number} | 
|  | */ | 
|  | registerProgress(progress) { | 
|  | const requestId = ++_lastRequestId; | 
|  | this._progresses.set(requestId, progress); | 
|  | return requestId; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | */ | 
|  | _onIndexingTotalWorkCalculated(event) { | 
|  | const requestId = /** @type {number} */ (event.data['requestId']); | 
|  | const totalWork = /** @type {number} */ (event.data['totalWork']); | 
|  |  | 
|  | const progress = this._progresses.get(requestId); | 
|  | if (!progress) { | 
|  | return; | 
|  | } | 
|  | progress.setTotalWork(totalWork); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | */ | 
|  | _onIndexingWorked(event) { | 
|  | const requestId = /** @type {number} */ (event.data['requestId']); | 
|  | const worked = /** @type {number} */ (event.data['worked']); | 
|  |  | 
|  | const progress = this._progresses.get(requestId); | 
|  | if (!progress) { | 
|  | return; | 
|  | } | 
|  | progress.worked(worked); | 
|  | if (progress.isCanceled()) { | 
|  | Host.InspectorFrontendHost.stopIndexing(requestId); | 
|  | this._onIndexingDone(event); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | */ | 
|  | _onIndexingDone(event) { | 
|  | const requestId = /** @type {number} */ (event.data['requestId']); | 
|  |  | 
|  | const progress = this._progresses.get(requestId); | 
|  | if (!progress) { | 
|  | return; | 
|  | } | 
|  | progress.done(); | 
|  | this._progresses.delete(requestId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | */ | 
|  | _onSearchCompleted(event) { | 
|  | const requestId = /** @type {number} */ (event.data['requestId']); | 
|  | const files = /** @type {!Array.<string>} */ (event.data['files']); | 
|  |  | 
|  | const callback = this._callbacks.get(requestId); | 
|  | if (!callback) { | 
|  | return; | 
|  | } | 
|  | callback.call(null, files); | 
|  | this._callbacks.delete(requestId); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @enum {symbol} */ | 
|  | export const Events = { | 
|  | FileSystemAdded: Symbol('FileSystemAdded'), | 
|  | FileSystemRemoved: Symbol('FileSystemRemoved'), | 
|  | FileSystemFilesChanged: Symbol('FileSystemFilesChanged'), | 
|  | ExcludedFolderAdded: Symbol('ExcludedFolderAdded'), | 
|  | ExcludedFolderRemoved: Symbol('ExcludedFolderRemoved') | 
|  | }; | 
|  |  | 
|  | let _lastRequestId = 0; | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | self.Persistence = self.Persistence || {}; | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | Persistence = Persistence || {}; | 
|  |  | 
|  | /** @constructor */ | 
|  | Persistence.IsolatedFileSystemManager = IsolatedFileSystemManager; | 
|  |  | 
|  | /** @enum {symbol} */ | 
|  | Persistence.IsolatedFileSystemManager.Events = Events; | 
|  |  | 
|  | /** @typedef {!{type: string, fileSystemName: string, rootURL: string, fileSystemPath: string}} */ | 
|  | Persistence.IsolatedFileSystemManager.FileSystem; | 
|  |  | 
|  | /** @typedef {!{changed:!Platform.Multimap<string, string>, added:!Platform.Multimap<string, string>, removed:!Platform.Multimap<string, string>}} */ | 
|  | Persistence.IsolatedFileSystemManager.FilesChangedData; | 
|  |  | 
|  | /** | 
|  | * @type {!IsolatedFileSystemManager} | 
|  | */ | 
|  | Persistence.isolatedFileSystemManager; |