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