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 */ 36WebInspector.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 51WebInspector.FileSystemWorkspaceBinding._scriptExtensions = ["js", "java", "coffee", "ts", "dart"].keySet(); 52WebInspector.FileSystemWorkspaceBinding._styleSheetExtensions = ["css", "scss", "sass", "less"].keySet(); 53WebInspector.FileSystemWorkspaceBinding._documentExtensions = ["htm", "html", "asp", "aspx", "phtml", "jsp"].keySet(); 54 55WebInspector.FileSystemWorkspaceBinding._lastRequestId = 0; 56 57/** 58 * @param {string} fileSystemPath 59 * @return {string} 60 */ 61WebInspector.FileSystemWorkspaceBinding.projectId = function(fileSystemPath) 62{ 63 return "filesystem:" + fileSystemPath; 64} 65 66WebInspector.FileSystemWorkspaceBinding.prototype = { 67 /** 68 * @param {!WebInspector.Event} event 69 */ 70 _fileSystemAdded: function(event) 71 { 72 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data); 73 var boundFileSystem = new WebInspector.FileSystemWorkspaceBinding.FileSystem(this, fileSystem, this._workspace); 74 this._boundFileSystems.put(fileSystem.normalizedPath(), boundFileSystem); 75 }, 76 77 /** 78 * @param {!WebInspector.Event} event 79 */ 80 _fileSystemRemoved: function(event) 81 { 82 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data); 83 var boundFileSystem = this._boundFileSystems.get(fileSystem.normalizedPath()); 84 boundFileSystem.dispose(); 85 this._boundFileSystems.remove(fileSystem.normalizedPath()); 86 }, 87 88 /** 89 * @param {string} projectId 90 * @return {string} 91 */ 92 fileSystemPath: function(projectId) 93 { 94 var fileSystemPath = projectId.substr("filesystem:".length); 95 var normalizedPath = WebInspector.IsolatedFileSystem.normalizePath(fileSystemPath); 96 var boundFileSystem = this._boundFileSystems.get(normalizedPath); 97 return projectId.substr("filesystem:".length); 98 }, 99 100 /** 101 * @return {number} 102 */ 103 _nextId: function() 104 { 105 return ++WebInspector.FileSystemWorkspaceBinding._lastRequestId; 106 }, 107 108 /** 109 * @param {function(!Array.<string>)} callback 110 * @return {number} 111 */ 112 registerCallback: function(callback) 113 { 114 var requestId = this._nextId(); 115 this._callbacks[requestId] = callback; 116 return requestId; 117 }, 118 119 /** 120 * @param {!WebInspector.Progress} progress 121 * @return {number} 122 */ 123 registerProgress: function(progress) 124 { 125 var requestId = this._nextId(); 126 this._progresses[requestId] = progress; 127 return requestId; 128 }, 129 130 /** 131 * @param {number} requestId 132 * @param {string} fileSystemPath 133 * @param {number} totalWork 134 */ 135 indexingTotalWorkCalculated: function(requestId, fileSystemPath, totalWork) 136 { 137 var progress = this._progresses[requestId]; 138 if (!progress) 139 return; 140 progress.setTotalWork(totalWork); 141 }, 142 143 /** 144 * @param {number} requestId 145 * @param {string} fileSystemPath 146 * @param {number} worked 147 */ 148 indexingWorked: function(requestId, fileSystemPath, worked) 149 { 150 var progress = this._progresses[requestId]; 151 if (!progress) 152 return; 153 progress.worked(worked); 154 }, 155 156 /** 157 * @param {number} requestId 158 * @param {string} fileSystemPath 159 */ 160 indexingDone: function(requestId, fileSystemPath) 161 { 162 var progress = this._progresses[requestId]; 163 if (!progress) 164 return; 165 progress.done(); 166 delete this._progresses[requestId]; 167 }, 168 169 /** 170 * @param {number} requestId 171 * @param {string} fileSystemPath 172 * @param {!Array.<string>} files 173 */ 174 searchCompleted: function(requestId, fileSystemPath, files) 175 { 176 var callback = this._callbacks[requestId]; 177 if (!callback) 178 return; 179 callback.call(null, files); 180 delete this._callbacks[requestId]; 181 }, 182} 183 184/** 185 * @constructor 186 * @implements {WebInspector.ProjectDelegate} 187 * @param {!WebInspector.IsolatedFileSystem} isolatedFileSystem 188 * @param {!WebInspector.Workspace} workspace 189 * @param {!WebInspector.FileSystemWorkspaceBinding} fileSystemWorkspaceBinding 190 */ 191WebInspector.FileSystemWorkspaceBinding.FileSystem = function(fileSystemWorkspaceBinding, isolatedFileSystem, workspace) 192{ 193 this._fileSystemWorkspaceBinding = fileSystemWorkspaceBinding; 194 this._fileSystem = isolatedFileSystem; 195 this._fileSystemURL = "file://" + this._fileSystem.normalizedPath() + "/"; 196 this._workspace = workspace; 197 198 this._projectId = WebInspector.FileSystemWorkspaceBinding.projectId(this._fileSystem.path()); 199 console.assert(!this._workspace.project(this._projectId)); 200 this._projectStore = this._workspace.addProject(this._projectId, this); 201 this.populate(); 202} 203 204WebInspector.FileSystemWorkspaceBinding.FileSystem.prototype = { 205 /** 206 * @return {string} 207 */ 208 type: function() 209 { 210 return WebInspector.projectTypes.FileSystem; 211 }, 212 213 /** 214 * @return {string} 215 */ 216 fileSystemPath: function() 217 { 218 return this._fileSystem.path(); 219 }, 220 221 /** 222 * @return {string} 223 */ 224 displayName: function() 225 { 226 var normalizedPath = this._fileSystem.normalizedPath(); 227 return normalizedPath.substr(normalizedPath.lastIndexOf("/") + 1); 228 }, 229 230 /** 231 * @param {string} path 232 * @return {string} 233 */ 234 _filePathForPath: function(path) 235 { 236 return "/" + path; 237 }, 238 239 /** 240 * @param {string} path 241 * @param {function(?string)} callback 242 */ 243 requestFileContent: function(path, callback) 244 { 245 var filePath = this._filePathForPath(path); 246 this._fileSystem.requestFileContent(filePath, callback); 247 }, 248 249 /** 250 * @param {string} path 251 * @param {function(?Date, ?number)} callback 252 */ 253 requestMetadata: function(path, callback) 254 { 255 var filePath = this._filePathForPath(path); 256 this._fileSystem.requestMetadata(filePath, callback); 257 }, 258 259 /** 260 * @return {boolean} 261 */ 262 canSetFileContent: function() 263 { 264 return true; 265 }, 266 267 /** 268 * @param {string} path 269 * @param {string} newContent 270 * @param {function(?string)} callback 271 */ 272 setFileContent: function(path, newContent, callback) 273 { 274 var filePath = this._filePathForPath(path); 275 this._fileSystem.setFileContent(filePath, newContent, callback.bind(this, "")); 276 }, 277 278 /** 279 * @return {boolean} 280 */ 281 canRename: function() 282 { 283 return true; 284 }, 285 286 /** 287 * @param {string} path 288 * @param {string} newName 289 * @param {function(boolean, string=, string=, string=, !WebInspector.ResourceType=)} callback 290 */ 291 rename: function(path, newName, callback) 292 { 293 var filePath = this._filePathForPath(path); 294 this._fileSystem.renameFile(filePath, newName, innerCallback.bind(this)); 295 296 /** 297 * @param {boolean} success 298 * @param {string=} newName 299 * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem} 300 */ 301 function innerCallback(success, newName) 302 { 303 if (!success) { 304 callback(false, newName); 305 return; 306 } 307 var validNewName = /** @type {string} */ (newName); 308 console.assert(validNewName); 309 var slash = filePath.lastIndexOf("/"); 310 var parentPath = filePath.substring(0, slash); 311 filePath = parentPath + "/" + validNewName; 312 var newURL = this._workspace.urlForPath(this._fileSystem.path(), filePath); 313 var extension = this._extensionForPath(validNewName); 314 var newOriginURL = this._fileSystemURL + filePath 315 var newContentType = this._contentTypeForExtension(extension); 316 callback(true, validNewName, newURL, newOriginURL, newContentType); 317 } 318 }, 319 320 /** 321 * @param {string} path 322 * @param {string} query 323 * @param {boolean} caseSensitive 324 * @param {boolean} isRegex 325 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback 326 */ 327 searchInFileContent: function(path, query, caseSensitive, isRegex, callback) 328 { 329 var filePath = this._filePathForPath(path); 330 this._fileSystem.requestFileContent(filePath, contentCallback); 331 332 /** 333 * @param {?string} content 334 */ 335 function contentCallback(content) 336 { 337 var result = []; 338 if (content !== null) 339 result = WebInspector.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex); 340 callback(result); 341 } 342 }, 343 344 /** 345 * @param {!WebInspector.ProjectSearchConfig} searchConfig 346 * @param {!WebInspector.Progress} progress 347 * @param {function(!Array.<string>)} callback 348 */ 349 findFilesMatchingSearchRequest: function(searchConfig, progress, callback) 350 { 351 var result = null; 352 var queriesToRun = searchConfig.queries().slice(); 353 if (!queriesToRun.length) 354 queriesToRun.push(""); 355 progress.setTotalWork(queriesToRun.length); 356 searchNextQuery.call(this); 357 358 /** 359 * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem} 360 */ 361 function searchNextQuery() 362 { 363 if (!queriesToRun.length) { 364 matchFileQueries.call(null, result); 365 return; 366 } 367 var query = queriesToRun.shift(); 368 this._searchInPath(searchConfig.isRegex() ? "" : query, progress, innerCallback.bind(this)); 369 } 370 371 /** 372 * @param {!Array.<string>} files 373 * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem} 374 */ 375 function innerCallback(files) 376 { 377 files = files.sort(); 378 progress.worked(1); 379 if (!result) 380 result = files; 381 else 382 result = result.intersectOrdered(files, String.naturalOrderComparator); 383 searchNextQuery.call(this); 384 } 385 386 /** 387 * @param {!Array.<string>} files 388 */ 389 function matchFileQueries(files) 390 { 391 files = files.filter(searchConfig.filePathMatchesFileQuery.bind(searchConfig)); 392 progress.done(); 393 callback(files); 394 } 395 }, 396 397 /** 398 * @param {string} query 399 * @param {!WebInspector.Progress} progress 400 * @param {function(!Array.<string>)} callback 401 */ 402 _searchInPath: function(query, progress, callback) 403 { 404 var requestId = this._fileSystemWorkspaceBinding.registerCallback(innerCallback.bind(this)); 405 InspectorFrontendHost.searchInPath(requestId, this._fileSystem.path(), query); 406 407 /** 408 * @param {!Array.<string>} files 409 * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem} 410 */ 411 function innerCallback(files) 412 { 413 /** 414 * @param {string} fullPath 415 * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem} 416 */ 417 function trimAndNormalizeFileSystemPath(fullPath) 418 { 419 var trimmedPath = fullPath.substr(this._fileSystem.path().length + 1); 420 if (WebInspector.isWin()) 421 trimmedPath = trimmedPath.replace(/\\/g, "/"); 422 return trimmedPath; 423 } 424 425 files = files.map(trimAndNormalizeFileSystemPath.bind(this)); 426 progress.worked(1); 427 callback(files); 428 } 429 }, 430 431 /** 432 * @param {!WebInspector.Progress} progress 433 */ 434 indexContent: function(progress) 435 { 436 progress.setTotalWork(1); 437 var requestId = this._fileSystemWorkspaceBinding.registerProgress(progress); 438 progress.addEventListener(WebInspector.Progress.Events.Canceled, this._indexingCanceled.bind(this, requestId)); 439 InspectorFrontendHost.indexPath(requestId, this._fileSystem.path()); 440 }, 441 442 /** 443 * @param {number} requestId 444 */ 445 _indexingCanceled: function(requestId) 446 { 447 InspectorFrontendHost.stopIndexing(requestId); 448 }, 449 450 /** 451 * @param {string} path 452 * @return {string} 453 */ 454 _extensionForPath: function(path) 455 { 456 var extensionIndex = path.lastIndexOf("."); 457 if (extensionIndex === -1) 458 return ""; 459 return path.substring(extensionIndex + 1).toLowerCase(); 460 }, 461 462 /** 463 * @param {string} extension 464 * @return {!WebInspector.ResourceType} 465 */ 466 _contentTypeForExtension: function(extension) 467 { 468 if (WebInspector.FileSystemWorkspaceBinding._scriptExtensions[extension]) 469 return WebInspector.resourceTypes.Script; 470 if (WebInspector.FileSystemWorkspaceBinding._styleSheetExtensions[extension]) 471 return WebInspector.resourceTypes.Stylesheet; 472 if (WebInspector.FileSystemWorkspaceBinding._documentExtensions[extension]) 473 return WebInspector.resourceTypes.Document; 474 return WebInspector.resourceTypes.Other; 475 }, 476 477 populate: function() 478 { 479 this._fileSystem.requestFilesRecursive("", this._addFile.bind(this)); 480 }, 481 482 /** 483 * @param {string} path 484 */ 485 refresh: function(path) 486 { 487 this._fileSystem.requestFilesRecursive(path, this._addFile.bind(this)); 488 }, 489 490 /** 491 * @param {string} path 492 */ 493 excludeFolder: function(path) 494 { 495 this._fileSystemWorkspaceBinding._isolatedFileSystemManager.mapping().addExcludedFolder(this._fileSystem.path(), path); 496 }, 497 498 /** 499 * @param {string} path 500 * @param {?string} name 501 * @param {string} content 502 * @param {function(?string)} callback 503 */ 504 createFile: function(path, name, content, callback) 505 { 506 this._fileSystem.createFile(path, name, innerCallback.bind(this)); 507 var createFilePath; 508 509 /** 510 * @param {?string} filePath 511 * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem} 512 */ 513 function innerCallback(filePath) 514 { 515 if (!filePath) { 516 callback(null); 517 return; 518 } 519 createFilePath = filePath; 520 if (!content) { 521 contentSet.call(this); 522 return; 523 } 524 this._fileSystem.setFileContent(filePath, content, contentSet.bind(this)); 525 } 526 527 /** 528 * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem} 529 */ 530 function contentSet() 531 { 532 this._addFile(createFilePath); 533 callback(createFilePath); 534 } 535 }, 536 537 /** 538 * @param {string} path 539 */ 540 deleteFile: function(path) 541 { 542 this._fileSystem.deleteFile(path); 543 this._removeFile(path); 544 }, 545 546 remove: function() 547 { 548 this._fileSystemWorkspaceBinding._isolatedFileSystemManager.removeFileSystem(this._fileSystem.path()); 549 }, 550 551 /** 552 * @param {string} filePath 553 */ 554 _addFile: function(filePath) 555 { 556 if (!filePath) 557 console.assert(false); 558 559 var slash = filePath.lastIndexOf("/"); 560 var parentPath = filePath.substring(0, slash); 561 var name = filePath.substring(slash + 1); 562 563 var url = this._workspace.urlForPath(this._fileSystem.path(), filePath); 564 var extension = this._extensionForPath(name); 565 var contentType = this._contentTypeForExtension(extension); 566 567 var fileDescriptor = new WebInspector.FileDescriptor(parentPath, name, this._fileSystemURL + filePath, url, contentType); 568 this._projectStore.addFile(fileDescriptor); 569 }, 570 571 /** 572 * @param {string} path 573 */ 574 _removeFile: function(path) 575 { 576 this._projectStore.removeFile(path); 577 }, 578 579 dispose: function() 580 { 581 this._workspace.removeProject(this._projectId); 582 } 583} 584 585/** 586 * @type {!WebInspector.FileSystemWorkspaceBinding} 587 */ 588WebInspector.fileSystemWorkspaceBinding; 589