| /* |
| * Copyright (C) 2013 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. |
| */ |
| |
| /** |
| * @constructor |
| * @param {!WebInspector.IsolatedFileSystemManager} manager |
| * @param {string} path |
| * @param {string} embedderPath |
| * @param {!DOMFileSystem} domFileSystem |
| */ |
| WebInspector.IsolatedFileSystem = function(manager, path, embedderPath, domFileSystem) |
| { |
| this._manager = manager; |
| this._path = path; |
| this._embedderPath = embedderPath; |
| this._domFileSystem = domFileSystem; |
| this._excludedFoldersSetting = WebInspector.settings.createLocalSetting("workspaceExcludedFolders", {}); |
| /** @type {!Set<string>} */ |
| this._excludedFolders = new Set(this._excludedFoldersSetting.get()[path] || []); |
| /** @type {!Set<string>} */ |
| this._nonConfigurableExcludedFolders = new Set(); |
| } |
| |
| WebInspector.IsolatedFileSystem.ImageExtensions = new Set(["jpeg", "jpg", "svg", "gif", "webp", "png", "ico", "tiff", "tif", "bmp"]); |
| |
| /** |
| * @constructor |
| * @param {!WebInspector.IsolatedFileSystemManager} manager |
| * @param {string} path |
| * @param {string} embedderPath |
| * @param {string} name |
| * @param {string} rootURL |
| * @return {!Promise<?WebInspector.IsolatedFileSystem>} |
| */ |
| WebInspector.IsolatedFileSystem.create = function(manager, path, embedderPath, name, rootURL) |
| { |
| return new Promise(promiseBody); |
| |
| /** |
| * @param {function(?WebInspector.IsolatedFileSystem)} resolve |
| * @param {function(!Error)} reject |
| */ |
| function promiseBody(resolve, reject) |
| { |
| var domFileSystem = InspectorFrontendHost.isolatedFileSystem(name, rootURL); |
| if (!domFileSystem) { |
| resolve(null); |
| return; |
| } |
| var fileSystem = new WebInspector.IsolatedFileSystem(manager, path, embedderPath, domFileSystem); |
| fileSystem.requestFileContent(".devtools", onConfigAvailable); |
| |
| /** |
| * @param {?string} projectText |
| */ |
| function onConfigAvailable(projectText) |
| { |
| if (projectText) { |
| try { |
| var projectObject = JSON.parse(projectText); |
| fileSystem._initializeProject(typeof projectObject === "object" ? /** @type {!Object} */ (projectObject) : null); |
| } catch (e) { |
| WebInspector.console.error("Invalid project file: " + projectText); |
| } |
| } |
| resolve(fileSystem); |
| } |
| } |
| } |
| |
| /** |
| * @param {!FileError} error |
| * @return {string} |
| */ |
| WebInspector.IsolatedFileSystem.errorMessage = function(error) |
| { |
| return WebInspector.UIString("File system error: %s", error.message); |
| } |
| |
| WebInspector.IsolatedFileSystem.prototype = { |
| /** |
| * @return {string} |
| */ |
| path: function() |
| { |
| return this._path; |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| embedderPath: function() |
| { |
| return this._embedderPath; |
| }, |
| |
| /** |
| * @param {?Object} projectObject |
| */ |
| _initializeProject: function(projectObject) |
| { |
| this._projectObject = projectObject; |
| |
| var projectExcludes = this.projectProperty("excludes"); |
| if (Array.isArray(projectExcludes)) { |
| for (var folder of /** @type {!Array<*>} */ (projectExcludes)) { |
| if (typeof folder === "string") |
| this._nonConfigurableExcludedFolders.add(folder); |
| } |
| } |
| }, |
| |
| /** |
| * @param {string} key |
| * @return {*} |
| */ |
| projectProperty: function(key) |
| { |
| return this._projectObject ? this._projectObject[key] : null; |
| }, |
| |
| /** |
| * @param {string} path |
| * @param {function(string)} fileCallback |
| * @param {function()=} finishedCallback |
| */ |
| requestFilesRecursive: function(path, fileCallback, finishedCallback) |
| { |
| var pendingRequests = 1; |
| this._requestEntries(path, innerCallback.bind(this)); |
| |
| /** |
| * @param {!Array.<!FileEntry>} entries |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function innerCallback(entries) |
| { |
| for (var i = 0; i < entries.length; ++i) { |
| var entry = entries[i]; |
| if (!entry.isDirectory) { |
| if (this._isFileExcluded(entry.fullPath)) |
| continue; |
| fileCallback(entry.fullPath.substr(1)); |
| } else { |
| if (this._isFileExcluded(entry.fullPath + "/")) |
| continue; |
| ++pendingRequests; |
| this._requestEntries(entry.fullPath, innerCallback.bind(this)); |
| } |
| } |
| if (finishedCallback && (--pendingRequests === 0)) |
| finishedCallback(); |
| } |
| }, |
| |
| /** |
| * @param {string} path |
| * @param {?string} name |
| * @param {function(?string)} callback |
| */ |
| createFile: function(path, name, callback) |
| { |
| var newFileIndex = 1; |
| if (!name) |
| name = "NewFile"; |
| var nameCandidate; |
| |
| this._domFileSystem.root.getDirectory(path, null, dirEntryLoaded.bind(this), errorHandler.bind(this)); |
| |
| /** |
| * @param {!DirectoryEntry} dirEntry |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function dirEntryLoaded(dirEntry) |
| { |
| var nameCandidate = name; |
| if (newFileIndex > 1) |
| nameCandidate += newFileIndex; |
| ++newFileIndex; |
| dirEntry.getFile(nameCandidate, { create: true, exclusive: true }, fileCreated, fileCreationError.bind(this)); |
| |
| function fileCreated(entry) |
| { |
| callback(entry.fullPath.substr(1)); |
| } |
| |
| /** |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function fileCreationError(error) |
| { |
| if (error.code === FileError.INVALID_MODIFICATION_ERR) { |
| dirEntryLoaded.call(this, dirEntry); |
| return; |
| } |
| |
| var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + " when testing if file exists '" + (this._path + "/" + path + "/" + nameCandidate) + "'"); |
| callback(null); |
| } |
| } |
| |
| /** |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function errorHandler(error) |
| { |
| var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); |
| var filePath = this._path + "/" + path; |
| if (nameCandidate) |
| filePath += "/" + nameCandidate; |
| console.error(errorMessage + " when getting content for file '" + (filePath) + "'"); |
| callback(null); |
| } |
| }, |
| |
| /** |
| * @param {string} path |
| */ |
| deleteFile: function(path) |
| { |
| this._domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this)); |
| |
| /** |
| * @param {!FileEntry} fileEntry |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function fileEntryLoaded(fileEntry) |
| { |
| fileEntry.remove(fileEntryRemoved, errorHandler.bind(this)); |
| } |
| |
| function fileEntryRemoved() |
| { |
| } |
| |
| /** |
| * @param {!FileError} error |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function errorHandler(error) |
| { |
| var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + " when deleting file '" + (this._path + "/" + path) + "'"); |
| } |
| }, |
| |
| /** |
| * @param {string} path |
| * @param {function(?string)} callback |
| */ |
| requestFileContent: function(path, callback) |
| { |
| this._domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this)); |
| |
| /** |
| * @param {!FileEntry} entry |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function fileEntryLoaded(entry) |
| { |
| entry.file(fileLoaded, errorHandler.bind(this)); |
| } |
| |
| /** |
| * @param {!Blob} file |
| */ |
| function fileLoaded(file) |
| { |
| var reader = new FileReader(); |
| reader.onloadend = readerLoadEnd; |
| if (WebInspector.IsolatedFileSystem.ImageExtensions.has(WebInspector.ParsedURL.extractExtension(path))) |
| reader.readAsDataURL(file); |
| else |
| reader.readAsText(file); |
| } |
| |
| /** |
| * @this {!FileReader} |
| */ |
| function readerLoadEnd() |
| { |
| /** @type {?string} */ |
| var string = null; |
| try { |
| string = /** @type {string} */ (this.result); |
| } catch (e) { |
| console.error("Can't read file: " + path + ": " + e); |
| } |
| callback(string); |
| } |
| |
| /** |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function errorHandler(error) |
| { |
| if (error.code === FileError.NOT_FOUND_ERR) { |
| callback(null); |
| return; |
| } |
| |
| var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + " when getting content for file '" + (this._path + "/" + path) + "'"); |
| callback(null); |
| } |
| }, |
| |
| /** |
| * @param {string} path |
| * @param {string} content |
| * @param {function()} callback |
| */ |
| setFileContent: function(path, content, callback) |
| { |
| WebInspector.userMetrics.actionTaken(WebInspector.UserMetrics.Action.FileSavedInWorkspace); |
| this._domFileSystem.root.getFile(path, { create: true }, fileEntryLoaded.bind(this), errorHandler.bind(this)); |
| |
| /** |
| * @param {!FileEntry} entry |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function fileEntryLoaded(entry) |
| { |
| entry.createWriter(fileWriterCreated.bind(this), errorHandler.bind(this)); |
| } |
| |
| /** |
| * @param {!FileWriter} fileWriter |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function fileWriterCreated(fileWriter) |
| { |
| fileWriter.onerror = errorHandler.bind(this); |
| fileWriter.onwriteend = fileWritten; |
| var blob = new Blob([content], { type: "text/plain" }); |
| fileWriter.write(blob); |
| |
| function fileWritten() |
| { |
| fileWriter.onwriteend = callback; |
| fileWriter.truncate(blob.size); |
| } |
| } |
| |
| /** |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function errorHandler(error) |
| { |
| var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + " when setting content for file '" + (this._path + "/" + path) + "'"); |
| callback(); |
| } |
| }, |
| |
| /** |
| * @param {string} path |
| * @param {string} newName |
| * @param {function(boolean, string=)} callback |
| */ |
| renameFile: function(path, newName, callback) |
| { |
| newName = newName ? newName.trim() : newName; |
| if (!newName || newName.indexOf("/") !== -1) { |
| callback(false); |
| return; |
| } |
| var fileEntry; |
| var dirEntry; |
| |
| this._domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this)); |
| |
| /** |
| * @param {!FileEntry} entry |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function fileEntryLoaded(entry) |
| { |
| if (entry.name === newName) { |
| callback(false); |
| return; |
| } |
| |
| fileEntry = entry; |
| fileEntry.getParent(dirEntryLoaded.bind(this), errorHandler.bind(this)); |
| } |
| |
| /** |
| * @param {!Entry} entry |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function dirEntryLoaded(entry) |
| { |
| dirEntry = entry; |
| dirEntry.getFile(newName, null, newFileEntryLoaded, newFileEntryLoadErrorHandler.bind(this)); |
| } |
| |
| /** |
| * @param {!FileEntry} entry |
| */ |
| function newFileEntryLoaded(entry) |
| { |
| callback(false); |
| } |
| |
| /** |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function newFileEntryLoadErrorHandler(error) |
| { |
| if (error.code !== FileError.NOT_FOUND_ERR) { |
| callback(false); |
| return; |
| } |
| fileEntry.moveTo(dirEntry, newName, fileRenamed, errorHandler.bind(this)); |
| } |
| |
| /** |
| * @param {!FileEntry} entry |
| */ |
| function fileRenamed(entry) |
| { |
| callback(true, entry.name); |
| } |
| |
| /** |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function errorHandler(error) |
| { |
| var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + " when renaming file '" + (this._path + "/" + path) + "' to '" + newName + "'"); |
| callback(false); |
| } |
| }, |
| |
| /** |
| * @param {!DirectoryEntry} dirEntry |
| * @param {function(!Array.<!FileEntry>)} callback |
| */ |
| _readDirectory: function(dirEntry, callback) |
| { |
| var dirReader = dirEntry.createReader(); |
| var entries = []; |
| |
| function innerCallback(results) |
| { |
| if (!results.length) { |
| callback(entries.sort()); |
| } else { |
| entries = entries.concat(toArray(results)); |
| dirReader.readEntries(innerCallback, errorHandler); |
| } |
| } |
| |
| function toArray(list) |
| { |
| return Array.prototype.slice.call(list || [], 0); |
| } |
| |
| dirReader.readEntries(innerCallback, errorHandler); |
| |
| function errorHandler(error) |
| { |
| var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + " when reading directory '" + dirEntry.fullPath + "'"); |
| callback([]); |
| } |
| }, |
| |
| /** |
| * @param {string} path |
| * @param {function(!Array.<!FileEntry>)} callback |
| */ |
| _requestEntries: function(path, callback) |
| { |
| this._domFileSystem.root.getDirectory(path, null, innerCallback.bind(this), errorHandler); |
| |
| /** |
| * @param {!DirectoryEntry} dirEntry |
| * @this {WebInspector.IsolatedFileSystem} |
| */ |
| function innerCallback(dirEntry) |
| { |
| this._readDirectory(dirEntry, callback); |
| } |
| |
| function errorHandler(error) |
| { |
| var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + " when requesting entry '" + path + "'"); |
| callback([]); |
| } |
| }, |
| |
| _saveExcludedFolders: function() |
| { |
| var settingValue = this._excludedFoldersSetting.get(); |
| settingValue[this._path] = Array.from(this._excludedFolders.values()); |
| this._excludedFoldersSetting.set(settingValue); |
| }, |
| |
| /** |
| * @param {string} path |
| */ |
| addExcludedFolder: function(path) |
| { |
| this._excludedFolders.add(path); |
| this._saveExcludedFolders(); |
| this._manager.dispatchEventToListeners(WebInspector.IsolatedFileSystemManager.Events.ExcludedFolderAdded, path); |
| }, |
| |
| /** |
| * @param {string} path |
| */ |
| removeExcludedFolder: function(path) |
| { |
| this._excludedFolders.delete(path); |
| this._saveExcludedFolders(); |
| this._manager.dispatchEventToListeners(WebInspector.IsolatedFileSystemManager.Events.ExcludedFolderRemoved, path); |
| }, |
| |
| fileSystemRemoved: function() |
| { |
| var settingValue = this._excludedFoldersSetting.get(); |
| delete settingValue[this._path]; |
| this._excludedFoldersSetting.set(settingValue); |
| }, |
| |
| /** |
| * @param {string} folderPath |
| * @return {boolean} |
| */ |
| _isFileExcluded: function(folderPath) |
| { |
| if (this._nonConfigurableExcludedFolders.has(folderPath) || this._excludedFolders.has(folderPath)) |
| return true; |
| var regex = this._manager.workspaceFolderExcludePatternSetting().asRegExp(); |
| return !!(regex && regex.test(folderPath)); |
| }, |
| |
| /** |
| * @return {!Set<string>} |
| */ |
| excludedFolders: function() |
| { |
| return this._excludedFolders; |
| }, |
| |
| /** |
| * @return {!Set<string>} |
| */ |
| nonConfigurableExcludedFolders: function() |
| { |
| return this._nonConfigurableExcludedFolders; |
| }, |
| |
| |
| /** |
| * @param {string} query |
| * @param {!WebInspector.Progress} progress |
| * @param {function(!Array.<string>)} callback |
| */ |
| searchInPath: function(query, progress, callback) |
| { |
| var requestId = this._manager.registerCallback(innerCallback); |
| InspectorFrontendHost.searchInPath(requestId, this._embedderPath, query); |
| |
| /** |
| * @param {!Array.<string>} files |
| */ |
| function innerCallback(files) |
| { |
| files = files.map(embedderPath => WebInspector.IsolatedFileSystemManager.normalizePath(embedderPath)); |
| progress.worked(1); |
| callback(files); |
| } |
| }, |
| |
| /** |
| * @param {!WebInspector.Progress} progress |
| */ |
| indexContent: function(progress) |
| { |
| progress.setTotalWork(1); |
| var requestId = this._manager.registerProgress(progress); |
| InspectorFrontendHost.indexPath(requestId, this._embedderPath); |
| } |
| } |