1 /* global 2 /** 3 * @package webfs 4 * @copyright Copyright(c) 2011 Ajax.org B.V. <info AT ajax.org> 5 * @author Tane Piper <tane AT ajax DOT org> 6 * @license http://github.com/ajaxorg/webfs/blob/master/LICENSE MIT License 7 */ 8 9 /** 10 * We need to ensure that we have the correct version of requestFileSystem and 11 * BlobBuilder available to the script 12 */ 13 window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; 14 window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder; 15 16 17 /** 18 * @class WebFS object 19 * @description Can take a pre-existing native local filesystem as a 20 * parameter. If not parameter passed, filesystem can be set with 21 * setFileSystem 22 */ 23 var WebFS = (function() { 24 25 /** 26 * @constructor 27 * @description Can take a pre-existing native local filesystem as a 28 * parameter. If not parameter passed, filesystem can be set with 29 * setFileSystem 30 * @param {DOMFileSystem} fs 31 * @type {void} 32 */ 33 function WebFS(fs) { 34 this.fs = fs; 35 if (fs) 36 this.root = fs.root; 37 } 38 39 // Filesystem system types flags 40 WebFS.prototype.TEMPORARY = 0; 41 WebFS.prototype.PERSISTENT = 1; 42 // Filesystem progress flags 43 WebFS.prototype.EMPTY = 0; 44 WebFS.prototype.LOADING = 1; 45 WebFS.prototype.DONE = 2; 46 // Filesystem error flags 47 WebFS.prototype.NOT_FOUND_ERR = 1; 48 WebFS.prototype.SECURITY_ERR = 2; 49 WebFS.prototype.ABORT_ERR = 3; 50 WebFS.prototype.NOT_READABLE_ERR = 4; 51 WebFS.prototype.ENCODING_ERR = 5; 52 WebFS.prototype.NO_MODIFICATION_ALLOWED_ERR = 6; 53 WebFS.prototype.INVALID_STATE_ERR = 7; 54 WebFS.prototype.SYNTAX_ERR = 8; 55 WebFS.prototype.INVALID_MODIFICATION_ERR = 9; 56 WebFS.prototype.QUOTA_EXCEEDED_ERR = 10; 57 WebFS.prototype.TYPE_MISMATCH_ERR = 11; 58 WebFS.prototype.PATH_EXISTS_ERR = 12; 59 60 WebFS.prototype.DIR_SEPARATOR = '/'; 61 WebFS.prototype.DIR_BLACKLIST = ['.', './', '..', '../', '/']; 62 WebFS.prototype.TYPE_FILE = 'file'; 63 WebFS.prototype.TYPE_DIR = 'dir'; 64 65 66 /** 67 * Creates a stats object 68 * @private 69 * @param {FileEntry, DirectoryEntry} entry 70 * @param {Function} callback 71 * @type {void} 72 */ 73 var Stats = function(entry, callback) { 74 var _self = {}; 75 76 if (entry.isFile) { 77 78 entry.file(function(file) { 79 80 var t = file.lastModifiedDate; 81 var m = t.getMonth() + 1; 82 var month = m < 10 ? "0" + m : m; 83 84 var time = [t.getFullYear(), month, t.getDate()].join('-') + 'T' + [t.getHours(), t.getMinutes(), t.getSeconds()].join(':') + 'Z'; 85 86 _self.mtime = time; 87 _self.atime = time; 88 _self.ctime = time; 89 90 _self.size = file.fileSize; 91 92 afterMetaData() 93 }); 94 } else { 95 entry.getMetadata(function(metadata) { 96 var t = metadata.modificationTime; 97 var m = t.getMonth() + 1; 98 var month = m < 10 ? "0" + m : m; 99 100 var time = [t.getFullYear(), month, t.getDate()].join('-') + 'T' + [t.getHours(), t.getMinutes(), t.getSeconds()].join(':') + 'Z'; 101 102 _self.mtime = time; 103 _self.atime = time; 104 _self.ctime = time; 105 106 _self.size = 0; 107 108 afterMetaData() 109 }); 110 } 111 112 function afterMetaData() { 113 114 _self.dev = 0; 115 _self.ino = 0; 116 _self.mode = 0; 117 _self.nlink = 0; 118 _self.uid = 0; 119 _self.gid = 0; 120 _self.rdev = 0; 121 _self.blocks = 0; 122 123 124 _self.isDirectory = entry.isDirectory; 125 _self.isFile = entry.isFile; 126 127 /** 128 * These next stats functions all return false for nodejs compatibility 129 */ 130 _self.isBlockDevice = false; 131 _self.isCharacterDevice = false; 132 _self.isSymbolicLink = false; 133 _self.isFIFO = false; 134 _self.isSocket = false; 135 136 callback(null, _self); 137 } 138 }; 139 140 /** 141 * Returns if requestFileSystem is available 142 * @type {void} 143 */ 144 WebFS.prototype.isAvailable = function(){ 145 return !!window.requestFileSystem; 146 }; 147 148 /** 149 * Error handler for file system operations 150 * @param {Error} error 151 * @type {void} 152 */ 153 WebFS.prototype.errorHandler = function(error) { 154 var msg; 155 156 switch(error.code) { 157 case this.NOT_FOUND_ERR: 158 error.message = "The file or directory has not been found"; 159 break; 160 case this.SECURITY_ERR: 161 error.message = "The file you are attempting to access is unsafe for web access or may be being accessed too many times."; 162 break; 163 case this.ABORT_ERR: 164 error.message = "The current operation has been aborted"; 165 break; 166 case this.NOT_READABLE_ERR: 167 error.message = "The file you are attempting to read is not readable, this may be a permissions issue."; 168 break; 169 case this.ENCODING_ERR: 170 error.message = "The data or URL passed is malformed"; 171 break; 172 case this.NO_MODIFICATION_ALLOWED_ERR: 173 error.message = "The file or directory cannot be modified."; 174 break; 175 case this.INVALID_STATE_ERR: 176 error.message = "The file or directory state has changed since the last operation."; 177 break; 178 case this.SYNTAX_ERR: 179 error.message = "There is a syntax error with this file operation."; 180 break; 181 case this.INVALID_MODIFICATION_ERR: 182 error.message = "Invalid file operation."; 183 break; 184 case this.QUOTA_EXCEEDED_ERR: 185 msg = "The quota for the filesystem has been exceeded."; 186 break; 187 case this.TYPE_MISMATCH_ERR: 188 error.message = "Incorrect file operation on file or directory."; 189 break; 190 case this.PATH_EXISTS_ERR: 191 error.message = "This path already exists"; 192 break; 193 } 194 195 return error; 196 }; 197 198 /** 199 * If the user does not use an external fs object, we can call this method 200 * to create a new file system object 201 * @param {Number} type 202 * @param {Number} size 203 * @param {Function} callback 204 * @type {void} 205 */ 206 WebFS.prototype.setFileSystem = function(type, size, callback) { 207 var _self = this; 208 209 var successHandler = function(fs) { 210 _self.fs = fs; 211 _self.root = fs.root; 212 callback(null, _self); 213 }; 214 215 var errorHandler = function(error) { 216 callback(_self.errorHandler(error)); 217 }; 218 219 requestFileSystem(type, (size * 1024 *1024), successHandler, errorHandler); 220 }; 221 222 /** 223 * Get the current raw filesystem for this WebFS object 224 * @type {void} 225 */ 226 WebFS.prototype.getFileSystem = function() { 227 return this.fs; 228 }; 229 230 /** 231 * Rename or move src to dest. If dest is a directory, must contain a trailing '/' char. 232 * @param {String} src 233 * @param {String} dest 234 * @param {Function} callback 235 * @type {void} 236 */ 237 WebFS.prototype.rename = function(src, dest, callback) { 238 var _self = this; 239 240 var errorHandler = function(error) { 241 callback(_self.errorHandler(error)); 242 }; 243 244 var doMove = function(srcDirEntry, destDirEntry, newName) { 245 var name = newName || null; 246 srcDirEntry.moveTo(destDirEntry, name, function(newDirEntry) { 247 callback(null, newDirEntry); 248 }, errorHandler); 249 }; 250 251 if (dest[dest.length - 1] == _self.DIR_SEPARATOR) { 252 _self.root.getDirectory(src, {}, function(srcDirEntry) { 253 // Create blacklist for dirs we can't re-create. 254 var create = _self.DIR_BLACKLIST.indexOf(dest) != -1 ? false : true; 255 256 _self.root.getDirectory(dest, {create: create}, function(destDirEntry) { 257 doMove(srcDirEntry, destDirEntry); 258 }, errorHandler); 259 }, function(error) { 260 // Try the src entry as a file instead. 261 _self.root.getFile(src, {}, function(srcDirEntry) { 262 _self.root.getDirectory(dest, {}, function(destDirEntry) { 263 doMove(srcDirEntry, destDirEntry); 264 }, errorHandler); 265 }, errorHandler); 266 }); 267 } else { 268 // Treat src/destination as files. 269 _self.root.getFile(src, {}, function(srcFileEntry) { 270 srcFileEntry.getParent(function(parentDirEntry) { 271 doMove(srcFileEntry, parentDirEntry, dest); 272 }, errorHandler); 273 }, errorHandler); 274 } 275 }; 276 277 /** 278 * Takes a file handler and truncates the content to the passed length 279 * @param {FileEntry} fileEntry 280 * @param {Number} len 281 * @param {Function} callback 282 * @type {void} 283 */ 284 WebFS.prototype.truncate = function(fileEntry, len, callback) { 285 var _self = this; 286 287 var errorHandler = function(error) { 288 callback(_self.errorHandler(error)); 289 }; 290 291 fileEntry.createWriter(function(fileWriter) { 292 fileWriter.onwriteend = function(e) { 293 callback(null, fileEntry, e); 294 }; 295 fileWriter.onerror = errorHandler; 296 297 fileWriter.truncate(len); 298 }); 299 }; 300 301 /** 302 * Stub chmod function for nodejs compatiblity 303 * @param {String} path 304 * @param {Number} mode 305 * @param {Function} callback 306 * @type {void} 307 */ 308 WebFS.prototype.chmod = function(path, mode, callback) { 309 callback(); 310 }; 311 312 /** 313 * Returns a stat object from a path 314 * @param {String} path 315 * @param {Function} callback 316 * @type {void} 317 */ 318 WebFS.prototype.stat = function(path, callback) { 319 var _self = this; 320 321 var errorHandler = function(error) { 322 callback(_self.errorHandler(error)); 323 }; 324 325 this.open(path, function(error, fileHandler) { 326 if (error && error.code == _self.TYPE_MISMATCH_ERR) { 327 // Get a directory instead 328 _self.root.getDirectory(path, {}, function(dirHandler) { 329 Stats(dirHandler, callback); 330 }, errorHandler); 331 } else if (error) { 332 errorHandler(error); 333 } else { 334 Stats(fileHandler, callback); 335 } 336 }); 337 }; 338 339 /** 340 * Returns a stat object from a path 341 * @param {String} path 342 * @param {Function} callback 343 * @type {void} 344 * @function 345 */ 346 WebFS.prototype.lstat = WebFS.prototype.stat; 347 348 /** 349 * Returns a stat object from a file descriptor 350 * @param {FileEntry, DirectoryEntry} fd 351 * @param {Function} callback 352 * @type {void} 353 */ 354 WebFS.prototype.fstat = function(fd, callback) { 355 Stats(fd, callback); 356 }; 357 358 /** 359 * Stub link function for nodejs compatibility 360 * @param {String} srcpath 361 * @param {String} destpath 362 * @param {Function} callback 363 * @type {void} 364 */ 365 WebFS.prototype.link = function(srcpath, destpath, callback) { 366 callback(); 367 }; 368 369 /** 370 * Stub symlink function for nodejs compatibility 371 * @param {String} linkdata 372 * @param {String} path 373 * @param {Function} callback 374 * @type {void} 375 */ 376 WebFS.prototype.symlink = function(linkdata, path, callback) { 377 callback(); 378 }; 379 380 /** 381 * Stub readlink function for nodejs compatibility 382 * @param {String} path 383 * @param {Function} callback 384 * @type {void} 385 */ 386 WebFS.prototype.readlink = function(path, callback) { 387 callback(); 388 }; 389 390 /** 391 * Stub realpath function for nodejs compatibility 392 * @param {String} path 393 * @param {Function} callback 394 * @type {void} 395 */ 396 WebFS.prototype.realpath = function(path, callback) { 397 callback(); 398 }; 399 400 /** 401 * Deletes a file from the path. Directories are removed recursivly 402 * @param {String} path 403 * @param {Function} callback 404 * @type {void} 405 */ 406 WebFS.prototype.unlink = function(path, callback) { 407 var _self = this; 408 409 var errorHandler = function(error) { 410 callback(_self.errorHandler(error)); 411 }; 412 413 _self.root.getFile(path, {}, function(fileEntry) { 414 fileEntry.remove(callback, errorHandler); 415 }, function(error) { 416 if (e.code == FileError.TYPE_MISMATCH_ERR) { 417 _self.root.getDirectory(path, {}, function(dirEntry) { 418 dirEntry.removeRecursively(callback, errorHandler); 419 }, errorHandler); 420 } else { 421 errorHandler(error); 422 } 423 }); 424 }; 425 426 /** 427 * Deletes a directory from the path. Directories are removed recursivly 428 * @param {String} path 429 * @param {Function} callback 430 * @type {void} 431 * @function 432 */ 433 WebFS.prototype.rmdir= WebFS.prototype.unlink; 434 435 /** 436 * Creates a directory on the filesystem, will recursivly create paths 437 * @param {String} path 438 * @param {Number} mode 439 * @param {Function} callback 440 * @type {void} 441 */ 442 WebFS.prototype.mkdir = function(path, mode, callback) { 443 var _self = this; 444 445 if (typeof callback != "function") { 446 callback = mode; 447 } 448 449 var errorHandler = function(error) { 450 callback(_self.errorHandler(error)); 451 }; 452 453 var createDir = function(rootDir, folders) { 454 if (folders[0] == '.' || folders[0] == '') { 455 folders = folders.slice(1); 456 } 457 458 rootDir.getDirectory(folders[0], {create: true}, function(dirEntry) { 459 if (folders.length) { 460 createDir(dirEntry, folders.slice(1)); 461 } else { 462 callback(null, dirEntry); 463 } 464 }, errorHandler); 465 }; 466 createDir(this.fs.root, path.split('/')); 467 }; 468 469 /** 470 * Reads the contents of a directory, returns the result as an array of entries 471 * @param {String} path 472 * @param {Function} callback 473 * @type {void} 474 */ 475 WebFS.prototype.readdir = function(path, callback) { 476 var _self = this; 477 478 var errorHandler = function(error) { 479 callback(_self.errorHandler(error)); 480 }; 481 482 var listHandler = function(dirHandler) { 483 var dirReader = dirHandler.createReader(); 484 var entries = []; 485 var readEntries = function() { 486 dirReader.readEntries(function(results) { 487 if (!results.length) { 488 callback(null, entries.sort()); 489 } else { 490 for (var i = 0, j = results.length; i < j; i++) { 491 if (results[i].isDirectory) 492 entries.push(results[i].fullPath + '/'); 493 else 494 entries.push(results[i].fullPath); 495 } 496 //entries = entries.concat(Array.prototype.slice.call(results || [], 0)); 497 readEntries(); 498 } 499 }, errorHandler); 500 }; 501 readEntries(); 502 }; 503 504 _self.root.getDirectory(path, {}, listHandler, errorHandler); 505 }; 506 507 /** 508 * 'Close' a file or directory handler by setting it to null 509 * @param {FileEntry, DirectoryEntry} fd 510 * @param {Function} callback 511 * @type {void} 512 */ 513 WebFS.prototype.close = function(fd, callback) { 514 fd = null; // Set to null for GC 515 callback(); 516 }; 517 518 /** 519 * Opens a file or directory and return a handler 520 * @param {String} path 521 * @param {String} flags 522 * @param {Number} mode 523 * @param {Function} callback 524 * @type {void} 525 */ 526 WebFS.prototype.open = function(path, flags, mode, callback) { 527 var _self = this; 528 529 if (typeof callback != "function") { 530 callback = mode; 531 } 532 if (typeof callback != "function") { 533 callback = flags; 534 } 535 536 var errorHandler = function(error) { 537 if (error && error.code == _self.TYPE_MISMATCH_ERR) { 538 _self.root.getDirectory(path, options, successHandler, function(error) { 539 callback(_self.errorHandler(error)); 540 }); 541 } 542 }; 543 544 var successHandler = function(fileHandler) { 545 callback(null, fileHandler); 546 }; 547 548 var options = {}; 549 // If the flag is to write or append, and the file does not exist 550 // then we need to ensure it's created 551 if (['w', 'w+', 'a', 'a+'].indexOf(flags) > -1) 552 options.create = true; 553 554 _self.root.getFile(path, options, successHandler, errorHandler); 555 }; 556 557 /** 558 * Writes the contents of a Blob or File to a FileEntry on the filesystem 559 * @param {FileEntry} fileHandler 560 * @param {Mixed} buffer 561 * @param {Number} offset 562 * @param {Number} length 563 * @param {Number} position 564 * @param {Function} callback 565 * @type {void} 566 */ 567 WebFS.prototype.write = function(fileHandler, buffer, offset, length, position, callback) { 568 var _self = this, 569 data; 570 571 if (typeof callback != "function") { 572 callback = position; 573 } 574 if (typeof callback != "function") { 575 callback = length; 576 } 577 if (typeof callback != "function") { 578 callback = offset; 579 } 580 581 var errorHandler = function(error) { 582 callback(_self.errorHandler(error)); 583 }; 584 585 var data = (typeof buffer == 'string') ? _self.createBlob(buffer) : buffer; 586 587 var writerHandler = function(fileWriter) { 588 589 fileWriter.onwriteend = function(e) { 590 callback(null, e.loaded, buffer); 591 }; 592 593 fileWriter.onerror = errorHandler; 594 595 fileWriter.write(data); 596 }; 597 598 fileHandler.createWriter(writerHandler, errorHandler); 599 }; 600 601 /** 602 * Asynchronously writes data to a file, replacing the file if it already exists. Data can be a string or a buffer. 603 * @param {String} filename 604 * @param {Mixed} data 605 * @param {String} encoding 606 * @param {Function} callback 607 * @type {void} 608 */ 609 WebFS.prototype.writeFile = function(filename, data, encoding, callback) { 610 var _self = this; 611 612 if (typeof callback != 'function') { 613 callback = encoding; 614 } 615 616 var buffer = (typeof data == 'string') ? _self.createBlob(data) : data; 617 618 var openFileHandler = function(error, fileHandler) { 619 _self.truncate(fileHandler, 0, function(error) { 620 if (error) 621 return callback(error); 622 _self.write(fileHandler, buffer, null, null, null, function(error, written, buffer_) { 623 callback(error, buffer_); 624 }); 625 }); 626 }; 627 628 _self.open(filename, 'w', null, openFileHandler); 629 }; 630 631 /** 632 * Read data from the file specified by file handler. 633 * @param {FileEntry} fileHandler 634 * @param {Mixed} buffer 635 * @param {Number} offset 636 * @param {Number} length 637 * @param {Number} position 638 * @type {void} 639 */ 640 WebFS.prototype.read = function(fileHandler, buffer, offset, length, position, callback) { 641 var _self = this, 642 data; 643 644 if (typeof callback != "function") { 645 callback = position; 646 } 647 if (typeof callback != "function") { 648 callback = length; 649 } 650 if (typeof callback != "function") { 651 callback = offset; 652 } 653 654 var errorHandler = function(error) { 655 callback(_self.errorHandler(error)); 656 }; 657 658 fileHandler.file(function(file) { 659 var reader = new FileReader(); 660 661 reader.onloadend = function(e) { 662 buffer.append(this.result); 663 callback(null, buffer.getBlob().size, buffer.getBlob()); 664 }; 665 666 reader.onerror = errorHandler; 667 668 // Since we want to support binary or string data, we should read 669 // as an array buffer and allow the user to determine the output 670 // from the Blob/File interface buffer 671 reader.readAsArrayBuffer(file); 672 }); 673 }; 674 675 /** 676 * Asynchronously reads the entire contents of a file and returns a buffer 677 * @param {String} filename 678 * @param {String} encoding 679 * @param {Function} callback 680 * @type {void} 681 */ 682 WebFS.prototype.readFile = function(filename, encoding, callback) { 683 var _self = this; 684 685 if (typeof callback != 'function') { 686 callback = encoding; 687 } 688 689 var successHandler = function(error, fileHandler) { 690 if (error) 691 return callback(error); 692 693 _self.read(fileHandler, new BlobBuilder(), null, null, null, callback); 694 }; 695 696 this.open(filename, null, null, successHandler); 697 }; 698 699 /** 700 * Takes data, string or binary, and creates a binary blob. 701 * @param {Mixed} data 702 * @param {String} encoding 703 * @type {void} 704 */ 705 WebFS.prototype.createBlob = function(data, encoding) { 706 var bb = new BlobBuilder(); 707 bb.append(data); 708 if (encoding) 709 return bb.getBlob(encoding); 710 else 711 return bb.getBlob(); 712 }; 713 714 /** 715 * Method to get content of a blob or file as a string 716 * @param {File, Blob} data 717 * @param {String} encoding 718 * @param {Function} callback 719 * @type {void} 720 */ 721 WebFS.prototype.readString = function(data, encoding, callback) { 722 723 var reader = new FileReader(), 724 encoding_; 725 726 if (typeof callback != 'function') { 727 callback = encoding; 728 encoding_ = 'UTF-8'; 729 } else { 730 encoding_ = encoding; 731 } 732 733 reader.onloadend = function(event) { 734 callback(null, this.result); 735 }; 736 737 reader.onerror = function(error) { 738 callback(error); 739 }; 740 741 data = reader.readAsText(data, encoding_); 742 }; 743 744 /** 745 * Method to get content as a binary string 746 * @param {File, Blob} data 747 * @param {Function} callback 748 * @type {void} 749 */ 750 WebFS.prototype.readBinaryString = function(data, callback) { 751 var reader = new FileReader(); 752 reader.onloadend = function(event) { 753 callback(null, this.result); 754 }; 755 reader.onerror = function(error) { 756 callback(error); 757 }; 758 759 reader.readAsBinaryString(data); 760 }; 761 762 /** 763 * Method to get content as a array buffer 764 * @param {File, Blob} data 765 * @param {Function} callback 766 * @type {void} 767 */ 768 WebFS.prototype.readArrayBuffer = function(data, callback) { 769 var reader; 770 reader = new FileReader(); 771 reader.onloadend = function(event) { 772 callback(null, this.result); 773 }; 774 reader.onerror = function(error) { 775 callback(error); 776 }; 777 778 reader.readAsArrayBuffer(data); 779 }; 780 781 /** 782 * Method to get content as a data url 783 * @param {File, Blob} data 784 * @param {Function} callback 785 * @type {void} 786 */ 787 WebFS.prototype.readDataUrl = function(data, callback) { 788 var reader = new FileReader(); 789 reader.onloadend = function(event) { 790 callback(null, this.result); 791 }; 792 reader.onerror = function(error) { 793 callback(error); 794 }; 795 796 reader.readAsDataURL(data); 797 }; 798 799 return WebFS; 800 })();