text: ﻿/*
   *
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.
   *
   */
  
  /* global IDBKeyRange */
  
  var LocalFileSystem = require('./LocalFileSystem'),
      FileSystem = require('./FileSystem'),
      FileEntry = require('./FileEntry'),
      FileError = require('./FileError'),
      DirectoryEntry = require('./DirectoryEntry'),
      File = require('./File');
  
  /*
  QUIRKS:
      Does not fail when removing non-empty directories
      Does not support metadata for directories
      Does not support requestAllFileSystems
      Does not support resolveLocalFileSystemURI
      Methods copyTo and moveTo do not support directories
  
      Heavily based on https://github.com/ebidel/idb.filesystem.js
   */
  
  
  (function(exports, global) {
      var indexedDB = global.indexedDB || global.mozIndexedDB;
      if (!indexedDB) {
          throw "Firefox OS File plugin: indexedDB not supported";
      }
  
      var fs_ = null;
  
      var idb_ = {};
      idb_.db = null;
      var FILE_STORE_ = 'entries';
  
      var DIR_SEPARATOR = '/';
      var DIR_OPEN_BOUND = String.fromCharCode(DIR_SEPARATOR.charCodeAt(0) + 1);
  
      var pathsPrefix = {
          // Read-only directory where the application is installed.
          applicationDirectory: location.origin + "/",
          // Where to put app-specific data files.
          dataDirectory: 'file:///persistent/',
          // Cached files that should survive app restarts.
          // Apps should not rely on the OS to delete files in here.
          cacheDirectory: 'file:///temporary/',
      };
  
  /*** Exported functionality ***/
  
      exports.requestFileSystem = function(successCallback, errorCallback, args) {
          var type = args[0];
          //var size = args[1];
  
          if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) {
              if (errorCallback) {
                  errorCallback(FileError.INVALID_MODIFICATION_ERR);
              }
              return;
          }
  
          var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent';
          var storageName = (location.protocol + location.host).replace(/:/g, '_');
  
          var root = new DirectoryEntry('', DIR_SEPARATOR);
          fs_ = new FileSystem(name, root);
  
          idb_.open(storageName, function() {
              successCallback(fs_);
          }, errorCallback);
      };
  
      require('./fileSystems').getFs = function(name, callback) {
          callback(new FileSystem(name, fs_.root));
      };
  
      // list a directory's contents (files and folders).
      exports.readEntries = function(successCallback, errorCallback, args) {
          var fullPath = args[0];
  
          if (!successCallback) {
              throw Error('Expected successCallback argument.');
          }
  
          var path = resolveToFullPath_(fullPath);
  
          idb_.getAllEntries(path.fullPath, path.storagePath, function(entries) {
              successCallback(entries);
          }, errorCallback);
      };
  
      exports.getFile = function(successCallback, errorCallback, args) {
          var fullPath = args[0];
          var path = args[1];
          var options = args[2] || {};
  
          // Create an absolute path if we were handed a relative one.
          path = resolveToFullPath_(fullPath, path);
  
          idb_.get(path.storagePath, function(fileEntry) {
              if (options.create === true && options.exclusive === true && fileEntry) {
                  // If create and exclusive are both true, and the path already exists,
                  // getFile must fail.
  
                  if (errorCallback) {
                      errorCallback(FileError.PATH_EXISTS_ERR);
                  }
              } else if (options.create === true && !fileEntry) {
                  // If create is true, the path doesn't exist, and no other error occurs,
                  // getFile must create it as a zero-length file and return a corresponding
                  // FileEntry.
                  var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
  
                  newFileEntry.file_ = new MyFile({
                      size: 0,
                      name: newFileEntry.name,
                      lastModifiedDate: new Date(),
                      storagePath: path.storagePath
                  });
  
                  idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
              } else if (options.create === true && fileEntry) {
                  if (fileEntry.isFile) {
                      // Overwrite file, delete then create new.
                      idb_['delete'](path.storagePath, function() {
                          var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
  
                          newFileEntry.file_ = new MyFile({
                              size: 0,
                              name: newFileEntry.name,
                              lastModifiedDate: new Date(),
                              storagePath: path.storagePath
                          });
  
                          idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
                      }, errorCallback);
                  } else {
                      if (errorCallback) {
                          errorCallback(FileError.INVALID_MODIFICATION_ERR);
                      }
                  }
              } else if ((!options.create || options.create === false) && !fileEntry) {
                  // If create is not true and the path doesn't exist, getFile must fail.
                  if (errorCallback) {
                      errorCallback(FileError.NOT_FOUND_ERR);
                  }
              } else if ((!options.create || options.create === false) && fileEntry &&
                  fileEntry.isDirectory) {
                  // If create is not true and the path exists, but is a directory, getFile
                  // must fail.
                  if (errorCallback) {
                      errorCallback(FileError.INVALID_MODIFICATION_ERR);
                  }
              } else {
                  // Otherwise, if no other error occurs, getFile must return a FileEntry
                  // corresponding to path.
  
                  successCallback(fileEntryFromIdbEntry(fileEntry));
              }
          }, errorCallback);
      };
  
      exports.getFileMetadata = function(successCallback, errorCallback, args) {
          var fullPath = args[0];
  
          exports.getFile(function(fileEntry) {
              successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate,
                  fileEntry.file_.size));
          }, errorCallback, [fullPath, null]);
      };
  
      exports.getMetadata = function(successCallback, errorCallback, args) {
          exports.getFile(function (fileEntry) {
              successCallback(
                  {
                      modificationTime: fileEntry.file_.lastModifiedDate,
                      size: fileEntry.file_.lastModifiedDate
                  });
          }, errorCallback, args);
      };
  
      exports.setMetadata = function(successCallback, errorCallback, args) {
          var fullPath = args[0];
          var metadataObject = args[1];
  
          exports.getFile(function (fileEntry) {
                fileEntry.file_.lastModifiedDate = metadataObject.modificationTime;
          }, errorCallback, [fullPath, null]);
      };
  
      exports.write = function(successCallback, errorCallback, args) {
          var fileName = args[0],
              data = args[1],
              position = args[2];
              //isBinary = args[3];
  
          if (!data) {
              if (errorCallback) {
                  errorCallback(FileError.INVALID_MODIFICATION_ERR);
              }
              return;
          }
  
          exports.getFile(function(fileEntry) {
              var blob_ = fileEntry.file_.blob_;
  
              if (!blob_) {
                  blob_ = new Blob([data], {type: data.type});
              } else {
                  // Calc the head and tail fragments
                  var head = blob_.slice(0, position);
                  var tail = blob_.slice(position + data.byteLength);
  
                  // Calc the padding
                  var padding = position - head.size;
                  if (padding < 0) {
                      padding = 0;
                  }
  
                  // Do the "write". In fact, a full overwrite of the Blob.
                  blob_ = new Blob([head, new Uint8Array(padding), data, tail],
                      {type: data.type});
              }
  
              // Set the blob we're writing on this file entry so we can recall it later.
              fileEntry.file_.blob_ = blob_;
              fileEntry.file_.lastModifiedDate = data.lastModifiedDate || null;
              fileEntry.file_.size = blob_.size;
              fileEntry.file_.name = blob_.name;
              fileEntry.file_.type = blob_.type;
  
              idb_.put(fileEntry, fileEntry.file_.storagePath, function() {
                  successCallback(data.byteLength);
              }, errorCallback);
          }, errorCallback, [fileName, null]);
      };
  
      exports.readAsText = function(successCallback, errorCallback, args) {
          var fileName = args[0],
              enc = args[1],
              startPos = args[2],
              endPos = args[3];
  
          readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback);
      };
  
      exports.readAsDataURL = function(successCallback, errorCallback, args) {
          var fileName = args[0],
              startPos = args[1],
              endPos = args[2];
  
          readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback);
      };
  
      exports.readAsBinaryString = function(successCallback, errorCallback, args) {
          var fileName = args[0],
              startPos = args[1],
              endPos = args[2];
  
          readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback);
      };
  
      exports.readAsArrayBuffer = function(successCallback, errorCallback, args) {
          var fileName = args[0],
              startPos = args[1],
              endPos = args[2];
  
          readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback);
      };
  
      exports.removeRecursively = exports.remove = function(successCallback, errorCallback, args) {
          var fullPath = args[0];
  
          // TODO: This doesn't protect against directories that have content in it.
          // Should throw an error instead if the dirEntry is not empty.
          idb_['delete'](fullPath, function() {
              successCallback();
          }, errorCallback);
      };
  
      exports.getDirectory = function(successCallback, errorCallback, args) {
          var fullPath = args[0];
          var path = args[1];
          var options = args[2];
  
          // Create an absolute path if we were handed a relative one.
          path = resolveToFullPath_(fullPath, path);
  
          idb_.get(path.storagePath, function(folderEntry) {
              if (!options) {
                  options = {};
              }
  
              if (options.create === true && options.exclusive === true && folderEntry) {
                  // If create and exclusive are both true, and the path already exists,
                  // getDirectory must fail.
                  if (errorCallback) {
                      errorCallback(FileError.INVALID_MODIFICATION_ERR);
                  }
              } else if (options.create === true && !folderEntry) {
                  // If create is true, the path doesn't exist, and no other error occurs,
                  // getDirectory must create it as a zero-length file and return a corresponding
                  // MyDirectoryEntry.
                  var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
  
                  idb_.put(dirEntry, path.storagePath, successCallback, errorCallback);
              } else if (options.create === true && folderEntry) {
  
                  if (folderEntry.isDirectory) {
                      // IDB won't save methods, so we need re-create the MyDirectoryEntry.
                      successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.fileSystem));
                  } else {
                      if (errorCallback) {
                          errorCallback(FileError.INVALID_MODIFICATION_ERR);
                      }
                  }
              } else if ((!options.create || options.create === false) && !folderEntry) {
                  // Handle root special. It should always exist.
                  if (path.fullPath === DIR_SEPARATOR) {
                      successCallback(fs_.root);
                      return;
                  }
  
                  // If create is not true and the path doesn't exist, getDirectory must fail.
                  if (errorCallback) {
                      errorCallback(FileError.NOT_FOUND_ERR);
                  }
              } else if ((!options.create || options.create === false) && folderEntry &&
                  folderEntry.isFile) {
                  // If create is not true and the path exists, but is a file, getDirectory
                  // must fail.
                  if (errorCallback) {
                      errorCallback(FileError.INVALID_MODIFICATION_ERR);
                  }
              } else {
                  // Otherwise, if no other error occurs, getDirectory must return a
                  // MyDirectoryEntry corresponding to path.
  
                  // IDB won't' save methods, so we need re-create MyDirectoryEntry.
                  successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.fileSystem));
              }
          }, errorCallback);
      };
  
      exports.getParent = function(successCallback, errorCallback, args) {
          var fullPath = args[0];
  
          if (fullPath === DIR_SEPARATOR) {
              successCallback(fs_.root);
              return;
          }
  
          var pathArr = fullPath.split(DIR_SEPARATOR);
          pathArr.pop();
          var namesa = pathArr.pop();
          var path = pathArr.join(DIR_SEPARATOR);
  
          exports.getDirectory(successCallback, errorCallback, [path, namesa, {create: false}]);
      };
  
      exports.copyTo = function(successCallback, errorCallback, args) {
          var srcPath = args[0];
          var parentFullPath = args[1];
          var name = args[2];
  
          // Read src file
          exports.getFile(function(srcFileEntry) {
  
              // Create dest file
              exports.getFile(function(dstFileEntry) {
  
                  exports.write(function() {
                      successCallback(dstFileEntry);
                  }, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]);
  
              }, errorCallback, [parentFullPath, name, {create: true}]);
  
          }, errorCallback, [srcPath, null]);
      };
  
      exports.moveTo = function(successCallback, errorCallback, args) {
          var srcPath = args[0];
          //var parentFullPath = args[1];
          //var name = args[2];
  
          exports.copyTo(function (fileEntry) {
  
              exports.remove(function () {
                  successCallback(fileEntry);
              }, errorCallback, [srcPath]);
  
          }, errorCallback, args);
      };
  
      exports.resolveLocalFileSystemURI = function(successCallback, errorCallback, args) {
          var path = args[0];
  
          // Ignore parameters
          if (path.indexOf('?') !== -1) {
              path = String(path).split("?")[0];
          }
  
          // support for encodeURI
          if (/\%5/g.test(path)) {
              path = decodeURI(path);
          }
  
          if (path.indexOf(pathsPrefix.dataDirectory) === 0) {
              path = path.substring(pathsPrefix.dataDirectory.length - 1);
  
              exports.requestFileSystem(function(fs) {
                  fs.root.getFile(path, {create: false}, successCallback, function() {
                      fs.root.getDirectory(path, {create: false}, successCallback, errorCallback);
                  });
              }, errorCallback, [LocalFileSystem.PERSISTENT]);
          } else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) {
              path = path.substring(pathsPrefix.cacheDirectory.length - 1);
  
              exports.requestFileSystem(function(fs) {
                  fs.root.getFile(path, {create: false}, successCallback, function() {
                      fs.root.getDirectory(path, {create: false}, successCallback, errorCallback);
                  });
              }, errorCallback, [LocalFileSystem.TEMPORARY]);
          } else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) {
              path = path.substring(pathsPrefix.applicationDirectory.length);
  
              var xhr = new XMLHttpRequest();
              xhr.open("GET", path, true);
              xhr.onreadystatechange = function () {
                  if (xhr.status === 200 && xhr.readyState === 4) {
                      exports.requestFileSystem(function(fs) {
                          fs.name = location.hostname;
                          fs.root.getFile(path, {create: true}, writeFile, errorCallback);
                      }, errorCallback, [LocalFileSystem.PERSISTENT]);
                  }
              };
  
              xhr.onerror = function () {
                  if (errorCallback) {
                      errorCallback(FileError.NOT_READABLE_ERR);
                  }
              };
  
              xhr.send();
          } else {
              if (errorCallback) {
                  errorCallback(FileError.NOT_FOUND_ERR);
              }
          }
  
          function writeFile(entry) {
              entry.createWriter(function (fileWriter) {
                  fileWriter.onwriteend = function (evt) {
                      if (!evt.target.error) {
                          entry.filesystemName = location.hostname;
                          successCallback(entry);
                      }
                  };
                  fileWriter.onerror = function () {
                      if (errorCallback) {
                          errorCallback(FileError.NOT_READABLE_ERR);
                      }
                  };
                  fileWriter.write(new Blob([xhr.response]));
              }, errorCallback);
          }
      };
  
      exports.requestAllPaths = function(successCallback) {
          successCallback(pathsPrefix);
      };
  
  /*** Helpers ***/
  
      /**
       * Interface to wrap the native File interface.
       *
       * This interface is necessary for creating zero-length (empty) files,
       * something the Filesystem API allows you to do. Unfortunately, File's
       * constructor cannot be called directly, making it impossible to instantiate
       * an empty File in JS.
       *
       * @param {Object} opts Initial values.
       * @constructor
       */
      function MyFile(opts) {
          var blob_ = new Blob();
  
          this.size = opts.size || 0;
          this.name = opts.name || '';
          this.type = opts.type || '';
          this.lastModifiedDate = opts.lastModifiedDate || null;
          this.storagePath = opts.storagePath || '';
  
          // Need some black magic to correct the object's size/name/type based on the
          // blob that is saved.
          Object.defineProperty(this, 'blob_', {
              enumerable: true,
              get: function() {
                  return blob_;
              },
              set: function(val) {
                  blob_ = val;
                  this.size = blob_.size;
                  this.name = blob_.name;
                  this.type = blob_.type;
                  this.lastModifiedDate = blob_.lastModifiedDate;
              }.bind(this)
          });
      }
  
      MyFile.prototype.constructor = MyFile;
  
      // When saving an entry, the fullPath should always lead with a slash and never
      // end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute
      // one. This method ensures path is legit!
      function resolveToFullPath_(cwdFullPath, path) {
          path = path || '';
          var fullPath = path;
          var prefix = '';
  
          cwdFullPath = cwdFullPath || DIR_SEPARATOR;
          if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) {
              prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
              cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
          }
  
          var relativePath = path[0] !== DIR_SEPARATOR;
          if (relativePath) {
              fullPath = cwdFullPath;
              if (cwdFullPath != DIR_SEPARATOR) {
                  fullPath += DIR_SEPARATOR + path;
              } else {
                  fullPath += path;
              }
          }
  
          // Adjust '..'s by removing parent directories when '..' flows in path.
          var parts = fullPath.split(DIR_SEPARATOR);
          for (var i = 0; i < parts.length; ++i) {
              var part = parts[i];
              if (part == '..') {
                  parts[i - 1] = '';
                  parts[i] = '';
              }
          }
          fullPath = parts.filter(function(el) {
              return el;
          }).join(DIR_SEPARATOR);
  
          // Add back in leading slash.
          if (fullPath[0] !== DIR_SEPARATOR) {
              fullPath = DIR_SEPARATOR + fullPath;
          }
  
          // Replace './' by current dir. ('./one/./two' -> one/two)
          fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR);
  
          // Replace '//' with '/'.
          fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR);
  
          // Replace '/.' with '/'.
          fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR);
  
          // Remove '/' if it appears on the end.
          if (fullPath[fullPath.length - 1] == DIR_SEPARATOR &&
              fullPath != DIR_SEPARATOR) {
              fullPath = fullPath.substring(0, fullPath.length - 1);
          }
  
          return {
              storagePath: prefix + fullPath,
              fullPath: fullPath,
              fileName: fullPath.split(DIR_SEPARATOR).pop(),
              fsName: prefix.split(DIR_SEPARATOR).pop()
          };
      }
  
      function fileEntryFromIdbEntry(fileEntry) {
          // IDB won't save methods, so we need re-create the FileEntry.
          var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.fileSystem);
          clonedFileEntry.file_ = fileEntry.file_;
  
          return clonedFileEntry;
      }
  
      function readAs(what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) {
          exports.getFile(function(fileEntry) {
              var fileReader = new FileReader(),
                  blob = fileEntry.file_.blob_.slice(startPos, endPos);
  
              fileReader.onload = function(e) {
                  successCallback(e.target.result);
              };
  
              fileReader.onerror = errorCallback;
  
              switch (what) {
                  case 'text':
                      fileReader.readAsText(blob, encoding);
                      break;
                  case 'dataURL':
                      fileReader.readAsDataURL(blob);
                      break;
                  case 'arrayBuffer':
                      fileReader.readAsArrayBuffer(blob);
                      break;
                  case 'binaryString':
                      fileReader.readAsBinaryString(blob);
                      break;
              }
  
          }, errorCallback, [fullPath, null]);
      }
  
  /*** Core logic to handle IDB operations ***/
  
      idb_.open = function(dbName, successCallback, errorCallback) {
          var self = this;
  
          // TODO: FF 12.0a1 isn't liking a db name with : in it.
          var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version*/);
  
          request.onerror = errorCallback || onError;
  
          request.onupgradeneeded = function(e) {
              // First open was called or higher db version was used.
  
              // console.log('onupgradeneeded: oldVersion:' + e.oldVersion,
              //           'newVersion:' + e.newVersion);
  
              self.db = e.target.result;
              self.db.onerror = onError;
  
              if (!self.db.objectStoreNames.contains(FILE_STORE_)) {
                  self.db.createObjectStore(FILE_STORE_/*,{keyPath: 'id', autoIncrement: true}*/);
              }
          };
  
          request.onsuccess = function(e) {
              self.db = e.target.result;
              self.db.onerror = onError;
              successCallback(e);
          };
  
          request.onblocked = errorCallback || onError;
      };
  
      idb_.close = function() {
          this.db.close();
          this.db = null;
      };
  
      idb_.get = function(fullPath, successCallback, errorCallback) {
          if (!this.db) {
              if (errorCallback) {
                  errorCallback(FileError.INVALID_MODIFICATION_ERR);
              }
              return;
          }
  
          var tx = this.db.transaction([FILE_STORE_], 'readonly');
  
          //var request = tx.objectStore(FILE_STORE_).get(fullPath);
          var range = IDBKeyRange.bound(fullPath, fullPath + DIR_OPEN_BOUND,
              false, true);
          var request = tx.objectStore(FILE_STORE_).get(range);
  
          tx.onabort = errorCallback || onError;
          tx.oncomplete = function(e) {
              successCallback(request.result);
          };
      };
  
      idb_.getAllEntries = function(fullPath, storagePath, successCallback, errorCallback) {
          if (!this.db) {
              if (errorCallback) {
                  errorCallback(FileError.INVALID_MODIFICATION_ERR);
              }
              return;
          }
  
          var results = [];
  
          if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) {
              storagePath = storagePath.substring(0, storagePath.length - 1);
          }
  
          var range = IDBKeyRange.bound(
                  storagePath + DIR_SEPARATOR, storagePath + DIR_OPEN_BOUND, false, true);
  
          var tx = this.db.transaction([FILE_STORE_], 'readonly');
          tx.onabort = errorCallback || onError;
          tx.oncomplete = function(e) {
              results = results.filter(function(val) {
                  var valPartsLen = val.fullPath.split(DIR_SEPARATOR).length;
                  var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
  
                  if (fullPath === DIR_SEPARATOR && valPartsLen < fullPathPartsLen + 1) {
                      // Hack to filter out entries in the root folder. This is inefficient
                      // because reading the entires of fs.root (e.g. '/') returns ALL
                      // results in the database, then filters out the entries not in '/'.
                      return val;
                  } else if (fullPath !== DIR_SEPARATOR &&
                      valPartsLen === fullPathPartsLen + 1) {
                      // If this a subfolder and entry is a direct child, include it in
                      // the results. Otherwise, it's not an entry of this folder.
                      return val;
                  }
              });
  
              successCallback(results);
          };
  
          var request = tx.objectStore(FILE_STORE_).openCursor(range);
  
          request.onsuccess = function(e) {
              var cursor = e.target.result;
              if (cursor) {
                  var val = cursor.value;
  
                  results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.fileSystem));
                  cursor['continue']();
              }
          };
      };
  
      idb_['delete'] = function(fullPath, successCallback, errorCallback) {
          if (!this.db) {
              if (errorCallback) {
                  errorCallback(FileError.INVALID_MODIFICATION_ERR);
              }
              return;
          }
  
          var tx = this.db.transaction([FILE_STORE_], 'readwrite');
          tx.oncomplete = successCallback;
          tx.onabort = errorCallback || onError;
  
          //var request = tx.objectStore(FILE_STORE_).delete(fullPath);
          var range = IDBKeyRange.bound(
              fullPath, fullPath + DIR_OPEN_BOUND, false, true);
          tx.objectStore(FILE_STORE_)['delete'](range);
      };
  
      idb_.put = function(entry, storagePath, successCallback, errorCallback) {
          if (!this.db) {
              if (errorCallback) {
                  errorCallback(FileError.INVALID_MODIFICATION_ERR);
              }
              return;
          }
  
          var tx = this.db.transaction([FILE_STORE_], 'readwrite');
          tx.onabort = errorCallback || onError;
          tx.oncomplete = function(e) {
              // TODO: Error is thrown if we pass the request event back instead.
              successCallback(entry);
          };
  
          tx.objectStore(FILE_STORE_).put(entry, storagePath);
      };
  
      // Global error handler. Errors bubble from request, to transaction, to db.
      function onError(e) {
          switch (e.target.errorCode) {
              case 12:
                  console.log('Error - Attempt to open db with a lower version than the ' +
                      'current one.');
                  break;
              default:
                  console.log('errorCode: ' + e.target.errorCode);
          }
  
          console.log(e, e.code, e.message);
      }
  
  // Clean up.
  // TODO: Is there a place for this?
  //    global.addEventListener('beforeunload', function(e) {
  //        idb_.db && idb_.db.close();
  //    }, false);
  
  })(module.exports, window);
  
  require("cordova/exec/proxy").add("File", module.exports);
  