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 Windows, WinJS, MSApp */
  
  var File = require('./File'),
      FileError = require('./FileError'),
      Flags = require('./Flags'),
      FileSystem = require('./FileSystem'),
      LocalFileSystem = require('./LocalFileSystem'),
      utils = require('cordova/utils');
  
  function Entry(isFile, isDirectory, name, fullPath, filesystemName, nativeURL) {
      this.isFile = !!isFile;
      this.isDirectory = !!isDirectory;
      this.name = name || '';
      this.fullPath = fullPath || '';
      this.filesystemName = filesystemName || null;
      this.nativeURL = nativeURL || null;
  }
  
  var FileEntry = function (name, fullPath, filesystemName, nativeURL) {
      FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, filesystemName, nativeURL]);
  };
  
  utils.extend(FileEntry, Entry);
  
  var DirectoryEntry = function (name, fullPath, filesystemName, nativeURL) {
      DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, filesystemName, nativeURL);
  };
  
  utils.extend(DirectoryEntry, Entry);
  
  
  var getFolderFromPathAsync = Windows.Storage.StorageFolder.getFolderFromPathAsync;
  var getFileFromPathAsync = Windows.Storage.StorageFile.getFileFromPathAsync;
  
  function writeBytesAsync(storageFile, data, position) {
      return storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite)
          .then(function (output) {
              output.seek(position);
              var dataWriter = new Windows.Storage.Streams.DataWriter(output);
              dataWriter.writeBytes(data);
              return dataWriter.storeAsync().then(function (size) {
                  output.size = position + size;
                  return dataWriter.flushAsync().then(function () {
                      output.close();
                      return size;
                  });
              });
          });
  }
  
  function writeTextAsync(storageFile, data, position) {
      return storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite)
          .then(function (output) {
              output.seek(position);
              var dataWriter = new Windows.Storage.Streams.DataWriter(output);
              dataWriter.writeString(data);
              return dataWriter.storeAsync().then(function (size) {
                  output.size = position + size;
                  return dataWriter.flushAsync().then(function () {
                      output.close();
                      return size;
                  });
              });
          });
  }
  
  function writeBlobAsync(storageFile, data, position) {
      return storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite)
          .then(function (output) {
              output.seek(position);
              var dataSize = data.size;
              var input = (data.detachStream || data.msDetachStream).call(data);
  
              // Copy the stream from the blob to the File stream 
              return Windows.Storage.Streams.RandomAccessStream.copyAsync(input, output)
                  .then(function () {
                      output.size = position + dataSize;
                      return output.flushAsync().then(function () {
                          input.close();
                          output.close();
  
                          return dataSize;
                      });
                  });
          });
  }
  
  function writeArrayBufferAsync(storageFile, data, position) {
      return writeBlobAsync(storageFile, new Blob([data]), position);
  }
  
  function cordovaPathToNative(path) {
      // turn / into \\
      var cleanPath = path.replace(/\//g, '\\');
      // turn  \\ into \
      cleanPath = cleanPath.replace(/\\+/g, '\\');
      return cleanPath;
  }
  
  function nativePathToCordova(path) {
      var cleanPath = path.replace(/\\/g, '/');
      return cleanPath;
  }
  
  var driveRE = new RegExp("^[/]*([A-Z]:)");
  var invalidNameRE = /[\\?*|"<>:]/;
  function validName(name) {
      return !invalidNameRE.test(name.replace(driveRE, ''));
  }
  
  function sanitize(path) {
      var slashesRE = new RegExp('/{2,}', 'g');
      var components = path.replace(slashesRE, '/').split(/\/+/);
      // Remove double dots, use old school array iteration instead of RegExp
      // since it is impossible to debug them
      for (var index = 0; index < components.length; ++index) {
          if (components[index] === "..") {
              components.splice(index, 1);
              if (index > 0) {
                  // if we're not in the start of array then remove preceeding path component,
                  // In case if relative path points above the root directory, just ignore double dots
                  // See file.spec.111 should not traverse above above the root directory for test case
                  components.splice(index - 1, 1);
                  --index;
              }
          }
      }
      return components.join('/');
  }
  
  var WinFS = function (name, root) {
      this.winpath = root.winpath;
      if (this.winpath && !/\/$/.test(this.winpath)) {
          this.winpath += "/";
      }
      this.makeNativeURL = function (path) {
          return FileSystem.encodeURIPath(this.root.nativeURL + sanitize(path.replace(':', '%3A')));
      };
      root.fullPath = '/';
      if (!root.nativeURL)
          root.nativeURL = 'file://' + sanitize(this.winpath + root.fullPath).replace(':', '%3A');
      WinFS.__super__.constructor.call(this, name, root);
  };
  
  utils.extend(WinFS, FileSystem);
  
  WinFS.prototype.__format__ = function (fullPath) {
      var path = sanitize('/' + this.name + (fullPath[0] === '/' ? '' : '/') + FileSystem.encodeURIPath(fullPath));
      return 'cdvfile://localhost' + path;
  };
  
  var windowsPaths = {
      dataDirectory: "ms-appdata:///local/",
      cacheDirectory: "ms-appdata:///temp/",
      tempDirectory: "ms-appdata:///temp/",
      syncedDataDirectory: "ms-appdata:///roaming/",
      applicationDirectory: "ms-appx:///",
      applicationStorageDirectory: "ms-appx:///"
  };
  
  var AllFileSystems;
  
  function getAllFS() {
      if (!AllFileSystems) {
          AllFileSystems = {
              'persistent': Object.freeze(new WinFS('persistent', {
                  name: 'persistent',
                  nativeURL: 'ms-appdata:///local',
                  winpath: nativePathToCordova(Windows.Storage.ApplicationData.current.localFolder.path)
              })),
              'temporary': Object.freeze(new WinFS('temporary', {
                  name: 'temporary',
                  nativeURL: 'ms-appdata:///temp',
                  winpath: nativePathToCordova(Windows.Storage.ApplicationData.current.temporaryFolder.path)
              })),
              'root': Object.freeze(new WinFS('root', {
                  name: 'root',
                  //nativeURL: 'file:///'
                  winpath: ''
              }))
          };
      }
      return AllFileSystems;
  }
  
  function getFS(name) {
      return getAllFS()[name];
  }
  
  FileSystem.prototype.__format__ = function (fullPath) {
      return getFS(this.name).__format__(fullPath);
  };
  
  require('./fileSystems').getFs = function (name, callback) {
      setTimeout(function () {
          callback(getFS(name));
      });
  };
  
  function getFilesystemFromPath(path) {
      var res;
      var allfs = getAllFS();
      Object.keys(allfs).some(function (fsn) {
          var fs = allfs[fsn];
          if (path.indexOf(fs.winpath) === 0)
              res = fs;
          return res;
      });
      return res;
  }
  
  var msapplhRE = new RegExp('^ms-appdata://localhost/');
  function pathFromURL(url) {
      url = url.replace(msapplhRE, 'ms-appdata:///');
      var path = decodeURIComponent(url);
      // support for file name with parameters
      if (/\?/g.test(path)) {
          path = String(path).split("?")[0];
      }
      if (path.indexOf("file:/") === 0) {
          if (path.indexOf("file://") !== 0) {
              url = "file:///" + url.substr(6);
          }
      }
  
      ['file://', 'ms-appdata:///', 'cdvfile://localhost/'].every(function (p) {
          if (path.indexOf(p) !== 0)
              return true;
          var thirdSlash = path.indexOf("/", p.length);
          if (thirdSlash < 0) {
              path = "";
          } else {
              path = sanitize(path.substr(thirdSlash));
          }
      });
  
      return path.replace(driveRE, '$1');
  }
  
  function getFilesystemFromURL(url) {
      url = url.replace(msapplhRE, 'ms-appdata:///');
      var res;
      if (url.indexOf("file:/") === 0)
          res = getFilesystemFromPath(pathFromURL(url));
      else {
          var allfs = getAllFS();
          Object.keys(allfs).every(function (fsn) {
              var fs = allfs[fsn];
              if (url.indexOf(fs.root.nativeURL) === 0 ||
                  url.indexOf('cdvfile://localhost/' + fs.name + '/') === 0) {
                  res = fs;
                  return false;
              }
              return true;
          });
      }
      return res;
  }
  
  function getFsPathForWinPath(fs, wpath) {
      var path = nativePathToCordova(wpath);
      if (path.indexOf(fs.winpath) !== 0)
          return null;
      return path.replace(fs.winpath, '/');
  }
  
  var WinError = {
      invalidArgument: -2147024809,
      fileNotFound: -2147024894,
      accessDenied: -2147024891
  };
  
  function openPath(path, ops) {
      ops = ops ? ops : {};
      return new WinJS.Promise(function (complete, failed) {
          getFileFromPathAsync(path).done(
              function (file) {
                  complete({file: file});
              },
              function (err) {
                  if (err.number != WinError.fileNotFound && err.number != WinError.invalidArgument)
                      failed(FileError.NOT_READABLE_ERR);
                  getFolderFromPathAsync(path)
                      .done(
                          function (dir) {
                              if (!ops.getContent)
                                  complete({folder: dir});
                              else
                                  WinJS.Promise.join({
                                      files: dir.getFilesAsync(),
                                      folders: dir.getFoldersAsync()
                                  }).done(
                                      function (a) {
                                          complete({
                                              folder: dir,
                                              files: a.files,
                                              folders: a.folders
                                          });
                                      },
                                      function (err) {
                                          failed(FileError.NOT_READABLE_ERR);
                                      }
                                  );
                          },
                          function (err) {
                              if (err.number == WinError.fileNotFound || err.number == WinError.invalidArgument)
                                  complete({});
                              else
                                  failed(FileError.NOT_READABLE_ERR);
                          }
                      );
              }
          );
      });
  }
  
  function copyFolder(src, dst, name) {
      name = name ? name : src.name;
      return new WinJS.Promise(function (complete, failed) {
          WinJS.Promise.join({
              fld: dst.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists),
              files: src.getFilesAsync(),
              folders: src.getFoldersAsync()
          }).done(
              function (the) {
                  if (!(the.files.length || the.folders.length)) {
                      complete();
                      return;
                  }
                  var todo = the.files.length;
                  var copyfolders = function () {
                      if (!(todo--)) {
                          complete();
                          return;
                      }
                      copyFolder(the.folders[todo], dst)
                          .done(function () {
                              copyfolders();
                          }, failed);
                  };
                  var copyfiles = function () {
                      if (!(todo--)) {
                          todo = the.folders.length;
                          copyfolders();
                          return;
                      }
                      the.files[todo].copyAsync(the.fld)
                          .done(function () {
                              copyfiles();
                          }, failed);
                  };
                  copyfiles();
              },
              failed
          );
      });
  }
  
  function moveFolder(src, dst, name) {
      name = name ? name : src.name;
      return new WinJS.Promise(function (complete, failed) {
          WinJS.Promise.join({
              fld: dst.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists),
              files: src.getFilesAsync(),
              folders: src.getFoldersAsync()
          }).done(
              function (the) {
                  if (!(the.files.length || the.folders.length)) {
                      complete();
                      return;
                  }
                  var todo = the.files.length;
                  var movefolders = function () {
                      if (!(todo--)) {
                          src.deleteAsync().done(complete, failed);
                          return;
                      }
                      moveFolder(the.folders[todo], dst)
                          .done(movefolders, failed);
                  };
                  var movefiles = function () {
                      if (!(todo--)) {
                          todo = the.folders.length;
                          movefolders();
                          return;
                      }
                      the.files[todo].moveAsync(the.fld)
                          .done(function () {
                              movefiles();
                          }, failed);
                  };
                  movefiles();
              },
              failed
          );
      });
  }
  
  function transport(success, fail, args, ops) { // ["fullPath","parent", "newName"]
      var src = args[0];
      var parent = args[1];
      var name = args[2];
  
      var srcFS = getFilesystemFromURL(src);
      var dstFS = getFilesystemFromURL(parent);
      var srcPath = pathFromURL(src);
      var dstPath = pathFromURL(parent);
      if (!(srcFS && dstFS && validName(name))) {
          fail(FileError.ENCODING_ERR);
          return;
      }
  
      var srcWinPath = cordovaPathToNative(sanitize(srcFS.winpath + srcPath));
      var dstWinPath = cordovaPathToNative(sanitize(dstFS.winpath + dstPath));
      var tgtFsPath = sanitize(dstPath + '/' + name);
      var tgtWinPath = cordovaPathToNative(sanitize(dstFS.winpath + dstPath + '/' + name));
      if (srcWinPath == dstWinPath || srcWinPath == tgtWinPath) {
          fail(FileError.INVALID_MODIFICATION_ERR);
          return;
      }
  
  
      WinJS.Promise.join({
          src: openPath(srcWinPath),
          dst: openPath(dstWinPath),
          tgt: openPath(tgtWinPath, {getContent: true})
      })
          .done(
              function (the) {
                  if ((!the.dst.folder) || !(the.src.folder || the.src.file)) {
                      fail(FileError.NOT_FOUND_ERR);
                      return;
                  }
                  if ((the.src.folder && the.tgt.file) ||
                      (the.src.file && the.tgt.folder) ||
                      (the.tgt.folder && (the.tgt.files.length || the.tgt.folders.length))) {
                      fail(FileError.INVALID_MODIFICATION_ERR);
                      return;
                  }
                  if (the.src.file)
                      ops.fileOp(the.src.file, the.dst.folder, name, Windows.Storage.NameCollisionOption.replaceExisting)
                          .done(
                              function (storageFile) {
                                  success(new FileEntry(
                                      name,
                                      tgtFsPath,
                                      dstFS.name,
                                      dstFS.makeNativeURL(tgtFsPath)
                                  ));
                              },
                              function (err) {
                                  fail(FileError.INVALID_MODIFICATION_ERR);
                              }
                          );
                  else
                      ops.folderOp(the.src.folder, the.dst.folder, name).done(
                          function () {
                              success(new DirectoryEntry(
                                  name,
                                  tgtFsPath,
                                  dstFS.name,
                                  dstFS.makeNativeURL(tgtFsPath)
                              ));
                          },
                          function () {
                              fail(FileError.INVALID_MODIFICATION_ERR);
                          }
                      );
              },
              function (err) {
                  fail(FileError.INVALID_MODIFICATION_ERR);
              }
          );
  }
  
  module.exports = {
      requestAllFileSystems: function () {
          return getAllFS();
      },
      requestAllPaths: function (success) {
          success(windowsPaths);
      },
      getFileMetadata: function (success, fail, args) {
          module.exports.getMetadata(success, fail, args);
      },
  
      getMetadata: function (success, fail, args) {
          var fs = getFilesystemFromURL(args[0]);
          var path = pathFromURL(args[0]);
          if (!fs || !validName(path)) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var fullPath = cordovaPathToNative(fs.winpath + path);
  
          var getMetadataForFile = function (storageFile) {
              storageFile.getBasicPropertiesAsync().then(
                  function (basicProperties) {
                      success(new File(storageFile.name, storageFile.path, storageFile.fileType, basicProperties.dateModified, basicProperties.size));
                  }, function () {
                      fail(FileError.NOT_READABLE_ERR);
                  }
              );
          };
  
          var getMetadataForFolder = function (storageFolder) {
              storageFolder.getBasicPropertiesAsync().then(
                  function (basicProperties) {
                      var metadata = {
                          size: basicProperties.size,
                          lastModifiedDate: basicProperties.dateModified
                      };
                      success(metadata);
                  },
                  function () {
                      fail(FileError.NOT_READABLE_ERR);
                  }
              );
          };
  
          getFileFromPathAsync(fullPath).then(getMetadataForFile,
              function () {
                  getFolderFromPathAsync(fullPath).then(getMetadataForFolder,
                      function () {
                          fail(FileError.NOT_FOUND_ERR);
                      }
                  );
              }
          );
      },
  
      getParent: function (win, fail, args) { // ["fullPath"]
          var fs = getFilesystemFromURL(args[0]);
          var path = pathFromURL(args[0]);
          if (!fs || !validName(path)) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          if (!path || (new RegExp('/[^/]*/?$')).test(path)) {
              win(new DirectoryEntry(fs.root.name, fs.root.fullPath, fs.name, fs.makeNativeURL(fs.root.fullPath)));
              return;
          }
  
          var parpath = path.replace(new RegExp('/[^/]+/?$', 'g'), '');
          var parname = path.substr(parpath.length);
          var fullPath = cordovaPathToNative(fs.winpath + parpath);
  
          var result = new DirectoryEntry(parname, parpath, fs.name, fs.makeNativeURL(parpath));
          getFolderFromPathAsync(fullPath).done(
              function () {
                  win(result);
              },
              function () {
                  fail(FileError.INVALID_STATE_ERR);
              }
          );
      },
  
      readAsText: function (win, fail, args) {
  
          var url = args[0],
              enc = args[1],
              startPos = args[2],
              endPos = args[3];
  
          var fs = getFilesystemFromURL(url);
          var path = pathFromURL(url);
          if (!fs) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var wpath = cordovaPathToNative(sanitize(fs.winpath + path));
  
          var encoding = Windows.Storage.Streams.UnicodeEncoding.utf8;
          if (enc == 'Utf16LE' || enc == 'utf16LE') {
              encoding = Windows.Storage.Streams.UnicodeEncoding.utf16LE;
          } else if (enc == 'Utf16BE' || enc == 'utf16BE') {
              encoding = Windows.Storage.Streams.UnicodeEncoding.utf16BE;
          }
  
          getFileFromPathAsync(wpath).then(function (file) {
              return file.openReadAsync();
          }).then(function (stream) {
              startPos = (startPos < 0) ? Math.max(stream.size + startPos, 0) : Math.min(stream.size, startPos);
              endPos = (endPos < 0) ? Math.max(endPos + stream.size, 0) : Math.min(stream.size, endPos);
              stream.seek(startPos);
  
              var readSize = endPos - startPos,
                  buffer = new Windows.Storage.Streams.Buffer(readSize);
  
              return stream.readAsync(buffer, readSize, Windows.Storage.Streams.InputStreamOptions.none);
          }).done(function (buffer) {
              try {
                  win(Windows.Security.Cryptography.CryptographicBuffer.convertBinaryToString(encoding, buffer));
              }
              catch (e) {
                  fail(FileError.ENCODING_ERR);
              }
          }, function () {
              fail(FileError.NOT_FOUND_ERR);
          });
      },
  
      readAsBinaryString: function (win, fail, args) {
          var url = args[0],
              startPos = args[1],
              endPos = args[2];
  
          var fs = getFilesystemFromURL(url);
          var path = pathFromURL(url);
          if (!fs) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var wpath = cordovaPathToNative(sanitize(fs.winpath + path));
  
          getFileFromPathAsync(wpath).then(
              function (storageFile) {
                  Windows.Storage.FileIO.readBufferAsync(storageFile).done(
                      function (buffer) {
                          var dataReader = Windows.Storage.Streams.DataReader.fromBuffer(buffer);
                          // var fileContent = dataReader.readString(buffer.length);
                          var byteArray = new Uint8Array(buffer.length),
                              byteString = "";
                          dataReader.readBytes(byteArray);
                          dataReader.close();
                          for (var i = 0; i < byteArray.length; i++) {
                              var charByte = byteArray[i];
                              // var charRepresentation = charByte <= 127 ? String.fromCharCode(charByte) : charByte.toString(16);
                              var charRepresentation = String.fromCharCode(charByte);
                              byteString += charRepresentation;
                          }
                          win(byteString.slice(startPos, endPos));
                      }
                  );
              }, function () {
                  fail(FileError.NOT_FOUND_ERR);
              }
          );
      },
  
      readAsArrayBuffer: function (win, fail, args) {
          var url = args[0];
          var fs = getFilesystemFromURL(url);
          var path = pathFromURL(url);
          if (!fs) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var wpath = cordovaPathToNative(sanitize(fs.winpath + path));
  
          getFileFromPathAsync(wpath).then(
              function (storageFile) {
                  var blob = MSApp.createFileFromStorageFile(storageFile);
                  var url = URL.createObjectURL(blob, {oneTimeOnly: true});
                  var xhr = new XMLHttpRequest();
                  xhr.open("GET", url, true);
                  xhr.responseType = 'arraybuffer';
                  xhr.onload = function () {
                      var resultArrayBuffer = xhr.response;
                      // get start and end position of bytes in buffer to be returned
                      var startPos = args[1] || 0,
                          endPos = args[2] || resultArrayBuffer.length;
                      // if any of them is specified, we'll slice output array
                      if (startPos !== 0 || endPos !== resultArrayBuffer.length) {
                          // slice method supported only on Windows 8.1, so we need to check if it's available 
                          // see http://msdn.microsoft.com/en-us/library/ie/dn641192(v=vs.94).aspx
                          if (resultArrayBuffer.slice) {
                              resultArrayBuffer = resultArrayBuffer.slice(startPos, endPos);
                          } else {
                              // if slice isn't available, we'll use workaround method
                              var tempArray = new Uint8Array(resultArrayBuffer),
                                  resBuffer = new ArrayBuffer(endPos - startPos),
                                  resArray = new Uint8Array(resBuffer);
  
                              for (var i = 0; i < resArray.length; i++) {
                                  resArray[i] = tempArray[i + startPos];
                              }
                              resultArrayBuffer = resBuffer;
                          }
                      }
                      win(resultArrayBuffer);
                  };
                  xhr.send();
              }, function () {
                  fail(FileError.NOT_FOUND_ERR);
              }
          );
      },
  
      readAsDataURL: function (win, fail, args) {
          var url = args[0];
          var fs = getFilesystemFromURL(url);
          var path = pathFromURL(url);
          if (!fs) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var wpath = cordovaPathToNative(sanitize(fs.winpath + path));
  
          getFileFromPathAsync(wpath).then(
              function (storageFile) {
                  Windows.Storage.FileIO.readBufferAsync(storageFile).done(
                      function (buffer) {
                          var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
                          //the method encodeToBase64String will add "77u/" as a prefix, so we should remove it
                          if (String(strBase64).substr(0, 4) == "77u/") {
                              strBase64 = strBase64.substr(4);
                          }
                          var mediaType = storageFile.contentType;
                          var result = "data:" + mediaType + ";base64," + strBase64;
                          win(result);
                      }
                  );
              }, function () {
                  fail(FileError.NOT_FOUND_ERR);
              }
          );
      },
  
      getDirectory: function (win, fail, args) {
          var dirurl = args[0];
          var path = args[1];
          var options = args[2];
  
          var fs = getFilesystemFromURL(dirurl);
          var dirpath = pathFromURL(dirurl);
          if (!fs || !validName(path)) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var fspath = sanitize(dirpath + '/' + path);
          var completePath = sanitize(fs.winpath + fspath);
  
          var name = completePath.substring(completePath.lastIndexOf('/') + 1);
  
          var wpath = cordovaPathToNative(completePath.substring(0, completePath.lastIndexOf('/')));
  
          var flag = "";
          if (options) {
              flag = new Flags(options.create, options.exclusive);
          } else {
              flag = new Flags(false, false);
          }
  
          getFolderFromPathAsync(wpath).done(
              function (storageFolder) {
                  if (flag.create === true && flag.exclusive === true) {
                      storageFolder.createFolderAsync(name, Windows.Storage.CreationCollisionOption.failIfExists).done(
                          function (storageFolder) {
                              win(new DirectoryEntry(storageFolder.name, fspath, fs.name, fs.makeNativeURL(fspath)));
                          }, function (err) {
                              fail(FileError.PATH_EXISTS_ERR);
                          }
                      );
                  } else if (flag.create === true && flag.exclusive === false) {
                      storageFolder.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists).done(
                          function (storageFolder) {
                              win(new DirectoryEntry(storageFolder.name, fspath, fs.name, fs.makeNativeURL(fspath)));
                          }, function () {
                              fail(FileError.INVALID_MODIFICATION_ERR);
                          }
                      );
                  } else if (flag.create === false) {
                      storageFolder.getFolderAsync(name).done(
                          function (storageFolder) {
                              win(new DirectoryEntry(storageFolder.name, fspath, fs.name, fs.makeNativeURL(fspath)));
                          },
                          function () {
                              // check if path actually points to a file
                              storageFolder.getFileAsync(name).done(
                                  function () {
                                      fail(FileError.TYPE_MISMATCH_ERR);
                                  }, function () {
                                      fail(FileError.NOT_FOUND_ERR);
                                  }
                              );
                          }
                      );
                  }
              }, function () {
                  fail(FileError.NOT_FOUND_ERR);
              }
          );
      },
  
      remove: function (win, fail, args) {
          var fs = getFilesystemFromURL(args[0]);
          var path = pathFromURL(args[0]);
          if (!fs || !validName(path)) {
              fail(FileError.ENCODING_ERR);
              return;
          }
  
          // FileSystem root can't be removed!
          if (!path || path == '/') {
              fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
              return;
          }
          var fullPath = cordovaPathToNative(fs.winpath + path);
  
          getFileFromPathAsync(fullPath).then(
              function (storageFile) {
                  storageFile.deleteAsync().done(win, function () {
                      fail(FileError.INVALID_MODIFICATION_ERR);
                  });
              },
              function () {
                  getFolderFromPathAsync(fullPath).done(
                      function (sFolder) {
                          sFolder.getFilesAsync()
                          // check for files
                              .then(function (fileList) {
                                  if (fileList) {
                                      if (fileList.length === 0) {
                                          return sFolder.getFoldersAsync();
                                      } else {
                                          fail(FileError.INVALID_MODIFICATION_ERR);
                                      }
                                  }
                              })
                              // check for folders
                              .done(function (folderList) {
                                  if (folderList) {
                                      if (folderList.length === 0) {
                                          sFolder.deleteAsync().done(
                                              win,
                                              function () {
                                                  fail(FileError.INVALID_MODIFICATION_ERR);
                                              }
                                          );
                                      } else {
                                          fail(FileError.INVALID_MODIFICATION_ERR);
                                      }
                                  }
                              });
                      },
                      function () {
                          fail(FileError.NOT_FOUND_ERR);
                      }
                  );
              }
          );
      },
  
      removeRecursively: function (successCallback, fail, args) {
  
          var fs = getFilesystemFromURL(args[0]);
          var path = pathFromURL(args[0]);
          if (!fs || !validName(path)) {
              fail(FileError.ENCODING_ERR);
              return;
          }
  
          // FileSystem root can't be removed!
          if (!path || path == '/') {
              fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
              return;
          }
          var fullPath = cordovaPathToNative(fs.winpath + path);
  
          getFolderFromPathAsync(fullPath).done(function (storageFolder) {
              storageFolder.deleteAsync().done(function (res) {
                  successCallback(res);
              }, function (err) {
                  fail(err);
              });
  
          }, function () {
              fail(FileError.FILE_NOT_FOUND_ERR);
          });
      },
  
      getFile: function (win, fail, args) {
  
          var dirurl = args[0];
          var path = args[1];
          var options = args[2];
  
          var fs = getFilesystemFromURL(dirurl);
          var dirpath = pathFromURL(dirurl);
          if (!fs || !validName(path)) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var fspath = sanitize(dirpath + '/' + path);
          var completePath = sanitize(fs.winpath + fspath);
  
          var fileName = completePath.substring(completePath.lastIndexOf('/') + 1);
  
          var wpath = cordovaPathToNative(completePath.substring(0, completePath.lastIndexOf('/')));
  
          var flag = "";
          if (options !== null) {
              flag = new Flags(options.create, options.exclusive);
          } else {
              flag = new Flags(false, false);
          }
  
          getFolderFromPathAsync(wpath).done(
              function (storageFolder) {
                  if (flag.create === true && flag.exclusive === true) {
                      storageFolder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.failIfExists).done(
                          function (storageFile) {
                              win(new FileEntry(storageFile.name, fspath, fs.name, fs.makeNativeURL(fspath)));
                          }, function () {
                              fail(FileError.PATH_EXISTS_ERR);
                          }
                      );
                  } else if (flag.create === true && flag.exclusive === false) {
                      storageFolder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.openIfExists).done(
                          function (storageFile) {
                              win(new FileEntry(storageFile.name, fspath, fs.name, fs.makeNativeURL(fspath)));
                          }, function () {
                              fail(FileError.INVALID_MODIFICATION_ERR);
                          }
                      );
                  } else if (flag.create === false) {
                      storageFolder.getFileAsync(fileName).done(
                          function (storageFile) {
                              win(new FileEntry(storageFile.name, fspath, fs.name, fs.makeNativeURL(fspath)));
                          }, function () {
                              // check if path actually points to a folder
                              storageFolder.getFolderAsync(fileName).done(
                                  function () {
                                      fail(FileError.TYPE_MISMATCH_ERR);
                                  }, function () {
                                      fail(FileError.NOT_FOUND_ERR);
                                  });
                          }
                      );
                  }
              }, function (err) {
                  fail(
                      err.number == WinError.accessDenied ?
                          FileError.SECURITY_ERR :
                          FileError.NOT_FOUND_ERR
                  );
              }
          );
      },
  
      readEntries: function (win, fail, args) { // ["fullPath"]
          var fs = getFilesystemFromURL(args[0]);
          var path = pathFromURL(args[0]);
          if (!fs || !validName(path)) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var fullPath = cordovaPathToNative(fs.winpath + path);
  
          var result = [];
  
          getFolderFromPathAsync(fullPath).done(function (storageFolder) {
              var promiseArr = [];
              var index = 0;
              promiseArr[index++] = storageFolder.getFilesAsync().then(function (fileList) {
                  if (fileList !== null) {
                      for (var i = 0; i < fileList.length; i++) {
                          var fspath = getFsPathForWinPath(fs, fileList[i].path);
                          if (!fspath) {
                              fail(FileError.NOT_FOUND_ERR);
                              return;
                          }
                          result.push(new FileEntry(fileList[i].name, fspath, fs.name, fs.makeNativeURL(fspath)));
                      }
                  }
              });
              promiseArr[index++] = storageFolder.getFoldersAsync().then(function (folderList) {
                  if (folderList !== null) {
                      for (var j = 0; j < folderList.length; j++) {
                          var fspath = getFsPathForWinPath(fs, folderList[j].path);
                          if (!fspath) {
                              fail(FileError.NOT_FOUND_ERR);
                              return;
                          }
                          result.push(new DirectoryEntry(folderList[j].name, fspath, fs.name, fs.makeNativeURL(fspath)));
                      }
                  }
              });
              WinJS.Promise.join(promiseArr).then(function () {
                  win(result);
              });
  
          }, function () {
              fail(FileError.NOT_FOUND_ERR);
          });
      },
  
      write: function (win, fail, args) {
  
          var url = args[0],
              data = args[1],
              position = args[2],
              isBinary = args[3];
  
          var fs = getFilesystemFromURL(url);
          var path = pathFromURL(url);
          if (!fs) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var completePath = sanitize(fs.winpath + path);
          var fileName = completePath.substring(completePath.lastIndexOf('/') + 1);
          var dirpath = completePath.substring(0, completePath.lastIndexOf('/'));
          var wpath = cordovaPathToNative(dirpath);
  
          function getWriteMethodForData(data, isBinary) {
  
              if (data instanceof Blob) {
                  return writeBlobAsync;
              }
  
              if (data instanceof ArrayBuffer) {
                  return writeArrayBufferAsync;
              }
  
              if (isBinary) {
                  return writeBytesAsync;
              }
  
              if (typeof data === 'string') {
                  return writeTextAsync;
              }
  
              throw new Error('Unsupported data type for write method');
          }
  
          var writePromise = getWriteMethodForData(data, isBinary);
  
          getFolderFromPathAsync(wpath).done(
              function (storageFolder) {
                  storageFolder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.openIfExists).done(
                      function (storageFile) {
                          writePromise(storageFile, data, position).done(
                              function (bytesWritten) {
                                  var written = bytesWritten || data.length;
                                  win(written);
                              },
                              function () {
                                  fail(FileError.INVALID_MODIFICATION_ERR);
                              }
                          );
                      },
                      function () {
                          fail(FileError.INVALID_MODIFICATION_ERR);
                      }
                  );
  
              },
              function () {
                  fail(FileError.NOT_FOUND_ERR);
              }
          );
      },
  
      truncate: function (win, fail, args) { // ["fileName","size"]
          var url = args[0];
          var size = args[1];
  
          var fs = getFilesystemFromURL(url);
          var path = pathFromURL(url);
          if (!fs) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          var completePath = sanitize(fs.winpath + path);
          var wpath = cordovaPathToNative(completePath);
          var dirwpath = cordovaPathToNative(completePath.substring(0, completePath.lastIndexOf('/')));
  
          getFileFromPathAsync(wpath).done(function (storageFile) {
              //the current length of the file.
              var leng = 0;
  
              storageFile.getBasicPropertiesAsync().then(function (basicProperties) {
                  leng = basicProperties.size;
                  if (Number(size) >= leng) {
                      win(this.length);
                      return;
                  }
                  if (Number(size) >= 0) {
                      Windows.Storage.FileIO.readTextAsync(storageFile, Windows.Storage.Streams.UnicodeEncoding.utf8).then(function (fileContent) {
                          fileContent = fileContent.substr(0, size);
                          var name = storageFile.name;
                          storageFile.deleteAsync().then(function () {
                              return getFolderFromPathAsync(dirwpath);
                          }).done(function (storageFolder) {
                              storageFolder.createFileAsync(name).then(function (newStorageFile) {
                                  Windows.Storage.FileIO.writeTextAsync(newStorageFile, fileContent).done(function () {
                                      win(String(fileContent).length);
                                  }, function () {
                                      fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
                                  });
                              }, function () {
                                  fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
                              });
                          });
                      }, function () {
                          fail(FileError.NOT_FOUND_ERR);
                      });
                  }
              });
          }, function () {
              fail(FileError.NOT_FOUND_ERR);
          });
      },
  
      copyTo: function (success, fail, args) { // ["fullPath","parent", "newName"]
          transport(success, fail, args,
              {
                  fileOp: function (file, folder, name, coll) {
                      return file.copyAsync(folder, name, coll);
                  },
                  folderOp: function (src, dst, name) {
                      return copyFolder(src, dst, name);
                  }
              }
          );
      },
  
      moveTo: function (success, fail, args) {
          transport(success, fail, args,
              {
                  fileOp: function (file, folder, name, coll) {
                      return file.moveAsync(folder, name, coll);
                  },
                  folderOp: function (src, dst, name) {
                      return moveFolder(src, dst, name);
                  }
              }
          );
      },
      tempFileSystem: null,
  
      persistentFileSystem: null,
  
      requestFileSystem: function (win, fail, args) {
  
          var type = args[0];
          var size = args[1];
          var MAX_SIZE = 10000000000;
          if (size > MAX_SIZE) {
              fail(FileError.QUOTA_EXCEEDED_ERR);
              return;
          }
  
          var fs;
          switch (type) {
              case LocalFileSystem.TEMPORARY:
                  fs = getFS('temporary');
                  break;
              case LocalFileSystem.PERSISTENT:
                  fs = getFS('persistent');
                  break;
          }
          if (fs)
              win(fs);
          else
              fail(FileError.NOT_FOUND_ERR);
      },
  
      resolveLocalFileSystemURI: function (success, fail, args) {
  
          var uri = args[0];
  
          var path = pathFromURL(uri);
          var fs = getFilesystemFromURL(uri);
          if (!fs || !validName(path)) {
              fail(FileError.ENCODING_ERR);
              return;
          }
          if (path.indexOf(fs.winpath) === 0)
              path = path.substr(fs.winpath.length);
          var abspath = cordovaPathToNative(fs.winpath + path);
  
          getFileFromPathAsync(abspath).done(
              function (storageFile) {
                  success(new FileEntry(storageFile.name, path, fs.name, fs.makeNativeURL(path)));
              }, function () {
                  getFolderFromPathAsync(abspath).done(
                      function (storageFolder) {
                          success(new DirectoryEntry(storageFolder.name, path, fs.name, fs.makeNativeURL(path)));
                      }, function () {
                          fail(FileError.NOT_FOUND_ERR);
                      }
                  );
              }
          );
      }
  
  
  };
  
  require("cordova/exec/proxy").add("File", module.exports);
  