text: // Copyright 2011 The Closure Library Authors. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS-IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  /**
   * @fileoverview Wrappers for the HTML5 File API. These wrappers closely mirror
   * the underlying APIs, but use Closure-style events and Deferred return values.
   * Their existence also makes it possible to mock the FileSystem API for testing
   * in browsers that don't support it natively.
   *
   * When adding public functions to anything under this namespace, be sure to add
   * its mock counterpart to goog.testing.fs.
   *
   */
  
  goog.provide('goog.fs');
  
  goog.require('goog.array');
  goog.require('goog.async.Deferred');
  goog.require('goog.fs.Error');
  goog.require('goog.fs.FileReader');
  goog.require('goog.fs.FileSystemImpl');
  goog.require('goog.fs.url');
  goog.require('goog.userAgent');
  
  
  /**
   * Get a wrapped FileSystem object.
   *
   * @param {goog.fs.FileSystemType_} type The type of the filesystem to get.
   * @param {number} size The size requested for the filesystem, in bytes.
   * @return {!goog.async.Deferred} The deferred {@link goog.fs.FileSystem}. If an
   *     error occurs, the errback is called with a {@link goog.fs.Error}.
   * @private
   */
  goog.fs.get_ = function(type, size) {
    var requestFileSystem =
        goog.global.requestFileSystem || goog.global.webkitRequestFileSystem;
  
    if (!goog.isFunction(requestFileSystem)) {
      return goog.async.Deferred.fail(new Error('File API unsupported'));
    }
  
    var d = new goog.async.Deferred();
    requestFileSystem(
        type, size, function(fs) { d.callback(new goog.fs.FileSystemImpl(fs)); },
        function(err) {
          d.errback(new goog.fs.Error(err, 'requesting filesystem'));
        });
    return d;
  };
  
  
  /**
   * The two types of filesystem.
   *
   * @enum {number}
   * @private
   */
  goog.fs.FileSystemType_ = {
    /**
     * A temporary filesystem may be deleted by the user agent at its discretion.
     */
    TEMPORARY: 0,
    /**
     * A persistent filesystem will never be deleted without the user's or
     * application's authorization.
     */
    PERSISTENT: 1
  };
  
  
  /**
   * Returns a temporary FileSystem object. A temporary filesystem may be deleted
   * by the user agent at its discretion.
   *
   * @param {number} size The size requested for the filesystem, in bytes.
   * @return {!goog.async.Deferred} The deferred {@link goog.fs.FileSystem}. If an
   *     error occurs, the errback is called with a {@link goog.fs.Error}.
   */
  goog.fs.getTemporary = function(size) {
    return goog.fs.get_(goog.fs.FileSystemType_.TEMPORARY, size);
  };
  
  
  /**
   * Returns a persistent FileSystem object. A persistent filesystem will never be
   * deleted without the user's or application's authorization.
   *
   * @param {number} size The size requested for the filesystem, in bytes.
   * @return {!goog.async.Deferred} The deferred {@link goog.fs.FileSystem}. If an
   *     error occurs, the errback is called with a {@link goog.fs.Error}.
   */
  goog.fs.getPersistent = function(size) {
    return goog.fs.get_(goog.fs.FileSystemType_.PERSISTENT, size);
  };
  
  
  /**
   * Creates a blob URL for a blob object.
   * Throws an error if the browser does not support Object Urls.
   *
   * TODO(user): Update references to this method to use
   * goog.fs.url.createObjectUrl instead.
   *
   * @param {!Blob} blob The object for which to create the URL.
   * @return {string} The URL for the object.
   */
  goog.fs.createObjectUrl = function(blob) {
    return goog.fs.url.createObjectUrl(blob);
  };
  
  
  /**
   * Revokes a URL created by {@link goog.fs.createObjectUrl}.
   * Throws an error if the browser does not support Object Urls.
   *
   * TODO(user): Update references to this method to use
   * goog.fs.url.revokeObjectUrl instead.
   *
   * @param {string} url The URL to revoke.
   */
  goog.fs.revokeObjectUrl = function(url) {
    goog.fs.url.revokeObjectUrl(url);
  };
  
  
  /**
   * Checks whether this browser supports Object Urls. If not, calls to
   * createObjectUrl and revokeObjectUrl will result in an error.
   *
   * TODO(user): Update references to this method to use
   * goog.fs.url.browserSupportsObjectUrls instead.
   *
   * @return {boolean} True if this browser supports Object Urls.
   */
  goog.fs.browserSupportsObjectUrls = function() {
    return goog.fs.url.browserSupportsObjectUrls();
  };
  
  
  /**
   * Concatenates one or more values together and converts them to a Blob.
   *
   * @param {...(string|!Blob|!ArrayBuffer)} var_args The values that will make up
   *     the resulting blob.
   * @return {!Blob} The blob.
   */
  goog.fs.getBlob = function(var_args) {
    var BlobBuilder = goog.global.BlobBuilder || goog.global.WebKitBlobBuilder;
  
    if (goog.isDef(BlobBuilder)) {
      var bb = new BlobBuilder();
      for (var i = 0; i < arguments.length; i++) {
        bb.append(arguments[i]);
      }
      return bb.getBlob();
    } else {
      return goog.fs.getBlobWithProperties(goog.array.toArray(arguments));
    }
  };
  
  
  /**
   * Creates a blob with the given properties.
   * See https://developer.mozilla.org/en-US/docs/Web/API/Blob for more details.
   *
   * @param {Array<string|!Blob>} parts The values that will make up the
   *     resulting blob.
   * @param {string=} opt_type The MIME type of the Blob.
   * @param {string=} opt_endings Specifies how strings containing newlines are to
   *     be written out.
   * @return {!Blob} The blob.
   */
  goog.fs.getBlobWithProperties = function(parts, opt_type, opt_endings) {
    var BlobBuilder = goog.global.BlobBuilder || goog.global.WebKitBlobBuilder;
  
    if (goog.isDef(BlobBuilder)) {
      var bb = new BlobBuilder();
      for (var i = 0; i < parts.length; i++) {
        bb.append(parts[i], opt_endings);
      }
      return bb.getBlob(opt_type);
    } else if (goog.isDef(goog.global.Blob)) {
      var properties = {};
      if (opt_type) {
        properties['type'] = opt_type;
      }
      if (opt_endings) {
        properties['endings'] = opt_endings;
      }
      return new Blob(parts, properties);
    } else {
      throw Error('This browser doesn\'t seem to support creating Blobs');
    }
  };
  
  
  /**
   * Converts a Blob or a File into a string. This should only be used when the
   * blob is known to be small.
   *
   * @param {!Blob} blob The blob to convert.
   * @param {string=} opt_encoding The name of the encoding to use.
   * @return {!goog.async.Deferred} The deferred string. If an error occurrs, the
   *     errback is called with a {@link goog.fs.Error}.
   * @deprecated Use {@link goog.fs.FileReader.readAsText} instead.
   */
  goog.fs.blobToString = function(blob, opt_encoding) {
    return goog.fs.FileReader.readAsText(blob, opt_encoding);
  };
  
  
  /**
   * Slices the blob. The returned blob contains data from the start byte
   * (inclusive) till the end byte (exclusive). Negative indices can be used
   * to count bytes from the end of the blob (-1 == blob.size - 1). Indices
   * are always clamped to blob range. If end is omitted, all the data till
   * the end of the blob is taken.
   *
   * @param {!Blob} blob The blob to be sliced.
   * @param {number} start Index of the starting byte.
   * @param {number=} opt_end Index of the ending byte.
   * @return {Blob} The blob slice or null if not supported.
   */
  goog.fs.sliceBlob = function(blob, start, opt_end) {
    if (!goog.isDef(opt_end)) {
      opt_end = blob.size;
    }
    if (blob.webkitSlice) {
      // Natively accepts negative indices, clamping to the blob range and
      // range end is optional. See http://trac.webkit.org/changeset/83873
      return blob.webkitSlice(start, opt_end);
    } else if (blob.mozSlice) {
      // Natively accepts negative indices, clamping to the blob range and
      // range end is optional. See https://developer.mozilla.org/en/DOM/Blob
      // and http://hg.mozilla.org/mozilla-central/rev/dae833f4d934
      return blob.mozSlice(start, opt_end);
    } else if (blob.slice) {
      // Old versions of Firefox and Chrome use the original specification.
      // Negative indices are not accepted, only range end is clamped and
      // range end specification is obligatory.
      // See http://www.w3.org/TR/2009/WD-FileAPI-20091117/
      if ((goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('13.0')) ||
          (goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('537.1'))) {
        if (start < 0) {
          start += blob.size;
        }
        if (start < 0) {
          start = 0;
        }
        if (opt_end < 0) {
          opt_end += blob.size;
        }
        if (opt_end < start) {
          opt_end = start;
        }
        return blob.slice(start, opt_end - start);
      }
      // IE and the latest versions of Firefox and Chrome use the new
      // specification. Natively accepts negative indices, clamping to the blob
      // range and range end is optional.
      // See http://dev.w3.org/2006/webapi/FileAPI/
      return blob.slice(start, opt_end);
    }
    return null;
  };
  