Home | History | Annotate | Download | only in front_end
      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  * @implements {WebInspector.ProjectDelegate}
     34  * @extends {WebInspector.Object}
     35  * @param {WebInspector.IsolatedFileSystem} isolatedFileSystem
     36  * @param {WebInspector.Workspace} workspace
     37  */
     38 WebInspector.FileSystemProjectDelegate = function(isolatedFileSystem, workspace)
     39 {
     40     this._fileSystem = isolatedFileSystem;
     41     this._normalizedFileSystemPath = this._fileSystem.path();
     42     if (WebInspector.isWin())
     43         this._normalizedFileSystemPath = this._normalizedFileSystemPath.replace(/\\/g, "/");
     44     this._fileSystemURL = "file://" + this._normalizedFileSystemPath + "/";
     45     this._workspace = workspace;
     46     /** @type {Object.<number, function(Array.<string>)>} */
     47     this._searchCallbacks = {};
     48     /** @type {Object.<number, function()>} */
     49     this._indexingCallbacks = {};
     50     /** @type {Object.<number, WebInspector.Progress>} */
     51     this._indexingProgresses = {};
     52 }
     53 
     54 WebInspector.FileSystemProjectDelegate._scriptExtensions = ["js", "java", "coffee", "ts", "dart"].keySet();
     55 WebInspector.FileSystemProjectDelegate._styleSheetExtensions = ["css", "scss", "sass", "less"].keySet();
     56 WebInspector.FileSystemProjectDelegate._documentExtensions = ["htm", "html", "asp", "aspx", "phtml", "jsp"].keySet();
     57 
     58 WebInspector.FileSystemProjectDelegate.projectId = function(fileSystemPath)
     59 {
     60     return "filesystem:" + fileSystemPath;
     61 }
     62 
     63 WebInspector.FileSystemProjectDelegate._lastRequestId = 0;
     64 
     65 WebInspector.FileSystemProjectDelegate.prototype = {
     66     /**
     67      * @return {string}
     68      */
     69     id: function()
     70     {
     71         return WebInspector.FileSystemProjectDelegate.projectId(this._fileSystem.path());
     72     },
     73 
     74     /**
     75      * @return {string}
     76      */
     77     type: function()
     78     {
     79         return WebInspector.projectTypes.FileSystem;
     80     },
     81 
     82     /**
     83      * @return {string}
     84      */
     85     fileSystemPath: function()
     86     {
     87         return this._fileSystem.path();
     88     },
     89 
     90     /**
     91      * @return {string}
     92      */
     93     displayName: function()
     94     {
     95         return this._normalizedFileSystemPath.substr(this._normalizedFileSystemPath.lastIndexOf("/") + 1);
     96     },
     97 
     98     /**
     99      * @param {string} path
    100      * @return {string}
    101      */
    102     _filePathForPath: function(path)
    103     {
    104         return "/" + path;
    105     },
    106 
    107     /**
    108      * @param {string} path
    109      * @param {function(?string,boolean,string)} callback
    110      */
    111     requestFileContent: function(path, callback)
    112     {
    113         var filePath = this._filePathForPath(path);
    114         this._fileSystem.requestFileContent(filePath, innerCallback.bind(this));
    115 
    116         /**
    117          * @param {?string} content
    118          */
    119         function innerCallback(content)
    120         {
    121             var extension = this._extensionForPath(path);
    122             var mimeType = WebInspector.ResourceType.mimeTypesForExtensions[extension];
    123             callback(content, false, mimeType);
    124         }
    125     },
    126 
    127     /**
    128      * @param {string} path
    129      * @param {function(?Date, ?number)} callback
    130      */
    131     requestMetadata: function(path, callback)
    132     {
    133         var filePath = this._filePathForPath(path);
    134         this._fileSystem.requestMetadata(filePath, callback);
    135     },
    136 
    137     /**
    138      * @return {boolean}
    139      */
    140     canSetFileContent: function()
    141     {
    142         return true;
    143     },
    144 
    145     /**
    146      * @param {string} path
    147      * @param {string} newContent
    148      * @param {function(?string)} callback
    149      */
    150     setFileContent: function(path, newContent, callback)
    151     {
    152         var filePath = this._filePathForPath(path);
    153         this._fileSystem.setFileContent(filePath, newContent, callback.bind(this, ""));
    154     },
    155 
    156     /**
    157      * @return {boolean}
    158      */
    159     canRename: function()
    160     {
    161         return true;
    162     },
    163 
    164     /**
    165      * @param {string} path
    166      * @param {string} newName
    167      * @param {function(boolean, string=)} callback
    168      */
    169     rename: function(path, newName, callback)
    170     {
    171         var filePath = this._filePathForPath(path);
    172         this._fileSystem.renameFile(filePath, newName, callback);
    173     },
    174 
    175     /**
    176      * @param {string} path
    177      * @param {string} query
    178      * @param {boolean} caseSensitive
    179      * @param {boolean} isRegex
    180      * @param {function(Array.<WebInspector.ContentProvider.SearchMatch>)} callback
    181      */
    182     searchInFileContent: function(path, query, caseSensitive, isRegex, callback)
    183     {
    184         var filePath = this._filePathForPath(path);
    185         this._fileSystem.requestFileContent(filePath, contentCallback.bind(this));
    186 
    187         /**
    188          * @param {?string} content
    189          */
    190         function contentCallback(content)
    191         {
    192             var result = [];
    193             if (content !== null)
    194                 result = WebInspector.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex);
    195             callback(result);
    196         }
    197     },
    198 
    199     /**
    200      * @param {string} query
    201      * @param {boolean} caseSensitive
    202      * @param {boolean} isRegex
    203      * @param {WebInspector.Progress} progress
    204      * @param {function(StringMap)} callback
    205      */
    206     searchInContent: function(query, caseSensitive, isRegex, progress, callback)
    207     {
    208         var requestId = ++WebInspector.FileSystemProjectDelegate._lastRequestId;
    209         this._searchCallbacks[requestId] = innerCallback.bind(this);
    210         InspectorFrontendHost.searchInPath(requestId, this._fileSystem.path(), isRegex ? "" : query);
    211 
    212         function innerCallback(files)
    213         {
    214             function trimAndNormalizeFileSystemPath(fullPath)
    215             {
    216                 var trimmedPath = fullPath.substr(this._fileSystem.path().length + 1);
    217                 if (WebInspector.isWin())
    218                     trimmedPath = trimmedPath.replace(/\\/g, "/");
    219                 return trimmedPath;
    220             }
    221 
    222             files = files.map(trimAndNormalizeFileSystemPath.bind(this));
    223             var result = new StringMap();
    224             progress.setTotalWork(files.length);
    225             if (files.length === 0) {
    226                 progress.done();
    227                 callback(result);
    228                 return;
    229             }
    230 
    231             var fileIndex = 0;
    232             var maxFileContentRequests = 20;
    233             var callbacksLeft = 0;
    234 
    235             function searchInNextFiles()
    236             {
    237                 for (; callbacksLeft < maxFileContentRequests; ++callbacksLeft) {
    238                     if (fileIndex >= files.length)
    239                         break;
    240                     var path = files[fileIndex++];
    241                     var filePath = this._filePathForPath(path);
    242                     this._fileSystem.requestFileContent(filePath, contentCallback.bind(this, path));
    243                 }
    244             }
    245 
    246             searchInNextFiles.call(this);
    247 
    248             /**
    249              * @param {string} path
    250              * @param {?string} content
    251              */
    252             function contentCallback(path, content)
    253             {
    254                 var matches = [];
    255                 if (content !== null)
    256                     matches = WebInspector.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex);
    257 
    258                 result.put(path, matches);
    259                 progress.worked(1);
    260 
    261                 --callbacksLeft;
    262                 if (fileIndex < files.length) {
    263                     searchInNextFiles.call(this);
    264                 } else {
    265                     if (callbacksLeft)
    266                         return;
    267                     progress.done();
    268                     callback(result);
    269                 }
    270             }
    271         }
    272     },
    273 
    274     /**
    275      * @param {number} requestId
    276      * @param {Array.<string>} files
    277      */
    278     searchCompleted: function(requestId, files)
    279     {
    280         if (!this._searchCallbacks[requestId])
    281             return;
    282         var callback = this._searchCallbacks[requestId];
    283         delete this._searchCallbacks[requestId];
    284         callback(files);
    285     },
    286 
    287     /**
    288      * @param {WebInspector.Progress} progress
    289      * @param {function()} callback
    290      */
    291     indexContent: function(progress, callback)
    292     {
    293         var requestId = ++WebInspector.FileSystemProjectDelegate._lastRequestId;
    294         this._indexingCallbacks[requestId] = callback;
    295         this._indexingProgresses[requestId] = progress;
    296         progress.setTotalWork(1);
    297         progress.addEventListener(WebInspector.Progress.Events.Canceled, this._indexingCanceled.bind(this, requestId));
    298         InspectorFrontendHost.indexPath(requestId, this._fileSystem.path());
    299     },
    300 
    301     /**
    302      * @param {number} requestId
    303      */
    304     _indexingCanceled: function(requestId)
    305     {
    306         if (!this._indexingProgresses[requestId])
    307             return;
    308         InspectorFrontendHost.stopIndexing(requestId);
    309         delete this._indexingProgresses[requestId];
    310         delete this._indexingCallbacks[requestId];
    311     },
    312 
    313     /**
    314      * @param {number} requestId
    315      * @param {number} totalWork
    316      */
    317     indexingTotalWorkCalculated: function(requestId, totalWork)
    318     {
    319         if (!this._indexingProgresses[requestId])
    320             return;
    321         var progress = this._indexingProgresses[requestId];
    322         progress.setTotalWork(totalWork);
    323     },
    324 
    325     /**
    326      * @param {number} requestId
    327      * @param {number} worked
    328      */
    329     indexingWorked: function(requestId, worked)
    330     {
    331         if (!this._indexingProgresses[requestId])
    332             return;
    333         var progress = this._indexingProgresses[requestId];
    334         progress.worked(worked);
    335     },
    336 
    337     /**
    338      * @param {number} requestId
    339      */
    340     indexingDone: function(requestId)
    341     {
    342         if (!this._indexingProgresses[requestId])
    343             return;
    344         var progress = this._indexingProgresses[requestId];
    345         var callback = this._indexingCallbacks[requestId];
    346         delete this._indexingProgresses[requestId];
    347         delete this._indexingCallbacks[requestId];
    348         progress.done();
    349         callback.call();
    350     },
    351 
    352     /**
    353      * @param {string} path
    354      * @return {string}
    355      */
    356     _extensionForPath: function(path)
    357     {
    358         var extensionIndex = path.lastIndexOf(".");
    359         if (extensionIndex === -1)
    360             return "";
    361         return path.substring(extensionIndex + 1).toLowerCase();
    362     },
    363 
    364     /**
    365      * @param {string} extension
    366      * @return {WebInspector.ResourceType}
    367      */
    368     _contentTypeForExtension: function(extension)
    369     {
    370         if (WebInspector.FileSystemProjectDelegate._scriptExtensions[extension])
    371             return WebInspector.resourceTypes.Script;
    372         if (WebInspector.FileSystemProjectDelegate._styleSheetExtensions[extension])
    373             return WebInspector.resourceTypes.Stylesheet;
    374         if (WebInspector.FileSystemProjectDelegate._documentExtensions[extension])
    375             return WebInspector.resourceTypes.Document;
    376         return WebInspector.resourceTypes.Other;
    377     },
    378 
    379     populate: function()
    380     {
    381         this._fileSystem.requestFilesRecursive("", this._addFile.bind(this));
    382     },
    383 
    384     /**
    385      * @param {string} path
    386      */
    387     refresh: function(path)
    388     {
    389         this._fileSystem.requestFilesRecursive(path, this._addFile.bind(this));
    390     },
    391 
    392     /**
    393      * @param {string} path
    394      * @param {?string} name
    395      * @param {function(?string)} callback
    396      */
    397     createFile: function(path, name, callback)
    398     {
    399         this._fileSystem.createFile(path, name, innerCallback.bind(this));
    400 
    401         function innerCallback(filePath)
    402         {
    403             this._addFile(filePath);
    404             callback(filePath);
    405         }
    406     },
    407 
    408     /**
    409      * @param {string} path
    410      */
    411     deleteFile: function(path)
    412     {
    413         this._fileSystem.deleteFile(path);
    414         this._removeFile(path);
    415     },
    416 
    417     remove: function()
    418     {
    419         WebInspector.isolatedFileSystemManager.removeFileSystem(this._fileSystem.path());
    420     },
    421 
    422     /**
    423      * @param {string} filePath
    424      */
    425     _addFile: function(filePath)
    426     {
    427         if (!filePath)
    428             console.assert(false);
    429 
    430         var slash = filePath.lastIndexOf("/");
    431         var parentPath = filePath.substring(0, slash);
    432         var name = filePath.substring(slash + 1);
    433 
    434         var url = this._workspace.urlForPath(this._fileSystem.path(), filePath);
    435         var extension = this._extensionForPath(name);
    436         var contentType = this._contentTypeForExtension(extension);
    437 
    438         var fileDescriptor = new WebInspector.FileDescriptor(parentPath, name, this._fileSystemURL + filePath, url, contentType, true);
    439         this.dispatchEventToListeners(WebInspector.ProjectDelegate.Events.FileAdded, fileDescriptor);
    440     },
    441 
    442     /**
    443      * @param {string} path
    444      */
    445     _removeFile: function(path)
    446     {
    447         this.dispatchEventToListeners(WebInspector.ProjectDelegate.Events.FileRemoved, path);
    448     },
    449 
    450     reset: function()
    451     {
    452         this.dispatchEventToListeners(WebInspector.ProjectDelegate.Events.Reset, null);
    453     },
    454 
    455     __proto__: WebInspector.Object.prototype
    456 }
    457 
    458 /**
    459  * @type {?WebInspector.FileSystemProjectDelegate}
    460  */
    461 WebInspector.fileSystemProjectDelegate = null;
    462 
    463 /**
    464  * @constructor
    465  * @param {WebInspector.IsolatedFileSystemManager} isolatedFileSystemManager
    466  * @param {WebInspector.Workspace} workspace
    467  */
    468 WebInspector.FileSystemWorkspaceProvider = function(isolatedFileSystemManager, workspace)
    469 {
    470     this._isolatedFileSystemManager = isolatedFileSystemManager;
    471     this._workspace = workspace;
    472     this._isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this);
    473     this._isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this);
    474     this._projectDelegates = {};
    475 }
    476 
    477 WebInspector.FileSystemWorkspaceProvider.prototype = {
    478     /**
    479      * @param {WebInspector.Event} event
    480      */
    481     _fileSystemAdded: function(event)
    482     {
    483         var fileSystem = /** @type {WebInspector.IsolatedFileSystem} */ (event.data);
    484         var projectId = WebInspector.FileSystemProjectDelegate.projectId(fileSystem.path());
    485         var projectDelegate = new WebInspector.FileSystemProjectDelegate(fileSystem, this._workspace)
    486         this._projectDelegates[projectDelegate.id()] = projectDelegate;
    487         console.assert(!this._workspace.project(projectDelegate.id()));
    488         this._workspace.addProject(projectDelegate);
    489         projectDelegate.populate();
    490     },
    491 
    492     /**
    493      * @param {WebInspector.Event} event
    494      */
    495     _fileSystemRemoved: function(event)
    496     {
    497         var fileSystem = /** @type {WebInspector.IsolatedFileSystem} */ (event.data);
    498         var projectId = WebInspector.FileSystemProjectDelegate.projectId(fileSystem.path());
    499         this._workspace.removeProject(projectId);
    500         delete this._projectDelegates[projectId];
    501     },
    502 
    503     /**
    504      * @param {WebInspector.UISourceCode} uiSourceCode
    505      */
    506     fileSystemPath: function(uiSourceCode)
    507     {
    508         var projectDelegate = this._projectDelegates[uiSourceCode.project().id()];
    509         return projectDelegate.fileSystemPath();
    510     },
    511 
    512     /**
    513      * @param {WebInspector.FileSystemProjectDelegate} fileSystemPath
    514      */
    515     delegate: function(fileSystemPath)
    516     {
    517         var projectId = WebInspector.FileSystemProjectDelegate.projectId(fileSystemPath);
    518         return this._projectDelegates[projectId];
    519     }
    520 }
    521 
    522 /**
    523  * @type {?WebInspector.FileSystemWorkspaceProvider}
    524  */
    525 WebInspector.fileSystemWorkspaceProvider = null;
    526