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