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