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 */ 116 function fileSystemLoaded(fs) 117 { 118 domFileSystem = fs; 119 this._requestEntries(domFileSystem, path, innerCallback.bind(this)); 120 } 121 122 /** 123 * @param {Array.<FileEntry>} entries 124 */ 125 function innerCallback(entries) 126 { 127 for (var i = 0; i < entries.length; ++i) { 128 var entry = entries[i]; 129 if (!entry.isDirectory) 130 callback(entry.fullPath.substr(1)); 131 else 132 this._requestEntries(domFileSystem, entry.fullPath, innerCallback.bind(this)); 133 } 134 } 135 }, 136 137 /** 138 * @param {string} path 139 * @param {?string} name 140 * @param {function(?string)} callback 141 */ 142 createFile: function(path, name, callback) 143 { 144 this._requestFileSystem(fileSystemLoaded.bind(this)); 145 var newFileIndex = 1; 146 if (!name) 147 name = "NewFile"; 148 var nameCandidate; 149 150 /** 151 * @param {DOMFileSystem} domFileSystem 152 */ 153 function fileSystemLoaded(domFileSystem) 154 { 155 domFileSystem.root.getDirectory(path, null, dirEntryLoaded.bind(this), errorHandler.bind(this)); 156 } 157 158 /** 159 * @param {DirectoryEntry} dirEntry 160 */ 161 function dirEntryLoaded(dirEntry) 162 { 163 var nameCandidate = name; 164 if (newFileIndex > 1) 165 nameCandidate += newFileIndex; 166 ++newFileIndex; 167 dirEntry.getFile(nameCandidate, { create: true, exclusive: true }, fileCreated, fileCreationError); 168 169 function fileCreated(entry) 170 { 171 callback(entry.fullPath.substr(1)); 172 } 173 174 function fileCreationError(error) 175 { 176 if (error.code === FileError.INVALID_MODIFICATION_ERR) { 177 dirEntryLoaded(dirEntry); 178 return; 179 } 180 181 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 182 console.error(errorMessage + " when testing if file exists '" + (this._path + "/" + path + "/" + nameCandidate) + "'"); 183 callback(null); 184 } 185 } 186 187 function errorHandler(error) 188 { 189 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 190 var filePath = this._path + "/" + path; 191 if (nameCandidate) 192 filePath += "/" + nameCandidate; 193 console.error(errorMessage + " when getting content for file '" + (filePath) + "'"); 194 callback(null); 195 } 196 }, 197 198 /** 199 * @param {string} path 200 */ 201 deleteFile: function(path) 202 { 203 this._requestFileSystem(fileSystemLoaded.bind(this)); 204 205 /** 206 * @param {DOMFileSystem} domFileSystem 207 */ 208 function fileSystemLoaded(domFileSystem) 209 { 210 domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this)); 211 } 212 213 /** 214 * @param {FileEntry} fileEntry 215 */ 216 function fileEntryLoaded(fileEntry) 217 { 218 fileEntry.remove(fileEntryRemoved.bind(this), errorHandler.bind(this)); 219 } 220 221 function fileEntryRemoved() 222 { 223 } 224 225 /** 226 * @param {FileError} error 227 */ 228 function errorHandler(error) 229 { 230 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 231 console.error(errorMessage + " when deleting file '" + (this._path + "/" + path) + "'"); 232 } 233 }, 234 235 /** 236 * @param {string} path 237 * @param {function(?Date, ?number)} callback 238 */ 239 requestMetadata: function(path, callback) 240 { 241 this._requestFileSystem(fileSystemLoaded.bind(this)); 242 243 /** 244 * @param {DOMFileSystem} domFileSystem 245 */ 246 function fileSystemLoaded(domFileSystem) 247 { 248 domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler); 249 } 250 251 /** 252 * @param {FileEntry} entry 253 */ 254 function fileEntryLoaded(entry) 255 { 256 entry.getMetadata(successHandler, errorHandler); 257 } 258 259 /** 260 * @param {Metadata} metadata 261 */ 262 function successHandler(metadata) 263 { 264 callback(metadata.modificationTime, metadata.size); 265 } 266 267 /** 268 * @param {FileError} error 269 */ 270 function errorHandler(error) 271 { 272 callback(null, null); 273 } 274 }, 275 276 /** 277 * @param {string} path 278 * @param {function(?string)} callback 279 */ 280 requestFileContent: function(path, callback) 281 { 282 this._requestFileSystem(fileSystemLoaded.bind(this)); 283 284 /** 285 * @param {DOMFileSystem} domFileSystem 286 */ 287 function fileSystemLoaded(domFileSystem) 288 { 289 domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler.bind(this)); 290 } 291 292 /** 293 * @param {FileEntry} entry 294 */ 295 function fileEntryLoaded(entry) 296 { 297 entry.file(fileLoaded, errorHandler.bind(this)); 298 } 299 300 /** 301 * @param {!Blob} file 302 */ 303 function fileLoaded(file) 304 { 305 var reader = new FileReader(); 306 reader.onloadend = readerLoadEnd; 307 reader.readAsText(file); 308 } 309 310 /** 311 * @this {FileReader} 312 */ 313 function readerLoadEnd() 314 { 315 callback(/** @type {string} */ (this.result)); 316 } 317 318 function errorHandler(error) 319 { 320 if (error.code === FileError.NOT_FOUND_ERR) { 321 callback(null); 322 return; 323 } 324 325 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 326 console.error(errorMessage + " when getting content for file '" + (this._path + "/" + path) + "'"); 327 callback(null); 328 } 329 }, 330 331 /** 332 * @param {string} path 333 * @param {string} content 334 * @param {function()} callback 335 */ 336 setFileContent: function(path, content, callback) 337 { 338 this._requestFileSystem(fileSystemLoaded); 339 340 /** 341 * @param {DOMFileSystem} domFileSystem 342 */ 343 function fileSystemLoaded(domFileSystem) 344 { 345 domFileSystem.root.getFile(path, { create: true }, fileEntryLoaded, errorHandler.bind(this)); 346 } 347 348 /** 349 * @param {FileEntry} entry 350 */ 351 function fileEntryLoaded(entry) 352 { 353 entry.createWriter(fileWriterCreated, errorHandler.bind(this)); 354 } 355 356 /** 357 * @param {FileWriter} fileWriter 358 */ 359 function fileWriterCreated(fileWriter) 360 { 361 fileWriter.onerror = errorHandler.bind(this); 362 fileWriter.onwriteend = fileTruncated; 363 fileWriter.truncate(0); 364 365 function fileTruncated() 366 { 367 fileWriter.onwriteend = writerEnd; 368 var blob = new Blob([content], { type: "text/plain" }); 369 fileWriter.write(blob); 370 } 371 } 372 373 function writerEnd() 374 { 375 callback(); 376 } 377 378 function errorHandler(error) 379 { 380 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 381 console.error(errorMessage + " when setting content for file '" + (this._path + "/" + path) + "'"); 382 callback(); 383 } 384 }, 385 386 /** 387 * @param {string} path 388 * @param {string} newName 389 * @param {function(boolean, string=)} callback 390 */ 391 renameFile: function(path, newName, callback) 392 { 393 newName = newName ? newName.trim() : newName; 394 if (!newName || newName.indexOf("/") !== -1) { 395 callback(false); 396 return; 397 } 398 var fileEntry; 399 var dirEntry; 400 var newFileEntry; 401 this._requestFileSystem(fileSystemLoaded); 402 403 /** 404 * @param {DOMFileSystem} domFileSystem 405 */ 406 function fileSystemLoaded(domFileSystem) 407 { 408 domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler.bind(this)); 409 } 410 411 /** 412 * @param {FileEntry} entry 413 */ 414 function fileEntryLoaded(entry) 415 { 416 if (entry.name === newName) { 417 callback(false); 418 return; 419 } 420 421 fileEntry = entry; 422 fileEntry.getParent(dirEntryLoaded, errorHandler.bind(this)); 423 } 424 425 /** 426 * @param {Entry} entry 427 */ 428 function dirEntryLoaded(entry) 429 { 430 dirEntry = entry; 431 dirEntry.getFile(newName, null, newFileEntryLoaded, newFileEntryLoadErrorHandler); 432 } 433 434 /** 435 * @param {FileEntry} entry 436 */ 437 function newFileEntryLoaded(entry) 438 { 439 callback(false); 440 } 441 442 function newFileEntryLoadErrorHandler(error) 443 { 444 if (error.code !== FileError.NOT_FOUND_ERR) { 445 callback(false); 446 return; 447 } 448 fileEntry.moveTo(dirEntry, newName, fileRenamed, errorHandler.bind(this)); 449 } 450 451 /** 452 * @param {FileEntry} entry 453 */ 454 function fileRenamed(entry) 455 { 456 callback(true, entry.name); 457 } 458 459 function errorHandler(error) 460 { 461 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 462 console.error(errorMessage + " when renaming file '" + (this._path + "/" + path) + "' to '" + newName + "'"); 463 callback(false); 464 } 465 }, 466 467 /** 468 * @param {DirectoryEntry} dirEntry 469 * @param {function(Array.<FileEntry>)} callback 470 */ 471 _readDirectory: function(dirEntry, callback) 472 { 473 var dirReader = dirEntry.createReader(); 474 var entries = []; 475 476 function innerCallback(results) 477 { 478 if (!results.length) 479 callback(entries.sort()); 480 else { 481 entries = entries.concat(toArray(results)); 482 dirReader.readEntries(innerCallback, errorHandler); 483 } 484 } 485 486 function toArray(list) 487 { 488 return Array.prototype.slice.call(list || [], 0); 489 } 490 491 dirReader.readEntries(innerCallback, errorHandler); 492 493 function errorHandler(error) 494 { 495 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 496 console.error(errorMessage + " when reading directory '" + dirEntry.fullPath + "'"); 497 callback([]); 498 } 499 }, 500 501 /** 502 * @param {DOMFileSystem} domFileSystem 503 * @param {string} path 504 * @param {function(Array.<FileEntry>)} callback 505 */ 506 _requestEntries: function(domFileSystem, path, callback) 507 { 508 domFileSystem.root.getDirectory(path, null, innerCallback.bind(this), errorHandler); 509 510 function innerCallback(dirEntry) 511 { 512 this._readDirectory(dirEntry, callback) 513 } 514 515 function errorHandler(error) 516 { 517 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error); 518 console.error(errorMessage + " when requesting entry '" + path + "'"); 519 callback([]); 520 } 521 } 522 } 523