Home | History | Annotate | Download | only in workspace
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @param {!WebInspector.IsolatedFileSystemManager} isolatedFileSystemManager
     34  * @param {!WebInspector.Workspace} workspace
     35  */
     36 WebInspector.FileSystemWorkspaceBinding = function(isolatedFileSystemManager, workspace)
     37 {
     38     this._isolatedFileSystemManager = isolatedFileSystemManager;
     39     this._workspace = workspace;
     40     this._isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this);
     41     this._isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this);
     42     /** @type {!StringMap.<!WebInspector.FileSystemWorkspaceBinding.FileSystem>} */
     43     this._boundFileSystems = new StringMap();
     44 
     45     /** @type {!Object.<number, function(!Array.<string>)>} */
     46     this._callbacks = {};
     47     /** @type {!Object.<number, !WebInspector.Progress>} */
     48     this._progresses = {};
     49 
     50     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.IndexingTotalWorkCalculated, this._onIndexingTotalWorkCalculated, this);
     51     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.IndexingWorked, this._onIndexingWorked, this);
     52     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.IndexingDone, this._onIndexingDone, this);
     53     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.SearchCompleted, this._onSearchCompleted, this);
     54 }
     55 
     56 WebInspector.FileSystemWorkspaceBinding._scriptExtensions = ["js", "java", "coffee", "ts", "dart"].keySet();
     57 WebInspector.FileSystemWorkspaceBinding._styleSheetExtensions = ["css", "scss", "sass", "less"].keySet();
     58 WebInspector.FileSystemWorkspaceBinding._documentExtensions = ["htm", "html", "asp", "aspx", "phtml", "jsp"].keySet();
     59 
     60 WebInspector.FileSystemWorkspaceBinding._lastRequestId = 0;
     61 
     62 /**
     63  * @param {string} fileSystemPath
     64  * @return {string}
     65  */
     66 WebInspector.FileSystemWorkspaceBinding.projectId = function(fileSystemPath)
     67 {
     68     return "filesystem:" + fileSystemPath;
     69 }
     70 
     71 WebInspector.FileSystemWorkspaceBinding.prototype = {
     72     /**
     73      * @param {!WebInspector.Event} event
     74      */
     75     _fileSystemAdded: function(event)
     76     {
     77         var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
     78         var boundFileSystem = new WebInspector.FileSystemWorkspaceBinding.FileSystem(this, fileSystem, this._workspace);
     79         this._boundFileSystems.set(fileSystem.normalizedPath(), boundFileSystem);
     80     },
     81 
     82     /**
     83      * @param {!WebInspector.Event} event
     84      */
     85     _fileSystemRemoved: function(event)
     86     {
     87         var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
     88         var boundFileSystem = this._boundFileSystems.get(fileSystem.normalizedPath());
     89         boundFileSystem.dispose();
     90         this._boundFileSystems.remove(fileSystem.normalizedPath());
     91     },
     92 
     93     /**
     94      * @param {string} projectId
     95      * @return {string}
     96      */
     97     fileSystemPath: function(projectId)
     98     {
     99         var fileSystemPath = projectId.substr("filesystem:".length);
    100         var normalizedPath = WebInspector.IsolatedFileSystem.normalizePath(fileSystemPath);
    101         var boundFileSystem = this._boundFileSystems.get(normalizedPath);
    102         return projectId.substr("filesystem:".length);
    103     },
    104 
    105     /**
    106      * @return {number}
    107      */
    108     _nextId: function()
    109     {
    110         return ++WebInspector.FileSystemWorkspaceBinding._lastRequestId;
    111     },
    112 
    113     /**
    114      * @param {function(!Array.<string>)} callback
    115      * @return {number}
    116      */
    117     registerCallback: function(callback)
    118     {
    119         var requestId = this._nextId();
    120         this._callbacks[requestId] = callback;
    121         return requestId;
    122     },
    123 
    124     /**
    125      * @param {!WebInspector.Progress} progress
    126      * @return {number}
    127      */
    128     registerProgress: function(progress)
    129     {
    130         var requestId = this._nextId();
    131         this._progresses[requestId] = progress;
    132         return requestId;
    133     },
    134 
    135     /**
    136      * @param {!WebInspector.Event} event
    137      */
    138     _onIndexingTotalWorkCalculated: function(event)
    139     {
    140         var requestId = /** @type {number} */ (event.data["requestId"]);
    141         var fileSystemPath = /** @type {string} */ (event.data["fileSystemPath"]);
    142         var totalWork = /** @type {number} */ (event.data["totalWork"]);
    143 
    144         var progress = this._progresses[requestId];
    145         if (!progress)
    146             return;
    147         progress.setTotalWork(totalWork);
    148     },
    149 
    150     /**
    151      * @param {!WebInspector.Event} event
    152      */
    153     _onIndexingWorked: function(event)
    154     {
    155         var requestId = /** @type {number} */ (event.data["requestId"]);
    156         var fileSystemPath = /** @type {string} */ (event.data["fileSystemPath"]);
    157         var worked = /** @type {number} */ (event.data["worked"]);
    158 
    159         var progress = this._progresses[requestId];
    160         if (!progress)
    161             return;
    162         progress.worked(worked);
    163     },
    164 
    165     /**
    166      * @param {!WebInspector.Event} event
    167      */
    168     _onIndexingDone: function(event)
    169     {
    170         var requestId = /** @type {number} */ (event.data["requestId"]);
    171         var fileSystemPath = /** @type {string} */ (event.data["fileSystemPath"]);
    172 
    173         var progress = this._progresses[requestId];
    174         if (!progress)
    175             return;
    176         progress.done();
    177         delete this._progresses[requestId];
    178     },
    179 
    180     /**
    181      * @param {!WebInspector.Event} event
    182      */
    183     _onSearchCompleted: function(event)
    184     {
    185         var requestId = /** @type {number} */ (event.data["requestId"]);
    186         var fileSystemPath = /** @type {string} */ (event.data["fileSystemPath"]);
    187         var files = /** @type {!Array.<string>} */ (event.data["files"]);
    188 
    189         var callback = this._callbacks[requestId];
    190         if (!callback)
    191             return;
    192         callback.call(null, files);
    193         delete this._callbacks[requestId];
    194     },
    195 }
    196 
    197 /**
    198  * @constructor
    199  * @implements {WebInspector.ProjectDelegate}
    200  * @param {!WebInspector.IsolatedFileSystem} isolatedFileSystem
    201  * @param {!WebInspector.Workspace} workspace
    202  * @param {!WebInspector.FileSystemWorkspaceBinding} fileSystemWorkspaceBinding
    203  */
    204 WebInspector.FileSystemWorkspaceBinding.FileSystem = function(fileSystemWorkspaceBinding, isolatedFileSystem, workspace)
    205 {
    206     this._fileSystemWorkspaceBinding = fileSystemWorkspaceBinding;
    207     this._fileSystem = isolatedFileSystem;
    208     this._fileSystemURL = "file://" + this._fileSystem.normalizedPath() + "/";
    209     this._workspace = workspace;
    210 
    211     this._projectId = WebInspector.FileSystemWorkspaceBinding.projectId(this._fileSystem.path());
    212     console.assert(!this._workspace.project(this._projectId));
    213     this._projectStore = this._workspace.addProject(this._projectId, this);
    214     this.populate();
    215 }
    216 
    217 WebInspector.FileSystemWorkspaceBinding.FileSystem.prototype = {
    218     /**
    219      * @return {string}
    220      */
    221     type: function()
    222     {
    223         return WebInspector.projectTypes.FileSystem;
    224     },
    225 
    226     /**
    227      * @return {string}
    228      */
    229     fileSystemPath: function()
    230     {
    231         return this._fileSystem.path();
    232     },
    233 
    234     /**
    235      * @return {string}
    236      */
    237     displayName: function()
    238     {
    239         var normalizedPath = this._fileSystem.normalizedPath();
    240         return normalizedPath.substr(normalizedPath.lastIndexOf("/") + 1);
    241     },
    242 
    243     /**
    244      * @return {string}
    245      */
    246     url: function()
    247     {
    248         // Overriddden by subclasses
    249         return "";
    250     },
    251 
    252     /**
    253      * @param {string} path
    254      * @return {string}
    255      */
    256     _filePathForPath: function(path)
    257     {
    258         return "/" + path;
    259     },
    260 
    261     /**
    262      * @param {string} path
    263      * @param {function(?string)} callback
    264      */
    265     requestFileContent: function(path, callback)
    266     {
    267         var filePath = this._filePathForPath(path);
    268         this._fileSystem.requestFileContent(filePath, callback);
    269     },
    270 
    271     /**
    272      * @param {string} path
    273      * @param {function(?Date, ?number)} callback
    274      */
    275     requestMetadata: function(path, callback)
    276     {
    277         var filePath = this._filePathForPath(path);
    278         this._fileSystem.requestMetadata(filePath, callback);
    279     },
    280 
    281     /**
    282      * @return {boolean}
    283      */
    284     canSetFileContent: function()
    285     {
    286         return true;
    287     },
    288 
    289     /**
    290      * @param {string} path
    291      * @param {string} newContent
    292      * @param {function(?string)} callback
    293      */
    294     setFileContent: function(path, newContent, callback)
    295     {
    296         var filePath = this._filePathForPath(path);
    297         this._fileSystem.setFileContent(filePath, newContent, callback.bind(this, ""));
    298     },
    299 
    300     /**
    301      * @return {boolean}
    302      */
    303     canRename: function()
    304     {
    305         return true;
    306     },
    307 
    308     /**
    309      * @param {string} path
    310      * @param {string} newName
    311      * @param {function(boolean, string=, string=, string=, !WebInspector.ResourceType=)} callback
    312      */
    313     rename: function(path, newName, callback)
    314     {
    315         var filePath = this._filePathForPath(path);
    316         this._fileSystem.renameFile(filePath, newName, innerCallback.bind(this));
    317 
    318         /**
    319          * @param {boolean} success
    320          * @param {string=} newName
    321          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
    322          */
    323         function innerCallback(success, newName)
    324         {
    325             if (!success) {
    326                 callback(false, newName);
    327                 return;
    328             }
    329             var validNewName = /** @type {string} */ (newName);
    330             console.assert(validNewName);
    331             var slash = filePath.lastIndexOf("/");
    332             var parentPath = filePath.substring(0, slash);
    333             filePath = parentPath + "/" + validNewName;
    334             filePath = filePath.substr(1);
    335             var newURL = this._workspace.urlForPath(this._fileSystem.path(), filePath);
    336             var extension = this._extensionForPath(validNewName);
    337             var newOriginURL = this._fileSystemURL + filePath
    338             var newContentType = this._contentTypeForExtension(extension);
    339             callback(true, validNewName, newURL, newOriginURL, newContentType);
    340         }
    341     },
    342 
    343     /**
    344      * @param {string} path
    345      * @param {string} query
    346      * @param {boolean} caseSensitive
    347      * @param {boolean} isRegex
    348      * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
    349      */
    350     searchInFileContent: function(path, query, caseSensitive, isRegex, callback)
    351     {
    352         var filePath = this._filePathForPath(path);
    353         this._fileSystem.requestFileContent(filePath, contentCallback);
    354 
    355         /**
    356          * @param {?string} content
    357          */
    358         function contentCallback(content)
    359         {
    360             var result = [];
    361             if (content !== null)
    362                 result = WebInspector.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex);
    363             callback(result);
    364         }
    365     },
    366 
    367     /**
    368      * @param {!WebInspector.ProjectSearchConfig} searchConfig
    369      * @param {!Array.<string>} filesMathingFileQuery
    370      * @param {!WebInspector.Progress} progress
    371      * @param {function(!Array.<string>)} callback
    372      */
    373     findFilesMatchingSearchRequest: function(searchConfig, filesMathingFileQuery, progress, callback)
    374     {
    375         var result = filesMathingFileQuery;
    376         var queriesToRun = searchConfig.queries().slice();
    377         if (!queriesToRun.length)
    378             queriesToRun.push("");
    379         progress.setTotalWork(queriesToRun.length);
    380         searchNextQuery.call(this);
    381 
    382         /**
    383          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
    384          */
    385         function searchNextQuery()
    386         {
    387             if (!queriesToRun.length) {
    388                 progress.done();
    389                 callback(result);
    390                 return;
    391             }
    392             var query = queriesToRun.shift();
    393             this._searchInPath(searchConfig.isRegex() ? "" : query, progress, innerCallback.bind(this));
    394         }
    395 
    396         /**
    397          * @param {!Array.<string>} files
    398          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
    399          */
    400         function innerCallback(files)
    401         {
    402             files = files.sort();
    403             progress.worked(1);
    404             result = result.intersectOrdered(files, String.naturalOrderComparator);
    405             searchNextQuery.call(this);
    406         }
    407     },
    408 
    409     /**
    410      * @param {string} query
    411      * @param {!WebInspector.Progress} progress
    412      * @param {function(!Array.<string>)} callback
    413      */
    414     _searchInPath: function(query, progress, callback)
    415     {
    416         var requestId = this._fileSystemWorkspaceBinding.registerCallback(innerCallback.bind(this));
    417         InspectorFrontendHost.searchInPath(requestId, this._fileSystem.path(), query);
    418 
    419         /**
    420          * @param {!Array.<string>} files
    421          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
    422          */
    423         function innerCallback(files)
    424         {
    425             /**
    426              * @param {string} fullPath
    427              * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
    428              */
    429             function trimAndNormalizeFileSystemPath(fullPath)
    430             {
    431                 var trimmedPath = fullPath.substr(this._fileSystem.path().length + 1);
    432                 if (WebInspector.isWin())
    433                     trimmedPath = trimmedPath.replace(/\\/g, "/");
    434                 return trimmedPath;
    435             }
    436 
    437             files = files.map(trimAndNormalizeFileSystemPath.bind(this));
    438             progress.worked(1);
    439             callback(files);
    440         }
    441     },
    442 
    443     /**
    444      * @param {!WebInspector.Progress} progress
    445      */
    446     indexContent: function(progress)
    447     {
    448         progress.setTotalWork(1);
    449         var requestId = this._fileSystemWorkspaceBinding.registerProgress(progress);
    450         progress.addEventListener(WebInspector.Progress.Events.Canceled, this._indexingCanceled.bind(this, requestId));
    451         InspectorFrontendHost.indexPath(requestId, this._fileSystem.path());
    452     },
    453 
    454     /**
    455      * @param {number} requestId
    456      */
    457     _indexingCanceled: function(requestId)
    458     {
    459         InspectorFrontendHost.stopIndexing(requestId);
    460     },
    461 
    462     /**
    463      * @param {string} path
    464      * @return {string}
    465      */
    466     _extensionForPath: function(path)
    467     {
    468         var extensionIndex = path.lastIndexOf(".");
    469         if (extensionIndex === -1)
    470             return "";
    471         return path.substring(extensionIndex + 1).toLowerCase();
    472     },
    473 
    474     /**
    475      * @param {string} extension
    476      * @return {!WebInspector.ResourceType}
    477      */
    478     _contentTypeForExtension: function(extension)
    479     {
    480         if (WebInspector.FileSystemWorkspaceBinding._scriptExtensions[extension])
    481             return WebInspector.resourceTypes.Script;
    482         if (WebInspector.FileSystemWorkspaceBinding._styleSheetExtensions[extension])
    483             return WebInspector.resourceTypes.Stylesheet;
    484         if (WebInspector.FileSystemWorkspaceBinding._documentExtensions[extension])
    485             return WebInspector.resourceTypes.Document;
    486         return WebInspector.resourceTypes.Other;
    487     },
    488 
    489     populate: function()
    490     {
    491         this._fileSystem.requestFilesRecursive("", this._addFile.bind(this));
    492     },
    493 
    494     /**
    495      * @param {string} path
    496      * @param {function()=} callback
    497      */
    498     refresh: function(path, callback)
    499     {
    500         this._fileSystem.requestFilesRecursive(path, this._addFile.bind(this), callback);
    501     },
    502 
    503     /**
    504      * @param {string} path
    505      */
    506     excludeFolder: function(path)
    507     {
    508         this._fileSystemWorkspaceBinding._isolatedFileSystemManager.mapping().addExcludedFolder(this._fileSystem.path(), path);
    509     },
    510 
    511     /**
    512      * @param {string} path
    513      * @param {?string} name
    514      * @param {string} content
    515      * @param {function(?string)} callback
    516      */
    517     createFile: function(path, name, content, callback)
    518     {
    519         this._fileSystem.createFile(path, name, innerCallback.bind(this));
    520         var createFilePath;
    521 
    522         /**
    523          * @param {?string} filePath
    524          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
    525          */
    526         function innerCallback(filePath)
    527         {
    528             if (!filePath) {
    529                 callback(null);
    530                 return;
    531             }
    532             createFilePath = filePath;
    533             if (!content) {
    534                 contentSet.call(this);
    535                 return;
    536             }
    537             this._fileSystem.setFileContent(filePath, content, contentSet.bind(this));
    538         }
    539 
    540         /**
    541          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
    542          */
    543         function contentSet()
    544         {
    545             this._addFile(createFilePath);
    546             callback(createFilePath);
    547         }
    548     },
    549 
    550     /**
    551      * @param {string} path
    552      */
    553     deleteFile: function(path)
    554     {
    555         this._fileSystem.deleteFile(path);
    556         this._removeFile(path);
    557     },
    558 
    559     remove: function()
    560     {
    561         this._fileSystemWorkspaceBinding._isolatedFileSystemManager.removeFileSystem(this._fileSystem.path());
    562     },
    563 
    564     /**
    565      * @param {string} filePath
    566      */
    567     _addFile: function(filePath)
    568     {
    569         if (!filePath)
    570             console.assert(false);
    571 
    572         var slash = filePath.lastIndexOf("/");
    573         var parentPath = filePath.substring(0, slash);
    574         var name = filePath.substring(slash + 1);
    575 
    576         var url = this._workspace.urlForPath(this._fileSystem.path(), filePath);
    577         var extension = this._extensionForPath(name);
    578         var contentType = this._contentTypeForExtension(extension);
    579 
    580         var fileDescriptor = new WebInspector.FileDescriptor(parentPath, name, this._fileSystemURL + filePath, url, contentType);
    581         this._projectStore.addFile(fileDescriptor);
    582     },
    583 
    584     /**
    585      * @param {string} path
    586      */
    587     _removeFile: function(path)
    588     {
    589         this._projectStore.removeFile(path);
    590     },
    591 
    592     dispose: function()
    593     {
    594         this._workspace.removeProject(this._projectId);
    595     }
    596 }
    597 
    598 /**
    599  * @type {!WebInspector.FileSystemWorkspaceBinding}
    600  */
    601 WebInspector.fileSystemWorkspaceBinding;
    602