text: var __extends = (this && this.__extends) || (function () {
      var extendStatics = Object.setPrototypeOf ||
          ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
          function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
      return function (d, b) {
          extendStatics(d, b);
          function __() { this.constructor = d; }
          d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
      };
  })();
  exports.__esModule = true;
  var file_system_1 = require("../core/file_system");
  var InMemory_1 = require("./InMemory");
  var api_error_1 = require("../core/api_error");
  var node_fs_1 = require("../core/node_fs");
  var path = require("path");
  var util_1 = require("../core/util");
  /**
   * The MountableFileSystem allows you to mount multiple backend types or
   * multiple instantiations of the same backend into a single file system tree.
   * The file systems do not need to know about each other; all interactions are
   * automatically facilitated through this interface.
   *
   * For example, if a file system is mounted at /mnt/blah, and a request came in
   * for /mnt/blah/foo.txt, the file system would see a request for /foo.txt.
   *
   * You can mount file systems when you configure the file system:
   * ```javascript
   * BrowserFS.configure({
   *   fs: "MountableFileSystem",
   *   options: {
   *     '/data': { fs: 'XmlHttpRequest', options: { index: "http://mysite.com/files/index.json" } },
   *     '/home': { fs: 'LocalStorage' }
   *   }
   * }, function(e) {
   *
   * });
   * ```
   *
   * For advanced users, you can also mount file systems *after* MFS is constructed:
   * ```javascript
   * BrowserFS.FileSystem.XmlHttpRequest.Create({
   *   index: "http://mysite.com/files/index.json"
   * }, function(e, xhrfs) {
   *   BrowserFS.FileSystem.MountableFileSystem.Create({
   *     '/data': xhrfs
   *   }, function(e, mfs) {
   *     BrowserFS.initialize(mfs);
   *
   *     // Added after-the-fact...
   *     BrowserFS.FileSystem.LocalStorage.Create(function(e, lsfs) {
   *       mfs.mount('/home', lsfs);
   *     });
   *   });
   * });
   * ```
   *
   * Since MountableFileSystem simply proxies requests to mounted file systems, it supports all of the operations that the mounted file systems support.
   *
   * With no mounted file systems, `MountableFileSystem` acts as a simple `InMemory` filesystem.
   */
  var MountableFileSystem = (function (_super) {
      __extends(MountableFileSystem, _super);
      /**
       * Creates a new, empty MountableFileSystem.
       */
      function MountableFileSystem() {
          var _this = _super.call(this) || this;
          // Contains the list of mount points in mntMap, sorted by string length in decreasing order.
          // Ensures that we scan the most specific mount points for a match first, which lets us
          // nest mount points.
          _this.mountList = [];
          _this.mntMap = {};
          // The InMemory file system serves purely to provide directory listings for
          // mounted file systems.
          _this.rootFs = new InMemory_1["default"]();
          return _this;
      }
      /**
       * Creates a MountableFileSystem instance with the given options.
       */
      MountableFileSystem.Create = function (opts, cb) {
          var fs = new MountableFileSystem();
          Object.keys(opts).forEach(function (mountPoint) {
              fs.mount(mountPoint, opts[mountPoint]);
          });
          cb(null, fs);
      };
      MountableFileSystem.isAvailable = function () {
          return true;
      };
      /**
       * Mounts the file system at the given mount point.
       */
      MountableFileSystem.prototype.mount = function (mountPoint, fs) {
          if (mountPoint[0] !== '/') {
              mountPoint = "/" + mountPoint;
          }
          mountPoint = path.resolve(mountPoint);
          if (this.mntMap[mountPoint]) {
              throw new api_error_1.ApiError(api_error_1.ErrorCode.EINVAL, "Mount point " + mountPoint + " is already taken.");
          }
          util_1.mkdirpSync(mountPoint, 0x1ff, this.rootFs);
          this.mntMap[mountPoint] = fs;
          this.mountList.push(mountPoint);
          this.mountList = this.mountList.sort(function (a, b) { return b.length - a.length; });
      };
      MountableFileSystem.prototype.umount = function (mountPoint) {
          if (mountPoint[0] !== '/') {
              mountPoint = "/" + mountPoint;
          }
          mountPoint = path.resolve(mountPoint);
          if (!this.mntMap[mountPoint]) {
              throw new api_error_1.ApiError(api_error_1.ErrorCode.EINVAL, "Mount point " + mountPoint + " is already unmounted.");
          }
          delete this.mntMap[mountPoint];
          this.mountList.splice(this.mountList.indexOf(mountPoint), 1);
          while (mountPoint !== '/') {
              if (this.rootFs.readdirSync(mountPoint).length === 0) {
                  this.rootFs.rmdirSync(mountPoint);
                  mountPoint = path.dirname(mountPoint);
              }
              else {
                  break;
              }
          }
      };
      /**
       * Returns the file system that the path points to.
       */
      MountableFileSystem.prototype._getFs = function (path) {
          var mountList = this.mountList, len = mountList.length;
          for (var i = 0; i < len; i++) {
              var mountPoint = mountList[i];
              // We know path is normalized, so it is a substring of the mount point.
              if (mountPoint.length <= path.length && path.indexOf(mountPoint) === 0) {
                  path = path.substr(mountPoint.length > 1 ? mountPoint.length : 0);
                  if (path === '') {
                      path = '/';
                  }
                  return { fs: this.mntMap[mountPoint], path: path };
              }
          }
          // Query our root file system.
          return { fs: this.rootFs, path: path };
      };
      // Global information methods
      MountableFileSystem.prototype.getName = function () {
          return MountableFileSystem.Name;
      };
      MountableFileSystem.prototype.diskSpace = function (path, cb) {
          cb(0, 0);
      };
      MountableFileSystem.prototype.isReadOnly = function () {
          return false;
      };
      MountableFileSystem.prototype.supportsLinks = function () {
          // I'm not ready for cross-FS links yet.
          return false;
      };
      MountableFileSystem.prototype.supportsProps = function () {
          return false;
      };
      MountableFileSystem.prototype.supportsSynch = function () {
          return true;
      };
      /**
       * Fixes up error messages so they mention the mounted file location relative
       * to the MFS root, not to the particular FS's root.
       * Mutates the input error, and returns it.
       */
      MountableFileSystem.prototype.standardizeError = function (err, path, realPath) {
          var index = err.message.indexOf(path);
          if (index !== -1) {
              err.message = err.message.substr(0, index) + realPath + err.message.substr(index + path.length);
              err.path = realPath;
          }
          return err;
      };
      // The following methods involve multiple file systems, and thus have custom
      // logic.
      // Note that we go through the Node API to use its robust default argument
      // processing.
      MountableFileSystem.prototype.rename = function (oldPath, newPath, cb) {
          var _this = this;
          // Scenario 1: old and new are on same FS.
          var fs1rv = this._getFs(oldPath);
          var fs2rv = this._getFs(newPath);
          if (fs1rv.fs === fs2rv.fs) {
              return fs1rv.fs.rename(fs1rv.path, fs2rv.path, function (e) {
                  if (e) {
                      _this.standardizeError(_this.standardizeError(e, fs1rv.path, oldPath), fs2rv.path, newPath);
                  }
                  cb(e);
              });
          }
          // Scenario 2: Different file systems.
          // Read old file, write new file, delete old file.
          return node_fs_1["default"].readFile(oldPath, function (err, data) {
              if (err) {
                  return cb(err);
              }
              node_fs_1["default"].writeFile(newPath, data, function (err) {
                  if (err) {
                      return cb(err);
                  }
                  node_fs_1["default"].unlink(oldPath, cb);
              });
          });
      };
      MountableFileSystem.prototype.renameSync = function (oldPath, newPath) {
          // Scenario 1: old and new are on same FS.
          var fs1rv = this._getFs(oldPath);
          var fs2rv = this._getFs(newPath);
          if (fs1rv.fs === fs2rv.fs) {
              try {
                  return fs1rv.fs.renameSync(fs1rv.path, fs2rv.path);
              }
              catch (e) {
                  this.standardizeError(this.standardizeError(e, fs1rv.path, oldPath), fs2rv.path, newPath);
                  throw e;
              }
          }
          // Scenario 2: Different file systems.
          var data = node_fs_1["default"].readFileSync(oldPath);
          node_fs_1["default"].writeFileSync(newPath, data);
          return node_fs_1["default"].unlinkSync(oldPath);
      };
      MountableFileSystem.prototype.readdirSync = function (p) {
          var fsInfo = this._getFs(p);
          // If null, rootfs did not have the directory
          // (or the target FS is the root fs).
          var rv = null;
          // Mount points are all defined in the root FS.
          // Ensure that we list those, too.
          if (fsInfo.fs !== this.rootFs) {
              try {
                  rv = this.rootFs.readdirSync(p);
              }
              catch (e) {
                  // Ignore.
              }
          }
          try {
              var rv2_1 = fsInfo.fs.readdirSync(fsInfo.path);
              if (rv === null) {
                  return rv2_1;
              }
              else {
                  // Filter out duplicates.
                  return rv2_1.concat(rv.filter(function (val) { return rv2_1.indexOf(val) === -1; }));
              }
          }
          catch (e) {
              if (rv === null) {
                  throw this.standardizeError(e, fsInfo.path, p);
              }
              else {
                  // The root FS had something.
                  return rv;
              }
          }
      };
      MountableFileSystem.prototype.readdir = function (p, cb) {
          var _this = this;
          var fsInfo = this._getFs(p);
          fsInfo.fs.readdir(fsInfo.path, function (err, files) {
              if (fsInfo.fs !== _this.rootFs) {
                  try {
                      var rv = _this.rootFs.readdirSync(p);
                      if (files) {
                          // Filter out duplicates.
                          files = files.concat(rv.filter(function (val) { return files.indexOf(val) === -1; }));
                      }
                      else {
                          files = rv;
                      }
                  }
                  catch (e) {
                      // Root FS and target FS did not have directory.
                      if (err) {
                          return cb(_this.standardizeError(err, fsInfo.path, p));
                      }
                  }
              }
              else if (err) {
                  // Root FS and target FS are the same, and did not have directory.
                  return cb(_this.standardizeError(err, fsInfo.path, p));
              }
              cb(null, files);
          });
      };
      MountableFileSystem.prototype.rmdirSync = function (p) {
          var fsInfo = this._getFs(p);
          if (this._containsMountPt(p)) {
              throw api_error_1.ApiError.ENOTEMPTY(p);
          }
          else {
              try {
                  fsInfo.fs.rmdirSync(fsInfo.path);
              }
              catch (e) {
                  throw this.standardizeError(e, fsInfo.path, p);
              }
          }
      };
      MountableFileSystem.prototype.rmdir = function (p, cb) {
          var _this = this;
          var fsInfo = this._getFs(p);
          if (this._containsMountPt(p)) {
              cb(api_error_1.ApiError.ENOTEMPTY(p));
          }
          else {
              fsInfo.fs.rmdir(fsInfo.path, function (err) {
                  cb(err ? _this.standardizeError(err, fsInfo.path, p) : null);
              });
          }
      };
      /**
       * Returns true if the given path contains a mount point.
       */
      MountableFileSystem.prototype._containsMountPt = function (p) {
          var mountPoints = this.mountList, len = mountPoints.length;
          for (var i = 0; i < len; i++) {
              var pt = mountPoints[i];
              if (pt.length >= p.length && pt.slice(0, p.length) === p) {
                  return true;
              }
          }
          return false;
      };
      return MountableFileSystem;
  }(file_system_1.BaseFileSystem));
  MountableFileSystem.Name = "MountableFileSystem";
  MountableFileSystem.Options = {};
  exports["default"] = MountableFileSystem;
  /**
   * Tricky: Define all of the functions that merely forward arguments to the
   * relevant file system, or return/throw an error.
   * Take advantage of the fact that the *first* argument is always the path, and
   * the *last* is the callback function (if async).
   * @todo Can use numArgs to make proxying more efficient.
   * @hidden
   */
  function defineFcn(name, isSync, numArgs) {
      if (isSync) {
          return function () {
              var args = [];
              for (var _i = 0; _i < arguments.length; _i++) {
                  args[_i] = arguments[_i];
              }
              var path = args[0];
              var rv = this._getFs(path);
              args[0] = rv.path;
              try {
                  return rv.fs[name].apply(rv.fs, args);
              }
              catch (e) {
                  this.standardizeError(e, rv.path, path);
                  throw e;
              }
          };
      }
      else {
          return function () {
              var _this = this;
              var args = [];
              for (var _i = 0; _i < arguments.length; _i++) {
                  args[_i] = arguments[_i];
              }
              var path = args[0];
              var rv = this._getFs(path);
              args[0] = rv.path;
              if (typeof args[args.length - 1] === 'function') {
                  var cb_1 = args[args.length - 1];
                  args[args.length - 1] = function () {
                      var args = [];
                      for (var _i = 0; _i < arguments.length; _i++) {
                          args[_i] = arguments[_i];
                      }
                      if (args.length > 0 && args[0] instanceof api_error_1.ApiError) {
                          _this.standardizeError(args[0], rv.path, path);
                      }
                      cb_1.apply(null, args);
                  };
              }
              return rv.fs[name].apply(rv.fs, args);
          };
      }
  }
  /**
   * @hidden
   */
  var fsCmdMap = [
      // 1 arg functions
      ['exists', 'unlink', 'readlink'],
      // 2 arg functions
      ['stat', 'mkdir', 'realpath', 'truncate'],
      // 3 arg functions
      ['open', 'readFile', 'chmod', 'utimes'],
      // 4 arg functions
      ['chown'],
      // 5 arg functions
      ['writeFile', 'appendFile']
  ];
  for (var i = 0; i < fsCmdMap.length; i++) {
      var cmds = fsCmdMap[i];
      for (var _i = 0, cmds_1 = cmds; _i < cmds_1.length; _i++) {
          var fnName = cmds_1[_i];
          MountableFileSystem.prototype[fnName] = defineFcn(fnName, false, i + 1);
          MountableFileSystem.prototype[fnName + 'Sync'] = defineFcn(fnName + 'Sync', true, i + 1);
      }
  }
  //# sourceMappingURL=MountableFileSystem.js.map