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