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} manager 34 * @param {string} path 35 * @param {string} name 36 * @param {string} rootURL 37 */ 38 WebInspector.IsolatedFileSystem = function(manager, path, name, rootURL) 39 { 40 this._manager = manager; 41 this._path = path; 42 this._name = name; 43 this._rootURL = rootURL; 44 } 45 46 /** 47 * @param {!FileError} error 48 * @return {string} 49 */ 50 WebInspector.IsolatedFileSystem.errorMessage = function(error) 51 { 52 return WebInspector.UIString("File system error: %s", error.message); 53 } 54 55 /** 56 * @param {string} fileSystemPath 57 * @return {string} 58 */ 59 WebInspector.IsolatedFileSystem.normalizePath = function(fileSystemPath) 60 { 61 if (WebInspector.isWin()) 62 return fileSystemPath.replace(/\\/g, "/"); 63 return fileSystemPath; 64 } 65 66 WebInspector.IsolatedFileSystem.prototype = { 67 /** 68 * @return {string} 69 */ 70 path: function() 71 { 72 return this._path; 73 }, 74 75 /** 76 * @return {string} 77 */ 78 normalizedPath: function() 79 { 80 if (this._normalizedPath) 81 return this._normalizedPath; 82 this._normalizedPath = WebInspector.IsolatedFileSystem.normalizePath(this._path); 83 return this._normalizedPath; 84 }, 85 86 /** 87 * @return {string} 88 */ 89 name: function() 90 { 91 return this._name; 92 }, 93 94 /** 95 * @return {string} 96 */ 97 rootURL: function() 98 { 99 return this._rootURL; 100 }, 101 102 /** 103 * @param {function(?DOMFileSystem)} callback 104 */ 105 _requestFileSystem: function(callback) 106 { 107 this._manager.requestDOMFileSystem(this._path, callback); 108 }, 109 110 /** 111 * @param {string} path 112 * @param {function(string)} fileCallback 113 * @param {function()=} finishedCallback 114 */ 115 requestFilesRecursive: function(path, fileCallback, finishedCallback) 116 { 117 var domFileSystem; 118 var pendingRequests = 0; 119 this._requestFileSystem(fileSystemLoaded.bind(this)); 120 /** 121 * @param {?DOMFileSystem} fs 122 * @this {WebInspector.IsolatedFileSystem} 123 */ 124 function fileSystemLoaded(fs) 125 { 126 domFileSystem = /** @type {!DOMFileSystem} */ (fs); 127 console.assert(domFileSystem); 128 ++pendingRequests; 129 this._requestEntries(domFileSystem, path, innerCallback.bind(this)); 130 } 131 132 /** 133 * @param {!Array.<!FileEntry>} entries 134 * @this {WebInspector.IsolatedFileSystem} 135 */ 136 function innerCallback(entries) 137 { 138 for (var i = 0; i < entries.length; ++i) { 139 var entry = entries[i]; 140 if (!entry.isDirectory) { 141 if (this._manager.mapping().isFileExcluded(this._path, entry.fullPath)) 142 continue; 143 fileCallback(entry.fullPath.substr(1)); 144 } 145 else { 146 if (this._manager.mapping().isFileExcluded(this._path, entry.fullPath + "/")) 147 continue; 148 ++pendingRequests; 149 this._requestEntries(domFileSystem, entry.fullPath, innerCallback.bind(this)); 150 } 151 } 152 if (finishedCallback && (--pendingRequests === 0)) 153 finishedCallback(); 154 } 155 }, 156 157 /** 158 * @param {string} path 159 * @param {?string} name 160 * @param {function(?string)} callback 161 */ 162 createFile: function(path, name, callback) 163 { 164 this._requestFileSystem(fileSystemLoaded.bind(this)); 165 var newFileIndex = 1; 166 if (!name) 167 name = "NewFile"; 168 var nameCandidate; 169 170 /** 171 * @param {?DOMFileSystem} fs 172 * @this {WebInspector.IsolatedFileSystem} 173 */ 174 function fileSystemLoaded(fs) 175 { 176 var domFileSystem = /** @type {!DOMFileSystem} */ (fs); 177 console.assert(domFileSystem); 178 domFileSystem.root.getDirectory(path, null, dirEntryLoaded.bind(this), errorHandler.bind(this)); 179 } 180 181 /** 182 * @param {!DirectoryEntry} dirEntry 183 * @this {WebInspector.IsolatedFileSystem} 184 */ 185 function dirEntryLoaded(dirEntry) 186 { 187 var nameCandidate = name; 188 if (newFileIndex > 1) 189 nameCandidate += newFileIndex; 190 ++newFileIndex; 191 dirEntry.getFile(nameCandidate, { create: true, exclusive: true }, fileCreated, fileCreationError.bind(this)); 192 193 function fileCreated(entry) 194 { 195 callback(entry.fullPath.substr(1)); 196 } 197 198 /** 199 * @this {WebInspector.IsolatedFileSystem} 200 */ 201 function fileCreationError(error) 202 { 203 if (error.code === FileError.INVALID_MODIFICATION_ERR) { 204 dirEntryLoaded.call(this, dirEntry); 205 return; 206 } 207 208 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 209 console.error(errorMessage + " when testing if file exists '" + (this._path + "/" + path + "/" + nameCandidate) + "'"); 210 callback(null); 211 } 212 } 213 214 /** 215 * @this {WebInspector.IsolatedFileSystem} 216 */ 217 function errorHandler(error) 218 { 219 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 220 var filePath = this._path + "/" + path; 221 if (nameCandidate) 222 filePath += "/" + nameCandidate; 223 console.error(errorMessage + " when getting content for file '" + (filePath) + "'"); 224 callback(null); 225 } 226 }, 227 228 /** 229 * @param {string} path 230 */ 231 deleteFile: function(path) 232 { 233 this._requestFileSystem(fileSystemLoaded.bind(this)); 234 235 /** 236 * @param {?DOMFileSystem} fs 237 * @this {WebInspector.IsolatedFileSystem} 238 */ 239 function fileSystemLoaded(fs) 240 { 241 var domFileSystem = /** @type {!DOMFileSystem} */ (fs); 242 console.assert(domFileSystem); 243 domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this)); 244 } 245 246 /** 247 * @param {!FileEntry} fileEntry 248 * @this {WebInspector.IsolatedFileSystem} 249 */ 250 function fileEntryLoaded(fileEntry) 251 { 252 fileEntry.remove(fileEntryRemoved, errorHandler.bind(this)); 253 } 254 255 function fileEntryRemoved() 256 { 257 } 258 259 /** 260 * @param {!FileError} error 261 * @this {WebInspector.IsolatedFileSystem} 262 */ 263 function errorHandler(error) 264 { 265 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 266 console.error(errorMessage + " when deleting file '" + (this._path + "/" + path) + "'"); 267 } 268 }, 269 270 /** 271 * @param {string} path 272 * @param {function(?Date, ?number)} callback 273 */ 274 requestMetadata: function(path, callback) 275 { 276 this._requestFileSystem(fileSystemLoaded); 277 278 /** 279 * @param {?DOMFileSystem} fs 280 */ 281 function fileSystemLoaded(fs) 282 { 283 var domFileSystem = /** @type {!DOMFileSystem} */ (fs); 284 console.assert(domFileSystem); 285 domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler); 286 } 287 288 /** 289 * @param {!FileEntry} entry 290 */ 291 function fileEntryLoaded(entry) 292 { 293 entry.getMetadata(successHandler, errorHandler); 294 } 295 296 /** 297 * @param {!Metadata} metadata 298 */ 299 function successHandler(metadata) 300 { 301 callback(metadata.modificationTime, metadata.size); 302 } 303 304 /** 305 * @param {!FileError} error 306 */ 307 function errorHandler(error) 308 { 309 callback(null, null); 310 } 311 }, 312 313 /** 314 * @param {string} path 315 * @param {function(?string)} callback 316 */ 317 requestFileContent: function(path, callback) 318 { 319 this._requestFileSystem(fileSystemLoaded.bind(this)); 320 321 /** 322 * @param {?DOMFileSystem} fs 323 * @this {WebInspector.IsolatedFileSystem} 324 */ 325 function fileSystemLoaded(fs) 326 { 327 var domFileSystem = /** @type {!DOMFileSystem} */ (fs); 328 console.assert(domFileSystem); 329 domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this)); 330 } 331 332 /** 333 * @param {!FileEntry} entry 334 * @this {WebInspector.IsolatedFileSystem} 335 */ 336 function fileEntryLoaded(entry) 337 { 338 entry.file(fileLoaded, errorHandler.bind(this)); 339 } 340 341 /** 342 * @param {!Blob} file 343 */ 344 function fileLoaded(file) 345 { 346 var reader = new FileReader(); 347 reader.onloadend = readerLoadEnd; 348 reader.readAsText(file); 349 } 350 351 /** 352 * @this {!FileReader} 353 */ 354 function readerLoadEnd() 355 { 356 callback(/** @type {string} */ (this.result)); 357 } 358 359 /** 360 * @this {WebInspector.IsolatedFileSystem} 361 */ 362 function errorHandler(error) 363 { 364 if (error.code === FileError.NOT_FOUND_ERR) { 365 callback(null); 366 return; 367 } 368 369 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 370 console.error(errorMessage + " when getting content for file '" + (this._path + "/" + path) + "'"); 371 callback(null); 372 } 373 }, 374 375 /** 376 * @param {string} path 377 * @param {string} content 378 * @param {function()} callback 379 */ 380 setFileContent: function(path, content, callback) 381 { 382 this._requestFileSystem(fileSystemLoaded.bind(this)); 383 WebInspector.userMetrics.FileSavedInWorkspace.record(); 384 385 /** 386 * @param {?DOMFileSystem} fs 387 * @this {WebInspector.IsolatedFileSystem} 388 */ 389 function fileSystemLoaded(fs) 390 { 391 var domFileSystem = /** @type {!DOMFileSystem} */ (fs); 392 console.assert(domFileSystem); 393 domFileSystem.root.getFile(path, { create: true }, fileEntryLoaded.bind(this), errorHandler.bind(this)); 394 } 395 396 /** 397 * @param {!FileEntry} entry 398 * @this {WebInspector.IsolatedFileSystem} 399 */ 400 function fileEntryLoaded(entry) 401 { 402 entry.createWriter(fileWriterCreated.bind(this), errorHandler.bind(this)); 403 } 404 405 /** 406 * @param {!FileWriter} fileWriter 407 * @this {WebInspector.IsolatedFileSystem} 408 */ 409 function fileWriterCreated(fileWriter) 410 { 411 fileWriter.onerror = errorHandler.bind(this); 412 fileWriter.onwriteend = fileTruncated; 413 fileWriter.truncate(0); 414 415 function fileTruncated() 416 { 417 fileWriter.onwriteend = writerEnd; 418 var blob = new Blob([content], { type: "text/plain" }); 419 fileWriter.write(blob); 420 } 421 } 422 423 function writerEnd() 424 { 425 callback(); 426 } 427 428 /** 429 * @this {WebInspector.IsolatedFileSystem} 430 */ 431 function errorHandler(error) 432 { 433 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 434 console.error(errorMessage + " when setting content for file '" + (this._path + "/" + path) + "'"); 435 callback(); 436 } 437 }, 438 439 /** 440 * @param {string} path 441 * @param {string} newName 442 * @param {function(boolean, string=)} callback 443 */ 444 renameFile: function(path, newName, callback) 445 { 446 newName = newName ? newName.trim() : newName; 447 if (!newName || newName.indexOf("/") !== -1) { 448 callback(false); 449 return; 450 } 451 var fileEntry; 452 var dirEntry; 453 var newFileEntry; 454 this._requestFileSystem(fileSystemLoaded.bind(this)); 455 456 /** 457 * @param {?DOMFileSystem} fs 458 * @this {WebInspector.IsolatedFileSystem} 459 */ 460 function fileSystemLoaded(fs) 461 { 462 var domFileSystem = /** @type {!DOMFileSystem} */ (fs); 463 console.assert(domFileSystem); 464 domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this)); 465 } 466 467 /** 468 * @param {!FileEntry} entry 469 * @this {WebInspector.IsolatedFileSystem} 470 */ 471 function fileEntryLoaded(entry) 472 { 473 if (entry.name === newName) { 474 callback(false); 475 return; 476 } 477 478 fileEntry = entry; 479 fileEntry.getParent(dirEntryLoaded.bind(this), errorHandler.bind(this)); 480 } 481 482 /** 483 * @param {!Entry} entry 484 * @this {WebInspector.IsolatedFileSystem} 485 */ 486 function dirEntryLoaded(entry) 487 { 488 dirEntry = entry; 489 dirEntry.getFile(newName, null, newFileEntryLoaded, newFileEntryLoadErrorHandler.bind(this)); 490 } 491 492 /** 493 * @param {!FileEntry} entry 494 */ 495 function newFileEntryLoaded(entry) 496 { 497 callback(false); 498 } 499 500 /** 501 * @this {WebInspector.IsolatedFileSystem} 502 */ 503 function newFileEntryLoadErrorHandler(error) 504 { 505 if (error.code !== FileError.NOT_FOUND_ERR) { 506 callback(false); 507 return; 508 } 509 fileEntry.moveTo(dirEntry, newName, fileRenamed, errorHandler.bind(this)); 510 } 511 512 /** 513 * @param {!FileEntry} entry 514 */ 515 function fileRenamed(entry) 516 { 517 callback(true, entry.name); 518 } 519 520 /** 521 * @this {WebInspector.IsolatedFileSystem} 522 */ 523 function errorHandler(error) 524 { 525 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 526 console.error(errorMessage + " when renaming file '" + (this._path + "/" + path) + "' to '" + newName + "'"); 527 callback(false); 528 } 529 }, 530 531 /** 532 * @param {!DirectoryEntry} dirEntry 533 * @param {function(!Array.<!FileEntry>)} callback 534 */ 535 _readDirectory: function(dirEntry, callback) 536 { 537 var dirReader = dirEntry.createReader(); 538 var entries = []; 539 540 function innerCallback(results) 541 { 542 if (!results.length) 543 callback(entries.sort()); 544 else { 545 entries = entries.concat(toArray(results)); 546 dirReader.readEntries(innerCallback, errorHandler); 547 } 548 } 549 550 function toArray(list) 551 { 552 return Array.prototype.slice.call(list || [], 0); 553 } 554 555 dirReader.readEntries(innerCallback, errorHandler); 556 557 function errorHandler(error) 558 { 559 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 560 console.error(errorMessage + " when reading directory '" + dirEntry.fullPath + "'"); 561 callback([]); 562 } 563 }, 564 565 /** 566 * @param {!DOMFileSystem} domFileSystem 567 * @param {string} path 568 * @param {function(!Array.<!FileEntry>)} callback 569 */ 570 _requestEntries: function(domFileSystem, path, callback) 571 { 572 domFileSystem.root.getDirectory(path, null, innerCallback.bind(this), errorHandler); 573 574 /** 575 * @param {!DirectoryEntry} dirEntry 576 * @this {WebInspector.IsolatedFileSystem} 577 */ 578 function innerCallback(dirEntry) 579 { 580 this._readDirectory(dirEntry, callback) 581 } 582 583 function errorHandler(error) 584 { 585 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 586 console.error(errorMessage + " when requesting entry '" + path + "'"); 587 callback([]); 588 } 589 } 590 } 591