diff --git a/.bowerrc b/.bowerrc
index 158a069..e28246d 100644
--- a/.bowerrc
+++ b/.bowerrc
@@ -1,3 +1,3 @@
{
- "directory": "client/www/lib"
+ "directory": "www/lib"
}
diff --git a/.gitignore b/.gitignore
index 3b48218..bd13e58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ client/hooks/
# Compiled Dirs (http://nodejs.org/api/addons.html)
client/platforms/
+platforms/
client/plugins/
client/www/lib/
coverage/
diff --git a/client/package.json b/client/package.json
deleted file mode 100644
index 7669c68..0000000
--- a/client/package.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "name": "mementos",
- "version": "0.0.1",
- "description": "Create and share moments as a narrative",
- "repository": {
- "type": "git",
- "url": "https://github.com/supertitanoboa/mementos.git"
- },
- "keywords": [
- "memento",
- "moment",
- "narrative"
- ],
- "author": "supertitanoboa",
- "bugs": {
- "url": "https://github.com/supertitanoboa/mementos/issues"
- },
- "homepage": "https://github.com/supertitanoboa/mementos",
- "devDependencies": {
- "bower": "^1.3.3",
- "chai": "^2.0.0",
- "gulp": "^3.8.11",
- "gulp-concat": "^2.2.0",
- "gulp-concat-css": "^2.1.0",
- "gulp-jshint": "^1.9.2",
- "gulp-karma": "0.0.4",
- "gulp-minify-css": "^0.3.0",
- "gulp-mocha": "^2.0.0",
- "gulp-rename": "^1.2.0",
- "gulp-sass": "^0.7.1",
- "gulp-shell": "^0.3.0",
- "gulp-util": "^2.2.14",
- "jasmine-core": "^2.2.0",
- "karma": "^0.12.31",
- "karma-chai": "^0.1.0",
- "karma-chai-sinon": "^0.1.4",
- "karma-chrome-launcher": "^0.1.7",
- "karma-coverage": "^0.2.7",
- "karma-jasmine": "^0.3.5",
- "karma-mocha": "^0.1.10",
- "karma-ng-html2js-preprocessor": "^0.1.2",
- "karma-sinon": "^1.0.4",
- "mocha": "^2.1.0",
- "sinon": "^1.12.2",
- "sinon-chai": "^2.7.0"
- },
- "cordovaPlugins": [
- "https://github.com/VitaliiBlagodir/cordova-plugin-datepicker",
- "org.apache.cordova.media-capture",
- "org.apache.cordova.camera",
- "org.apache.cordova.media"
- ],
- "dependencies": {
- "node-sass": "^2.0.1"
- }
-}
diff --git a/client/www/app/auth/user.signin.js b/client/www/app/auth/user.signin.js
deleted file mode 100644
index 714d816..0000000
--- a/client/www/app/auth/user.signin.js
+++ /dev/null
@@ -1,48 +0,0 @@
-(function() {
- angular
- .module('app.user.auth')
- .controller('UserSignin', UserSignin);
-
- /* @ngInject */
- function UserSignin(Notifications, DataHandler, dataservice, $state, Events, $ionicHistory, Alerts) {
- vm = this;
- vm.credentials = {};
- vm.signin = signin;
- vm.goBack = goBack;
-
- /////////////////////////////////////
-
- // FIX! Try to make these run when platform is ready!
- Notifications.activate();
- DataHandler.activate();
- //
-
- function signin(credentials) {
- return dataservice.signin(credentials)
- .then(function(res) {
- var userInfo = {
- sessionID: res.data.sessionID,
- userID: res.data.userID
- };
-
- Events.trigger('userLogin', userInfo);
-
- $state.go('moment');
- })
- .catch(function(err) {
- console.error(err);
- if(err.data === 'Invalid username or password'){
- Alerts.showIncorrectPassword();
- } else {
- Alerts.showUserDoesNotExist();
- }
- });
- }
-
- // NOTE: all this nav functionality are candidates for a nav service
- function goBack() {
- return $ionicHistory.goBack()
- }
- }
-})();
-
diff --git a/client/www/app/auth/user.signup.js b/client/www/app/auth/user.signup.js
deleted file mode 100644
index 486cac4..0000000
--- a/client/www/app/auth/user.signup.js
+++ /dev/null
@@ -1,53 +0,0 @@
-(function() {
- angular
- .module('app.user.auth')
- .controller('UserSignup', UserSignup);
-
- /* @ngInject */
- function UserSignup(Notifications, DataHandler, dataservice, $state, Events, $ionicHistory, Alerts) {
- vm = this;
- vm.credentials = {};
- vm.repeatPassword = '';
- vm.passwordError = false;
- vm.signup = signup;
- vm.goBack = goBack;
-
- /////////////////////////////////////
-
- // FIX! Try to make these run when platform is ready!
- Notifications.activate();
- DataHandler.activate();
- //
-
- function signup(credentials) {
- if(credentials.password !== vm.repeatPassword) {
- return vm.passwordError = true;
- }
-
- return dataservice.signup(credentials)
- .then(function(res) {
- var userInfo = {
- sessionID: res.data.sessionID,
- userID: res.data.userID
- };
-
- // reset credentials
- vm.credentials = {};
- vm.repeatPassword = '';
-
- Events.trigger('userLogin', userInfo);
-
- $state.go('moment');
- })
- .catch(function(err) {
- console.error(err);
- Alerts.showUserExists();
- });
- }
-
- // NOTE: all this nav functionality are candidates for a nav service
- function goBack() {
- return $ionicHistory.goBack()
- }
- }
-})();
diff --git a/client/www/app/core/CurrentMode.js b/client/www/app/core/CurrentMode.js
deleted file mode 100644
index b4914c1..0000000
--- a/client/www/app/core/CurrentMode.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/*NOTE: cosider putting all 'Current' into one service*/
-(function() {
-
- angular
- .module('app.core')
- .factory('CurrentMode', CurrentMode);
-
- function CurrentMode() {
- var mode = {mode: false}
-
- return {
- set: function(obj) {
- mode = angular.copy(obj);
- },
- get: function() {
- return angular.copy(moment);
- }
- };
- }
-
-})();
diff --git a/client/www/app/core/CurrentViewer.js b/client/www/app/core/CurrentViewer.js
deleted file mode 100644
index 772ad0d..0000000
--- a/client/www/app/core/CurrentViewer.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/*NOTE: cosider putting all 'Current' into one service*/
-(function() {
-
- angular
- .module('app.core')
- .factory('CurrentViewer', CurrentViewer);
-
- function CurrentViewer() {
- var viewer = {}
-
- return {
- set: function(obj) {
- viewer = angular.copy(obj);
- },
- get: function() {
- return angular.copy(viewer);
- }
- };
- }
-
-})();
diff --git a/client/www/app/core/core.module.js b/client/www/app/core/core.module.js
deleted file mode 100644
index 2af3c57..0000000
--- a/client/www/app/core/core.module.js
+++ /dev/null
@@ -1,6 +0,0 @@
-(function() {
- 'use strict';
-
- angular.module('app.core', ['app.media', 'app.data', 'app.tools', 'app.third-party.tools']);
-
-})();
diff --git a/client/www/app/moment/moment.module.js b/client/www/app/moment/moment.module.js
deleted file mode 100644
index f0499bf..0000000
--- a/client/www/app/moment/moment.module.js
+++ /dev/null
@@ -1,8 +0,0 @@
-(function() {
-
- 'use strict';
- angular.module('app.moment', [
-
- 'app.moment.items',
- ]);
-})();
diff --git a/client/config.xml b/config.xml
similarity index 100%
rename from client/config.xml
rename to config.xml
diff --git a/client/hooks/README.md b/hooks/README.md
similarity index 100%
rename from client/hooks/README.md
rename to hooks/README.md
diff --git a/hooks/after_platform_add/010_install_plugins.js b/hooks/after_platform_add/010_install_plugins.js
new file mode 100755
index 0000000..b23635c
--- /dev/null
+++ b/hooks/after_platform_add/010_install_plugins.js
@@ -0,0 +1,29 @@
+#!/usr/bin/env node
+
+/**
+ * Install all plugins listed in package.json
+ * https://raw.githubusercontent.com/diegonetto/generator-ionic/master/templates/hooks/after_platform_add/install_plugins.js
+ */
+var exec = require('child_process').exec;
+var path = require('path');
+var sys = require('sys');
+
+var packageJSON = null;
+
+try {
+ packageJSON = require('../../package.json');
+} catch(ex) {
+ console.log('\nThere was an error fetching your package.json file.')
+ console.log('\nPlease ensure a valid package.json is in the root of this project\n')
+ return;
+}
+
+var cmd = process.platform === 'win32' ? 'cordova.cmd' : 'cordova';
+// var script = path.resolve(__dirname, '../../node_modules/cordova/bin', cmd);
+
+packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];
+packageJSON.cordovaPlugins.forEach(function (plugin) {
+ exec('cordova plugin add ' + plugin, function (error, stdout, stderr) {
+ sys.puts(stdout);
+ });
+});
diff --git a/hooks/after_plugin_add/010_register_plugin.js b/hooks/after_plugin_add/010_register_plugin.js
new file mode 100755
index 0000000..380cc5b
--- /dev/null
+++ b/hooks/after_plugin_add/010_register_plugin.js
@@ -0,0 +1,16 @@
+#!/usr/bin/env node
+
+/**
+ * Push plugins to cordovaPlugins array after_plugin_add
+ */
+var fs = require('fs');
+var packageJSON = require('../../package.json');
+
+packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];
+process.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) {
+ if(packageJSON.cordovaPlugins.indexOf(plugin) == -1) {
+ packageJSON.cordovaPlugins.push(plugin);
+ }
+});
+
+fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2));
diff --git a/hooks/after_plugin_rm/010_deregister_plugin.js b/hooks/after_plugin_rm/010_deregister_plugin.js
new file mode 100755
index 0000000..65fa4cc
--- /dev/null
+++ b/hooks/after_plugin_rm/010_deregister_plugin.js
@@ -0,0 +1,18 @@
+#!/usr/bin/env node
+
+/**
+ * Remove plugins from cordovaPlugins array after_plugin_rm
+ */
+var fs = require('fs');
+var packageJSON = require('../../package.json');
+
+packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];
+
+process.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) {
+ var index = packageJSON.cordovaPlugins.indexOf(plugin);
+ if (index > -1) {
+ packageJSON.cordovaPlugins.splice(index, 1);
+ }
+});
+
+fs.writeFile('package.json', JSON.stringify(packageJSON, null, 2));
diff --git a/client/hooks/after_prepare/010_add_platform_class.js b/hooks/after_prepare/010_add_platform_class.js
similarity index 100%
rename from client/hooks/after_prepare/010_add_platform_class.js
rename to hooks/after_prepare/010_add_platform_class.js
diff --git a/hooks/after_prepare/020_remove_sass_from_platforms.js b/hooks/after_prepare/020_remove_sass_from_platforms.js
new file mode 100755
index 0000000..da3193a
--- /dev/null
+++ b/hooks/after_prepare/020_remove_sass_from_platforms.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+/**
+ * After prepare, files are copied to the platforms/ios and platforms/android folders.
+ * Lets clean up some of those files that arent needed with this hook.
+ */
+var fs = require('fs');
+var path = require('path');
+
+var deleteFolderRecursive = function(removePath) {
+ if( fs.existsSync(removePath) ) {
+ fs.readdirSync(removePath).forEach(function(file,index){
+ var curPath = path.join(removePath, file);
+ if(fs.lstatSync(curPath).isDirectory()) { // recurse
+ deleteFolderRecursive(curPath);
+ } else { // delete file
+ fs.unlinkSync(curPath);
+ }
+ });
+ fs.rmdirSync(removePath);
+ }
+};
+
+var iosPlatformsDir = path.resolve(__dirname, '../../platforms/ios/www/lib/ionic/scss');
+var androidPlatformsDir = path.resolve(__dirname, '../../platforms/android/assets/www/lib/ionic/scss');
+
+deleteFolderRecursive(iosPlatformsDir);
+deleteFolderRecursive(androidPlatformsDir);
diff --git a/hooks/before_platform_add/init_directories.js b/hooks/before_platform_add/init_directories.js
new file mode 100755
index 0000000..babde34
--- /dev/null
+++ b/hooks/before_platform_add/init_directories.js
@@ -0,0 +1,23 @@
+#!/usr/bin/env node
+
+/**
+ * On a fresh clone, the local platforms/ and plugins/ directories will be
+ * missing, so ensure they get created before the first platform is added.
+ */
+var fs = require('fs');
+var path = require('path');
+
+var platformsDir = path.resolve(__dirname, '../../platforms');
+var pluginsDir = path.resolve(__dirname, '../../plugins');
+
+try {
+ fs.mkdirSync(platformsDir, function (err) {
+ if (err) { console.error(err); }
+ });
+} catch(ex) {}
+
+try {
+ fs.mkdirSync(pluginsDir, function (err) {
+ if (err) { console.error(err); }
+ });
+} catch(ex) {}
diff --git a/client/ionic.project b/ionic.project
similarity index 100%
rename from client/ionic.project
rename to ionic.project
diff --git a/package.json b/package.json
index e57a5be..7669c68 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,12 @@
"sinon": "^1.12.2",
"sinon-chai": "^2.7.0"
},
+ "cordovaPlugins": [
+ "https://github.com/VitaliiBlagodir/cordova-plugin-datepicker",
+ "org.apache.cordova.media-capture",
+ "org.apache.cordova.camera",
+ "org.apache.cordova.media"
+ ],
"dependencies": {
"node-sass": "^2.0.1"
}
diff --git a/plugins/android.json b/plugins/android.json
new file mode 100644
index 0000000..88cc361
--- /dev/null
+++ b/plugins/android.json
@@ -0,0 +1,87 @@
+{
+ "prepare_queue": {
+ "installed": [],
+ "uninstalled": []
+ },
+ "config_munge": {
+ "files": {
+ "res/xml/config.xml": {
+ "parents": {
+ "/*": [
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ }
+ ]
+ }
+ },
+ "AndroidManifest.xml": {
+ "parents": {
+ "/*": [
+ {
+ "xml": "",
+ "count": 5
+ },
+ {
+ "xml": "",
+ "count": 2
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ }
+ ]
+ }
+ }
+ }
+ },
+ "installed_plugins": {
+ "org.apache.cordova.camera": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.file": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.file-transfer": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.media": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.media-capture": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "com.plugin.datepicker": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ }
+ },
+ "dependent_plugins": {}
+}
\ No newline at end of file
diff --git a/plugins/com.plugin.datepicker/.fetch.json b/plugins/com.plugin.datepicker/.fetch.json
new file mode 100644
index 0000000..2e1e986
--- /dev/null
+++ b/plugins/com.plugin.datepicker/.fetch.json
@@ -0,0 +1 @@
+{"source":{"type":"git","url":"https://github.com/VitaliiBlagodir/cordova-plugin-datepicker","subdir":"."}}
\ No newline at end of file
diff --git a/plugins/com.plugin.datepicker/LICENSE.md b/plugins/com.plugin.datepicker/LICENSE.md
new file mode 100644
index 0000000..66c7de2
--- /dev/null
+++ b/plugins/com.plugin.datepicker/LICENSE.md
@@ -0,0 +1,20 @@
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/plugins/com.plugin.datepicker/README.md b/plugins/com.plugin.datepicker/README.md
new file mode 100644
index 0000000..a7ddff3
--- /dev/null
+++ b/plugins/com.plugin.datepicker/README.md
@@ -0,0 +1,150 @@
+# DatePicker Plugin for Cordova/PhoneGap 3.0 (iOS and Android)
+
+This is a combined version of DatePicker iOS and Android plugin for Cordova/Phonegap 3.0.
+- Original iOS version: https://github.com/sectore/phonegap3-ios-datepicker-plugin
+
+- Original Android version: https://github.com/bikasv/cordova-android-plugins/tree/master/datepicker
+
+## Installation
+
+1) Make sure that you have [Node](http://nodejs.org/) and [Cordova CLI](https://github.com/apache/cordova-cli) or [PhoneGap's CLI](https://github.com/mwbrooks/phonegap-cli) installed on your machine.
+
+2) Add a plugin to your project using Cordova CLI:
+
+```bash
+cordova plugin add https://github.com/VitaliiBlagodir/cordova-plugin-datepicker
+```
+Or using PhoneGap CLI:
+
+```bash
+phonegap local plugin add https://github.com/VitaliiBlagodir/cordova-plugin-datepicker
+```
+
+## Usage
+
+```js
+var options = {
+ date: new Date(),
+ mode: 'date'
+};
+
+datePicker.show(options, function(date){
+ alert("date result " + date);
+});
+```
+
+## Options
+
+### mode - iOS, Android
+The mode of the date picker.
+
+Type: String
+
+Values: `date` | `time` | `datetime` (iOS only)
+
+Default: `date`
+
+### date - iOS, Android
+Selected date.
+
+Type: String
+
+Default: `new Date()`
+
+### minDate - iOS, Android
+Minimum date.
+
+Type: Date | empty String
+
+Default: `(empty String)`
+
+### maxDate - iOS, Android
+Maximum date.
+
+Type: Date | empty String
+
+Default: `(empty String)`
+
+### allowOldDates - iOS
+Shows or hide dates earlier then selected date.
+
+Type: Boolean
+
+Values: `true` | `false`
+
+Default: `true`
+
+### allowFutureDates - iOS
+Shows or hide dates after selected date.
+
+Type: Boolean
+
+Values: `true` | `false`
+
+Default: `true`
+
+### doneButtonLabel - iOS
+Label of done button.
+
+Typ: String
+
+Default: `Done`
+
+### doneButtonColor - iOS
+Hex color of done button.
+
+Typ: String
+
+Default: `#0000FF`
+
+### cancelButtonLabel - iOS
+Label of cancel button.
+
+Type: String
+
+Default: `Cancel`
+
+### cancelButtonColor - iOS
+Hex color of cancel button.
+
+Type: String
+
+Default: `#000000`
+
+### x - iOS (iPad only)
+X position of date picker. The position is absolute to the root view of the application.
+
+Type: String
+
+Default: `0`
+
+### y - iOS (iPad only)
+Y position of date picker. The position is absolute to the root view of the application.
+
+Type: String
+
+Default: `0`
+
+### minuteInterval - iOS
+Interval between options in the minute section of the date picker.
+
+Type: Integer
+
+Default: `1`
+
+## Requirements
+- PhoneGap 3.0 or newer / Cordova 3.0 or newer
+- Android 2.3.1 or newer / iOS 5 or newer
+
+## Example
+
+```js
+var options = {
+ date: new Date(),
+ mode: 'date'
+};
+
+datePicker.show(options, function(date){
+ alert("date result " + date);
+});
+```
\ No newline at end of file
diff --git a/plugins/com.plugin.datepicker/plugin.xml b/plugins/com.plugin.datepicker/plugin.xml
new file mode 100644
index 0000000..9a5c07a
--- /dev/null
+++ b/plugins/com.plugin.datepicker/plugin.xml
@@ -0,0 +1,50 @@
+
+
+
+
+ DatePicker
+
+ Cordova/PhoneGap DatePicker Plugin for iOS and Android
+
+ MIT
+
+ cordova,phonegap,datepicker,android,ios,ios7,ios8
+
+ https://github.com/VitaliiBlagodir/cordova-plugin-datepicker.git
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/com.plugin.datepicker/src/android/DatePickerPlugin.java b/plugins/com.plugin.datepicker/src/android/DatePickerPlugin.java
new file mode 100644
index 0000000..a5d9052
--- /dev/null
+++ b/plugins/com.plugin.datepicker/src/android/DatePickerPlugin.java
@@ -0,0 +1,265 @@
+/**
+ * @author Bikas Vaibhav (http://bikasv.com) 2013
+ * Rewrote the plug-in at https://github.com/phonegap/phonegap-plugins/tree/master/Android/DatePicker
+ * It can now accept `min` and `max` dates for DatePicker.
+ */
+
+package com.plugin.datepicker;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+import java.text.SimpleDateFormat;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.annotation.SuppressLint;
+import android.app.DatePickerDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.Dialog;
+import android.app.TimePickerDialog;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.widget.DatePicker;
+import android.widget.DatePicker.OnDateChangedListener;
+import android.widget.TimePicker;
+
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+
+import android.os.Build;
+
+@SuppressWarnings("deprecation")
+@SuppressLint("NewApi")
+public class DatePickerPlugin extends CordovaPlugin {
+
+ private static final String ACTION_DATE = "date";
+ private static final String ACTION_TIME = "time";
+ private final String pluginName = "DatePickerPlugin";
+
+ @Override
+ public boolean execute(final String action, final JSONArray data, final CallbackContext callbackContext) {
+ Log.d(pluginName, "DatePicker called with options: " + data);
+ boolean result = false;
+
+ this.show(data, callbackContext);
+ result = true;
+
+ return result;
+ }
+
+ public synchronized void show(final JSONArray data, final CallbackContext callbackContext) {
+ final DatePickerPlugin datePickerPlugin = this;
+ final Context currentCtx = cordova.getActivity();
+ final Calendar c = Calendar.getInstance();
+ final Runnable runnable;
+
+ String action = "date";
+ long minDateLong = 0, maxDateLong = 0;
+
+ int month = -1, day = -1, year = -1, hour = -1, min = -1;
+ try {
+ JSONObject obj = data.getJSONObject(0);
+ action = obj.getString("mode");
+
+ String optionDate = obj.getString("date");
+
+ String[] datePart = optionDate.split("/");
+ month = Integer.parseInt(datePart[0]);
+ day = Integer.parseInt(datePart[1]);
+ year = Integer.parseInt(datePart[2]);
+ hour = Integer.parseInt(datePart[3]);
+ min = Integer.parseInt(datePart[4]);
+
+ minDateLong = obj.getLong("minDate");
+ maxDateLong = obj.getLong("maxDate");
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ // By default initalize these fields to 'now'
+ final int mYear = year == -1 ? c.get(Calendar.YEAR) : year;
+ final int mMonth = month == -1 ? c.get(Calendar.MONTH) : month - 1;
+ final int mDay = day == -1 ? c.get(Calendar.DAY_OF_MONTH) : day;
+ final int mHour = hour == -1 ? c.get(Calendar.HOUR_OF_DAY) : hour;
+ final int mMinutes = min == -1 ? c.get(Calendar.MINUTE) : min;
+
+ final long minDate = minDateLong;
+ final long maxDate = maxDateLong;
+
+ if (ACTION_TIME.equalsIgnoreCase(action)) {
+ runnable = new Runnable() {
+ @Override
+ public void run() {
+ final TimeSetListener timeSetListener = new TimeSetListener(datePickerPlugin, callbackContext);
+ final TimePickerDialog timeDialog = new TimePickerDialog(currentCtx, timeSetListener, mHour,
+ mMinutes, false);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ timeDialog.setCancelable(true);
+ timeDialog.setCanceledOnTouchOutside(false);
+ timeDialog.setButton(DialogInterface.BUTTON_NEGATIVE, currentCtx.getString(android.R.string.cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ callbackContext.success("cancel");
+ }
+ });
+ timeDialog.setOnKeyListener(new Dialog.OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ // TODO Auto-generated method stub
+ //callbackContext.success("");
+ return false;
+ }
+ });
+ }
+ timeDialog.show();
+ }
+ };
+
+ } else if (ACTION_DATE.equalsIgnoreCase(action)) {
+ runnable = new Runnable() {
+ @Override
+ public void run() {
+ final DateSetListener dateSetListener = new DateSetListener(datePickerPlugin, callbackContext);
+ final DatePickerDialog dateDialog = new DatePickerDialog(currentCtx, dateSetListener, mYear,
+ mMonth, mDay);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ DatePicker dp = dateDialog.getDatePicker();
+ if(minDate > 0) {
+ dp.setMinDate(minDate);
+ }
+ if(maxDate > 0 && maxDate > minDate) {
+ dp.setMaxDate(maxDate);
+ }
+
+ dateDialog.setCancelable(true);
+ dateDialog.setCanceledOnTouchOutside(false);
+ dateDialog.setButton(DialogInterface.BUTTON_NEGATIVE, currentCtx.getString(android.R.string.cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ callbackContext.success("cancel");
+ }
+ });
+ dateDialog.setOnKeyListener(new Dialog.OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ // TODO Auto-generated method stub
+ //callbackContext.success("");
+ return false;
+ }
+ });
+ }
+ else {
+ java.lang.reflect.Field mDatePickerField = null;
+ try {
+ mDatePickerField = dateDialog.getClass().getDeclaredField("mDatePicker");
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ mDatePickerField.setAccessible(true);
+ DatePicker pickerView = null;
+ try {
+ pickerView = (DatePicker) mDatePickerField.get(dateDialog);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ final Calendar startDate = Calendar.getInstance();
+ startDate.setTimeInMillis(minDate);
+ final Calendar endDate = Calendar.getInstance();
+ endDate.setTimeInMillis(maxDate);
+
+ final int minYear = startDate.get(Calendar.YEAR);
+ final int minMonth = startDate.get(Calendar.MONTH);
+ final int minDay = startDate.get(Calendar.DAY_OF_MONTH);
+ final int maxYear = endDate.get(Calendar.YEAR);
+ final int maxMonth = endDate.get(Calendar.MONTH);
+ final int maxDay = endDate.get(Calendar.DAY_OF_MONTH);
+
+ if(startDate !=null || endDate != null) {
+ pickerView.init(mYear, mMonth, mDay, new OnDateChangedListener() {
+ @Override
+ public void onDateChanged(DatePicker view, int year, int month, int day) {
+ if(maxDate > 0 && maxDate > minDate) {
+ if(year > maxYear || month > maxMonth && year == maxYear || day > maxDay && year == maxYear && month == maxMonth){
+ view.updateDate(maxYear, maxMonth, maxDay);
+ }
+ }
+ if(minDate > 0) {
+ if(year < minYear || month < minMonth && year == minYear || day < minDay && year == minYear && month == minMonth) {
+ view.updateDate(minYear, minMonth, minDay);
+ }
+ }
+ }
+ });
+ }
+ }
+ dateDialog.show();
+ }
+ };
+
+ } else {
+ Log.d(pluginName, "Unknown action. Only 'date' or 'time' are valid actions");
+ return;
+ }
+
+ cordova.getActivity().runOnUiThread(runnable);
+ }
+
+ private final class DateSetListener implements OnDateSetListener {
+ private final DatePickerPlugin datePickerPlugin;
+ private final CallbackContext callbackContext;
+
+ private DateSetListener(DatePickerPlugin datePickerPlugin, CallbackContext callbackContext) {
+ this.datePickerPlugin = datePickerPlugin;
+ this.callbackContext = callbackContext;
+ }
+
+ /**
+ * Return a string containing the date in the format YYYY/MM/DD
+ */
+ @Override
+ public void onDateSet(final DatePicker view, final int year, final int monthOfYear, final int dayOfMonth) {
+ String returnDate = year + "/" + (monthOfYear + 1) + "/" + dayOfMonth;
+ callbackContext.success(returnDate);
+ }
+ }
+
+ private final class TimeSetListener implements OnTimeSetListener {
+ private final DatePickerPlugin datePickerPlugin;
+ private final CallbackContext callbackContext;
+
+ private TimeSetListener(DatePickerPlugin datePickerPlugin, CallbackContext callbackContext) {
+ this.datePickerPlugin = datePickerPlugin;
+ this.callbackContext = callbackContext;
+ }
+
+ /**
+ * Return the current date with the time modified as it was set in the
+ * time picker.
+ */
+ @Override
+ public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute) {
+ Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
+ calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
+ calendar.set(Calendar.MINUTE, minute);
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+ String toReturn = sdf.format(calendar.getTime());
+
+ callbackContext.success(toReturn);
+ }
+ }
+
+}
diff --git a/plugins/com.plugin.datepicker/src/ios/DatePicker.h b/plugins/com.plugin.datepicker/src/ios/DatePicker.h
new file mode 100644
index 0000000..24077fa
--- /dev/null
+++ b/plugins/com.plugin.datepicker/src/ios/DatePicker.h
@@ -0,0 +1,21 @@
+/*
+ Phonegap DatePicker Plugin for using Cordova 3 and iOS 7
+ https://github.com/sectore/phonegap3-ios-datepicker-plugin
+
+ Based on a previous plugin version by Greg Allen and Sam de Freyssinet.
+
+ Rewrite by Jens Krause (www.websector.de)
+
+ MIT Licensed
+*/
+
+#import
+#import
+
+@interface DatePicker : CDVPlugin {
+
+}
+
+- (void)show:(CDVInvokedUrlCommand*)command;
+
+@end
\ No newline at end of file
diff --git a/plugins/com.plugin.datepicker/src/ios/DatePicker.m b/plugins/com.plugin.datepicker/src/ios/DatePicker.m
new file mode 100644
index 0000000..7b53f57
--- /dev/null
+++ b/plugins/com.plugin.datepicker/src/ios/DatePicker.m
@@ -0,0 +1,298 @@
+/*
+
+ Phonegap DatePicker Plugin for using Cordova 3 and iOS 7
+ https://github.com/sectore/phonegap3-ios-datepicker-plugin
+
+ Based on a previous plugin version by Greg Allen and Sam de Freyssinet.
+
+ Rewrite by Jens Krause (www.websector.de)
+
+ MIT Licensed
+
+ */
+
+#import "DatePicker.h"
+#import
+
+@interface DatePicker ()
+
+@property (nonatomic) UIPopoverController *datePickerPopover;
+
+@property (nonatomic) IBOutlet UIView* datePickerContainer;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *datePickerComponentsContainerVSpace;
+@property (nonatomic) IBOutlet UIView* datePickerComponentsContainer;
+@property (nonatomic) IBOutlet UIButton *cancelButton;
+@property (nonatomic) IBOutlet UIButton *doneButton;
+@property (nonatomic) IBOutlet UIDatePicker *datePicker;
+
+@end
+
+@implementation DatePicker
+
+#define isIPhone (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
+#define ANIMATION_DURATION 0.3
+
+#pragma mark - UIDatePicker
+
+- (void)show:(CDVInvokedUrlCommand*)command {
+ NSMutableDictionary *options = [command argumentAtIndex:0];
+ if (isIPhone) {
+ [self showForPhone: options];
+ } else {
+ [self showForPad: options];
+ }
+}
+
+- (BOOL)showForPhone:(NSMutableDictionary *)options {
+ if(!self.datePickerContainer){
+ [[NSBundle mainBundle] loadNibNamed:@"DatePicker" owner:self options:nil];
+ }
+
+ [self updateDatePicker:options];
+ [self updateCancelButton:options];
+ [self updateDoneButton:options];
+
+ UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
+
+ CGFloat width;
+ CGFloat height;
+
+ if(UIInterfaceOrientationIsLandscape(deviceOrientation)){
+ width = self.webView.superview.frame.size.height;
+ height= self.webView.superview.frame.size.width;
+ } else {
+ width = self.webView.superview.frame.size.width;
+ height= self.webView.superview.frame.size.height;
+ }
+
+ self.datePickerContainer.frame = CGRectMake(0, 0, width, height);
+
+ [self.webView.superview addSubview: self.datePickerContainer];
+ [self.datePickerContainer layoutIfNeeded];
+
+ CGRect frame = self.datePickerComponentsContainer.frame;
+ self.datePickerComponentsContainer.frame = CGRectOffset(frame,
+ 0,
+ frame.size.height );
+
+
+ self.datePickerContainer.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0];
+
+ [UIView animateWithDuration:ANIMATION_DURATION
+ delay:0
+ options:UIViewAnimationOptionCurveEaseOut
+ animations:^{
+ self.datePickerComponentsContainer.frame = frame;
+ self.datePickerContainer.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
+
+ } completion:^(BOOL finished) {
+
+ }];
+
+ return true;
+}
+
+- (BOOL)showForPad:(NSMutableDictionary *)options {
+ self.datePickerPopover = [self createPopover:options];
+ return true;
+}
+
+- (void)hide {
+ if (isIPhone) {
+ CGRect frame = CGRectOffset(self.datePickerComponentsContainer.frame,
+ 0,
+ self.datePickerComponentsContainer.frame.size.height);
+
+ [UIView animateWithDuration:ANIMATION_DURATION
+ delay:0
+ options:UIViewAnimationOptionCurveEaseOut
+ animations:^{
+ self.datePickerComponentsContainer.frame = frame;
+ self.datePickerContainer.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0];
+
+ } completion:^(BOOL finished) {
+ [self.datePickerContainer removeFromSuperview];
+ }];
+
+ } else {
+ [self.datePickerPopover dismissPopoverAnimated:YES];
+ }
+}
+
+#pragma mark - Actions
+- (IBAction)doneAction:(id)sender {
+ [self jsDateSelected];
+ [self hide];
+}
+
+- (IBAction)cancelAction:(id)sender {
+ [self jsCancel];
+ [self hide];
+}
+
+
+- (void)dateChangedAction:(id)sender {
+ [self jsDateSelected];
+}
+
+#pragma mark - JS API
+
+- (void)jsCancel {
+ NSLog(@"JS Cancel is going to be executed");
+ NSString *jsCallback = @"datePicker._dateSelectionCanceled();";
+
+ [self.commandDelegate evalJs:jsCallback];
+}
+
+- (void)jsDateSelected {
+ NSTimeInterval seconds = [self.datePicker.date timeIntervalSince1970];
+ NSString *jsCallback = [NSString stringWithFormat:@"datePicker._dateSelected(\"%f\");", seconds];
+
+ [self.commandDelegate evalJs:jsCallback];
+}
+
+
+#pragma mark - UIPopoverControllerDelegate methods
+
+- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
+
+}
+
+#pragma mark - Factory methods
+
+- (UIPopoverController *)createPopover:(NSMutableDictionary *)options {
+
+ CGFloat pickerViewWidth = 320.0f;
+ CGFloat pickerViewHeight = 216.0f;
+ UIView *datePickerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, pickerViewWidth, pickerViewHeight)];
+
+ CGRect frame = CGRectMake(0, 0, 0, 0);
+ // in iOS8, UIDatePicker couldn't be shared in multi UIViews, it will cause crash. so create new UIDatePicker instance every time
+ if (! self.datePicker || [[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0){
+ self.datePicker = [self createDatePicker:options frame:frame];
+ [self.datePicker addTarget:self action:@selector(dateChangedAction:) forControlEvents:UIControlEventValueChanged];
+ }
+ [self updateDatePicker:options];
+ [datePickerView addSubview:self.datePicker];
+
+ UIViewController *datePickerViewController = [[UIViewController alloc]init];
+ datePickerViewController.view = datePickerView;
+
+ UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:datePickerViewController];
+ popover.delegate = self;
+ [popover setPopoverContentSize:CGSizeMake(pickerViewWidth, pickerViewHeight) animated:NO];
+
+ CGFloat x = [[options objectForKey:@"x"] intValue];
+ CGFloat y = [[options objectForKey:@"y"] intValue];
+ CGRect anchor = CGRectMake(x, y, 1, 1);
+ [popover presentPopoverFromRect:anchor inView:self.webView.superview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
+
+ return popover;
+}
+
+- (UIDatePicker *)createDatePicker:(NSMutableDictionary *)options frame:(CGRect)frame {
+ UIDatePicker *datePicker = [[UIDatePicker alloc] initWithFrame:frame];
+ return datePicker;
+}
+
+#define DATETIME_FORMAT @"yyyy-MM-dd'T'HH:mm:ss'Z'"
+
+- (void)updateDatePicker:(NSMutableDictionary *)options {
+ NSDateFormatter *formatter = [self createISODateFormatter: DATETIME_FORMAT timezone:[NSTimeZone defaultTimeZone]];
+ NSString *mode = [options objectForKey:@"mode"];
+ NSString *dateString = [options objectForKey:@"date"];
+ BOOL allowOldDates = ([[options objectForKey:@"allowOldDates"] intValue] == 0) ? NO : YES;
+ BOOL allowFutureDates = ([[options objectForKey:@"allowFutureDates"] intValue] == 0) ? NO : YES;
+ NSString *minDateString = [options objectForKey:@"minDate"];
+ NSString *maxDateString = [options objectForKey:@"maxDate"];
+ NSString *minuteIntervalString = [options objectForKey:@"minuteInterval"];
+ NSInteger minuteInterval = [minuteIntervalString integerValue];
+
+ if (allowOldDates) {
+ self.datePicker.minimumDate = nil;
+ }
+ else {
+ self.datePicker.minimumDate = [NSDate date];
+ }
+
+ if(minDateString && minDateString.length > 0){
+ self.datePicker.minimumDate = [formatter dateFromString:minDateString];
+ }
+
+ if (allowFutureDates) {
+ self.datePicker.maximumDate = nil;
+ }
+ else {
+ self.datePicker.maximumDate = [NSDate date];
+ }
+
+ if(maxDateString && maxDateString.length > 0){
+ self.datePicker.maximumDate = [formatter dateFromString:maxDateString];
+ }
+
+ self.datePicker.date = [formatter dateFromString:dateString];
+
+ if ([mode isEqualToString:@"date"]) {
+ self.datePicker.datePickerMode = UIDatePickerModeDate;
+ }
+ else if ([mode isEqualToString:@"time"]) {
+ self.datePicker.datePickerMode = UIDatePickerModeTime;
+ } else {
+ self.datePicker.datePickerMode = UIDatePickerModeDateAndTime;
+ }
+
+ if (minuteInterval) {
+ self.datePicker.minuteInterval = minuteInterval;
+ }
+}
+
+- (NSDateFormatter *)createISODateFormatter:(NSString *)format timezone:(NSTimeZone *)timezone {
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ // Locale needed to avoid formatter bug on phones set to 12-hour
+ // time to avoid it adding AM/PM to the string we supply
+ // See: http://stackoverflow.com/questions/6613110/what-is-the-best-way-to-deal-with-the-nsdateformatter-locale-feature
+ NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
+ [dateFormatter setLocale: loc];
+ [dateFormatter setTimeZone:timezone];
+ [dateFormatter setDateFormat:format];
+
+ return dateFormatter;
+}
+
+- (void)updateCancelButton:(NSMutableDictionary *)options {
+
+ NSString *label = [options objectForKey:@"cancelButtonLabel"];
+ [self.cancelButton setTitle:label forState:UIControlStateNormal];
+
+ NSString *tintColorHex = [options objectForKey:@"cancelButtonColor"];
+ self.cancelButton.tintColor = [self colorFromHexString: tintColorHex];
+
+}
+
+- (void)updateDoneButton:(NSMutableDictionary *)options {
+
+ NSString *label = [options objectForKey:@"doneButtonLabel"];
+ [self.doneButton setTitle:label forState:UIControlStateNormal];
+
+ NSString *tintColorHex = [options objectForKey:@"doneButtonColor"];
+ [self.doneButton setTintColor: [self colorFromHexString: tintColorHex]];
+
+}
+
+
+#pragma mark - Utilities
+
+/*! Converts a hex string into UIColor
+ It based on http://stackoverflow.com/questions/1560081/how-can-i-create-a-uicolor-from-a-hex-string
+
+ @param hexString The hex string which has to be converted
+ */
+- (UIColor *)colorFromHexString:(NSString *)hexString {
+ unsigned rgbValue = 0;
+ NSScanner *scanner = [NSScanner scannerWithString:hexString];
+ [scanner setScanLocation:1]; // bypass '#' character
+ [scanner scanHexInt:&rgbValue];
+ return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0];
+}
+
+@end
diff --git a/plugins/com.plugin.datepicker/src/ios/DatePicker.xib b/plugins/com.plugin.datepicker/src/ios/DatePicker.xib
new file mode 100644
index 0000000..4242b1a
--- /dev/null
+++ b/plugins/com.plugin.datepicker/src/ios/DatePicker.xib
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAC5AAAABAAAABCepkign7sVkKCGKqChmveQ
+y4kaoNIj9HDSYSYQ1v50INiArZDa/tGg28CQENzes6DdqayQ3r6VoN+JjpDgnneg4WlwkOJ+WaDjSVKQ
+5F47oOUpNJDmR1gg5xJREOgnOiDo8jMQ6gccIOrSFRDr5v4g7LH3EO3G4CDukdkQ76/8oPBxuxDxj96g
+8n/BkPNvwKD0X6OQ9U+ioPY/hZD3L4Sg+CiiEPkPZqD6CIQQ+viDIPvoZhD82GUg/chIEP64RyD/qCoQ
+AJgpIAGIDBACeAsgA3EokARhJ6AFUQqQBkEJoAcw7JAHjUOgCRDOkAmtvyAK8LCQC+CvoAzZzRANwJGg
+DrmvEA+priAQmZEQEYmQIBJ5cxATaXIgFFlVEBVJVCAWOTcQFyk2IBgiU5AZCRggGgI1kBryNKAb4heQ
+HNIWoB3B+ZAesfigH6HbkCB2KyAhgb2QIlYNICNq2hAkNe8gJUq8ECYV0SAnKp4QJ/7toCkKgBAp3s+g
+KupiECu+saAs036QLZ6ToC6zYJAvfnWgMJNCkDFnkiAycySQM0d0IDRTBpA1J1YgNjLokDcHOCA4HAUQ
+OOcaIDn75xA6xvwgO9vJEDywGKA9u6sQPo/6oD+bjRBAb9ygQYSpkEJPvqBDZIuQRC+goEVEbZBF89Mg
+Ry2KEEfTtSBJDWwQSbOXIErtThBLnLOgTNZqkE18laBOtkyQT1x3oFCWLpBRPFmgUnYQkFMcO6BUVfKQ
+VPwdoFY11JBW5TogWB7xEFjFHCBZ/tMQWqT+IFvetRBchOAgXb6XEF5kwiBfnnkQYE3eoGGHlZBiLcCg
+Y2d3kGQNoqBlR1mQZe2EoGcnO5BnzWagaQcdkGmtSKBq5v+Qa5ZlIGzQHBBtdkcgbq/+EG9WKSBwj+AQ
+cTYLIHJvwhBzFe0gdE+kEHT/CaB2OMCQdt7roHgYopB4vs2gefiEkHqer6B72GaQfH6RoH24SJB+XnOg
+f5gqkAABAAECAwEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
+AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
+AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
+AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/com.plugin.datepicker/www/android/DatePicker.js b/plugins/com.plugin.datepicker/www/android/DatePicker.js
new file mode 100644
index 0000000..02b84d7
--- /dev/null
+++ b/plugins/com.plugin.datepicker/www/android/DatePicker.js
@@ -0,0 +1,68 @@
+/**
+ * Phonegap DatePicker Plugin Copyright (c) Greg Allen 2011 MIT Licensed
+ * Reused and ported to Android plugin by Daniel van 't Oever
+ */
+
+/**
+ * Constructor
+ */
+function DatePicker() {
+ //this._callback;
+}
+
+/**
+ * show - true to show the ad, false to hide the ad
+ */
+DatePicker.prototype.show = function(options, cb) {
+
+ if (options.date) {
+ options.date = (options.date.getMonth() + 1) + "/" +
+ (options.date.getDate()) + "/" +
+ (options.date.getFullYear()) + "/" +
+ (options.date.getHours()) + "/" +
+ (options.date.getMinutes());
+ }
+
+ var defaults = {
+ mode : 'date',
+ date : '',
+ minDate: 0,
+ maxDate: 0
+ };
+
+ for (var key in defaults) {
+ if (typeof options[key] !== "undefined") {
+ defaults[key] = options[key];
+ }
+ }
+
+ //this._callback = cb;
+
+ var callback = function(message) {
+ var timestamp = Date.parse(message);
+ if(isNaN(timestamp) == false) {
+ cb(new Date(message));
+ }
+ else {
+ cb();
+ }
+ }
+
+ cordova.exec(callback,
+ null,
+ "DatePickerPlugin",
+ defaults.mode,
+ [defaults]
+ );
+};
+
+var datePicker = new DatePicker();
+module.exports = datePicker;
+
+// Make plugin work under window.plugins
+if (!window.plugins) {
+ window.plugins = {};
+}
+if (!window.plugins.datePicker) {
+ window.plugins.datePicker = datePicker;
+}
diff --git a/plugins/com.plugin.datepicker/www/ios/DatePicker.js b/plugins/com.plugin.datepicker/www/ios/DatePicker.js
new file mode 100644
index 0000000..fa21abd
--- /dev/null
+++ b/plugins/com.plugin.datepicker/www/ios/DatePicker.js
@@ -0,0 +1,111 @@
+/**
+ Phonegap DatePicker Plugin
+ https://github.com/sectore/phonegap3-ios-datepicker-plugin
+
+ Copyright (c) Greg Allen 2011
+ Additional refactoring by Sam de Freyssinet
+
+ Rewrite by Jens Krause (www.websector.de)
+
+ MIT Licensed
+*/
+
+var exec = require('cordova/exec');
+/**
+ * Constructor
+ */
+function DatePicker() {
+ this._callback;
+}
+
+/**
+ * show - true to show the ad, false to hide the ad
+ */
+DatePicker.prototype.show = function(options, cb) {
+ var padDate = function(date) {
+ if (date.length == 1) {
+ return ("0" + date);
+ }
+ return date;
+ };
+
+ var formatDate = function(date){
+ date = date.getFullYear()
+ + "-"
+ + padDate(date.getMonth()+1)
+ + "-"
+ + padDate(date.getDate())
+ + "T"
+ + padDate(date.getHours())
+ + ":"
+ + padDate(date.getMinutes())
+ + ":00Z";
+
+ return date
+ }
+
+ if (options.date) {
+ options.date = formatDate(options.date);
+ }
+
+ if (options.minDate) {
+ options.minDate = formatDate(options.minDate);
+ }
+
+ if (options.maxDate) {
+ options.maxDate = formatDate(options.maxDate);
+ }
+
+ var defaults = {
+ mode: 'date',
+ date: new Date(),
+ allowOldDates: true,
+ allowFutureDates: true,
+ minDate: '',
+ maxDate: '',
+ doneButtonLabel: 'Done',
+ doneButtonColor: '#0000FF',
+ cancelButtonLabel: 'Cancel',
+ cancelButtonColor: '#000000',
+ x: '0',
+ y: '0',
+ minuteInterval: 1
+ };
+
+ for (var key in defaults) {
+ if (typeof options[key] !== "undefined")
+ defaults[key] = options[key];
+ }
+ this._callback = cb;
+
+ exec(null,
+ null,
+ "DatePicker",
+ "show",
+ [defaults]
+ );
+};
+
+DatePicker.prototype._dateSelected = function(date) {
+ var d = new Date(parseFloat(date) * 1000);
+ if (this._callback)
+ this._callback(d);
+}
+
+DatePicker.prototype._dateSelectionCanceled = function() {
+ if (this._callback)
+ this._callback();
+}
+
+
+
+var datePicker = new DatePicker();
+module.exports = datePicker;
+
+// Make plugin work under window.plugins
+if (!window.plugins) {
+ window.plugins = {};
+}
+if (!window.plugins.datePicker) {
+ window.plugins.datePicker = datePicker;
+}
diff --git a/plugins/ios.json b/plugins/ios.json
new file mode 100644
index 0000000..9ebe6e4
--- /dev/null
+++ b/plugins/ios.json
@@ -0,0 +1,109 @@
+{
+ "prepare_queue": {
+ "installed": [],
+ "uninstalled": []
+ },
+ "config_munge": {
+ "files": {
+ "config.xml": {
+ "parents": {
+ "/*": [
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ },
+ {
+ "xml": "",
+ "count": 1
+ }
+ ]
+ }
+ },
+ "framework": {
+ "parents": {
+ "ImageIO.framework": [
+ {
+ "xml": "true",
+ "count": 1
+ }
+ ],
+ "CoreLocation.framework": [
+ {
+ "xml": "false",
+ "count": 1
+ }
+ ],
+ "CoreGraphics.framework": [
+ {
+ "xml": "false",
+ "count": 3
+ }
+ ],
+ "AssetsLibrary.framework": [
+ {
+ "xml": "false",
+ "count": 3
+ }
+ ],
+ "MobileCoreServices.framework": [
+ {
+ "xml": "false",
+ "count": 3
+ }
+ ]
+ }
+ },
+ "*-Info.plist": {
+ "parents": {
+ "NSLocationWhenInUseUsageDescription": [
+ {
+ "xml": "",
+ "count": 1
+ }
+ ]
+ }
+ }
+ }
+ },
+ "installed_plugins": {
+ "com.plugin.datepicker": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.camera": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.file": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.file-transfer": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.media": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ },
+ "org.apache.cordova.media-capture": {
+ "PACKAGE_NAME": "com.ionicframework.mementos313309"
+ }
+ },
+ "dependent_plugins": {}
+}
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/.fetch.json b/plugins/org.apache.cordova.camera/.fetch.json
new file mode 100644
index 0000000..aaaf5ea
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/.fetch.json
@@ -0,0 +1 @@
+{"source":{"type":"registry","id":"org.apache.cordova.camera"}}
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/CONTRIBUTING.md b/plugins/org.apache.cordova.camera/CONTRIBUTING.md
new file mode 100644
index 0000000..f7dbcab
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/CONTRIBUTING.md
@@ -0,0 +1,37 @@
+
+
+# Contributing to Apache Cordova
+
+Anyone can contribute to Cordova. And we need your contributions.
+
+There are multiple ways to contribute: report bugs, improve the docs, and
+contribute code.
+
+For instructions on this, start with the
+[contribution overview](http://cordova.apache.org/#contribute).
+
+The details are explained there, but the important items are:
+ - Sign and submit an Apache ICLA (Contributor License Agreement).
+ - Have a Jira issue open that corresponds to your contribution.
+ - Run the tests so your patch doesn't break existing functionality.
+
+We look forward to your contributions!
diff --git a/plugins/org.apache.cordova.camera/LICENSE b/plugins/org.apache.cordova.camera/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/NOTICE b/plugins/org.apache.cordova.camera/NOTICE
new file mode 100644
index 0000000..8ec56a5
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/NOTICE
@@ -0,0 +1,5 @@
+Apache Cordova
+Copyright 2012 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/plugins/org.apache.cordova.camera/README.md b/plugins/org.apache.cordova.camera/README.md
new file mode 100644
index 0000000..f84180e
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/README.md
@@ -0,0 +1,22 @@
+
+
+# org.apache.cordova.camera
+
+Plugin documentation: [doc/index.md](doc/index.md)
diff --git a/plugins/org.apache.cordova.camera/RELEASENOTES.md b/plugins/org.apache.cordova.camera/RELEASENOTES.md
new file mode 100644
index 0000000..077a235
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/RELEASENOTES.md
@@ -0,0 +1,168 @@
+
+# Release Notes
+
+### 0.2.1 (Sept 5, 2013)
+* [CB-4656] Don't add line-breaks to base64-encoded images (Fixes type=DataURI)
+* [CB-4432] copyright notice change
+
+### 0.2.3 (Sept 25, 2013)
+* CB-4889 bumping&resetting version
+* CB-4889 forgot index.html
+* CB-4889 renaming core inside cameraProxy
+* [Windows8] commandProxy has moved
+* [Windows8] commandProxy has moved
+* added Camera API for FirefoxOS
+* Rename CHANGELOG.md -> RELEASENOTES.md
+* [CB-4823] Fix XCode 5 camera plugin warnings
+* Fix compiler warnings
+* [CB-4765] Move ExifHelper.java into Camera Plugin
+* [CB-4764] Remove reference to DirectoryManager from CameraLauncher
+* [CB-4763] Use a copy of FileHelper.java within camera-plugin.
+* [CB-4752] Incremented plugin version on dev branch.
+* CB-4633: We really should close cursors. It's just the right thing to do.
+* No longer causes a stack trace, but it doesn't cause the error to be called.
+* CB-4889 renaming org.apache.cordova.core.camera to org.apache.cordova.camera
+
+ ### 0.2.4 (Oct 28, 2013)
+* CB-5128: added repo + issue tag to plugin.xml for camera plugin
+* CB-4958 - iOS - Camera plugin should not show the status bar
+* [CB-4919] updated plugin.xml for FxOS
+* [CB-4915] Incremented plugin version on dev branch.
+
+### 0.2.5 (Dec 4, 2013)
+* fix camera for firefox os
+* getPicture via web activities
+* [ubuntu] specify policy_group
+* add ubuntu platform
+* 1. User Agent detection now detects AmazonWebView. 2. Change to use amazon-fireos as the platform if user agent string contains 'cordova-amazon-fireos'
+* Added amazon-fireos platform.
+
+### 0.2.6 (Jan 02, 2014)
+* CB-5658 Add doc/index.md for Camera plugin
+* CB-2442 CB-2419 Use Windows.Storage.ApplicationData.current.localFolder, instead of writing to app package.
+* [BlackBerry10] Adding platform level permissions
+* CB-5599 Android: Catch and ignore OutOfMemoryError in getRotatedBitmap()
+
+### 0.2.7 (Feb 05, 2014)
+* CB-4919 firefox os quirks added and supported platforms list is updated
+* getPicture via web activities
+* Documented quirk for CB-5335 + CB-5206 for WP7+8
+* reference the correct firefoxos implementation
+* [BlackBerry10] Add permission to access_shared
+
+### 0.2.8 (Feb 26, 2014)
+* CB-1826 Catch OOM on gallery image resize
+
+### 0.2.9 (Apr 17, 2014)
+* CB-6460: Update license headers
+* CB-6422: [windows8] use cordova/exec/proxy
+* [WP8] When only targetWidth or targetHeight is provided, use it as the only bound
+* CB-4027, CB-5102, CB-2737, CB-2387: [WP] Fix camera issues, cropping, memory leaks
+* CB-6212: [iOS] fix warnings compiled under arm64 64-bit
+* [BlackBerry10] Add rim xml namespaces declaration
+* Add NOTICE file
+
+### 0.3.0 (Jun 05, 2014)
+* CB-2083 documented saveToPhotoAlbum quirk on WP8
+* CB-5895 documented saveToPhotoAlbum quirk on WP8
+* Remove deprecated symbols for iOS < 6
+* documentation translation: cordova-plugin-camera
+* Lisa testing pulling in plugins for plugin: cordova-plugin-camera
+* Lisa testing pulling in plugins for plugin: cordova-plugin-camera
+* Lisa testing pulling in plugins for plugin: cordova-plugin-camera
+* Lisa testing pulling in plugins for plugin: cordova-plugin-camera
+* ubuntu: use application directory for images
+* CB-6795 Add license
+* Little fix in code formatting
+* CB-6613 Use WinJS functionality to get base64-encoded content of image instead of File plugin functionality
+* CB-6612 camera.getPicture now always returns encoded JPEG image
+* Removed invalid note from CB-5398
+* CB-6576 - Returns a specific error message when app has no access to library.
+* CB-6491 add CONTRIBUTING.md
+* CB-6546 android: Fix a couple bugs with allowEdit pull request
+* CB-6546 android: Add support for allowEdit Camera option
+
+### 0.3.1 (Aug 06, 2014)
+* **FFOS** update CameraProxy.js
+* CB-7187 ios: Add explicit dependency on CoreLocation.framework
+* [BlackBerry10] Doc correction - sourceType is supported
+* CB-7071 android: Fix callback firing before CROP intent is sent when allowEdit=true
+* CB-6875 android: Handle exception when SDCard is not mounted
+* ios: Delete postImage (dead code)
+* Prevent NPE on processResiultFromGallery when intent comes null
+* Remove iOS doc reference to non-existing navigator.fileMgr API
+* Docs updated with some default values
+* Removes File plugin dependency from windows8 code.
+* Use WinJS functionality to resize image instead of File plugin functionality
+* CB-6127 Updated translations for docs
+
+### 0.3.2 (Sep 17, 2014)
+* CB-7551 [Camera][iOS 8] Scaled images show a white line
+* CB-7558 hasPendingOperation flag in Camera plugin's takePicture should be reversed to fix memory errors
+* CB-7557 Camera plugin tests is missing a File dependency
+* CB-7423 do cleanup after copyImage manual test
+* CB-7471 cordova-plugin-camera documentation translation: cordova-plugin-camera
+* CB-7413 Resolve 'ms-appdata' URIs with File plugin
+* Fixed minor bugs with the browser
+* CB-7433 Adds missing window reference to prevent manual tests failure on Android and iOS
+* CB-7249 cordova-plugin-camera documentation translation: cordova-plugin-camera
+* CB-4003 Add config option to not use location information in Camera plugin (and default to not use it)
+* CB-7461 Geolocation fails in Camera plugin in iOS 8
+* CB-7378 Use single Proxy for both windows8 and windows.
+* CB-7378 Adds support for windows platform
+* CB-7433 Fixes manual tests failure on windows
+* CB-6958 Get the correct default for "quality" in the test
+* add documentation for manual tests
+* CB-7249 cordova-plugin-camera documentation translation: cordova-plugin-camera
+* CB-4003 Add config option to not use location information in Camera plugin (and default to not use it)
+* CB-7461 Geolocation fails in Camera plugin in iOS 8
+* CB-7433 Fixes manual tests failure on windows
+* CB-7378 Use single Proxy for both windows8 and windows.
+* CB-7378 Adds support for windows platform
+* CB-6958 Get the correct default for "quality" in the test
+* add documentation for manual tests
+* Updated docs for browser
+* Added support for the browser
+* CB-7286 [BlackBerry10] Use getUserMedia if camera card is unavailable
+* CB-7180 Update Camera plugin to support generic plugin webView UIView (which can be either a UIWebView or WKWebView)
+* Renamed test dir, added nested plugin.xml
+* CB-6958 added manual tests
+* CB-6958 Port camera tests to plugin-test-framework
+
+### 0.3.3 (Oct 03, 2014)
+* CB-7600 Adds informative message to error callback in manual test.
+
+### 0.3.4 (Dec 02, 2014)
+* CB-7977 Mention `deviceready` in plugin docs
+* CB-7979 Each plugin doc should have a ## Installation section
+* Fix memory leak of image data in `imagePickerControllerReturnImageResult`
+* Pass uri to crop instead of pulling the low resolution image out of the intent return (close #43)
+* Add orientation support for PNG to Android (closes #45)
+* CB-7700 cordova-plugin-camera documentation translation: cordova-plugin-camera
+
+### 0.3.5 (Feb 04, 2015)
+* CB-8351 ios: Stop using now-deprecated [NSData base64EncodedString]
+* CB-8351 ios: Stop using now-deprecated integerValueForKey: class extension
+* CB-8351 ios: Use argumentForIndex rather than NSArray extension
+* CB-8032 ios: Add nativeURL external method support for CDVFileSystem->makeEntryForPath:isDirectory:
+* CB-7938 ios: Added XCTest unit tests project, with stubs (adapted from SplashScreen unit test setup)
+* CB-7937 ios: Re-factor iOS Camera plugin so that it is testable
diff --git a/plugins/org.apache.cordova.camera/doc/de/index.md b/plugins/org.apache.cordova.camera/doc/de/index.md
new file mode 100644
index 0000000..6607cfd
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/de/index.md
@@ -0,0 +1,409 @@
+
+
+# org.apache.cordova.camera
+
+Dieses Plugin stellt eine API für Aufnahmen und für die Auswahl der Bilder aus dem System-Image-Library.
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+Nimmt ein Foto mit der Kamera, oder ein Foto aus dem Gerät Bildergalerie abgerufen. Das Bild wird an den Erfolg-Rückruf als eine base64-codierte übergeben `String` , oder als den URI für die Image-Datei. Die Methode selbst gibt ein `CameraPopoverHandle` -Objekt, das verwendet werden kann, um die Datei-Auswahl-Popover neu zu positionieren.
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### Beschreibung
+
+Die `camera.getPicture` -Funktion öffnet das Gerät Standard-Kamera-Anwendung, die Benutzern ermöglicht, Bilder ausrichten. Dieses Verhalten tritt standardmäßig, wenn `Camera.sourceType` gleich `Camera.PictureSourceType.CAMERA` . Sobald der Benutzer die Fotoschnäpper, die Kameraanwendung geschlossen wird und die Anwendung wird wiederhergestellt.
+
+Wenn `Camera.sourceType` ist `Camera.PictureSourceType.PHOTOLIBRARY` oder `Camera.PictureSourceType.SAVEDPHOTOALBUM` , dann ein Dialog-Displays, die Benutzern ermöglicht, ein vorhandenes Bild auszuwählen. Die `camera.getPicture` Funktion gibt ein `CameraPopoverHandle` -Objekt, das verwendet werden kann, um den Bild-Auswahl-Dialog, zum Beispiel beim ändert sich der Orientierung des Geräts neu positionieren.
+
+Der Rückgabewert wird gesendet, um die `cameraSuccess` Callback-Funktion in einem der folgenden Formate, je nach dem angegebenen `cameraOptions` :
+
+* A `String` mit dem base64-codierte Foto-Bild.
+
+* A `String` , die die Bild-Datei-Stelle auf lokalem Speicher (Standard).
+
+Sie können tun, was Sie wollen, mit dem codierten Bildes oder URI, zum Beispiel:
+
+* Rendern Sie das Bild in ein `` Tag, wie im folgenden Beispiel
+
+* Die Daten lokal zu speichern ( `LocalStorage` , [Lawnchair][1], etc..)
+
+* Post die Daten an einen entfernten server
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**Hinweis**: Fotoauflösung auf neueren Geräten ist ganz gut. Fotos aus dem Gerät Galerie ausgewählt sind nicht zu einer niedrigeren Qualität herunterskaliert auch wenn ein `quality` -Parameter angegeben wird. Um Speicherprobleme zu vermeiden, legen Sie `Camera.destinationType` auf `FILE_URI` statt`DATA_URL`.
+
+### Unterstützte Plattformen
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Browser
+* Firefox OS
+* iOS
+* Tizen
+* Windows Phone 7 und 8
+* Windows 8
+
+### "Einstellungen" (iOS)
+
+* **CameraUsesGeolocation** (Boolean, Standardwert ist False). Zur Erfassung von JPEGs, auf true festgelegt, um Geolocation-Daten im EXIF-Header zu erhalten. Dies löst einen Antrag auf Geolocation-Berechtigungen, wenn auf True festgelegt.
+
+
+
+
+### Amazon Fire OS Macken
+
+Amazon Fire OS verwendet Absichten zum Starten von der Kamera-Aktivität auf dem Gerät, um Bilder zu erfassen und auf Handys mit wenig Speicher, Cordova Tätigkeit getötet werden kann. In diesem Szenario kann das Bild nicht angezeigt, wenn die Aktivität von Cordova wiederhergestellt wird.
+
+### Android Eigenarten
+
+Android verwendet Absichten zum Starten von der Kamera-Aktivität auf dem Gerät, um Bilder zu erfassen und auf Handys mit wenig Speicher, Cordova Tätigkeit getötet werden kann. In diesem Szenario kann das Bild nicht angezeigt, wenn die Aktivität von Cordova wiederhergestellt wird.
+
+### Browser-Eigenheiten
+
+Fotos können nur als base64-codierte Bild zurückgeben werden.
+
+### Firefox OS Macken
+
+Kamera-Plugin ist derzeit implementiert mithilfe von [Web-Aktivitäten][2].
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### iOS Macken
+
+Darunter eine JavaScript `alert()` entweder des Rückrufs Funktionen können Probleme verursachen. Wickeln Sie die Warnung innerhalb einer `setTimeout()` erlauben die iOS-Bild-Picker oder Popover vollständig zu schließen, bevor die Warnung angezeigt:
+
+ setTimeout(function() {/ / Mach dein Ding hier!}, 0);
+
+
+### Windows Phone 7 Macken
+
+Die native Kameraanwendung aufrufen, während das Gerät via Zune angeschlossen ist funktioniert nicht und löst eine Fehler-Callback.
+
+### Tizen Macken
+
+Tizen unterstützt nur eine `destinationType` der `Camera.DestinationType.FILE_URI` und eine `sourceType` von`Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### Beispiel
+
+Nehmen Sie ein Foto und rufen Sie sie als base64-codierte Bild:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+Nehmen Sie ein Foto und rufen Sie das Bild-Datei-Speicherort:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+Optionale Parameter die Kameraeinstellungen anpassen.
+
+ {Qualität: 75, DestinationType: Camera.DestinationType.DATA_URL, SourceType: Camera.PictureSourceType.CAMERA, AllowEdit: stimmt, EncodingType: Camera.EncodingType.JPEG, TargetWidth: 100, TargetHeight: 100, PopoverOptions: CameraPopoverOptions, SaveToPhotoAlbum: false};
+
+
+### Optionen
+
+* **Qualität**: Qualität des gespeicherten Bildes, ausgedrückt als ein Bereich von 0-100, wo 100 in der Regel voller Auflösung ohne Verlust aus der Dateikomprimierung ist. Der Standardwert ist 50. *(Anzahl)* (Beachten Sie, dass Informationen über die Kamera Auflösung nicht verfügbar ist.)
+
+* **DestinationType**: Wählen Sie das Format des Rückgabewerts. Der Standardwert ist FILE_URI. Im Sinne `navigator.camera.DestinationType` *(Anzahl)*
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+
+* **SourceType**: Legen Sie die Quelle des Bildes. Der Standardwert ist die Kamera. Im Sinne `navigator.camera.PictureSourceType` *(Anzahl)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+
+* **AllowEdit**: einfache Bearbeitung des Bildes vor Auswahl zu ermöglichen. *(Boolesch)*
+
+* **EncodingType**: die zurückgegebene Image-Datei ist Codierung auswählen. Standardwert ist JPEG. Im Sinne `navigator.camera.EncodingType` *(Anzahl)*
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+
+* **TargetWidth**: Breite in Pixel zum Bild skalieren. Muss mit **TargetHeight**verwendet werden. Seitenverhältnis bleibt konstant. *(Anzahl)*
+
+* **TargetHeight**: Höhe in Pixel zum Bild skalieren. Muss mit **TargetWidth**verwendet werden. Seitenverhältnis bleibt konstant. *(Anzahl)*
+
+* **MediaType**: Legen Sie den Typ der Medien zur Auswahl. Funktioniert nur, wenn `PictureSourceType` ist `PHOTOLIBRARY` oder `SAVEDPHOTOALBUM` . Im Sinne `nagivator.camera.MediaType` *(Anzahl)*
+
+ Camera.MediaType = {
+ PICTURE: 0, // allow selection of still pictures only. STANDARD. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **CorrectOrientation**: Drehen Sie das Bild um die Ausrichtung des Geräts während der Aufnahme zu korrigieren. *(Boolesch)*
+
+* **SaveToPhotoAlbum**: das Bild auf das Fotoalbum auf dem Gerät zu speichern, nach Einnahme. *(Boolesch)*
+
+* **PopoverOptions**: iOS-nur Optionen, die Popover Lage in iPad angeben. In definierten`CameraPopoverOptions`.
+
+* **CameraDirection**: Wählen Sie die Kamera (vorn oder hinten-gerichtete) verwenden. Der Standardwert ist zurück. Im Sinne `navigator.camera.Direction` *(Anzahl)*
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+
+### Amazon Fire OS Macken
+
+* `cameraDirection`Ergebnisse in einem hinten gerichteter Foto Wert.
+
+* Ignoriert die `allowEdit` Parameter.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`und `Camera.PictureSourceType.SAVEDPHOTOALBUM` beide das gleiche Fotoalbum anzuzeigen.
+
+### Android Eigenarten
+
+* `cameraDirection`Ergebnisse in einem hinten gerichteter Foto Wert.
+
+* Ignoriert die `allowEdit` Parameter.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`und `Camera.PictureSourceType.SAVEDPHOTOALBUM` beide das gleiche Fotoalbum anzuzeigen.
+
+### BlackBerry 10 Macken
+
+* Ignoriert die `quality` Parameter.
+
+* Ignoriert die `allowEdit` Parameter.
+
+* `Camera.MediaType`wird nicht unterstützt.
+
+* Ignoriert die `correctOrientation` Parameter.
+
+* Ignoriert die `cameraDirection` Parameter.
+
+### Firefox OS Macken
+
+* Ignoriert die `quality` Parameter.
+
+* `Camera.DestinationType`wird ignoriert, und gleich `1` (Bilddatei-URI)
+
+* Ignoriert die `allowEdit` Parameter.
+
+* Ignoriert die `PictureSourceType` Parameter (Benutzer wählt es in einem Dialogfenster)
+
+* Ignoriert die`encodingType`
+
+* Ignoriert die `targetWidth` und`targetHeight`
+
+* `Camera.MediaType`wird nicht unterstützt.
+
+* Ignoriert die `correctOrientation` Parameter.
+
+* Ignoriert die `cameraDirection` Parameter.
+
+### iOS Macken
+
+* Legen Sie `quality` unter 50 Speicherfehler auf einigen Geräten zu vermeiden.
+
+* Bei der Verwendung `destinationType.FILE_URI` , Fotos werden im temporären Verzeichnis der Anwendung gespeichert. Den Inhalt des temporären Verzeichnis der Anwendung wird gelöscht, wenn die Anwendung beendet.
+
+### Tizen Macken
+
+* nicht unterstützte Optionen
+
+* gibt immer einen Datei-URI
+
+### Windows Phone 7 und 8 Eigenarten
+
+* Ignoriert die `allowEdit` Parameter.
+
+* Ignoriert die `correctOrientation` Parameter.
+
+* Ignoriert die `cameraDirection` Parameter.
+
+* Ignoriert die `saveToPhotoAlbum` Parameter. WICHTIG: Alle Aufnahmen die wp7/8 Cordova-Kamera-API werden immer in Kamerarolle des Telefons kopiert. Abhängig von den Einstellungen des Benutzers könnte dies auch bedeuten, dass das Bild in ihre OneDrive automatisch hochgeladen ist. Dies könnte möglicherweise bedeuten, dass das Bild für ein breiteres Publikum als Ihre Anwendung vorgesehen ist. Wenn diese einen Blocker für Ihre Anwendung, Sie müssen die CameraCaptureTask zu implementieren, wie im Msdn dokumentiert: Sie können kommentieren oder Up-Abstimmung das Beiträge zu diesem Thema im [Bugtracker][3]
+
+* Ignoriert die `mediaType` -Eigenschaft des `cameraOptions` wie das Windows Phone SDK keine Möglichkeit, Fotothek Videos wählen.
+
+ [3]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+OnError-Callback-Funktion, die eine Fehlermeldung bereitstellt.
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### Parameter
+
+* **Meldung**: die Nachricht wird durch das Gerät systemeigenen Code bereitgestellt. *(String)*
+
+## cameraSuccess
+
+OnSuccess Callback-Funktion, die die Bilddaten bereitstellt.
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### Parameter
+
+* **CMYK**: Base64-Codierung der Bilddaten, *oder* die Image-Datei-URI, je nach `cameraOptions` in Kraft. *(String)*
+
+### Beispiel
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+Ein Handle für das Dialogfeld "Popover" erstellt von`navigator.camera.getPicture`.
+
+### Methoden
+
+* **SetPosition**: Legen Sie die Position der Popover.
+
+### Unterstützte Plattformen
+
+* iOS
+
+### setPosition
+
+Legen Sie die Position von der Popover.
+
+**Parameter**:
+
+* `cameraPopoverOptions`: die `CameraPopoverOptions` angeben, dass die neue Position
+
+### Beispiel
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+nur iOS-Parametern, die Anker-Element Lage und Pfeil Richtung der Popover angeben, bei der Auswahl von Bildern aus einem iPad Bibliothek oder Album.
+
+ {X: 0, y: 32, Breite: 320, Höhe: 480, ArrowDir: Camera.PopoverArrowDirection.ARROW_ANY};
+
+
+### CameraPopoverOptions
+
+* **X**: x Pixelkoordinate des Bildschirmelement auf dem der Popover zu verankern. *(Anzahl)*
+
+* **y**: y Pixelkoordinate des Bildschirmelement auf dem der Popover zu verankern. *(Anzahl)*
+
+* **Breite**: Breite in Pixeln, das Bildschirmelement auf dem der Popover zu verankern. *(Anzahl)*
+
+* **Höhe**: Höhe in Pixeln, das Bildschirmelement auf dem der Popover zu verankern. *(Anzahl)*
+
+* **ArrowDir**: Richtung der Pfeil auf der Popover zeigen sollte. Im Sinne `Camera.PopoverArrowDirection` *(Anzahl)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+Beachten Sie, dass die Größe der Popover ändern kann, um die Richtung des Pfeils und Ausrichtung des Bildschirms anzupassen. Achten Sie darauf, um Orientierung zu berücksichtigen, wenn Sie den Anker-Element-Speicherort angeben.
+
+## Navigator.Camera.Cleanup
+
+Entfernt Mittelstufe Fotos von der Kamera aus der vorübergehenden Verwahrung genommen.
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### Beschreibung
+
+Entfernt Mittelstufe Image-Dateien, die nach der Berufung in vorübergehender Verwahrung gehalten werden `camera.getPicture` . Gilt nur, wenn der Wert des `Camera.sourceType` ist gleich `Camera.PictureSourceType.CAMERA` und der `Camera.destinationType` entspricht`Camera.DestinationType.FILE_URI`.
+
+### Unterstützte Plattformen
+
+* iOS
+
+### Beispiel
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/doc/es/index.md b/plugins/org.apache.cordova.camera/doc/es/index.md
new file mode 100644
index 0000000..9611441
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/es/index.md
@@ -0,0 +1,424 @@
+
+
+# org.apache.cordova.camera
+
+Este plugin proporciona una API para tomar fotografías y por elegir imágenes de biblioteca de imágenes del sistema.
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+Toma una foto con la cámara, u obtiene una foto de la galería de imágenes del dispositivo. La imagen es retornada como un objeto `String` codificada en base64 o como la URI de esta. El método devuelve un objeto `CameraPopoverHandle` que puede usarse para reposicionar el diálogo de selección de archivo.
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### Descripción
+
+La función `camera.getPicture` abre la aplicación predeterminada de cámara del dispositivo que permite a los usuarios tomar fotografías. Este comportamiento es el predeterminado, cuando `Camera.sourceType` es igual a `Camera.PictureSourceType.CAMERA`. Una vez que el usuario toma la foto, la aplicación de la cámara se cierra y se restablece la aplicación.
+
+Si `Camera.sourceType` es `Camera.PictureSourceType.PHOTOLIBRARY` o `Camera.PictureSourceType.SAVEDPHOTOALBUM`, entonces aperece un cuadro de diálogo que permite a los usuarios seleccionar una imagen existente. La función `camera.getPicture` devuelve un objeto `CameraPopoverHandle`, que puede utilizarse para reposicionar el diálogo de selección de imagen, por ejemplo, cuando cambia la orientación del dispositivo.
+
+El valor devuelto es enviado a la función `cameraSuccess`, en uno de los formatos siguientes, dependiendo de `cameraOptions` especificadas:
+
+* Una `String` que contiene la imagen codificada en base64.
+
+* Una `String` que representa la ubicación del archivo de imagen en almacenamiento local (por defecto).
+
+Puedes hacer lo que quieras con la imagen codificada o URI, por ejemplo:
+
+* Representar la imagen en una etiqueta de ``, como en el ejemplo siguiente
+
+* Guardar los datos localmente (`LocalStorage`, [Lawnchair][1], etc.)
+
+* Enviar los datos a un servidor remoto
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**Nota**: resolución de la foto en los nuevos dispositivos es bastante bueno. Fotos seleccionadas de la Galería del dispositivo no son degradadas a una calidad más baja, incluso si se especifica un parámetro de `quality`. Para evitar problemas comunes de memoria, establezca `Camera.destinationType` como `FILE_URI` en lugar de `DATA_URL`.
+
+### Plataformas soportadas
+
+* Amazon fire OS
+* Android
+* BlackBerry 10
+* Explorador
+* Firefox OS
+* iOS
+* Tizen
+* Windows Phone 7 y 8
+* Windows 8
+
+### Preferencias (iOS)
+
+* **CameraUsesGeolocation** (booleano, el valor predeterminado de false). Para la captura de imágenes JPEG, establecido en true para obtener datos de geolocalización en la cabecera EXIF. Esto activará la solicitud de permisos de geolocalización si establecido en true.
+
+
+
+
+### Amazon fuego OS rarezas
+
+Amazon fuego OS utiliza los intentos para poner en marcha la actividad de la cámara del dispositivo para capturar imágenes y en teléfonos con poca memoria, puede matar la actividad Cordova. En este escenario, la imagen puede que no aparezca cuando se restaura la actividad de cordova.
+
+### Rarezas Android
+
+Android utiliza los intents para iniciar la actividad de la cámara del dispositivo para capturar imágenes y en teléfonos con poca memoria, la actividad de Cordova puede ser terminada. En este escenario, la imagen no aparezca cuando se restaura la actividad Cordova.
+
+### Navegador rarezas
+
+Sólo puede devolver fotos como imagen codificada en base64.
+
+### Firefox OS rarezas
+
+Cámara plugin actualmente se implementa mediante [Actividades Web][2].
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### iOS rarezas
+
+Incluyendo un JavaScript `alert()` en cualquiera de las funciones de devolución de llamada puede causar problemas. Envolver la alerta dentro un `setTimeout()` para permitir el iOS image picker o popover cerrar completamente antes de Mostrar la alerta:
+
+ setTimeout(function() {
+ // do your thing here!
+ }, 0);
+
+
+### Windows Phone 7 rarezas
+
+Invocando la aplicación de cámara nativa mientras el dispositivo está conectado vía Zune no funciona y desencadena un callback de error.
+
+### Rarezas Tizen
+
+Tizen sólo admite un `destinationType` de `Camera.DestinationType.FILE_URI` y un `sourceType` de `Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### Ejemplo
+
+Tomar una foto y recuperarlo como una imagen codificada en base64:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+Tomar una foto y recuperar la ubicación del archivo de la imagen:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+Parámetros opcionales para personalizar la configuración de la cámara.
+
+ { quality : 75,
+ destinationType : Camera.DestinationType.DATA_URL,
+ sourceType : Camera.PictureSourceType.CAMERA,
+ allowEdit : true,
+ encodingType: Camera.EncodingType.JPEG,
+ targetWidth: 100,
+ targetHeight: 100,
+ popoverOptions: CameraPopoverOptions,
+ saveToPhotoAlbum: false };
+
+
+### Opciones
+
+* **calidad**: calidad de la imagen guardada, expresada en un rango de 0-100, donde 100 es típicamente resolución sin pérdida de compresión del archivo. El valor predeterminado es 50. *(Número)* (Tenga en cuenta que no está disponible información sobre resolución de la cámara).
+
+* **destinationType**: elegir el formato del valor devuelto. El valor predeterminado es FILE_URI. Definido en `navigator.camera.DestinationType` *(número)*
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+
+* **sourceType**: establecer el origen de la imagen. El valor predeterminado es cámara. Definido en `navigator.camera.PictureSourceType` *(número)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+
+* **allowEdit**: permite edición sencilla de imagen antes de la selección. *(Booleano)*
+
+* **encodingType**: elegir la codificación del archivo de imagen devuelta. Por defecto es JPEG. Definido en `navigator.camera.EncodingType` *(número)*
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+
+* **targetWidth**: ancho en píxeles a escala de la imagen. Debe usarse con **targetHeight**. Proporción se mantiene constante. *(Número)*
+
+* **targetHeight**: altura en píxeles a escala de la imagen. Debe usarse con **targetWidth**. Proporción se mantiene constante. *(Número)*
+
+* **mediaType**: definir el tipo de medios para seleccionar. Sólo funciona cuando `PictureSourceType` es `PHOTOLIBRARY` o `SAVEDPHOTOALBUM` . Definido en `nagivator.camera.MediaType` *(número)*
+
+ Camera.MediaType = {
+ PICTURE: 0, // allow selection of still pictures only. DE FORMA PREDETERMINADA. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **correctOrientation**: rotar la imagen para corregir la orientación del dispositivo durante la captura. *(Booleano)*
+
+* **saveToPhotoAlbum**: guardar la imagen en el álbum de fotos en el dispositivo después de su captura. *(Booleano)*
+
+* **popoverOptions**: opciones sólo iOS que especifican popover ubicación en iPad. Definido en`CameraPopoverOptions`.
+
+* **cameraDirection**: elegir la cámara para usar (o parte posterior-frontal). El valor predeterminado es atrás. Definido en `navigator.camera.Direction` *(número)*
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+
+### Amazon fuego OS rarezas
+
+* Cualquier valor de `cameraDirection` da como resultado una foto orientada hacia atrás.
+
+* Ignora el `allowEdit` parámetro.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY` y `Camera.PictureSourceType.SAVEDPHOTOALBUM` Mostrar el mismo álbum de fotos.
+
+### Rarezas Android
+
+* Cualquier valor de `cameraDirection` da como resultado una foto orientada hacia atrás.
+
+* Ignora el `allowEdit` parámetro.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY` y `Camera.PictureSourceType.SAVEDPHOTOALBUM` Mostrar el mismo álbum de fotos.
+
+### BlackBerry 10 rarezas
+
+* Ignora el `quality` parámetro.
+
+* Ignora el `allowEdit` parámetro.
+
+* `Camera.MediaType`No se admite.
+
+* Ignora el `correctOrientation` parámetro.
+
+* Ignora el `cameraDirection` parámetro.
+
+### Firefox OS rarezas
+
+* Ignora el `quality` parámetro.
+
+* `Camera.DestinationType`se ignora y es igual a `1` (URI del archivo de imagen)
+
+* Ignora el `allowEdit` parámetro.
+
+* Ignora el `PictureSourceType` parámetro (el usuario lo elige en una ventana de diálogo)
+
+* Ignora el`encodingType`
+
+* Ignora el `targetWidth` y`targetHeight`
+
+* `Camera.MediaType`No se admite.
+
+* Ignora el `correctOrientation` parámetro.
+
+* Ignora el `cameraDirection` parámetro.
+
+### iOS rarezas
+
+* Establecer `quality` por debajo de 50 para evitar errores de memoria en algunos dispositivos.
+
+* Cuando se utiliza `destinationType.FILE_URI` , fotos se guardan en el directorio temporal de la aplicación. El contenido del directorio temporal de la aplicación se eliminará cuando finalice la aplicación.
+
+### Rarezas Tizen
+
+* opciones no compatibles
+
+* siempre devuelve un identificador URI de archivo
+
+### Windows Phone 7 y 8 rarezas
+
+* Ignora el `allowEdit` parámetro.
+
+* Ignora el `correctOrientation` parámetro.
+
+* Ignora el `cameraDirection` parámetro.
+
+* Ignora el `saveToPhotoAlbum` parámetro. IMPORTANTE: Todas las imágenes tomadas con la cámara wp7/8 cordova API siempre se copian en rollo de cámara del teléfono. Dependiendo de la configuración del usuario, esto podría significar también que la imagen es auto-subido a su OneDrive. Esto potencialmente podría significar que la imagen está disponible a una audiencia más amplia que su aplicación previsto. Si un bloqueador para su aplicación, usted necesitará aplicar el CameraCaptureTask como se documenta en msdn: también puede comentar o votar hasta el tema relacionado en el [issue tracker de][3]
+
+* Ignora el `mediaType` propiedad de `cameraOptions` como el SDK de Windows Phone no proporciona una manera para elegir vídeos fototeca.
+
+ [3]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+onError función callback que proporciona un mensaje de error.
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### Parámetros
+
+* **mensaje**: el mensaje es proporcionado por código nativo del dispositivo. *(String)*
+
+## cameraSuccess
+
+onSuccess función callback que proporciona los datos de imagen.
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### Parámetros
+
+* **imageData**: codificación en Base64 de los datos de imagen, *o* el archivo de imagen URI, dependiendo de `cameraOptions` en vigor. *(String)*
+
+### Ejemplo
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+Un identificador para el cuadro de diálogo popover creado por`navigator.camera.getPicture`.
+
+### Métodos
+
+* **setPosition**: establecer la posición de la popover.
+
+### Plataformas soportadas
+
+* iOS
+
+### setPosition
+
+Establecer la posición de la popover.
+
+**Parámetros**:
+
+* `cameraPopoverOptions`: el `CameraPopoverOptions` que especifican la nueva posición
+
+### Ejemplo
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+Sólo iOS parámetros que especifican la dirección ancla elemento ubicación y la flecha de la popover al seleccionar imágenes de biblioteca o álbum de un iPad.
+
+ { x : 0,
+ y : 32,
+ width : 320,
+ height : 480,
+ arrowDir : Camera.PopoverArrowDirection.ARROW_ANY
+ };
+
+
+### CameraPopoverOptions
+
+* **x**: coordenadas de píxeles del elemento de la pantalla en la que anclar el popover x. *(Número)*
+
+* **y**: coordenada píxeles del elemento de la pantalla en la que anclar el popover. *(Número)*
+
+* **anchura**: anchura, en píxeles, del elemento sobre el que anclar el popover pantalla. *(Número)*
+
+* **altura**: alto, en píxeles, del elemento sobre el que anclar el popover pantalla. *(Número)*
+
+* **arrowDir**: dirección de la flecha en el popover debe apuntar. Definido en `Camera.PopoverArrowDirection` *(número)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+Tenga en cuenta que puede cambiar el tamaño de la popover para ajustar la dirección de la flecha y orientación de la pantalla. Asegúrese de que para tener en cuenta los cambios de orientación cuando se especifica la ubicación del elemento de anclaje.
+
+## Navigator.Camera.Cleanup
+
+Elimina intermedio fotos tomadas por la cámara de almacenamiento temporal.
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### Descripción
+
+Elimina los archivos de imagen intermedia que se mantienen en depósito temporal después de llamar a `camera.getPicture`. Se aplica sólo cuando el valor de `Camera.sourceType` es igual a `Camera.PictureSourceType.CAMERA` y el `Camera.destinationType` es igual a `Camera.DestinationType.FILE_URI`.
+
+### Plataformas soportadas
+
+* iOS
+
+### Ejemplo
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/doc/fr/index.md b/plugins/org.apache.cordova.camera/doc/fr/index.md
new file mode 100644
index 0000000..cba31d9
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/fr/index.md
@@ -0,0 +1,417 @@
+
+
+# org.apache.cordova.camera
+
+Ce plugin fournit une API pour la prise de photos et de choisir des images de la bibliothèque d'images du système.
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+Prend une photo à l'aide de la caméra, ou récupère une photo de la Galerie d'images de l'appareil. L'image est passée au callback "succès" comme une `String` encodée en base64 ou l'URI du fichier de l'image. La méthode elle-même renvoie un objet `CameraPopoverHandle` qui permet de repositionner la boite de dialogue de selection d'image.
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### Description
+
+La fonction `camera.getPicture` ouvre l'application par défaut de l'appareil qui permet aux utilisateurs de prendre des photos. Ce comportement se produit par défaut, lorsque `Camera.sourceType` est égal à `Camera.PictureSourceType.CAMERA`. Une fois que l'utilisateur a pris la photo, l'application "camera" se ferme et l'application est restaurée.
+
+Si `Camera.sourceType` est `Camera.PictureSourceType.PHOTOLIBRARY` ou `Camera.PictureSourceType.SAVEDPHOTOALBUM`, alors une boîte de dialogue s'affiche pour permettre aux utilisateurs de sélectionner une image existante. La fonction `camera.getPicture` retourne un objet `CameraPopoverHandle` qui permet de repositionner le dialogue de sélection d'image, par exemple, lorsque l'orientation de l'appareil change.
+
+La valeur de retour est envoyée à la fonction callback `cameraSuccess`, dans l'un des formats suivants, selon spécifié par `cameraOptions` :
+
+* A `String` contenant l'image photo codée en base64.
+
+* A `String` qui représente l'emplacement du fichier image sur le stockage local (par défaut).
+
+Vous pouvez faire ce que vous voulez avec l'image encodée ou l'URI, par exemple :
+
+* Afficher l'image dans un `` tag, comme dans l'exemple ci-dessous
+
+* Enregistrer les données localement ( `LocalStorage` , [poids][1], etc..)
+
+* Publier les données sur un serveur distant
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**NOTE**: la résolution de Photo sur les nouveaux appareils est assez bonne. Les photos sélectionnées de la Galerie de l'appareil ne sont pas réduites avec une baisse de la qualité, même si un paramètre de `qualité` est spécifié. Pour éviter les problèmes de mémoire, définissez `Camera.destinationType` à `FILE_URI` plutôt que `DATA_URL`.
+
+### Plates-formes prises en charge
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Navigateur
+* Firefox OS
+* iOS
+* Paciarelli
+* Windows Phone 7 et 8
+* Windows 8
+
+### Préférences (iOS)
+
+* **CameraUsesGeolocation** (boolean, par défaut, false). Pour capturer des images JPEG, true pour obtenir des données de géolocalisation dans l'en-tête EXIF. Cela va déclencher une demande d'autorisations de géolocalisation si défini à true.
+
+
+
+
+### Amazon Fire OS Quirks
+
+Amazon Fire OS utilise des intentions pour lancer l'activité de l'appareil photo sur l'appareil pour capturer des images et sur les téléphones avec peu de mémoire, l'activité de Cordova peut être tuée. Dans ce scénario, l'image peut ne pas apparaître lorsque l'activité de cordova est restaurée.
+
+### Quirks Android
+
+Android utilise des intentions pour lancer l'activité de l'appareil photo sur l'appareil pour capturer des images et sur les téléphones avec peu de mémoire, l'activité de Cordova peut être tuée. Dans ce scénario, l'image peut ne pas apparaître lorsque l'activité de Cordova est restaurée.
+
+### Bizarreries navigateur
+
+Peut retourner uniquement les photos comme image codée en base64.
+
+### Firefox OS Quirks
+
+Appareil photo plugin est actuellement mis en œuvre à l'aide [d'Activités sur le Web][2].
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### iOS Quirks
+
+Y compris un JavaScript `alert()` dans les deux le rappel fonctions peuvent causer des problèmes. Envelopper l'alerte dans un `setTimeout()` pour permettre le sélecteur d'image iOS ou kangourou pour fermer entièrement avant que l'alerte s'affiche :
+
+ setTimeout(function() {/ / votre code ici!}, 0) ;
+
+
+### Windows Phone 7 Quirks
+
+Invoquant l'application native caméra alors que l'appareil est connecté via Zune ne fonctionne pas et déclenche un rappel de l'erreur.
+
+### Bizarreries de paciarelli
+
+Paciarelli prend uniquement en charge un `destinationType` de `Camera.DestinationType.FILE_URI` et un `sourceType` de`Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### Exemple
+
+Prendre une photo, puis extrayez-la comme une image codée en base64 :
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+Prendre une photo et récupérer l'emplacement du fichier de l'image :
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+Paramètres optionnels pour personnaliser les réglages de l'appareil.
+
+ { quality : 75,
+ destinationType : Camera.DestinationType.DATA_URL,
+ sourceType : Camera.PictureSourceType.CAMERA,
+ allowEdit : true,
+ encodingType: Camera.EncodingType.JPEG,
+ targetWidth: 100,
+ targetHeight: 100,
+ popoverOptions: CameraPopoverOptions,
+ saveToPhotoAlbum: false };
+
+
+### Options
+
+* **qualité**: qualité de l'image enregistrée, exprimée en une gamme de 0 à 100, 100 étant généralement pleine résolution sans perte de compression de fichiers. La valeur par défaut est 50. *(Nombre)* (Notez que les informations sur la résolution de la caméra sont indisponibles).
+
+* **destinationType**: choisissez le format de la valeur de retour. La valeur par défaut est FILE_URI. Définies dans `navigator.camera.DestinationType` *(nombre)*
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+
+* **sourceType**: définissez la source de l'image. La valeur par défaut est la caméra. Définies dans `navigator.camera.PictureSourceType` *(nombre)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+
+* **allowEdit**: permettre un montage simple d'image avant la sélection. *(Booléen)*
+
+* **encodingType**: choisir le fichier image retournée de codage. Valeur par défaut est JPEG. Définies dans `navigator.camera.EncodingType` *(nombre)*
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+
+* **targetWidth**: largeur en pixels de l'image de l'échelle. Doit être utilisé avec **targetHeight**. Aspect ratio reste constant. *(Nombre)*
+
+* **targetHeight**: hauteur en pixels de l'image de l'échelle. Doit être utilisé avec **targetWidth**. Aspect ratio reste constant. *(Nombre)*
+
+* **mediaType**: définir le type de média pour choisir de. Ne fonctionne que quand `PictureSourceType` est `PHOTOLIBRARY` ou `SAVEDPHOTOALBUM` . Définies dans `nagivator.camera.MediaType` *(nombre)*
+
+ Camera.MediaType = {
+ PICTURE: 0, // allow selection of still pictures only. PAR DÉFAUT. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **correctOrientation**: faire pivoter l'image afin de corriger l'orientation de l'appareil lors de la capture. *(Booléen)*
+
+* **saveToPhotoAlbum**: enregistrer l'image sur l'album photo sur l'appareil après la capture. *(Booléen)*
+
+* **popoverOptions**: iOS uniquement des options qui spécifient l'emplacement de kangourou dans iPad. Défini dans`CameraPopoverOptions`.
+
+* **cameraDirection**: choisissez la caméra à utiliser (ou dos-face). La valeur par défaut est de retour. Définies dans `navigator.camera.Direction` *(nombre)*
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+
+### Amazon Fire OS Quirks
+
+* Tout `cameraDirection` résultats dans le back-face photo de valeur.
+
+* Ignore la `allowEdit` paramètre.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`et `Camera.PictureSourceType.SAVEDPHOTOALBUM` les deux affichent le même album photo.
+
+### Quirks Android
+
+* Tout `cameraDirection` résultats dans le back-face photo de valeur.
+
+* Ignore la `allowEdit` paramètre.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`et `Camera.PictureSourceType.SAVEDPHOTOALBUM` les deux affichent le même album photo.
+
+### BlackBerry 10 Quirks
+
+* Ignore la `quality` paramètre.
+
+* Ignore la `allowEdit` paramètre.
+
+* `Camera.MediaType`n'est pas pris en charge.
+
+* Ignore la `correctOrientation` paramètre.
+
+* Ignore la `cameraDirection` paramètre.
+
+### Firefox OS Quirks
+
+* Ignore la `quality` paramètre.
+
+* `Camera.DestinationType`est ignorée et est égal à `1` (URI du fichier image)
+
+* Ignore la `allowEdit` paramètre.
+
+* Ignore la `PictureSourceType` paramètre (utilisateur il choisit dans une fenêtre de dialogue)
+
+* Ignore le`encodingType`
+
+* Ignore la `targetWidth` et`targetHeight`
+
+* `Camera.MediaType`n'est pas pris en charge.
+
+* Ignore la `correctOrientation` paramètre.
+
+* Ignore la `cameraDirection` paramètre.
+
+### iOS Quirks
+
+* La valeur `quality` inférieur à 50 pour éviter les erreurs de mémoire sur certains appareils.
+
+* Lorsque vous utilisez `destinationType.FILE_URI` , les photos sont sauvegardées dans le répertoire temporaire de l'application. Le contenu du répertoire temporaire de l'application est supprimé lorsque l'application se termine.
+
+### Bizarreries de paciarelli
+
+* options non prises en charge
+
+* retourne toujours un URI de fichier
+
+### Windows Phone 7 et 8 Quirks
+
+* Ignore la `allowEdit` paramètre.
+
+* Ignore la `correctOrientation` paramètre.
+
+* Ignore la `cameraDirection` paramètre.
+
+* Ignore la `saveToPhotoAlbum` paramètre. IMPORTANT : Toutes les images prises avec la caméra de cordova wp7/8 API sont toujours copiés au rôle d'appareil photo du téléphone. Selon les paramètres de l'utilisateur, cela pourrait également signifier que l'image est auto-téléchargées à leur OneDrive. Potentiellement, cela pourrait signifier que l'image est disponible à un public plus large que votre application destinée. Si ce un bloqueur pour votre application, vous devrez implémenter le CameraCaptureTask tel que documenté sur msdn : vous pouvez aussi commenter ou haut-vote la question connexe dans le [gestionnaire d'incidents][3]
+
+* Ignore la `mediaType` propriété de `cameraOptions` comme le kit de développement Windows Phone ne fournit pas un moyen de choisir les vidéos de PHOTOLIBRARY.
+
+ [3]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+fonction de rappel onError qui fournit un message d'erreur.
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### Paramètres
+
+* **message**: le message est fourni par du code natif de l'appareil. *(String)*
+
+## cameraSuccess
+
+fonction de rappel onSuccess qui fournit les données d'image.
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### Paramètres
+
+* **imageData**: codage Base64 de l'image, *ou* le fichier image URI, selon `cameraOptions` en vigueur. *(String)*
+
+### Exemple
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+Un handle vers la boîte de dialogue de kangourou créé par`navigator.camera.getPicture`.
+
+### Méthodes
+
+* **setPosition**: définir la position de la kangourou.
+
+### Plates-formes prises en charge
+
+* iOS
+
+### setPosition
+
+Définir la position de la kangourou.
+
+**Paramètres**:
+
+* `cameraPopoverOptions`: la `CameraPopoverOptions` qui spécifie la nouvelle position
+
+### Exemple
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+iOS uniquement les paramètres qui spécifient la direction ancre élément emplacement et de la flèche de la kangourou lors de la sélection des images de la bibliothèque de l'iPad ou l'album.
+
+ { x : 0, y : 32, width : 320, height : 480, arrowDir : Camera.PopoverArrowDirection.ARROW_ANY };
+
+
+### CameraPopoverOptions
+
+* **x**: coordonnée de pixel de l'élément de l'écran sur lequel ancrer le kangourou x. *(Nombre)*
+
+* **y**: coordonnée de y pixels de l'élément de l'écran sur lequel ancrer le kangourou. *(Nombre)*
+
+* **largeur**: largeur, en pixels, de l'élément de l'écran sur lequel ancrer le kangourou. *(Nombre)*
+
+* **hauteur**: hauteur, en pixels, de l'élément de l'écran sur lequel ancrer le kangourou. *(Nombre)*
+
+* **arrowDir**: Direction de la flèche sur le kangourou doit pointer. Définies dans `Camera.PopoverArrowDirection` *(nombre)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+Notez que la taille de la kangourou peut changer pour s'adapter à la direction de la flèche et l'orientation de l'écran. Assurez-vous que tenir compte des changements d'orientation lors de la spécification de l'emplacement d'élément d'ancrage.
+
+## Navigator.Camera.Cleanup
+
+Supprime les intermédiaires photos prises par la caméra de stockage temporaire.
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### Description
+
+Supprime les intermédiaires les fichiers image qui sont gardées en dépôt temporaire après avoir appelé `camera.getPicture` . S'applique uniquement lorsque la valeur de `Camera.sourceType` est égale à `Camera.PictureSourceType.CAMERA` et le `Camera.destinationType` est égal à`Camera.DestinationType.FILE_URI`.
+
+### Plates-formes prises en charge
+
+* iOS
+
+### Exemple
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/doc/index.md b/plugins/org.apache.cordova.camera/doc/index.md
new file mode 100644
index 0000000..0aee1e6
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/index.md
@@ -0,0 +1,455 @@
+
+
+# org.apache.cordova.camera
+
+This plugin defines a global `navigator.camera` object, which provides an API for taking pictures and for choosing images from
+the system's image library.
+
+Although the object is attached to the global scoped `navigator`, it is not available until after the `deviceready` event.
+
+ document.addEventListener("deviceready", onDeviceReady, false);
+ function onDeviceReady() {
+ console.log(navigator.camera);
+ }
+
+## Installation
+
+ cordova plugin add org.apache.cordova.camera
+
+## navigator.camera.getPicture
+
+Takes a photo using the camera, or retrieves a photo from the device's
+image gallery. The image is passed to the success callback as a
+base64-encoded `String`, or as the URI for the image file. The method
+itself returns a `CameraPopoverHandle` object that can be used to
+reposition the file selection popover.
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+### Description
+
+The `camera.getPicture` function opens the device's default camera
+application that allows users to snap pictures. This behavior occurs
+by default, when `Camera.sourceType` equals
+`Camera.PictureSourceType.CAMERA`. Once the user snaps the photo, the
+camera application closes and the application is restored.
+
+If `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or
+`Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays
+that allows users to select an existing image. The
+`camera.getPicture` function returns a `CameraPopoverHandle` object,
+which can be used to reposition the image selection dialog, for
+example, when the device orientation changes.
+
+The return value is sent to the `cameraSuccess` callback function, in
+one of the following formats, depending on the specified
+`cameraOptions`:
+
+- A `String` containing the base64-encoded photo image.
+
+- A `String` representing the image file location on local storage (default).
+
+You can do whatever you want with the encoded image or URI, for
+example:
+
+- Render the image in an `` tag, as in the example below
+
+- Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)
+
+- Post the data to a remote server
+
+__NOTE__: Photo resolution on newer devices is quite good. Photos
+selected from the device's gallery are not downscaled to a lower
+quality, even if a `quality` parameter is specified. To avoid common
+memory problems, set `Camera.destinationType` to `FILE_URI` rather
+than `DATA_URL`.
+
+### Supported Platforms
+
+- Amazon Fire OS
+- Android
+- BlackBerry 10
+- Browser
+- Firefox OS
+- iOS
+- Tizen
+- Windows Phone 7 and 8
+- Windows 8
+
+### Preferences (iOS)
+
+- __CameraUsesGeolocation__ (boolean, defaults to false). For capturing JPEGs, set to true to get geolocation data in the EXIF header. This will trigger a request for geolocation permissions if set to true.
+
+
+
+
+### Amazon Fire OS Quirks
+
+Amazon Fire OS uses intents to launch the camera activity on the device to capture
+images, and on phones with low memory, the Cordova activity may be killed. In this
+scenario, the image may not appear when the cordova activity is restored.
+
+### Android Quirks
+
+Android uses intents to launch the camera activity on the device to capture
+images, and on phones with low memory, the Cordova activity may be killed. In this
+scenario, the image may not appear when the Cordova activity is restored.
+
+### Browser Quirks
+
+Can only return photos as base64-encoded image.
+
+### Firefox OS Quirks
+
+Camera plugin is currently implemented using [Web Activities](https://hacks.mozilla.org/2013/01/introducing-web-activities/).
+
+### iOS Quirks
+
+Including a JavaScript `alert()` in either of the callback functions
+can cause problems. Wrap the alert within a `setTimeout()` to allow
+the iOS image picker or popover to fully close before the alert
+displays:
+
+ setTimeout(function() {
+ // do your thing here!
+ }, 0);
+
+### Windows Phone 7 Quirks
+
+Invoking the native camera application while the device is connected
+via Zune does not work, and triggers an error callback.
+
+### Tizen Quirks
+
+Tizen only supports a `destinationType` of
+`Camera.DestinationType.FILE_URI` and a `sourceType` of
+`Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### Example
+
+Take a photo and retrieve it as a base64-encoded image:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+Take a photo and retrieve the image's file location:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+## CameraOptions
+
+Optional parameters to customize the camera settings.
+
+ { quality : 75,
+ destinationType : Camera.DestinationType.DATA_URL,
+ sourceType : Camera.PictureSourceType.CAMERA,
+ allowEdit : true,
+ encodingType: Camera.EncodingType.JPEG,
+ targetWidth: 100,
+ targetHeight: 100,
+ popoverOptions: CameraPopoverOptions,
+ saveToPhotoAlbum: false };
+
+### Options
+
+- __quality__: Quality of the saved image, expressed as a range of 0-100, where 100 is typically full resolution with no loss from file compression. The default is 50. _(Number)_ (Note that information about the camera's resolution is unavailable.)
+
+- __destinationType__: Choose the format of the return value. The default is FILE_URI. Defined in `navigator.camera.DestinationType` _(Number)_
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+- __sourceType__: Set the source of the picture. The default is CAMERA. Defined in `navigator.camera.PictureSourceType` _(Number)_
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+- __allowEdit__: Allow simple editing of image before selection. _(Boolean)_
+
+- __encodingType__: Choose the returned image file's encoding. Default is JPEG. Defined in `navigator.camera.EncodingType` _(Number)_
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+- __targetWidth__: Width in pixels to scale image. Must be used with __targetHeight__. Aspect ratio remains constant. _(Number)_
+
+- __targetHeight__: Height in pixels to scale image. Must be used with __targetWidth__. Aspect ratio remains constant. _(Number)_
+
+- __mediaType__: Set the type of media to select from. Only works when `PictureSourceType` is `PHOTOLIBRARY` or `SAVEDPHOTOALBUM`. Defined in `nagivator.camera.MediaType` _(Number)_
+
+ Camera.MediaType = {
+ PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+- __correctOrientation__: Rotate the image to correct for the orientation of the device during capture. _(Boolean)_
+
+- __saveToPhotoAlbum__: Save the image to the photo album on the device after capture. _(Boolean)_
+
+- __popoverOptions__: iOS-only options that specify popover location in iPad. Defined in `CameraPopoverOptions`.
+
+- __cameraDirection__: Choose the camera to use (front- or back-facing). The default is BACK. Defined in `navigator.camera.Direction` _(Number)_
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+### Amazon Fire OS Quirks
+
+- Any `cameraDirection` value results in a back-facing photo.
+
+- Ignores the `allowEdit` parameter.
+
+- `Camera.PictureSourceType.PHOTOLIBRARY` and `Camera.PictureSourceType.SAVEDPHOTOALBUM` both display the same photo album.
+
+### Android Quirks
+
+- Any `cameraDirection` value results in a back-facing photo.
+
+- Ignores the `allowEdit` parameter.
+
+- `Camera.PictureSourceType.PHOTOLIBRARY` and `Camera.PictureSourceType.SAVEDPHOTOALBUM` both display the same photo album.
+
+### BlackBerry 10 Quirks
+
+- Ignores the `quality` parameter.
+
+- Ignores the `allowEdit` parameter.
+
+- `Camera.MediaType` is not supported.
+
+- Ignores the `correctOrientation` parameter.
+
+- Ignores the `cameraDirection` parameter.
+
+### Firefox OS Quirks
+
+- Ignores the `quality` parameter.
+
+- `Camera.DestinationType` is ignored and equals `1` (image file URI)
+
+- Ignores the `allowEdit` parameter.
+
+- Ignores the `PictureSourceType` parameter (user chooses it in a dialog window)
+
+- Ignores the `encodingType`
+
+- Ignores the `targetWidth` and `targetHeight`
+
+- `Camera.MediaType` is not supported.
+
+- Ignores the `correctOrientation` parameter.
+
+- Ignores the `cameraDirection` parameter.
+
+### iOS Quirks
+
+- Set `quality` below 50 to avoid memory errors on some devices.
+
+- When using `destinationType.FILE_URI`, photos are saved in the application's temporary directory. The contents of the application's temporary directory is deleted when the application ends.
+
+### Tizen Quirks
+
+- options not supported
+
+- always returns a FILE URI
+
+### Windows Phone 7 and 8 Quirks
+
+- Ignores the `allowEdit` parameter.
+
+- Ignores the `correctOrientation` parameter.
+
+- Ignores the `cameraDirection` parameter.
+
+- Ignores the `saveToPhotoAlbum` parameter. IMPORTANT: All images taken with the wp7/8 cordova camera API are always copied to the phone's camera roll. Depending on the user's settings, this could also mean the image is auto-uploaded to their OneDrive. This could potentially mean the image is available to a wider audience than your app intended. If this a blocker for your application, you will need to implement the CameraCaptureTask as documented on msdn : [http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx)
+You may also comment or up-vote the related issue in the [issue tracker](https://issues.apache.org/jira/browse/CB-2083)
+
+- Ignores the `mediaType` property of `cameraOptions` as the Windows Phone SDK does not provide a way to choose videos from PHOTOLIBRARY.
+
+
+## CameraError
+
+onError callback function that provides an error message.
+
+ function(message) {
+ // Show a helpful message
+ }
+
+### Parameters
+
+- __message__: The message is provided by the device's native code. _(String)_
+
+
+## cameraSuccess
+
+onSuccess callback function that provides the image data.
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+### Parameters
+
+- __imageData__: Base64 encoding of the image data, _or_ the image file URI, depending on `cameraOptions` in effect. _(String)_
+
+### Example
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+A handle to the popover dialog created by `navigator.camera.getPicture`.
+
+### Methods
+
+- __setPosition__: Set the position of the popover.
+
+### Supported Platforms
+
+- iOS
+
+### setPosition
+
+Set the position of the popover.
+
+__Parameters__:
+
+- `cameraPopoverOptions`: the `CameraPopoverOptions` that specify the new position
+
+### Example
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+iOS-only parameters that specify the anchor element location and arrow
+direction of the popover when selecting images from an iPad's library
+or album.
+
+ { x : 0,
+ y : 32,
+ width : 320,
+ height : 480,
+ arrowDir : Camera.PopoverArrowDirection.ARROW_ANY
+ };
+
+### CameraPopoverOptions
+
+- __x__: x pixel coordinate of screen element onto which to anchor the popover. _(Number)_
+
+- __y__: y pixel coordinate of screen element onto which to anchor the popover. _(Number)_
+
+- __width__: width, in pixels, of the screen element onto which to anchor the popover. _(Number)_
+
+- __height__: height, in pixels, of the screen element onto which to anchor the popover. _(Number)_
+
+- __arrowDir__: Direction the arrow on the popover should point. Defined in `Camera.PopoverArrowDirection` _(Number)_
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+Note that the size of the popover may change to adjust to the
+direction of the arrow and orientation of the screen. Make sure to
+account for orientation changes when specifying the anchor element
+location.
+
+## navigator.camera.cleanup
+
+Removes intermediate photos taken by the camera from temporary
+storage.
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+### Description
+
+Removes intermediate image files that are kept in temporary storage
+after calling `camera.getPicture`. Applies only when the value of
+`Camera.sourceType` equals `Camera.PictureSourceType.CAMERA` and the
+`Camera.destinationType` equals `Camera.DestinationType.FILE_URI`.
+
+### Supported Platforms
+
+- iOS
+
+### Example
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
diff --git a/plugins/org.apache.cordova.camera/doc/it/index.md b/plugins/org.apache.cordova.camera/doc/it/index.md
new file mode 100644
index 0000000..47a01bf
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/it/index.md
@@ -0,0 +1,409 @@
+
+
+# org.apache.cordova.camera
+
+Questo plugin fornisce un'API per scattare foto e per aver scelto immagini dalla libreria di immagini del sistema.
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+Prende una foto utilizzando la fotocamera, o recupera una foto dalla galleria di immagini del dispositivo. L'immagine viene passata al metodo di callback successo come una codifica base64 `String` , o come l'URI per il file di immagine. Il metodo stesso restituisce un `CameraPopoverHandle` che può essere utilizzato per riposizionare il Muffin di selezione file.
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### Descrizione
+
+il `camera.getPicture` funzione apre predefinito fotocamera applicazione il dispositivo che consente agli utenti di scattare foto. Questo comportamento si verifica per impostazione predefinita, quando `Camera.sourceType` è uguale a `Camera.PictureSourceType.CAMERA` . Una volta che l'utente scatta la foto, si chiude l'applicazione fotocamera e l'applicazione viene ripristinato.
+
+Se `Camera.sourceType` è `Camera.PictureSourceType.PHOTOLIBRARY` o `Camera.PictureSourceType.SAVEDPHOTOALBUM` , quindi un display finestra di dialogo che consente agli utenti di selezionare un'immagine esistente. La `camera.getPicture` la funzione restituisce un `CameraPopoverHandle` oggetto, che può essere utilizzato per riposizionare la finestra di selezione immagine, ad esempio, quando l'orientamento del dispositivo.
+
+Il valore restituito viene inviato alla `cameraSuccess` funzione di callback, in uno dei seguenti formati, a seconda che l'oggetto specificato `cameraOptions` :
+
+* A `String` contenente l'immagine della foto con codifica base64.
+
+* A `String` che rappresenta il percorso del file di immagine su archiviazione locale (predefinito).
+
+Si può fare quello che vuoi con l'immagine codificata o URI, ad esempio:
+
+* Il rendering dell'immagine in un `` tag, come nell'esempio qui sotto
+
+* Salvare i dati localmente ( `LocalStorage` , [Lawnchair][1], ecc.)
+
+* Inviare i dati a un server remoto
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**Nota**: risoluzione foto sui più recenti dispositivi è abbastanza buona. Foto selezionate dalla galleria del dispositivo non è percepiranno di qualità inferiore, anche se un `quality` è specificato il parametro. Per evitare problemi di memoria comune, impostare `Camera.destinationType` a `FILE_URI` piuttosto che`DATA_URL`.
+
+### Piattaforme supportate
+
+* Amazon fuoco OS
+* Android
+* BlackBerry 10
+* Browser
+* Firefox OS
+* iOS
+* Tizen
+* Windows Phone 7 e 8
+* Windows 8
+
+### Preferenze (iOS)
+
+* **CameraUsesGeolocation** (boolean, default è false). Per l'acquisizione di immagini JPEG, impostato su true per ottenere dati di geolocalizzazione nell'intestazione EXIF. Questo innescherà una richiesta per le autorizzazioni di geolocalizzazione, se impostato su true.
+
+
+
+
+### Amazon fuoco OS stranezze
+
+Amazon fuoco OS utilizza intenti a lanciare l'attività della fotocamera sul dispositivo per catturare immagini e sui telefoni con poca memoria, l'attività di Cordova può essere ucciso. In questo scenario, l'immagine potrebbe non apparire quando viene ripristinata l'attività di cordova.
+
+### Stranezze Android
+
+Android utilizza intenti a lanciare l'attività della fotocamera sul dispositivo per catturare immagini e sui telefoni con poca memoria, l'attività di Cordova può essere ucciso. In questo scenario, l'immagine potrebbe non apparire quando viene ripristinata l'attività di Cordova.
+
+### Stranezze browser
+
+Può restituire solo la foto come immagine con codifica base64.
+
+### Firefox OS stranezze
+
+Fotocamera plugin è attualmente implementato mediante [Attività Web][2].
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### iOS stranezze
+
+Compreso un JavaScript `alert()` in entrambi il callback funzioni possono causare problemi. Avvolgere l'avviso all'interno di un `setTimeout()` per consentire la selezione immagine iOS o muffin per chiudere completamente la prima che viene visualizzato l'avviso:
+
+ setTimeout(function() {/ / fai la tua cosa qui!}, 0);
+
+
+### Windows Phone 7 capricci
+
+Richiamando l'applicazione nativa fotocamera mentre il dispositivo è collegato tramite Zune non funziona e innesca un callback di errore.
+
+### Tizen stranezze
+
+Tizen supporta solo un `destinationType` di `Camera.DestinationType.FILE_URI` e un `sourceType` di`Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### Esempio
+
+Scattare una foto e recuperarla come un'immagine con codifica base64:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+Scattare una foto e recuperare il percorso del file dell'immagine:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+Parametri opzionali per personalizzare le impostazioni della fotocamera.
+
+ {qualità: 75, destinationType: Camera.DestinationType.DATA_URL, sourceType: Camera.PictureSourceType.CAMERA, allowEdit: vero, encodingType: Camera.EncodingType.JPEG, targetWidth: 100, targetHeight: 100, popoverOptions: CameraPopoverOptions, saveToPhotoAlbum: false};
+
+
+### Opzioni
+
+* **qualità**: qualità dell'immagine salvata, espressa come un intervallo di 0-100, dove 100 è tipicamente piena risoluzione senza perdita di compressione file. Il valore predefinito è 50. *(Numero)* (Si noti che informazioni sulla risoluzione della fotocamera non sono disponibile).
+
+* **destinationType**: Scegli il formato del valore restituito. Il valore predefinito è FILE_URI. Definito in `navigator.camera.DestinationType` *(numero)*
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+
+* **sourceType**: impostare l'origine dell'immagine. Il valore predefinito è la fotocamera. Definito in `navigator.camera.PictureSourceType` *(numero)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+
+* **Proprietà allowEdit**: consentire la semplice modifica dell'immagine prima di selezione. *(Booleano)*
+
+* **encodingType**: scegliere il file immagine restituita di codifica. Predefinito è JPEG. Definito in `navigator.camera.EncodingType` *(numero)*
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+
+* **targetWidth**: larghezza in pixel all'immagine della scala. Deve essere usato con **targetHeight**. Proporzioni rimane costante. *(Numero)*
+
+* **targetHeight**: altezza in pixel all'immagine della scala. Deve essere usato con **targetWidth**. Proporzioni rimane costante. *(Numero)*
+
+* **mediaType**: impostare il tipo di supporto per scegliere da. Funziona solo quando `PictureSourceType` è `PHOTOLIBRARY` o `SAVEDPHOTOALBUM` . Definito in `nagivator.camera.MediaType` *(numero)*
+
+ Camera.MediaType = {
+ PICTURE: 0, // allow selection of still pictures only. PER IMPOSTAZIONE PREDEFINITA. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **correctOrientation**: ruotare l'immagine per correggere l'orientamento del dispositivo durante l'acquisizione. *(Booleano)*
+
+* **saveToPhotoAlbum**: salvare l'immagine nell'album di foto sul dispositivo dopo la cattura. *(Booleano)*
+
+* **popoverOptions**: solo iOS opzioni che specificano la posizione di muffin in iPad. Definito in`CameraPopoverOptions`.
+
+* **cameraDirection**: scegliere la telecamera da utilizzare (o retro-frontale). Il valore predefinito è tornato. Definito in `navigator.camera.Direction` *(numero)*
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+
+### Amazon fuoco OS stranezze
+
+* Qualsiasi `cameraDirection` valore i risultati in una foto di lamatura.
+
+* Ignora il `allowEdit` parametro.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`e `Camera.PictureSourceType.SAVEDPHOTOALBUM` entrambi visualizzare l'album fotografico stesso.
+
+### Stranezze Android
+
+* Qualsiasi `cameraDirection` valore i risultati in una foto di lamatura.
+
+* Ignora il `allowEdit` parametro.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`e `Camera.PictureSourceType.SAVEDPHOTOALBUM` entrambi visualizzare l'album fotografico stesso.
+
+### BlackBerry 10 capricci
+
+* Ignora il `quality` parametro.
+
+* Ignora il `allowEdit` parametro.
+
+* `Camera.MediaType`non è supportato.
+
+* Ignora il `correctOrientation` parametro.
+
+* Ignora il `cameraDirection` parametro.
+
+### Firefox OS stranezze
+
+* Ignora il `quality` parametro.
+
+* `Camera.DestinationType`viene ignorato e corrisponde a `1` (URI del file di immagine)
+
+* Ignora il `allowEdit` parametro.
+
+* Ignora il `PictureSourceType` parametro (utente ne sceglie in una finestra di dialogo)
+
+* Ignora il`encodingType`
+
+* Ignora le `targetWidth` e`targetHeight`
+
+* `Camera.MediaType`non è supportato.
+
+* Ignora il `correctOrientation` parametro.
+
+* Ignora il `cameraDirection` parametro.
+
+### iOS stranezze
+
+* Impostare `quality` inferiore al 50 per evitare errori di memoria su alcuni dispositivi.
+
+* Quando si utilizza `destinationType.FILE_URI` , foto vengono salvati nella directory temporanea dell'applicazione. Il contenuto della directory temporanea dell'applicazione viene eliminato quando l'applicazione termina.
+
+### Tizen stranezze
+
+* opzioni non supportate
+
+* restituisce sempre un URI del FILE
+
+### Windows Phone 7 e 8 stranezze
+
+* Ignora il `allowEdit` parametro.
+
+* Ignora il `correctOrientation` parametro.
+
+* Ignora il `cameraDirection` parametro.
+
+* Ignora il `saveToPhotoAlbum` parametro. IMPORTANTE: Tutte le immagini scattate con la fotocamera di cordova wp7/8 API vengono sempre copiate rotolo fotocamera del telefono cellulare. A seconda delle impostazioni dell'utente, questo potrebbe anche significare che l'immagine viene caricato in automatico a loro OneDrive. Questo potenzialmente potrebbe significare che l'immagine è disponibile a un pubblico più ampio di app destinate. Se questo un blocco dell'applicazione, sarà necessario implementare il CameraCaptureTask come documentato su msdn: si può anche commentare o up-voto la questione correlata nel [tracciatore di problemi][3]
+
+* Ignora la `mediaType` proprietà di `cameraOptions` come il SDK di Windows Phone non fornisce un modo per scegliere il video da PHOTOLIBRARY.
+
+ [3]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+funzione di callback onError che fornisce un messaggio di errore.
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### Parametri
+
+* **messaggio**: il messaggio è fornito dal codice nativo del dispositivo. *(String)*
+
+## cameraSuccess
+
+funzione di callback onSuccess che fornisce i dati di immagine.
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### Parametri
+
+* **imageData**: Base64 codifica dei dati immagine, *o* il file di immagine URI, a seconda `cameraOptions` in vigore. *(String)*
+
+### Esempio
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+Un handle per la finestra di dialogo di muffin creato da`navigator.camera.getPicture`.
+
+### Metodi
+
+* **setPosition**: impostare la posizione dei muffin.
+
+### Piattaforme supportate
+
+* iOS
+
+### setPosition
+
+Impostare la posizione dei muffin.
+
+**Parametri**:
+
+* `cameraPopoverOptions`: il `CameraPopoverOptions` che specificare la nuova posizione
+
+### Esempio
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+iOS solo parametri che specificano l'ancoraggio elemento posizione e freccia direzione il Muffin quando si selezionano le immagini dalla libreria un iPad o un album.
+
+ {x: 0, y: 32, larghezza: 320, altezza: 480, arrowDir: Camera.PopoverArrowDirection.ARROW_ANY};
+
+
+### CameraPopoverOptions
+
+* **x**: pixel coordinata x dell'elemento dello schermo su cui ancorare il muffin. *(Numero)*
+
+* **y**: coordinata y di pixel dell'elemento dello schermo su cui ancorare il muffin. *(Numero)*
+
+* **larghezza**: larghezza, in pixel, dell'elemento dello schermo su cui ancorare il muffin. *(Numero)*
+
+* **altezza**: altezza, in pixel, dell'elemento dello schermo su cui ancorare il muffin. *(Numero)*
+
+* **arrowDir**: direzione dovrebbe puntare la freccia il muffin. Definito in `Camera.PopoverArrowDirection` *(numero)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+Si noti che la dimensione del muffin possa cambiare per regolare la direzione della freccia e l'orientamento dello schermo. Assicurarsi che tenere conto di modifiche di orientamento quando si specifica la posizione di elemento di ancoraggio.
+
+## Navigator.camera.Cleanup
+
+Rimuove intermedio foto scattate con la fotocamera da deposito temporaneo.
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### Descrizione
+
+Rimuove intermedio i file di immagine che vengono tenuti in custodia temporanea dopo la chiamata `camera.getPicture` . Si applica solo quando il valore di `Camera.sourceType` è uguale a `Camera.PictureSourceType.CAMERA` e il `Camera.destinationType` è uguale a`Camera.DestinationType.FILE_URI`.
+
+### Piattaforme supportate
+
+* iOS
+
+### Esempio
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/doc/ja/index.md b/plugins/org.apache.cordova.camera/doc/ja/index.md
new file mode 100644
index 0000000..357804a
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/ja/index.md
@@ -0,0 +1,409 @@
+
+
+# org.apache.cordova.camera
+
+このプラグインは写真を撮るため、システムのイメージ ライブラリからイメージを選択するために API を提供します。
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+カメラを使用して写真を取るか、デバイスの画像ギャラリーから写真を取得します。 イメージは base64 エンコードとして成功時のコールバックに渡される `String` 、またはイメージ ファイルの URI。 メソッド自体を返します、 `CameraPopoverHandle` オブジェクト ファイル選択ポップ オーバーの位置を変更するために使用することができます。
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### 説明
+
+`camera.getPicture`関数はデバイスのデフォルトのカメラ アプリケーションの写真をスナップするユーザーことができますを開きます。 既定では、この現象が発生したときに `Camera.sourceType` に等しい `Camera.PictureSourceType.CAMERA` 。 ユーザーは写真をスナップ、カメラ アプリケーションを閉じるし、アプリケーションが復元されます。
+
+場合 `Camera.sourceType` は、 `Camera.PictureSourceType.PHOTOLIBRARY` または `Camera.PictureSourceType.SAVEDPHOTOALBUM` 、その後、ダイアログが表示されますユーザーを既存のイメージを選択することができます。 `camera.getPicture`関数を返す、 `CameraPopoverHandle` オブジェクトは、たとえば、イメージの選択ダイアログには、デバイスの向きが変更されたときの位置を変更するために使用することができます。
+
+戻り値に送信されます、 `cameraSuccess` の指定によって、次の形式のいずれかのコールバック関数 `cameraOptions` :
+
+* A `String` 写真の base64 でエンコードされたイメージを含んでいます。
+
+* A `String` (既定値) のローカル記憶域上のイメージ ファイルの場所を表します。
+
+自由に変更、エンコードされたイメージ、または URI などを行うことができます。
+
+* イメージをレンダリングする `` 以下の例のように、タグ
+
+* ローカル データの保存 ( `LocalStorage` 、 [Lawnchair][1]など)。
+
+* リモート サーバーにデータを投稿します。
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**注**: 新しいデバイス上の写真の解像度はかなり良いです。 デバイスのギャラリーから選択した写真が下方の品質に縮小しない場合でも、 `quality` パラメーターを指定します。 一般的なメモリの問題を回避する設定 `Camera.destinationType` を `FILE_URI` よりもむしろ`DATA_URL`.
+
+### サポートされているプラットフォーム
+
+* アマゾン火 OS
+* アンドロイド
+* ブラックベリー 10
+* ブラウザー
+* Firefox の OS
+* iOS
+* Tizen
+* Windows Phone 7 と 8
+* Windows 8
+
+### 環境設定 (iOS)
+
+* **CameraUsesGeolocation**(ブール値、デフォルトは false)。 Jpeg 画像をキャプチャするため EXIF ヘッダーで地理位置情報データを取得する場合は true に設定します。 これは、場合地理位置情報のアクセス許可に対する要求をトリガーする true に設定します。
+
+
+
+
+### アマゾン火 OS 癖
+
+アマゾン火 OS イメージをキャプチャするデバイス上のカメラの活動を開始する意図を使用して、メモリの少ない携帯電話、コルドバ活動が殺されるかもしれない。 このシナリオでは、コルドバの活動が復元されるとき、画像が表示されません。
+
+### Android の癖
+
+アンドロイド、イメージをキャプチャするデバイス上でカメラのアクティビティを開始する意図を使用し、メモリの少ない携帯電話、コルドバ活動が殺されるかもしれない。 このシナリオではコルドバ活動が復元されると、イメージが表示されません。
+
+### ブラウザーの癖
+
+Base64 エンコード イメージとして写真を返すのみことができます。
+
+### Firefox OS 癖
+
+カメラのプラグインは現在、 [Web アクティビティ][2]を使用して実装されていた.
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### iOS の癖
+
+JavaScript を含む `alert()` 関数コールバックのいずれかの問題を引き起こすことができます。 内でアラートのラップ、 `setTimeout()` iOS イメージ ピッカーまたは完全が終了するまで、警告が表示されますポップ オーバーを許可します。
+
+ setTimeout(function() {//ここにあなたのことを行います !}, 0);
+
+
+### Windows Phone 7 の癖
+
+ネイティブ カメラ アプリケーションを呼び出すと、デバイスが Zune を介して接続されている動作しませんし、エラー コールバックをトリガーします。
+
+### Tizen の癖
+
+Tizen のみをサポートしている、 `destinationType` の `Camera.DestinationType.FILE_URI` と `sourceType` の`Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### 例
+
+写真を撮るし、base64 エンコード イメージとして取得します。
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+写真を撮るし、イメージのファイルの場所を取得します。
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+カメラの設定をカスタマイズするオプションのパラメーター。
+
+ {品質: 75、destinationType: Camera.DestinationType.DATA_URL、sourceType: Camera.PictureSourceType.CAMERA、allowEdit: true の場合、encodingType: Camera.EncodingType.JPEG、targetWidth: 100、targetHeight: 100、popoverOptions: CameraPopoverOptions、saveToPhotoAlbum: false};
+
+
+### オプション
+
+* **品質**: 0-100、100 がファイルの圧縮から損失なしで通常のフル解像度の範囲で表される、保存されたイメージの品質。 既定値は 50 です。 *(数)*(カメラの解像度についての情報が利用できないことに注意してください)。
+
+* **destinationType**: 戻り値の形式を選択します。既定値は FILE_URI です。定義されている `navigator.camera.DestinationType` *(番号)*
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+
+* **sourceType**: 画像のソースを設定します。既定値は、カメラです。定義されている `navigator.camera.PictureSourceType` *(番号)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+
+* **allowEdit**: 単純な選択の前に画像の編集を許可します。*(ブール値)*
+
+* **encodingType**: 返されるイメージ ファイルのエンコーディングを選択します。デフォルトは JPEG です。定義されている `navigator.camera.EncodingType` *(番号)*
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+
+* **targetWidth**: スケール イメージにピクセル単位の幅。**TargetHeight**を使用する必要があります。縦横比は変わりません。*(数)*
+
+* **targetHeight**: スケール イメージにピクセル単位の高さ。**TargetWidth**を使用する必要があります。縦横比は変わりません。*(数)*
+
+* **mediaType**: から選択するメディアの種類を設定します。 場合にのみ働きます `PictureSourceType` は `PHOTOLIBRARY` または `SAVEDPHOTOALBUM` 。 定義されている `nagivator.camera.MediaType` *(番号)*
+
+ Camera.MediaType = {
+ PICTURE: 0, // allow selection of still pictures only. 既定値です。 Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **correctOrientation**: キャプチャ中に、デバイスの向きを修正する画像を回転させます。*(ブール値)*
+
+* **saveToPhotoAlbum**: キャプチャ後、デバイス上のフォト アルバムに画像を保存します。*(ブール値)*
+
+* **popoverOptions**: iPad のポップ オーバーの場所を指定する iOS のみのオプションです。定義されています。`CameraPopoverOptions`.
+
+* **cameraDirection**: (前面または背面側) を使用するカメラを選択します。既定値は戻るです。定義されている `navigator.camera.Direction` *(番号)*
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+
+### アマゾン火 OS 癖
+
+* 任意 `cameraDirection` 背面写真で結果の値します。
+
+* 無視、 `allowEdit` パラメーター。
+
+* `Camera.PictureSourceType.PHOTOLIBRARY``Camera.PictureSourceType.SAVEDPHOTOALBUM`両方のアルバムが表示されます同じ写真。
+
+### Android の癖
+
+* 任意 `cameraDirection` 背面写真で結果の値します。
+
+* 無視、 `allowEdit` パラメーター。
+
+* `Camera.PictureSourceType.PHOTOLIBRARY``Camera.PictureSourceType.SAVEDPHOTOALBUM`両方のアルバムが表示されます同じ写真。
+
+### ブラックベリー 10 癖
+
+* 無視、 `quality` パラメーター。
+
+* 無視、 `allowEdit` パラメーター。
+
+* `Camera.MediaType`サポートされていません。
+
+* 無視、 `correctOrientation` パラメーター。
+
+* 無視、 `cameraDirection` パラメーター。
+
+### Firefox OS 癖
+
+* 無視、 `quality` パラメーター。
+
+* `Camera.DestinationType`無視され、等しい `1` (イメージ ファイル URI)
+
+* 無視、 `allowEdit` パラメーター。
+
+* 無視、 `PictureSourceType` パラメーター (ユーザーが選択ダイアログ ウィンドウに)
+
+* 無視します、`encodingType`
+
+* 無視、 `targetWidth` と`targetHeight`
+
+* `Camera.MediaType`サポートされていません。
+
+* 無視、 `correctOrientation` パラメーター。
+
+* 無視、 `cameraDirection` パラメーター。
+
+### iOS の癖
+
+* 設定 `quality` 一部のデバイスでメモリ不足エラーを避けるために 50 の下。
+
+* 使用する場合 `destinationType.FILE_URI` 、写真、アプリケーションの一時ディレクトリに保存されます。アプリケーションの一時ディレクトリの内容は、アプリケーションの終了時に削除されます。
+
+### Tizen の癖
+
+* サポートされていないオプション
+
+* 常にファイルの URI を返す
+
+### Windows Phone 7 と 8 癖
+
+* 無視、 `allowEdit` パラメーター。
+
+* 無視、 `correctOrientation` パラメーター。
+
+* 無視、 `cameraDirection` パラメーター。
+
+* 無視、 `saveToPhotoAlbum` パラメーター。 重要: wp7/8 コルドバ カメラ API で撮影したすべての画像は携帯電話のカメラ巻き物に常にコピーします。 ユーザーの設定に応じて、これも、画像はその OneDrive に自動アップロードを意味できます。 イメージは意図したアプリより広い聴衆に利用できる可能性があります可能性があります。 場合は、このアプリケーションのブロッカー、msdn で説明されているように、CameraCaptureTask を実装する必要があります: コメントにすることがありますもかアップ投票関連の問題を[課題追跡システム][3]で
+
+* 無視、 `mediaType` のプロパティ `cameraOptions` として Windows Phone SDK には、フォト ライブラリからビデオを選択する方法は行いません。
+
+ [3]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+エラー メッセージを提供する onError コールバック関数。
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### パラメーター
+
+* **メッセージ**: メッセージは、デバイスのネイティブ コードによって提供されます。*(文字列)*
+
+## cameraSuccess
+
+画像データを提供する onSuccess コールバック関数。
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### パラメーター
+
+* **imagedata を扱う**: Base64 エンコード イメージのデータ、*または*画像ファイルによって URI の `cameraOptions` 効果。*(文字列)*
+
+### 例
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+によって作成されたポップオーバーパン ダイアログへのハンドル`navigator.camera.getPicture`.
+
+### メソッド
+
+* **setPosition**: ポップ オーバーの位置を設定します。
+
+### サポートされているプラットフォーム
+
+* iOS
+
+### setPosition
+
+ポップ オーバーの位置を設定します。
+
+**パラメーター**:
+
+* `cameraPopoverOptions`:、 `CameraPopoverOptions` の新しい位置を指定します。
+
+### 例
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+iOS だけ指定パラメーターをポップ オーバーのアンカー要素の場所および矢印方向計算されたライブラリまたはアルバムから画像を選択するとき。
+
+ {x: 0, y: 32、幅: 320、高さ: 480、arrowDir: Camera.PopoverArrowDirection.ARROW_ANY};
+
+
+### CameraPopoverOptions
+
+* **x**: ピクセルの x 座標画面要素にポップ オーバーのアンカーになります。*(数)*
+
+* **y**: y ピクセル座標の画面要素にポップ オーバーのアンカーになります。*(数)*
+
+* **幅**: ポップ オーバーのアンカーになる上の画面要素のピクセル単位の幅。*(数)*
+
+* **高さ**: ポップ オーバーのアンカーになる上の画面要素のピクセル単位の高さ。*(数)*
+
+* **arrowDir**: 方向のポップ オーバーで矢印をポイントする必要があります。定義されている `Camera.PopoverArrowDirection` *(番号)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+矢印の方向と、画面の向きを調整するポップ オーバーのサイズを変更可能性がありますに注意してください。 アンカー要素の位置を指定するときの方向の変化を考慮することを確認します。
+
+## navigator.camera.cleanup
+
+削除中間一時ストレージからカメラで撮影した写真。
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### 説明
+
+削除を呼び出した後に一時記憶域に保存されている画像ファイルを中間 `camera.getPicture` 。 場合にのみ適用されるの値 `Camera.sourceType` に等しい `Camera.PictureSourceType.CAMERA` と、 `Camera.destinationType` に等しい`Camera.DestinationType.FILE_URI`.
+
+### サポートされているプラットフォーム
+
+* iOS
+
+### 例
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/doc/ko/index.md b/plugins/org.apache.cordova.camera/doc/ko/index.md
new file mode 100644
index 0000000..217c475
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/ko/index.md
@@ -0,0 +1,409 @@
+
+
+# org.apache.cordova.camera
+
+이 플러그인 사진 촬영을 위한 및 시스템의 이미지 라이브러리에서 이미지를 선택 하기 위한 API를 제공 합니다.
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+카메라를 사용 하 여 사진을 걸립니다 또는 소자의 이미지 갤러리에서 사진을 검색 합니다. 이미지 base64 인코딩으로 성공 콜백에 전달 됩니다 `String` , 또는 이미지 파일에 대 한 URI로. 방법 자체는 반환 합니다 한 `CameraPopoverHandle` 개체 파일 선택 popover를 재배치 하는 데 사용할 수 있습니다.
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### 설명
+
+`camera.getPicture`기능 스냅 사진을 사용자가 디바이스의 기본 카메라 응용 프로그램을 엽니다. 이 동작은 기본적으로 발생 할 때 `Camera.sourceType` 와 `Camera.PictureSourceType.CAMERA` . 일단 사용자 스냅 사진, 카메라 응용 프로그램 종료 하 고 응용 프로그램 복원 됩니다.
+
+경우 `Camera.sourceType` 은 `Camera.PictureSourceType.PHOTOLIBRARY` 또는 `Camera.PictureSourceType.SAVEDPHOTOALBUM` , 사용자가 기존 이미지를 선택할 수 있도록 다음 대화 상자 표시. `camera.getPicture`반환 함수는 `CameraPopoverHandle` 장치 방향 변경 될 때 이미지 선택 대화 상자, 예를 들어, 위치를 변경 하려면 사용할 수 있는 개체.
+
+반환 값에 전송 되는 `cameraSuccess` 콜백 함수에 따라 지정 된 다음 형식 중 하나에 `cameraOptions` :
+
+* A `String` base64 인코딩된 사진 이미지를 포함 합니다.
+
+* A `String` 로컬 저장소 (기본값)의 이미지 파일 위치를 나타내는.
+
+할 수 있는 당신이 원하는대로 인코딩된 이미지 또는 URI, 예를 들면:
+
+* 렌더링 이미지는 `` 아래 예제와 같이 태그
+
+* 로컬로 데이터를 저장 ( `LocalStorage` , [Lawnchair][1], 등.)
+
+* 원격 서버에 데이터 게시
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**참고**: 더 새로운 장치에 사진 해상도 아주 좋은. 소자의 갤러리에서 선택 된 사진을 하지 낮은 품질에 관하여는 경우에는 `quality` 매개 변수를 지정 합니다. 일반적인 메모리 문제를 방지 하려면 설정 `Camera.destinationType` 을 `FILE_URI` 보다는`DATA_URL`.
+
+### 지원 되는 플랫폼
+
+* 아마존 화재 운영 체제
+* 안 드 로이드
+* 블랙베리 10
+* 브라우저
+* Firefox 운영 체제
+* iOS
+* Tizen
+* Windows Phone 7과 8
+* 윈도우 8
+
+### 환경 설정 (iOS)
+
+* **CameraUsesGeolocation** (boolean, 기본값: false)입니다. 캡처 Jpeg, EXIF 헤더에 지리적 데이터를 true로 설정 합니다. 이 경우 위치 정보 사용 권한에 대 한 요청을 일으킬 것 이다 true로 설정 합니다.
+
+
+
+
+### 아마존 화재 OS 단점
+
+아마존 화재 OS 의도 사용 하 여 이미지 캡처 장치에서 카메라 활동을 시작 하 고 낮은 메모리와 휴대 전화에 코르 도우 바 활동 살해 수 있습니다. 코르도바 활동 복원 되 면이 시나리오에서는 이미지가 나타나지 않을 수 있습니다.
+
+### 안 드 로이드 단점
+
+안 드 로이드 의도 사용 하 여 이미지 캡처 장치에서 카메라 활동을 시작 하 고 낮은 메모리와 휴대 전화에 코르 도우 바 활동 살해 수 있습니다. 코르도바 활동 복원 되 면이 시나리오에서는 이미지가 나타나지 않을 수 있습니다.
+
+### 브라우저 만지면
+
+수 base64 인코딩 이미지로 사진을 반환 합니다.
+
+### 파이어 폭스 OS 단점
+
+카메라 플러그인은 현재 [웹 활동][2] 를 사용 하 여 구현.
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### iOS 단점
+
+자바 스크립트를 포함 하 여 `alert()` 함수는 콜백 중에 문제를 일으킬 수 있습니다. 내 경고를 래핑하는 `setTimeout()` 허용 iOS 이미지 피커 또는 popover를 완벽 하 게 경고를 표시 하기 전에 닫습니다:
+
+ setTimeout(function() {/ / 여기 짓!}, 0);
+
+
+### Windows Phone 7 단점
+
+장치 Zune 통해 연결 된 동안 네이티브 카메라 응용 프로그램을 호출 하면 작동 하지 않습니다 하 고 오류 콜백 트리거합니다.
+
+### Tizen 특수
+
+Tizen만 지원 한 `destinationType` 의 `Camera.DestinationType.FILE_URI` 와 `sourceType` 의`Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### 예를 들어
+
+촬영 및 base64 인코딩 이미지로 검색:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+촬영 하 고 이미지의 파일 위치를 검색:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+카메라 설정을 사용자 지정 하는 선택적 매개 변수.
+
+ {품질: 75, destinationType: Camera.DestinationType.DATA_URL, sourceType: Camera.PictureSourceType.CAMERA, allowEdit: 사실, encodingType: Camera.EncodingType.JPEG, targetWidth: 100, targetHeight: 100, popoverOptions: CameraPopoverOptions, saveToPhotoAlbum: false};
+
+
+### 옵션
+
+* **품질**: 범위 0-100, 100은 파일 압축에서 손실 없이 일반적으로 전체 해상도 저장된 된 이미지의 품질. 기본값은 50입니다. *(수)* (Note 카메라의 해상도 대 한 정보는 사용할 수 없습니다.)
+
+* **destinationType**: 반환 값의 형식을 선택 합니다. 기본값은 FILE_URI입니다. 에 정의 된 `navigator.camera.DestinationType` *(수)*
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+
+* **sourceType**: 그림의 소스를 설정 합니다. 기본값은 카메라입니다. 에 정의 된 `navigator.camera.PictureSourceType` *(수)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+
+* **allowEdit**: 선택 하기 전에 이미지의 간단한 편집을 허용 합니다. *(부울)*
+
+* **encodingType**: 반환 된 이미지 파일의 인코딩을 선택 합니다. 기본값은 JPEG입니다. 에 정의 된 `navigator.camera.EncodingType` *(수)*
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+
+* **targetWidth**: 스케일 이미지를 픽셀 너비. **TargetHeight**와 함께 사용 해야 합니다. 가로 세로 비율이 일정 하 게 유지 합니다. *(수)*
+
+* **targetHeight**: 스케일 이미지를 픽셀 단위로 높이. **TargetWidth**와 함께 사용 해야 합니다. 가로 세로 비율이 일정 하 게 유지 합니다. *(수)*
+
+* **mediaType**:에서 선택 미디어 유형을 설정 합니다. 때에 작동 `PictureSourceType` 는 `PHOTOLIBRARY` 또는 `SAVEDPHOTOALBUM` . 에 정의 된 `nagivator.camera.MediaType` *(수)*
+
+ Camera.MediaType = {
+ PICTURE: 0, // allow selection of still pictures only. 기본입니다. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **correctOrientation**: 캡처 도중 장치의 방향에 대 한 해결 하기 위해 이미지를 회전 합니다. *(부울)*
+
+* **saveToPhotoAlbum**: 캡처 후 장치에서 사진 앨범에 이미지를 저장 합니다. *(부울)*
+
+* **popoverOptions**: iPad에 popover 위치를 지정 하는 iOS 전용 옵션. 에 정의 된`CameraPopoverOptions`.
+
+* **cameraDirection**: (앞 이나 뒤로-연결)를 사용 하 여 카메라를 선택 하십시오. 기본값은 다시. 에 정의 된 `navigator.camera.Direction` *(수)*
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+
+### 아마존 화재 OS 단점
+
+* 어떤 `cameraDirection` 다시 연결 사진에 결과 값.
+
+* 무시는 `allowEdit` 매개 변수.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`그리고 `Camera.PictureSourceType.SAVEDPHOTOALBUM` 둘 다 동일한 사진 앨범을 표시 합니다.
+
+### 안 드 로이드 단점
+
+* 어떤 `cameraDirection` 다시 연결 사진에 결과 값.
+
+* 무시는 `allowEdit` 매개 변수.
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`그리고 `Camera.PictureSourceType.SAVEDPHOTOALBUM` 둘 다 동일한 사진 앨범을 표시 합니다.
+
+### 블랙베리 10 단점
+
+* 무시는 `quality` 매개 변수.
+
+* 무시는 `allowEdit` 매개 변수.
+
+* `Camera.MediaType`지원 되지 않습니다.
+
+* 무시는 `correctOrientation` 매개 변수.
+
+* 무시는 `cameraDirection` 매개 변수.
+
+### 파이어 폭스 OS 단점
+
+* 무시는 `quality` 매개 변수.
+
+* `Camera.DestinationType`무시 되 고 `1` (이미지 파일 URI)
+
+* 무시는 `allowEdit` 매개 변수.
+
+* 무시는 `PictureSourceType` 매개 변수 (사용자가 선택 그것 대화 창에서)
+
+* 무시 하는`encodingType`
+
+* 무시는 `targetWidth` 와`targetHeight`
+
+* `Camera.MediaType`지원 되지 않습니다.
+
+* 무시는 `correctOrientation` 매개 변수.
+
+* 무시는 `cameraDirection` 매개 변수.
+
+### iOS 단점
+
+* 설정 `quality` 일부 장치 메모리 오류를 피하기 위해 50 아래.
+
+* 사용 하는 경우 `destinationType.FILE_URI` , 사진 응용 프로그램의 임시 디렉터리에 저장 됩니다. 응용 프로그램이 종료 될 때 응용 프로그램의 임시 디렉터리의 내용은 삭제 됩니다.
+
+### Tizen 특수
+
+* 지원 되지 않는 옵션
+
+* 항상 파일 URI를 반환 합니다.
+
+### Windows Phone 7, 8 특수
+
+* 무시는 `allowEdit` 매개 변수.
+
+* 무시는 `correctOrientation` 매개 변수.
+
+* 무시는 `cameraDirection` 매개 변수.
+
+* 무시는 `saveToPhotoAlbum` 매개 변수. 중요: 모든 이미지 API wp7/8 코르도바 카메라로 촬영 항상 복사 됩니다 휴대 전화의 카메라 롤에. 사용자의 설정에 따라이 또한 그들의 OneDrive에 자동 업로드 이미지는 의미. 이 잠재적으로 이미지는 당신의 애플 리 케이 션을 위한 보다 넓은 청중에 게 사용할 수 있는 의미. 이 경우 응용 프로그램에 대 한 차단, 당신은 msdn에 설명 대로 단말기를 구현 해야 합니다: 수 있습니다 또한 의견 또는 [이슈 트래커][3] 에서 업-투표 관련된 문제
+
+* 무시는 `mediaType` 속성을 `cameraOptions` 으로 Windows Phone SDK PHOTOLIBRARY에서 비디오를 선택 하는 방법을 제공 하지 않습니다.
+
+ [3]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+오류 메시지를 제공 하는 onError 콜백 함수.
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### 매개 변수
+
+* **메시지**: 메시지는 장치의 네이티브 코드에 의해 제공 됩니다. *(문자열)*
+
+## cameraSuccess
+
+이미지 데이터를 제공 하는 onSuccess 콜백 함수.
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### 매개 변수
+
+* **imageData**: Base64 인코딩은 이미지 데이터, *또는* 이미지 파일에 따라 URI의 `cameraOptions` 적용. *(문자열)*
+
+### 예를 들어
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+에 의해 만들어진 popover 대화에 대 한 핸들`navigator.camera.getPicture`.
+
+### 메서드
+
+* **setPosition**:는 popover의 위치를 설정 합니다.
+
+### 지원 되는 플랫폼
+
+* iOS
+
+### setPosition
+
+popover의 위치를 설정 합니다.
+
+**매개 변수**:
+
+* `cameraPopoverOptions`:는 `CameraPopoverOptions` 새 위치를 지정 하는
+
+### 예를 들어
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+iOS 전용 매개 변수 iPad의 보관 함 또는 앨범에서 이미지를 선택 하면 앵커 요소 위치와 화살표의 방향으로 popover 지정 하는.
+
+ {x: 0, y: 32, 폭: 320, 높이: 480, arrowDir: Camera.PopoverArrowDirection.ARROW_ANY};
+
+
+### CameraPopoverOptions
+
+* **x**: x는 popover 앵커는 화면 요소의 픽셀 좌표. *(수)*
+
+* **y**: y 픽셀 좌표는 popover 앵커는 화면 요소입니다. *(수)*
+
+* **폭**: 폭 (픽셀)는 popover 앵커는 화면 요소. *(수)*
+
+* **높이**: 높이 (픽셀)는 popover 앵커는 화면 요소. *(수)*
+
+* **arrowDir**: 방향 화살표는 popover 가리켜야 합니다. 에 정의 된 `Camera.PopoverArrowDirection` *(수)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+참고는 popover의 크기 조정 화살표 방향 및 화면 방향 변경 될 수 있습니다. 앵커 요소 위치를 지정 하는 경우 방향 변경에 대 한 계정에 있는지 확인 합니다.
+
+## navigator.camera.cleanup
+
+제거 임시 저장소에서 카메라로 찍은 사진을 중간.
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### 설명
+
+제거 중간 전화 후 임시 저장소에 보관 된 이미지 파일 `camera.getPicture` . 경우에만 적용의 값 `Camera.sourceType` 와 `Camera.PictureSourceType.CAMERA` 와 `Camera.destinationType` 같음`Camera.DestinationType.FILE_URI`.
+
+### 지원 되는 플랫폼
+
+* iOS
+
+### 예를 들어
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/doc/pl/index.md b/plugins/org.apache.cordova.camera/doc/pl/index.md
new file mode 100644
index 0000000..ac87549
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/pl/index.md
@@ -0,0 +1,424 @@
+
+
+# org.apache.cordova.camera
+
+Wtyczka dostarcza API do robienia zdjęć i wybór zdjęć z biblioteki obrazu systemu.
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+Pobiera zdjęcia za pomocą aparatu lub z galerii zdjęć w urządzeniu. Obraz jest przekazywany do funkcji zwrotnej success jako `String` kodowany za pomocą base64 lub jako URI do pliku. Sama metoda zwraca obiekt `CameraPopoverHandle`, który może służyć do zmiany położenia wyskakującego okna wyboru pliku.
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### Opis
+
+Funkcja `camera.getPicture` otwiera na urządzeniu domyślną aplikację aparatu, która pozwala użytkownikowi zrobić zdjęcie. To zachowanie występuje domyślnie, gdy `Camera.sourceType` jest równe `Camera.PictureSourceType.CAMERA`. Gdy użytkownik wykona zdjęcie, aplikacja aparatu zakończy działanie i nastąpi powrót do głównej aplikacji.
+
+Jeśli `Camera.sourceType` jest równe `Camera.PictureSourceType.PHOTOLIBRARY` lub `Camera.PictureSourceType.SAVEDPHOTOALBUM`, wtedy zostanie wyświetlone okno dialogowe pozwalające użytkownikowi na wybór istniejącego obrazu. Funkcja `camera.getPicture` zwraca obiekt `CameraPopoverHandle`, który obsługuje zmianę położenia okna wyboru obrazu, np. po zmianie orientacji urządzenia.
+
+Zwracana wartość jest wysyłana do funkcji zwrotnej `cameraSuccess` w jednym z następujących formatów, w zależności od określonego parametru `cameraOptions`:
+
+* `String` zawierający obraz zakodowany przy pomocy base64.
+
+* `String` reprezentujący lokalizację pliku obrazu w lokalnym magazynie (domyślnie).
+
+Z zakodowanym obrazem lub URI możesz zrobić co zechcesz, na przykład:
+
+* Przedstawić obraz w tagu ``, jak w przykładzie poniżej
+
+* Zapisać lokalnie dane (`LocalStorage`, [Lawnchair][1], etc.)
+
+* Wysłać dane na zdalny serwer
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**Uwaga**: zdjęcie rozdzielczości na nowsze urządzenia jest bardzo dobry. Zdjęcia wybrane z galerii urządzenia nie są skalowane do niższej jakości, nawet jeśli określono parametr `quality`. Aby uniknąć typowych problemów z pamięcią lepiej ustawić`Camera.destinationType` na `FILE_URI` niż `DATA_URL`.
+
+### Obsługiwane platformy
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Przeglądarka
+* Firefox OS
+* iOS
+* Tizen
+* Windows Phone 7 i 8
+* Windows 8
+
+### Preferencje (iOS)
+
+* **CameraUsesGeolocation** (boolean, wartość domyślna to false). Do przechwytywania JPEG, zestaw do true, aby uzyskać danych geolokalizacyjnych w nagłówku EXIF. To spowoduje wniosek o geolokalizacji uprawnienia, jeśli zestaw na wartość true.
+
+
+
+
+### Amazon ogień OS dziwactwa
+
+Amazon ogień OS używa intencje do rozpoczęcia działalności aparatu na urządzenie do przechwytywania obrazów, i na telefony z pamięci, Cordova aktywność może zostać zabity. W takim scenariuszu obrazy mogą nie być wyświetlane po przywróceniu aktywności Cordovy.
+
+### Dziwactwa Androida
+
+Android używa Intencji (Intents) do uruchomienia aktywności aparatu i na urządzeniach z małą ilością dostępnej pamięci aktywność Cordova może zostać przerwana. W tym scenariuszu obraz mogą nie być wyświetlane po przywróceniu aktywności Cordova.
+
+### Quirks przeglądarki
+
+Może zwracać tylko zdjęcia jako obraz w formacie algorytmem base64.
+
+### Firefox OS dziwactwa
+
+Aparat plugin jest obecnie implementowane za pomocą [Działania sieci Web][2].
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### Dziwactwa iOS
+
+Umieszczenie w funkcji zwrotnej wywołania `alert()` w JavaScript może powodować problemy. Aby umożliwić systemowi iOS na całkowite zamknięcie okna wyboru obrazu lub wyskakującego powiadomienia przed wyświetleniem alarmu należy opakować go w `setTimeout()`:
+
+ setTimeout(function() {
+ // do your thing here!
+ }, 0);
+
+
+### Dziwactwa Windows Phone 7
+
+Wywoływanie aparat native aplikacji, podczas gdy urządzenie jest podłączone przez Zune nie działa i powoduje błąd wywołania zwrotnego.
+
+### Dziwactwa Tizen
+
+Tizen obsługuje tylko parametr `destinationType` jako `Camera.DestinationType.FILE_URI` oraz `sourceType` jako `Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### Przykład
+
+Zrobienie zdjęcia i pobranie go jako obraz zakodowany base64:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+Zrobienie zdjęcia i pobranie lokacji pliku obrazu:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+Opcjonalne parametry dostosowania ustawień aparatu.
+
+ { quality : 75,
+ destinationType : Camera.DestinationType.DATA_URL,
+ sourceType : Camera.PictureSourceType.CAMERA,
+ allowEdit : true,
+ encodingType: Camera.EncodingType.JPEG,
+ targetWidth: 100,
+ targetHeight: 100,
+ popoverOptions: CameraPopoverOptions,
+ saveToPhotoAlbum: false };
+
+
+### Opcje
+
+* **quality**: Jakość zapisywanego obrazu, wyrażona w przedziale 0-100, gdzie 100 zazwyczaj jest maksymalną rozdzielczością bez strat w czasie kompresji pliku. Wartością domyślną jest 50. *(Liczba)* (Pamiętaj, że informacja o rozdzielczości aparatu jest niedostępna.)
+
+* **destinationType**: Wybierz format zwracanej wartości. Wartością domyślną jest FILE_URI. Zdefiniowane w `navigator.camera.DestinationType` *(numer)*
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+
+* **sourceType**: Ustaw źródło obrazu. Wartością domyślną jest aparat fotograficzny. Zdefiniowane w `navigator.camera.PictureSourceType` *(numer)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+
+* **allowEdit**: Pozwala na prostą edycję obrazu przed zaznaczeniem. *(Boolean)*
+
+* **encodingType**: Wybierz plik obrazu zwracany jest kodowanie. Domyślnie jest JPEG. Zdefiniowane w `navigator.camera.EncodingType` *(numer)*
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+
+* **targetWidth**: Szerokość w pikselach skalowanego obrazu. Musi być użyte z **targetHeight**. Współczynnik proporcji pozostaje stały. *(Liczba)*
+
+* **targetHeight**: Wysokość w pikselach skalowanego obrazu. Musi być użyte z **targetWidth**. Współczynnik proporcji pozostaje stały. *(Liczba)*
+
+* **mediaType**: Ustawia typ nośnika, z którego będzie wybrany. Działa tylko wtedy, gdy `PictureSourceType` jest `PHOTOLIBRARY` lub `SAVEDPHOTOALBUM`. Zdefiniowane w `nagivator.camera.MediaType` *(Liczba)*
+
+ Camera.MediaType = {
+ PICTURE: 0, // umożliwia wybór tylko zdjęcia. DOMYŚLNIE. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **correctOrientation**: Obraca obraz aby skorygować orientację urządzenia podczas przechwytywania. *(Boolean)*
+
+* **saveToPhotoAlbum**: Po przechwyceniu zapisuje na urządzeniu obraz w albumie na zdjęcia. *(Boolean)*
+
+* **popoverOptions**: Opcja tylko dla platformy iOS, która określa położenie wyskakującego okna na iPadzie. Zdefiniowane w `CameraPopoverOptions`.
+
+* **cameraDirection**: Wybierz aparat do korzystania (lub z powrotem przodem). Wartością domyślną jest z powrotem. Zdefiniowane w `navigator.camera.Direction` *(numer)*
+
+ Camera.Direction = {
+ BACK : 0, // Używa tylnej kamery
+ FRONT : 1 // Używa przedniej kamery
+ };
+
+
+### Amazon ogień OS dziwactwa
+
+* Jakakolwiek wartość w `cameraDirection` skutkuje użyciem tylnej kamery.
+
+* Parametr `allowEdit` jest ignorowany.
+
+* Oba parametry `Camera.PictureSourceType.PHOTOLIBRARY` oraz `Camera.PictureSourceType.SAVEDPHOTOALBUM` wyświetlają ten sam album ze zdjęciami.
+
+### Dziwactwa Androida
+
+* Jakakolwiek wartość w `cameraDirection` skutkuje użyciem tylnej kamery.
+
+* Parametr `allowEdit` jest ignorowany.
+
+* Oba parametry `Camera.PictureSourceType.PHOTOLIBRARY` oraz `Camera.PictureSourceType.SAVEDPHOTOALBUM` wyświetlają ten sam album ze zdjęciami.
+
+### Jeżyna 10 dziwactwa
+
+* Parametr `quality` jest ignorowany.
+
+* Parametr `allowEdit` jest ignorowany.
+
+* Nie jest wspierane `Camera.MediaType`.
+
+* Parametr `correctOrientation` jest ignorowany.
+
+* Parametr `cameraDirection` jest ignorowany.
+
+### Firefox OS dziwactwa
+
+* Parametr `quality` jest ignorowany.
+
+* `Camera.DestinationType`jest ignorowane i jest równa `1` (plik obrazu URI)
+
+* Parametr `allowEdit` jest ignorowany.
+
+* Ignoruje `PictureSourceType` parametr (użytkownik wybiera go w oknie dialogowym)
+
+* Ignoruje`encodingType`
+
+* Ignoruje `targetWidth` i`targetHeight`
+
+* Nie jest wspierane `Camera.MediaType`.
+
+* Parametr `correctOrientation` jest ignorowany.
+
+* Parametr `cameraDirection` jest ignorowany.
+
+### Dziwactwa iOS
+
+* Ustaw `quality` poniżej 50 aby uniknąć błędów pamięci na niektórych urządzeniach.
+
+* Podczas korzystania z `destinationType.FILE_URI` , zdjęcia są zapisywane w katalogu tymczasowego stosowania. Zawartość katalogu tymczasowego stosowania jest usuwany po zakończeniu aplikacji.
+
+### Dziwactwa Tizen
+
+* opcje nie są obsługiwane
+
+* zawsze zwraca FILE URI
+
+### Windows Phone 7 i 8 dziwactwa
+
+* Parametr `allowEdit` jest ignorowany.
+
+* Parametr `correctOrientation` jest ignorowany.
+
+* Parametr `cameraDirection` jest ignorowany.
+
+* Ignoruje `saveToPhotoAlbum` parametr. Ważne: Wszystkie zdjęcia zrobione aparatem wp7/8 cordova API są zawsze kopiowane do telefonu w kamerze. W zależności od ustawień użytkownika może to też oznaczać że obraz jest automatycznie przesłane do ich OneDrive. Potencjalnie może to oznaczać, że obraz jest dostępne dla szerszego grona odbiorców niż Twoja aplikacja przeznaczona. Jeśli ten bloker aplikacji, trzeba będzie wdrożenie CameraCaptureTask, opisane na msdn: można także komentarz lub górę głosowanie powiązanych kwestii w [śledzenia błędów][3]
+
+* Ignoruje `mediaType` Właściwość `cameraOptions` jako SDK Windows Phone nie umożliwiają wybór filmów z PHOTOLIBRARY.
+
+ [3]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+Funkcja zwrotna onError, która zawiera komunikat o błędzie.
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### Parametry
+
+* **message**: Natywny kod komunikatu zapewniany przez urządzenie. *(Ciąg znaków)*
+
+## cameraSuccess
+
+Funkcja zwrotna onSuccess, która dostarcza dane obrazu.
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### Parametry
+
+* **imageData**: Dane obrazu kodowane przy pomocy Base64 *lub* URI pliku obrazu, w zależności od użycia `cameraOptions`. *(Ciąg znaków)*
+
+### Przykład
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+Uchwyt do okna dialogowego popover, stworzony przez`navigator.camera.getPicture`.
+
+### Metody
+
+* **setPosition**: Ustawia pozycję wyskakującego okna.
+
+### Obsługiwane platformy
+
+* iOS
+
+### setPosition
+
+Ustawia pozycję wyskakującego okna.
+
+**Parametry**:
+
+* `cameraPopoverOptions`: `CameraPopoverOptions`, która określa nową pozycję
+
+### Przykład
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+Parametry dotyczące tylko platformy iOS, które określają pozycję zakotwiczenia elementu oraz kierunek strzałki wyskakującego okna podczas wybierania obrazów z biblioteki lub albumu iPada.
+
+ { x : 0,
+ y : 32,
+ width : 320,
+ height : 480,
+ arrowDir : Camera.PopoverArrowDirection.ARROW_ANY
+ };
+
+
+### CameraPopoverOptions
+
+* **x**: współrzędna piksela x elementu ekranu, na którym zakotwiczone jest wyskakujące okno. *(Liczba)*
+
+* **y**: współrzędna piksela y elementu ekranu, na którym zakotwiczone jest wyskakujące okno. *(Liczba)*
+
+* **width**: szerokość w pikselach elementu ekranu, na którym zakotwiczone jest wyskakujące okno. *(Liczba)*
+
+* **height**: wysokość w pikselach elementu ekranu, na którym zakotwiczone jest wyskakujące okno. *(Liczba)*
+
+* **arrowDir**: Kierunek, który powinna wskazywać strzałka na wyskakującym oknie. Zdefiniowane w `Camera.PopoverArrowDirection` *(Liczba)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+Pamiętaj, że wielkość wyskakującego okna może ulec zmianie by dostosować się do kierunku strzałki oraz orientacji ekranu. Upewnij się co do zmiany orientacji podczas określania położenia zakotwiczenia elementu.
+
+## Navigator.Camera.CleanUp
+
+Usuwa pośrednie zdjęcia zrobione przez aparat z tymczasowego magazynu.
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### Opis
+
+Usuwa pośrednie pliki graficzne, które po wywołaniu `camera.getPicture` są przechowywane w tymczasowym magazynie. Ma zastosowanie tylko, gdy wartość `Camera.sourceType` jest równa `Camera.PictureSourceType.CAMERA` i `Camera.destinationType` jest równa `Camera.DestinationType.FILE_URI`.
+
+### Obsługiwane platformy
+
+* iOS
+
+### Przykład
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/doc/ru/index.md b/plugins/org.apache.cordova.camera/doc/ru/index.md
new file mode 100644
index 0000000..c7e5216
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/ru/index.md
@@ -0,0 +1,417 @@
+
+
+# org.apache.cordova.camera
+
+Этот плагин предоставляет API для съемки и для выбора изображения из библиотеки изображений системы.
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+Снимает фотографию с помощью камеры, или получает фотографию из галереи изображений устройства. Изображение передается на функцию обратного вызова успешного завершения как `String` в base64-кодировке, или как URI указывающего на файл изображения. Метод возвращает объект `CameraPopoverHandle`, который может использоваться для перемещения инструмента выбора файла.
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### Описание
+
+Функция `camera.getPicture` открывает приложение камеры устройства, которое позволяет снимать фотографии. Это происходит по умолчанию, когда `Camera.sourceType` равно `Camera.PictureSourceType.CAMERA` . Как только пользователь делает снимок,приложение камеры закрывается и приложение восстанавливается.
+
+Если `Camera.sourceType` является `Camera.PictureSourceType.PHOTOLIBRARY` или `Camera.PictureSourceType.SAVEDPHOTOALBUM` , то показывается диалоговое окно, которое позволяет пользователям выбрать существующее изображение. Функция `camera.getPicture` возвращает объект `CameraPopoverHandle` объект, который может использоваться для перемещения диалога выбора изображения, например, при изменении ориентации устройства.
+
+Возвращаемое значение отправляется в функцию обратного вызова `cameraSuccess` в одном из следующих форматов, в зависимости от параметра `cameraOptions` :
+
+* A объект `String` содержащий фото изображение в base64-кодировке.
+
+* Объект `String` представляющий расположение файла изображения на локальном хранилище (по умолчанию).
+
+Вы можете сделать все, что угодно вы хотите с закодированным изображением или URI, например:
+
+* Отобразить изображение с помощью тега ``, как показано в примере ниже
+
+* Сохранять данные локально (`LocalStorage`, [Lawnchair][1], и т.д.)
+
+* Отправлять данные на удаленный сервер
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**Примечание**: разрешение фото на более новых устройствах является достаточно хорошим. Фотографии из галереи устройства не масштабируются к более низкому качеству, даже если указан параметр `quality`. Чтобы избежать общих проблем с памятью, установите `Camera.destinationType` в `FILE_URI` вместо `DATA_URL`.
+
+### Поддерживаемые платформы
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Обозреватель
+* Firefox OS
+* iOS
+* Tizen
+* Windows Phone 7 и 8
+* Windows 8
+
+### Предпочтения (iOS)
+
+* **CameraUsesGeolocation** (логическое значение, по умолчанию false). Для захвата изображения JPEG, значение true, чтобы получить данные геопозиционирования в заголовке EXIF. Это вызовет запрос на разрешения геолокации, если задано значение true.
+
+
+
+
+### Особенности Amazon Fire OS
+
+Amazon Fire OS используют намерения для запуска активности камеры на устройстве для съемки фотографий, и на устройствах с низким объемам памяти, активность Cordova может быть завершена. В этом случае изображение может не появиться при восстановлении активности Cordova.
+
+### Особенности Android
+
+Android используют намерения для запуска активности камеры на устройстве для съемки фотографий, и на устройствах с низким объемам памяти, активность Cordova может быть завершена. В этом случае изображение может не появиться при восстановлении активности Cordova.
+
+### Браузер причуды
+
+Может возвращать только фотографии как изображения в кодировке base64.
+
+### Особенности Firefox OS
+
+Плагин Camera на данный момент реализован с использованием [Web Activities][2].
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### Особенности iOS
+
+Включение функции JavaScript `alert()` в любой из функций обратного вызова функции может вызвать проблемы. Оберните вызов alert в `setTimeout()` для позволения окну выбора изображений iOS полностью закрыться перед отображение оповещения:
+
+ setTimeout(function() {/ / ваши вещи!}, 0);
+
+
+### Особенности Windows Phone 7
+
+Вызов встроенного приложения камеры, в то время как устройство подключено к Zune не работает, и инициирует обратный вызов для ошибки.
+
+### Особенности Tizen
+
+Tizen поддерживает только значение `destinationType` равное `Camera.DestinationType.FILE_URI` и значение `sourceType` равное `Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### Пример
+
+Сделайте фотографию и получите его как изображение в base64-кодировке:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+Сделайте фотографию и получить расположение файла с изображением:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+Необязательные параметры для настройки параметров камеры.
+
+ { quality : 75,
+ destinationType : Camera.DestinationType.DATA_URL,
+ sourceType : Camera.PictureSourceType.CAMERA,
+ allowEdit : true,
+ encodingType: Camera.EncodingType.JPEG,
+ targetWidth: 100,
+ targetHeight: 100,
+ popoverOptions: CameraPopoverOptions,
+ saveToPhotoAlbum: false };
+
+
+### Параметры
+
+* **quality**: качество сохраняемого изображения, выражается в виде числа в диапазоне от 0 до 100, где 100 является обычно полным изображением без потери качества при сжатии. Значение по умолчанию — 50. *(Число)* (Обратите внимание, что информация о разрешении камеры недоступна.)
+
+* **параметр destinationType**: выберите формат возвращаемого значения. Значение по умолчанию — FILE_URI. Определяется в `navigator.camera.DestinationType` *(число)*
+
+ Camera.DestinationType = {
+ DATA_URL: 0, / / возвращение изображения в base64-кодировке строки
+ FILE_URI: 1, / / возврат файла изображения URI
+ NATIVE_URI: 2 / / возвращение образа собственного URI (например, Библиотека активов: / / на iOS или содержание: / / на андроиде)
+ };
+
+
+* **тип источника**: установить источник рисунка. По умолчанию используется камера. Определяется в `navigator.camera.PictureSourceType` *(число)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY: 0,
+ CAMERA: 1,
+ SAVEDPHOTOALBUM: 2
+ };
+
+
+* **allowEdit**: позволит редактирование изображения средствами телефона перед окончательным выбором изображения. *(Логический)*
+
+* **Тип_шифрования**: выберите возвращенный файл в кодировку. Значение по умолчанию — JPEG. Определяется в `navigator.camera.EncodingType` *(число)*
+
+ Camera.EncodingType = {
+ JPEG: 0, // возвращает изображение в формате JPEG
+ PNG: 1 // возвращает рисунок в формате PNG
+ };
+
+
+* **targetWidth**: ширина изображения в пикселах к которой необходимо осуществить масштабирование. Это значение должно использоваться совместно с **targetHeight**. Пропорции изображения останутся неизменными. *(Число)*
+
+* **targetHeight**: высота изображения в пикселах к которой необходимо осуществить масштабирование. Это значение должно использоваться совместно с **targetWidth**. Пропорции изображения останутся неизменными. *(Число)*
+
+* **тип носителя**: Установите источник получения изображения, из которого надо выбрать изображение. Работает только если `PictureSourceType` равно `PHOTOLIBRARY` или `SAVEDPHOTOALBUM` . Определяется в `nagivator.camera.MediaType` *(число)*
+
+ Camera.MediaType = {
+ PICTURE: 0, / / разрешить выбор только сохраненных изображений. DEFAULT. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **correctOrientation**: вращает изображение, чтобы внести исправления к ориентации устройства во время захвата. *(Логический)*
+
+* **saveToPhotoAlbum**: сохранить изображение в фотоальбом на устройстве после захвата. *(Логическое)*
+
+* **popoverOptions**: только для iOS параметры, которые определяют местоположение инструмента в iPad. Определены в`CameraPopoverOptions`.
+
+* **cameraDirection**: выбрать камеру для использования (передней или задней стороне). Значение по умолчанию — обратно. Определяется в `navigator.camera.Direction` *(число)*
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+
+### Особенности Amazon Fire OS
+
+* Любое значение `cameraDirection` возвращает фотографию сделанную задней камерой.
+
+* Игнорирует параметр `allowEdit`.
+
+* Оба параметра `Camera.PictureSourceType.PHOTOLIBRARY` и `Camera.PictureSourceType.SAVEDPHOTOALBUM` отображают один и тот же фотоальбом.
+
+### Особенности Android
+
+* Любое значение `cameraDirection` возвращает фотографию сделанную задней камерой.
+
+* Игнорирует параметр `allowEdit`.
+
+* Оба параметра `Camera.PictureSourceType.PHOTOLIBRARY` и `Camera.PictureSourceType.SAVEDPHOTOALBUM` отображают один и тот же фотоальбом.
+
+### Особенности BlackBerry 10
+
+* Игнорирует `quality` параметр.
+
+* Игнорирует параметр `allowEdit`.
+
+* `Camera.MediaType` не поддерживается.
+
+* Игнорирует параметр `correctOrientation`.
+
+* Игнорирует параметр `cameraDirection`.
+
+### Особенности Firefox OS
+
+* Игнорирует `quality` параметр.
+
+* Значение `Camera.DestinationType` игнорируется и равно `1` (URI для файла изображения)
+
+* Игнорирует параметр `allowEdit`.
+
+* Игнорирует параметр `PictureSourceType` (пользователь выбирает его в диалоговом окне)
+
+* Игнорирует параметр `encodingType`
+
+* Игнорирует `targetWidth` и `targetHeight`
+
+* `Camera.MediaType` не поддерживается.
+
+* Игнорирует параметр `correctOrientation`.
+
+* Игнорирует параметр `cameraDirection`.
+
+### Особенности iOS
+
+* Установите `quality` ниже 50, для того чтобы избежать ошибок памяти на некоторых устройствах.
+
+* При использовании `destinationType.FILE_URI` , фотографии сохраняются во временном каталоге приложения. Содержимое приложения временного каталога удаляется при завершении приложения.
+
+### Особенности Tizen
+
+* options, не поддерживается
+
+* всегда возвращает URI файла
+
+### Особенности Windows Phone 7 и 8
+
+* Игнорирует параметр `allowEdit`.
+
+* Игнорирует параметр `correctOrientation`.
+
+* Игнорирует параметр `cameraDirection`.
+
+* Игнорирует `saveToPhotoAlbum` параметр. Важно: Все изображения, снятые камерой wp7/8 cordova API всегда копируются в рулон камеры телефона. В зависимости от параметров пользователя это также может означать, что изображение автоматически загружены на их OneDrive. Потенциально это может означать, что этот образ доступен для более широкой аудитории, чем ваше приложение предназначено. Если этот блокатор для вашего приложения, вам нужно будет осуществить CameraCaptureTask, как описано на сайте msdn: вы можете также комментарий или вверх голосование связанный с этим вопрос [отслеживания][3]
+
+* Игнорирует свойство `mediaType` объекта `cameraOptions` так как Windows Phone SDK не предоставляет способ выбрать видео из PHOTOLIBRARY.
+
+ [3]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+Функция обратного вызова вызываемая в случае возникновения ошибки.
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### Параметры
+
+* **сообщение**: сообщение об ошибке предоставляемое платформой устройства. *(Строка)*
+
+## cameraSuccess
+
+Функция обратного вызова onSuccess, получающая данные изображения.
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### Параметры
+
+* **imageData**: Данные изображения в Base64 кодировке, *или* URI, в зависимости от применяемых параметров `cameraOptions`. *(Строка)*
+
+### Пример
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+Дескриптор диалогового окна инструмента, созданный `navigator.camera.getPicture`.
+
+### Методы
+
+* **setPosition**: Задайте положение инструмента выбора изображения.
+
+### Поддерживаемые платформы
+
+* iOS
+
+### setPosition
+
+Устанавливает положение инструмента выбора изображения.
+
+**Параметры**:
+
+* `cameraPopoverOptions`: Объект `CameraPopoverOptions`, определяющий новое положение
+
+### Пример
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+Параметры только для iOS, которые определяют расположение элемента привязки и направление стрелки инструмента при выборе изображений из библиотеки изображений iPad или альбома.
+
+ {x: 0, y: 32, ширина: 320, высота: 480, arrowDir: Camera.PopoverArrowDirection.ARROW_ANY};
+
+
+### CameraPopoverOptions
+
+* **x**: x координата в пикселях элемента экрана, на котором закрепить инструмента. *(Число)*
+
+* **x**: y координата в пикселях элемента экрана, на котором закрепить инструмента. *(Число)*
+
+* **width**: ширина в пикселях элемента экрана, на котором закрепить инструмент выбора изображения. *(Число)*
+
+* **height**: высота в пикселях элемента экрана, на котором закрепить инструмент выбора изображения. *(Число)*
+
+* **arrowDir**: Направление, куда должна указывать стрелка на инструменте. Определено в `Camera.PopoverArrowDirection` *(число)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+Обратите внимание, что размер инструмента может изменяться для корректировки в зависимости направлении стрелки и ориентации экрана. Убедитесь, что учитываете возможные изменения ориентации при указании расположения элемента привязки.
+
+## navigator.camera.cleanup
+
+Удаляет промежуточные фотографии, сделанные камерой из временного хранилища.
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### Описание
+
+Удаляет промежуточные файлы изображений, которые хранятся во временном хранилище после вызова метода `camera.getPicture` . Применяется только тогда, когда значение `Camera.sourceType` равно `Camera.PictureSourceType.CAMERA` и `Camera.destinationType` равняется `Camera.DestinationType.FILE_URI`.
+
+### Поддерживаемые платформы
+
+* iOS
+
+### Пример
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/doc/zh/index.md b/plugins/org.apache.cordova.camera/doc/zh/index.md
new file mode 100644
index 0000000..c4aefd3
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/doc/zh/index.md
@@ -0,0 +1,410 @@
+
+
+# org.apache.cordova.camera
+
+這個外掛程式提供了一個 API,拍照,從系統的圖像庫中選擇圖像。
+
+ cordova plugin add org.apache.cordova.camera
+
+
+## navigator.camera.getPicture
+
+需要使用的相機,一張照片或從設備的圖像庫檢索一張照片。 圖像作為 base64 編碼傳遞成功回檔到 `String` ,或作為影像檔的 URI。 該方法本身返回 `CameraPopoverHandle` 可以用於重新置放檔選擇彈出的物件。
+
+ navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
+
+
+### 說明
+
+`camera.getPicture`函數將打開該設備的預設攝像頭應用程式,使使用者能夠對齊圖片。 預設情況下,會發生此行為時 `Camera.sourceType` 等於 `Camera.PictureSourceType.CAMERA` 。 一旦使用者快照照片、 攝像頭應用程式關閉,並恢復該應用程式。
+
+如果 `Camera.sourceType` 是 `Camera.PictureSourceType.PHOTOLIBRARY` 或 `Camera.PictureSourceType.SAVEDPHOTOALBUM` ,然後允許使用者選擇一個現有圖像對話方塊的顯示。 `camera.getPicture`函數返回 `CameraPopoverHandle` 物件,可用於設備方向更改時重新置放圖像選擇對話方塊,例如。
+
+傳回值發送到 `cameraSuccess` 回呼函數,根據指定的以下格式之一 `cameraOptions` :
+
+* A `String` 包含的 base64 編碼的照片圖像。
+
+* A `String` 表示在本機存放區 (預設值) 上的影像檔位置。
+
+你可以做任何你想與編碼的圖像或 URI,例如:
+
+* 呈現在圖像 `` 標記,如下面的示例所示
+
+* 保存本地的資料 ( `LocalStorage` , [Lawnchair][1],等等.)
+
+* 將資料發佈到遠端伺服器
+
+ [1]: http://brianleroux.github.com/lawnchair/
+
+**注**: 在較新的設備上的照片解析度是相當好。 從設備的庫選擇了照片不到較低的品質,壓縮螢幕使即使 `quality` 指定參數。 為了避免常見的記憶體問題,設置 `Camera.destinationType` 到 `FILE_URI` 而不是`DATA_URL`.
+
+### 支援的平臺
+
+* 亞馬遜火 OS
+* Android 系統
+* 黑莓 10
+* 瀏覽器
+* 火狐瀏覽器的作業系統
+* iOS
+* 泰
+* Windows Phone 7 和 8
+* Windows 8
+
+### 首選項 (iOS)
+
+* **CameraUsesGeolocation**(布林值,預設值為 false)。 用於捕獲 jpeg 檔,設置為 true,以在 EXIF 頭資訊中獲取地理定位資料。 這將觸發請求的地理位置的許可權,如果設置為 true。
+
+
+
+
+### 亞馬遜火 OS 怪癖
+
+亞馬遜火 OS 使用意向啟動捕獲圖像,在設備上的相機活動和與低記憶體手機,科爾多瓦活動可能被殺。 在此方案中,可能不會顯示圖像還原科爾多瓦活動時。
+
+### Android 的怪癖
+
+Android 使用意向啟動捕獲圖像,在設備上的相機活動和與低記憶體手機,科爾多瓦活動可能被殺。 在此方案中,可能不會顯示圖像還原科爾多瓦活動時。
+
+### 瀏覽器的怪癖
+
+可以只返回照片作為 base64 編碼的圖像。
+
+### 火狐瀏覽器作業系統的怪癖
+
+觀景窗外掛程式目前實施使用[Web 活動][2].
+
+ [2]: https://hacks.mozilla.org/2013/01/introducing-web-activities/
+
+### iOS 的怪癖
+
+包括 JavaScript `alert()` 在任何回呼函數可能會導致問題。 包裝內的警報 `setTimeout()` 允許 iOS 圖像選取器或氣泡框以完全關閉之前,警報將顯示:
+
+ setTimeout(function() {/ / 做你的事!},0) ;
+
+
+### Windows Phone 7 的怪癖
+
+調用本機攝像頭應用程式,而通過 Zune 所連接的設備不能工作,並且觸發錯誤回檔。
+
+### 泰怪癖
+
+泰僅支援 `destinationType` 的 `Camera.DestinationType.FILE_URI` 和 `sourceType` 的`Camera.PictureSourceType.PHOTOLIBRARY`.
+
+### 示例
+
+拍一張照片,並檢索它作為一個 base64 編碼的圖像:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.DATA_URL
+ });
+
+ function onSuccess(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+拍一張照片和檢索圖像的檔位置:
+
+ navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
+ destinationType: Camera.DestinationType.FILE_URI });
+
+ function onSuccess(imageURI) {
+ var image = document.getElementById('myImage');
+ image.src = imageURI;
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
+
+
+## CameraOptions
+
+要自訂相機設置的可選參數。
+
+ {品質: 75,destinationType: Camera.DestinationType.DATA_URL,sourceType: Camera.PictureSourceType.CAMERA,allowEdit: 為 true,encodingType: Camera.EncodingType.JPEG,targetWidth: 100,targetHeight: 100,popoverOptions: CameraPopoverOptions,saveToPhotoAlbum: 虛假} ;
+
+
+### 選項
+
+* **品質**: 保存的圖像,表示為範圍 0-100,100,是通常全解析度,無損失從檔案壓縮的品質。 預設值為 50。 *(人數)*(請注意相機的解析度有關的資訊是不可用)。
+
+* **可**: 選擇傳回值的格式。預設值是 FILE_URI。定義在 `navigator.camera.DestinationType` *(人數)*
+
+ Camera.DestinationType = {
+ DATA_URL : 0, // Return image as base64-encoded string
+ FILE_URI : 1, // Return image file URI
+ NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)
+ };
+
+
+* **時**: 設置圖片的來源。預設值是觀景窗。定義在 `navigator.camera.PictureSourceType` *(人數)*
+
+ Camera.PictureSourceType = {
+ PHOTOLIBRARY : 0,
+ CAMERA : 1,
+ SAVEDPHOTOALBUM : 2
+ };
+
+
+* **allowEdit**: 允許簡單編輯前選擇圖像。*(布林)*
+
+* **encodingType**: 選擇返回的影像檔的編碼。預設值為 JPEG。定義在 `navigator.camera.EncodingType` *(人數)*
+
+ Camera.EncodingType = {
+ JPEG : 0, // Return JPEG encoded image
+ PNG : 1 // Return PNG encoded image
+ };
+
+
+* **targetWidth**: 向尺度圖像的圖元寬度。必須用**targetHeight**。縱橫比保持不變。*(人數)*
+
+* **targetHeight**: 以圖元為單位向尺度圖像的高度。必須用**targetWidth**。縱橫比保持不變。*(人數)*
+
+* **媒體類型**: 設置的媒體,從選擇類型。 時才起作用 `PictureSourceType` 是 `PHOTOLIBRARY` 或 `SAVEDPHOTOALBUM` 。 定義在 `nagivator.camera.MediaType` *(人數)*
+
+ Camera.MediaType = {
+ PICTURE: 0, // allow selection of still pictures only. 預設情況。 Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
+ ALLMEDIA : 2 // allow selection from all media types
+ };
+
+
+* **correctOrientation**: 旋轉圖像,該設備時捕獲的定向的正確。*(布林)*
+
+* **saveToPhotoAlbum**: 將圖像保存到相冊在設備上捕獲後。*(布林)*
+
+* **popoverOptions**: 只有 iOS 在 iPad 中指定氣泡框位置的選項。在中定義`CameraPopoverOptions`.
+
+* **cameraDirection**: 選擇相機以使用 (前面或後面-面向)。預設值是背。定義在 `navigator.camera.Direction` *(人數)*
+
+ Camera.Direction = {
+ BACK : 0, // Use the back-facing camera
+ FRONT : 1 // Use the front-facing camera
+ };
+
+
+### 亞馬遜火 OS 怪癖
+
+* 任何 `cameraDirection` 值回朝的照片中的結果。
+
+* 忽略 `allowEdit` 參數。
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`和 `Camera.PictureSourceType.SAVEDPHOTOALBUM` 都顯示相同的相冊。
+
+### Android 的怪癖
+
+* 任何 `cameraDirection` 值結果在背面的照片。
+
+* 忽略 `allowEdit` 參數。
+
+* `Camera.PictureSourceType.PHOTOLIBRARY`和 `Camera.PictureSourceType.SAVEDPHOTOALBUM` 都顯示相同的寫真集。
+
+### 黑莓 10 的怪癖
+
+* 忽略 `quality` 參數。
+
+* 忽略 `allowEdit` 參數。
+
+* `Camera.MediaType`不受支援。
+
+* 忽略 `correctOrientation` 參數。
+
+* 忽略 `cameraDirection` 參數。
+
+### 火狐瀏覽器作業系統的怪癖
+
+* 忽略 `quality` 參數。
+
+* `Camera.DestinationType`將被忽略並且等於 `1` (影像檔的 URI)
+
+* 忽略 `allowEdit` 參數。
+
+* 忽略 `PictureSourceType` 參數 (使用者選擇它在對話方塊視窗中)
+
+* 忽略`encodingType`
+
+* 忽略了 `targetWidth` 和`targetHeight`
+
+* `Camera.MediaType`不受支援。
+
+* 忽略 `correctOrientation` 參數。
+
+* 忽略 `cameraDirection` 參數。
+
+### iOS 的怪癖
+
+* 設置 `quality` 低於 50,避免在某些設備上的記憶體不足錯誤。
+
+* 當使用 `destinationType.FILE_URI` ,照片都保存在應用程式的臨時目錄。應用程式結束時,將刪除該應用程式的臨時目錄中的內容。
+
+### 泰怪癖
+
+* 不支援的選項
+
+* 總是返回一個檔的 URI
+
+### Windows Phone 7 和 8 的怪癖
+
+* 忽略 `allowEdit` 參數。
+
+* 忽略 `correctOrientation` 參數。
+
+* 忽略 `cameraDirection` 參數。
+
+* 忽略 `saveToPhotoAlbum` 參數。 重要: 使用 wp7/8 科爾多瓦攝像頭 API 拍攝的所有圖像總是都複製到手機的相機膠捲。 根據使用者的設置,這可能也意味著圖像是自動上傳到他們另。 這有可能意味著的圖像,可以比你的應用程式的目的更多的觀眾。 如果此阻滯劑您的應用程式,您將需要實現 CameraCaptureTask 在 msdn 上記載: [HTTP://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx][3]你可能還評論或在[問題追蹤器][4]的向上投票的相關的問題
+
+* 忽略了 `mediaType` 屬性的 `cameraOptions` 作為 Windows Phone SDK 並不提供從 PHOTOLIBRARY 中選擇視頻的方法。
+
+ [3]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx
+ [4]: https://issues.apache.org/jira/browse/CB-2083
+
+## CameraError
+
+onError 的回呼函數提供了一條錯誤訊息。
+
+ function(message) {
+ // Show a helpful message
+ }
+
+
+### 參數
+
+* **消息**: 消息提供的設備的本機代碼。*(字串)*
+
+## cameraSuccess
+
+提供的圖像資料的 onSuccess 回呼函數。
+
+ function(imageData) {
+ // Do something with the image
+ }
+
+
+### 參數
+
+* **圖像資料**: Base64 編碼進行編碼的圖像資料,*或*影像檔的 URI,取決於 `cameraOptions` 效果。*(字串)*
+
+### 示例
+
+ // Show image
+ //
+ function cameraCallback(imageData) {
+ var image = document.getElementById('myImage');
+ image.src = "data:image/jpeg;base64," + imageData;
+ }
+
+
+## CameraPopoverHandle
+
+由創建的氣泡框對話方塊的控制碼`navigator.camera.getPicture`.
+
+### 方法
+
+* **setPosition**: 設置氣泡框的位置。
+
+### 支援的平臺
+
+* iOS
+
+### setPosition
+
+設置氣泡框的位置。
+
+**參數**:
+
+* `cameraPopoverOptions`: `CameraPopoverOptions` ,指定新的位置
+
+### 示例
+
+ var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
+ { destinationType: Camera.DestinationType.FILE_URI,
+ sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
+ popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)
+ });
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function() {
+ var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
+ cameraPopoverHandle.setPosition(cameraPopoverOptions);
+ }
+
+
+## CameraPopoverOptions
+
+iOS 僅指定氣泡框的錨元素的位置和箭頭方向,從 iPad 庫或專輯選擇圖像時的參數。
+
+ {x: 0,y: 32,寬度: 320,高度: 480,arrowDir: Camera.PopoverArrowDirection.ARROW_ANY} ;
+
+
+### CameraPopoverOptions
+
+* **x**: x 螢幕元素到其錨定氣泡框上的圖元座標。*(人數)*
+
+* **y**: 螢幕元素到其錨定氣泡框上的 y 圖元座標。*(人數)*
+
+* **寬度**: 寬度以圖元為單位),到其錨定氣泡框上的螢幕元素。*(人數)*
+
+* **高度**: 高度以圖元為單位),到其錨定氣泡框上的螢幕元素。*(人數)*
+
+* **arrowDir**: 氣泡框上的箭頭應指向的方向。定義在 `Camera.PopoverArrowDirection` *(人數)*
+
+ Camera.PopoverArrowDirection = {
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ };
+
+
+請注意氣泡框的大小可能會更改箭頭的方向和螢幕的方向進行調整。 請確保帳戶方向更改時指定錨元素位置。
+
+## navigator.camera.cleanup
+
+刪除中間從臨時存儲攝像機所拍攝的照片。
+
+ navigator.camera.cleanup( cameraSuccess, cameraError );
+
+
+### 描述
+
+刪除中間打完電話後保留在臨時存儲中的影像檔 `camera.getPicture` 。 只有當適用的價值 `Camera.sourceType` 等於 `Camera.PictureSourceType.CAMERA` 和 `Camera.destinationType` 等於`Camera.DestinationType.FILE_URI`.
+
+### 支援的平臺
+
+* iOS
+
+### 示例
+
+ navigator.camera.cleanup(onSuccess, onFail);
+
+ function onSuccess() {
+ console.log("Camera cleanup success.")
+ }
+
+ function onFail(message) {
+ alert('Failed because: ' + message);
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/package.json b/plugins/org.apache.cordova.camera/package.json
new file mode 100644
index 0000000..eb32fcb
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/package.json
@@ -0,0 +1,28 @@
+{
+ "version": "0.3.5",
+ "name": "org.apache.cordova.camera",
+ "cordova_name": "Camera",
+ "description": "Cordova Camera Plugin",
+ "license": "Apache 2.0",
+ "repo": "https://git-wip-us.apache.org/repos/asf/cordova-plugin-camera.git",
+ "issue": "https://issues.apache.org/jira/browse/CB/component/12320645",
+ "keywords": [
+ "cordova",
+ "camera"
+ ],
+ "platforms": [
+ "firefoxos",
+ "android",
+ "amazon-fireos",
+ "ubuntu",
+ "ios",
+ "blackberry10",
+ "wp7",
+ "wp8",
+ "windows8",
+ "browser",
+ "windows"
+ ],
+ "engines": [],
+ "englishdoc": "\n\n# org.apache.cordova.camera\n\nThis plugin defines a global `navigator.camera` object, which provides an API for taking pictures and for choosing images from\nthe system's image library.\n\nAlthough the object is attached to the global scoped `navigator`, it is not available until after the `deviceready` event.\n\n document.addEventListener(\"deviceready\", onDeviceReady, false);\n function onDeviceReady() {\n console.log(navigator.camera);\n }\n\n## Installation\n\n cordova plugin add org.apache.cordova.camera\n\n## navigator.camera.getPicture\n\nTakes a photo using the camera, or retrieves a photo from the device's\nimage gallery. The image is passed to the success callback as a\nbase64-encoded `String`, or as the URI for the image file. The method\nitself returns a `CameraPopoverHandle` object that can be used to\nreposition the file selection popover.\n\n navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );\n\n### Description\n\nThe `camera.getPicture` function opens the device's default camera\napplication that allows users to snap pictures. This behavior occurs\nby default, when `Camera.sourceType` equals\n`Camera.PictureSourceType.CAMERA`. Once the user snaps the photo, the\ncamera application closes and the application is restored.\n\nIf `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or\n`Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays\nthat allows users to select an existing image. The\n`camera.getPicture` function returns a `CameraPopoverHandle` object,\nwhich can be used to reposition the image selection dialog, for\nexample, when the device orientation changes.\n\nThe return value is sent to the `cameraSuccess` callback function, in\none of the following formats, depending on the specified\n`cameraOptions`:\n\n- A `String` containing the base64-encoded photo image.\n\n- A `String` representing the image file location on local storage (default).\n\nYou can do whatever you want with the encoded image or URI, for\nexample:\n\n- Render the image in an `` tag, as in the example below\n\n- Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)\n\n- Post the data to a remote server\n\n__NOTE__: Photo resolution on newer devices is quite good. Photos\nselected from the device's gallery are not downscaled to a lower\nquality, even if a `quality` parameter is specified. To avoid common\nmemory problems, set `Camera.destinationType` to `FILE_URI` rather\nthan `DATA_URL`.\n\n### Supported Platforms\n\n- Amazon Fire OS\n- Android\n- BlackBerry 10\n- Browser\n- Firefox OS\n- iOS\n- Tizen\n- Windows Phone 7 and 8\n- Windows 8\n\n### Preferences (iOS)\n\n- __CameraUsesGeolocation__ (boolean, defaults to false). For capturing JPEGs, set to true to get geolocation data in the EXIF header. This will trigger a request for geolocation permissions if set to true.\n\n \n\n\n### Amazon Fire OS Quirks\n\nAmazon Fire OS uses intents to launch the camera activity on the device to capture\nimages, and on phones with low memory, the Cordova activity may be killed. In this\nscenario, the image may not appear when the cordova activity is restored.\n\n### Android Quirks\n\nAndroid uses intents to launch the camera activity on the device to capture\nimages, and on phones with low memory, the Cordova activity may be killed. In this\nscenario, the image may not appear when the Cordova activity is restored.\n\n### Browser Quirks\n\nCan only return photos as base64-encoded image.\n\n### Firefox OS Quirks\n\nCamera plugin is currently implemented using [Web Activities](https://hacks.mozilla.org/2013/01/introducing-web-activities/). \n\n### iOS Quirks\n\nIncluding a JavaScript `alert()` in either of the callback functions\ncan cause problems. Wrap the alert within a `setTimeout()` to allow\nthe iOS image picker or popover to fully close before the alert\ndisplays:\n\n setTimeout(function() {\n // do your thing here!\n }, 0);\n\n### Windows Phone 7 Quirks\n\nInvoking the native camera application while the device is connected\nvia Zune does not work, and triggers an error callback.\n\n### Tizen Quirks\n\nTizen only supports a `destinationType` of\n`Camera.DestinationType.FILE_URI` and a `sourceType` of\n`Camera.PictureSourceType.PHOTOLIBRARY`.\n\n### Example\n\nTake a photo and retrieve it as a base64-encoded image:\n\n navigator.camera.getPicture(onSuccess, onFail, { quality: 50,\n destinationType: Camera.DestinationType.DATA_URL\n });\n\n function onSuccess(imageData) {\n var image = document.getElementById('myImage');\n image.src = \"data:image/jpeg;base64,\" + imageData;\n }\n\n function onFail(message) {\n alert('Failed because: ' + message);\n }\n\nTake a photo and retrieve the image's file location:\n\n navigator.camera.getPicture(onSuccess, onFail, { quality: 50,\n destinationType: Camera.DestinationType.FILE_URI });\n\n function onSuccess(imageURI) {\n var image = document.getElementById('myImage');\n image.src = imageURI;\n }\n\n function onFail(message) {\n alert('Failed because: ' + message);\n }\n\n## CameraOptions\n\nOptional parameters to customize the camera settings.\n\n { quality : 75,\n destinationType : Camera.DestinationType.DATA_URL,\n sourceType : Camera.PictureSourceType.CAMERA,\n allowEdit : true,\n encodingType: Camera.EncodingType.JPEG,\n targetWidth: 100,\n targetHeight: 100,\n popoverOptions: CameraPopoverOptions,\n saveToPhotoAlbum: false };\n\n### Options\n\n- __quality__: Quality of the saved image, expressed as a range of 0-100, where 100 is typically full resolution with no loss from file compression. The default is 50. _(Number)_ (Note that information about the camera's resolution is unavailable.)\n\n- __destinationType__: Choose the format of the return value. The default is FILE_URI. Defined in `navigator.camera.DestinationType` _(Number)_\n\n Camera.DestinationType = {\n DATA_URL : 0, // Return image as base64-encoded string\n FILE_URI : 1, // Return image file URI\n NATIVE_URI : 2 // Return image native URI (e.g., assets-library:// on iOS or content:// on Android)\n };\n\n- __sourceType__: Set the source of the picture. The default is CAMERA. Defined in `navigator.camera.PictureSourceType` _(Number)_\n\n Camera.PictureSourceType = {\n PHOTOLIBRARY : 0,\n CAMERA : 1,\n SAVEDPHOTOALBUM : 2\n };\n\n- __allowEdit__: Allow simple editing of image before selection. _(Boolean)_\n\n- __encodingType__: Choose the returned image file's encoding. Default is JPEG. Defined in `navigator.camera.EncodingType` _(Number)_\n\n Camera.EncodingType = {\n JPEG : 0, // Return JPEG encoded image\n PNG : 1 // Return PNG encoded image\n };\n\n- __targetWidth__: Width in pixels to scale image. Must be used with __targetHeight__. Aspect ratio remains constant. _(Number)_\n\n- __targetHeight__: Height in pixels to scale image. Must be used with __targetWidth__. Aspect ratio remains constant. _(Number)_\n\n- __mediaType__: Set the type of media to select from. Only works when `PictureSourceType` is `PHOTOLIBRARY` or `SAVEDPHOTOALBUM`. Defined in `nagivator.camera.MediaType` _(Number)_\n\n Camera.MediaType = {\n PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType\n VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI\n ALLMEDIA : 2 // allow selection from all media types\n };\n\n- __correctOrientation__: Rotate the image to correct for the orientation of the device during capture. _(Boolean)_\n\n- __saveToPhotoAlbum__: Save the image to the photo album on the device after capture. _(Boolean)_\n\n- __popoverOptions__: iOS-only options that specify popover location in iPad. Defined in `CameraPopoverOptions`.\n\n- __cameraDirection__: Choose the camera to use (front- or back-facing). The default is BACK. Defined in `navigator.camera.Direction` _(Number)_\n\n Camera.Direction = {\n BACK : 0, // Use the back-facing camera\n FRONT : 1 // Use the front-facing camera\n };\n\n### Amazon Fire OS Quirks\n\n- Any `cameraDirection` value results in a back-facing photo.\n\n- Ignores the `allowEdit` parameter.\n\n- `Camera.PictureSourceType.PHOTOLIBRARY` and `Camera.PictureSourceType.SAVEDPHOTOALBUM` both display the same photo album.\n\n### Android Quirks\n\n- Any `cameraDirection` value results in a back-facing photo.\n\n- Ignores the `allowEdit` parameter.\n\n- `Camera.PictureSourceType.PHOTOLIBRARY` and `Camera.PictureSourceType.SAVEDPHOTOALBUM` both display the same photo album.\n\n### BlackBerry 10 Quirks\n\n- Ignores the `quality` parameter.\n\n- Ignores the `allowEdit` parameter.\n\n- `Camera.MediaType` is not supported.\n\n- Ignores the `correctOrientation` parameter.\n\n- Ignores the `cameraDirection` parameter.\n\n### Firefox OS Quirks\n\n- Ignores the `quality` parameter.\n\n- `Camera.DestinationType` is ignored and equals `1` (image file URI)\n\n- Ignores the `allowEdit` parameter.\n\n- Ignores the `PictureSourceType` parameter (user chooses it in a dialog window)\n\n- Ignores the `encodingType`\n\n- Ignores the `targetWidth` and `targetHeight`\n\n- `Camera.MediaType` is not supported.\n\n- Ignores the `correctOrientation` parameter.\n\n- Ignores the `cameraDirection` parameter.\n\n### iOS Quirks\n\n- Set `quality` below 50 to avoid memory errors on some devices.\n\n- When using `destinationType.FILE_URI`, photos are saved in the application's temporary directory. The contents of the application's temporary directory is deleted when the application ends.\n\n### Tizen Quirks\n\n- options not supported\n\n- always returns a FILE URI\n\n### Windows Phone 7 and 8 Quirks\n\n- Ignores the `allowEdit` parameter.\n\n- Ignores the `correctOrientation` parameter.\n\n- Ignores the `cameraDirection` parameter.\n\n- Ignores the `saveToPhotoAlbum` parameter. IMPORTANT: All images taken with the wp7/8 cordova camera API are always copied to the phone's camera roll. Depending on the user's settings, this could also mean the image is auto-uploaded to their OneDrive. This could potentially mean the image is available to a wider audience than your app intended. If this a blocker for your application, you will need to implement the CameraCaptureTask as documented on msdn : [http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394006.aspx)\nYou may also comment or up-vote the related issue in the [issue tracker](https://issues.apache.org/jira/browse/CB-2083)\n\n- Ignores the `mediaType` property of `cameraOptions` as the Windows Phone SDK does not provide a way to choose videos from PHOTOLIBRARY.\n\n\n## CameraError\n\nonError callback function that provides an error message.\n\n function(message) {\n // Show a helpful message\n }\n\n### Parameters\n\n- __message__: The message is provided by the device's native code. _(String)_\n\n\n## cameraSuccess\n\nonSuccess callback function that provides the image data.\n\n function(imageData) {\n // Do something with the image\n }\n\n### Parameters\n\n- __imageData__: Base64 encoding of the image data, _or_ the image file URI, depending on `cameraOptions` in effect. _(String)_\n\n### Example\n\n // Show image\n //\n function cameraCallback(imageData) {\n var image = document.getElementById('myImage');\n image.src = \"data:image/jpeg;base64,\" + imageData;\n }\n\n\n## CameraPopoverHandle\n\nA handle to the popover dialog created by `navigator.camera.getPicture`.\n\n### Methods\n\n- __setPosition__: Set the position of the popover.\n\n### Supported Platforms\n\n- iOS\n\n### setPosition\n\nSet the position of the popover.\n\n__Parameters__:\n\n- `cameraPopoverOptions`: the `CameraPopoverOptions` that specify the new position\n\n### Example\n\n var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,\n { destinationType: Camera.DestinationType.FILE_URI,\n sourceType: Camera.PictureSourceType.PHOTOLIBRARY,\n popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY)\n });\n\n // Reposition the popover if the orientation changes.\n window.onorientationchange = function() {\n var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);\n cameraPopoverHandle.setPosition(cameraPopoverOptions);\n }\n\n\n## CameraPopoverOptions\n\niOS-only parameters that specify the anchor element location and arrow\ndirection of the popover when selecting images from an iPad's library\nor album.\n\n { x : 0,\n y : 32,\n width : 320,\n height : 480,\n arrowDir : Camera.PopoverArrowDirection.ARROW_ANY\n };\n\n### CameraPopoverOptions\n\n- __x__: x pixel coordinate of screen element onto which to anchor the popover. _(Number)_\n\n- __y__: y pixel coordinate of screen element onto which to anchor the popover. _(Number)_\n\n- __width__: width, in pixels, of the screen element onto which to anchor the popover. _(Number)_\n\n- __height__: height, in pixels, of the screen element onto which to anchor the popover. _(Number)_\n\n- __arrowDir__: Direction the arrow on the popover should point. Defined in `Camera.PopoverArrowDirection` _(Number)_\n\n Camera.PopoverArrowDirection = {\n ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants\n ARROW_DOWN : 2,\n ARROW_LEFT : 4,\n ARROW_RIGHT : 8,\n ARROW_ANY : 15\n };\n\nNote that the size of the popover may change to adjust to the\ndirection of the arrow and orientation of the screen. Make sure to\naccount for orientation changes when specifying the anchor element\nlocation.\n\n## navigator.camera.cleanup\n\nRemoves intermediate photos taken by the camera from temporary\nstorage.\n\n navigator.camera.cleanup( cameraSuccess, cameraError );\n\n### Description\n\nRemoves intermediate image files that are kept in temporary storage\nafter calling `camera.getPicture`. Applies only when the value of\n`Camera.sourceType` equals `Camera.PictureSourceType.CAMERA` and the\n`Camera.destinationType` equals `Camera.DestinationType.FILE_URI`.\n\n### Supported Platforms\n\n- iOS\n\n### Example\n\n navigator.camera.cleanup(onSuccess, onFail);\n\n function onSuccess() {\n console.log(\"Camera cleanup success.\")\n }\n\n function onFail(message) {\n alert('Failed because: ' + message);\n }\n\n"
+}
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/plugin.xml b/plugins/org.apache.cordova.camera/plugin.xml
new file mode 100644
index 0000000..76caeee
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/plugin.xml
@@ -0,0 +1,266 @@
+
+
+
+
+ Camera
+ Cordova Camera Plugin
+ Apache 2.0
+ cordova,camera
+ https://git-wip-us.apache.org/repos/asf/cordova-plugin-camera.git
+ https://issues.apache.org/jira/browse/CB/component/12320645
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ access_shared
+ use_camera
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/src/android/CameraLauncher.java b/plugins/org.apache.cordova.camera/src/android/CameraLauncher.java
new file mode 100644
index 0000000..b5574ab
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/android/CameraLauncher.java
@@ -0,0 +1,972 @@
+/*
+ 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.
+*/
+package org.apache.cordova.camera;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.LOG;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.util.Base64;
+import android.util.Log;
+
+/**
+ * This class launches the camera view, allows the user to take a picture, closes the camera view,
+ * and returns the captured image. When the camera view is closed, the screen displayed before
+ * the camera view was shown is redisplayed.
+ */
+public class CameraLauncher extends CordovaPlugin implements MediaScannerConnectionClient {
+
+ private static final int DATA_URL = 0; // Return base64 encoded string
+ private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android)
+ private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI
+
+ private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
+ private static final int CAMERA = 1; // Take picture from camera
+ private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture library (same as PHOTOLIBRARY for Android)
+
+ private static final int PICTURE = 0; // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
+ private static final int VIDEO = 1; // allow selection of video only, ONLY RETURNS URL
+ private static final int ALLMEDIA = 2; // allow selection from all media types
+
+ private static final int JPEG = 0; // Take a picture of type JPEG
+ private static final int PNG = 1; // Take a picture of type PNG
+ private static final String GET_PICTURE = "Get Picture";
+ private static final String GET_VIDEO = "Get Video";
+ private static final String GET_All = "Get All";
+
+ private static final String LOG_TAG = "CameraLauncher";
+ private static final int CROP_CAMERA = 100;
+
+ private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
+ private int targetWidth; // desired width of the image
+ private int targetHeight; // desired height of the image
+ private Uri imageUri; // Uri of captured image
+ private int encodingType; // Type of encoding to use
+ private int mediaType; // What type of media to retrieve
+ private boolean saveToPhotoAlbum; // Should the picture be saved to the device's photo album
+ private boolean correctOrientation; // Should the pictures orientation be corrected
+ private boolean orientationCorrected; // Has the picture's orientation been corrected
+ private boolean allowEdit; // Should we allow the user to crop the image.
+
+ public CallbackContext callbackContext;
+ private int numPics;
+
+ private MediaScannerConnection conn; // Used to update gallery app with newly-written files
+ private Uri scanMe; // Uri of image to be added to content store
+ private Uri croppedUri;
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @param callbackContext The callback id used when calling back into JavaScript.
+ * @return A PluginResult object with a status and message.
+ */
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ this.callbackContext = callbackContext;
+
+ if (action.equals("takePicture")) {
+ int srcType = CAMERA;
+ int destType = FILE_URI;
+ this.saveToPhotoAlbum = false;
+ this.targetHeight = 0;
+ this.targetWidth = 0;
+ this.encodingType = JPEG;
+ this.mediaType = PICTURE;
+ this.mQuality = 80;
+
+ this.mQuality = args.getInt(0);
+ destType = args.getInt(1);
+ srcType = args.getInt(2);
+ this.targetWidth = args.getInt(3);
+ this.targetHeight = args.getInt(4);
+ this.encodingType = args.getInt(5);
+ this.mediaType = args.getInt(6);
+ this.allowEdit = args.getBoolean(7);
+ this.correctOrientation = args.getBoolean(8);
+ this.saveToPhotoAlbum = args.getBoolean(9);
+
+ // If the user specifies a 0 or smaller width/height
+ // make it -1 so later comparisons succeed
+ if (this.targetWidth < 1) {
+ this.targetWidth = -1;
+ }
+ if (this.targetHeight < 1) {
+ this.targetHeight = -1;
+ }
+
+ try {
+ if (srcType == CAMERA) {
+ this.takePicture(destType, encodingType);
+ }
+ else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
+ this.getImage(srcType, destType, encodingType);
+ }
+ }
+ catch (IllegalArgumentException e)
+ {
+ callbackContext.error("Illegal Argument Exception");
+ PluginResult r = new PluginResult(PluginResult.Status.ERROR);
+ callbackContext.sendPluginResult(r);
+ return true;
+ }
+
+ PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
+ r.setKeepCallback(true);
+ callbackContext.sendPluginResult(r);
+
+ return true;
+ }
+ return false;
+ }
+
+ //--------------------------------------------------------------------------
+ // LOCAL METHODS
+ //--------------------------------------------------------------------------
+
+ private String getTempDirectoryPath() {
+ File cache = null;
+
+ // SD Card Mounted
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ cache = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
+ "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/");
+ }
+ // Use internal storage
+ else {
+ cache = cordova.getActivity().getCacheDir();
+ }
+
+ // Create the cache directory if it doesn't exist
+ cache.mkdirs();
+ return cache.getAbsolutePath();
+ }
+
+ /**
+ * Take a picture with the camera.
+ * When an image is captured or the camera view is cancelled, the result is returned
+ * in CordovaActivity.onActivityResult, which forwards the result to this.onActivityResult.
+ *
+ * The image can either be returned as a base64 string or a URI that points to the file.
+ * To display base64 string in an img tag, set the source to:
+ * img.src="data:image/jpeg;base64,"+result;
+ * or to display URI in an img tag
+ * img.src=result;
+ *
+ * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
+ * @param returnType Set the type of image to return.
+ */
+ public void takePicture(int returnType, int encodingType) {
+ // Save the number of images currently on disk for later
+ this.numPics = queryImgDB(whichContentStore()).getCount();
+
+ // Display camera
+ Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
+
+ // Specify file so that large image is captured and returned
+ File photo = createCaptureFile(encodingType);
+ intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
+ this.imageUri = Uri.fromFile(photo);
+
+ if (this.cordova != null) {
+ this.cordova.startActivityForResult((CordovaPlugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
+ }
+// else
+// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity");
+ }
+
+ /**
+ * Create a file in the applications temporary directory based upon the supplied encoding.
+ *
+ * @param encodingType of the image to be taken
+ * @return a File object pointing to the temporary picture
+ */
+ private File createCaptureFile(int encodingType) {
+ File photo = null;
+ if (encodingType == JPEG) {
+ photo = new File(getTempDirectoryPath(), ".Pic.jpg");
+ } else if (encodingType == PNG) {
+ photo = new File(getTempDirectoryPath(), ".Pic.png");
+ } else {
+ throw new IllegalArgumentException("Invalid Encoding Type: " + encodingType);
+ }
+ return photo;
+ }
+
+ /**
+ * Get image from photo library.
+ *
+ * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
+ * @param srcType The album to get image from.
+ * @param returnType Set the type of image to return.
+ * @param encodingType
+ */
+ // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do!
+ // TODO: Images from kitkat filechooser not going into crop function
+ public void getImage(int srcType, int returnType, int encodingType) {
+ Intent intent = new Intent();
+ String title = GET_PICTURE;
+ croppedUri = null;
+ if (this.mediaType == PICTURE) {
+ intent.setType("image/*");
+ if (this.allowEdit) {
+ intent.setAction(Intent.ACTION_PICK);
+ intent.putExtra("crop", "true");
+ if (targetWidth > 0) {
+ intent.putExtra("outputX", targetWidth);
+ }
+ if (targetHeight > 0) {
+ intent.putExtra("outputY", targetHeight);
+ }
+ if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) {
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ }
+ File photo = createCaptureFile(encodingType);
+ croppedUri = Uri.fromFile(photo);
+ intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, croppedUri);
+ } else {
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ }
+ } else if (this.mediaType == VIDEO) {
+ intent.setType("video/*");
+ title = GET_VIDEO;
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ } else if (this.mediaType == ALLMEDIA) {
+ // I wanted to make the type 'image/*, video/*' but this does not work on all versions
+ // of android so I had to go with the wildcard search.
+ intent.setType("*/*");
+ title = GET_All;
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ }
+ if (this.cordova != null) {
+ this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent,
+ new String(title)), (srcType + 1) * 16 + returnType + 1);
+ }
+ }
+
+ /**
+ * Brings up the UI to perform crop on passed image URI
+ *
+ * @param picUri
+ */
+ private void performCrop(Uri picUri) {
+ try {
+ Intent cropIntent = new Intent("com.android.camera.action.CROP");
+ // indicate image type and Uri
+ cropIntent.setDataAndType(picUri, "image/*");
+ // set crop properties
+ cropIntent.putExtra("crop", "true");
+ // indicate output X and Y
+ if (targetWidth > 0) {
+ cropIntent.putExtra("outputX", targetWidth);
+ }
+ if (targetHeight > 0) {
+ cropIntent.putExtra("outputY", targetHeight);
+ }
+ if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) {
+ cropIntent.putExtra("aspectX", 1);
+ cropIntent.putExtra("aspectY", 1);
+ }
+ // create new file handle to get full resolution crop
+ croppedUri = Uri.fromFile(new File(getTempDirectoryPath(), System.currentTimeMillis() + ".jpg"));
+ cropIntent.putExtra("output", croppedUri);
+
+ // start the activity - we handle returning in onActivityResult
+
+ if (this.cordova != null) {
+ this.cordova.startActivityForResult((CordovaPlugin) this,
+ cropIntent, CROP_CAMERA);
+ }
+ } catch (ActivityNotFoundException anfe) {
+ Log.e(LOG_TAG, "Crop operation not supported on this device");
+ // Send Uri back to JavaScript for viewing image
+ this.callbackContext.success(picUri.toString());
+ }
+ }
+
+ /**
+ * Applies all needed transformation to the image received from the camera.
+ *
+ * @param destType In which form should we return the image
+ * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
+ */
+ private void processResultFromCamera(int destType, Intent intent) throws IOException {
+ int rotate = 0;
+
+ // Create an ExifHelper to save the exif data that is lost during compression
+ ExifHelper exif = new ExifHelper();
+ try {
+ if (this.encodingType == JPEG) {
+ exif.createInFile(getTempDirectoryPath() + "/.Pic.jpg");
+ exif.readExifData();
+ rotate = exif.getOrientation();
+ } else if (this.encodingType == PNG) {
+ exif.createInFile(getTempDirectoryPath() + "/.Pic.png");
+ exif.readExifData();
+ rotate = exif.getOrientation();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ Bitmap bitmap = null;
+ Uri uri = null;
+
+ // If sending base64 image back
+ if (destType == DATA_URL) {
+ bitmap = getScaledBitmap(FileHelper.stripFileProtocol(imageUri.toString()));
+ if (bitmap == null) {
+ // Try to get the bitmap from intent.
+ bitmap = (Bitmap)intent.getExtras().get("data");
+ }
+
+ // Double-check the bitmap.
+ if (bitmap == null) {
+ Log.d(LOG_TAG, "I either have a null image path or bitmap");
+ this.failPicture("Unable to create bitmap!");
+ return;
+ }
+
+ if (rotate != 0 && this.correctOrientation) {
+ bitmap = getRotatedBitmap(rotate, bitmap, exif);
+ }
+
+ this.processPicture(bitmap);
+ checkForDuplicateImage(DATA_URL);
+ }
+
+ // If sending filename back
+ else if (destType == FILE_URI || destType == NATIVE_URI) {
+ if (this.saveToPhotoAlbum) {
+ Uri inputUri = getUriFromMediaStore();
+ try {
+ //Just because we have a media URI doesn't mean we have a real file, we need to make it
+ uri = Uri.fromFile(new File(FileHelper.getRealPath(inputUri, this.cordova)));
+ } catch (NullPointerException e) {
+ uri = null;
+ }
+ } else {
+ uri = Uri.fromFile(new File(getTempDirectoryPath(), System.currentTimeMillis() + ".jpg"));
+ }
+
+ if (uri == null) {
+ this.failPicture("Error capturing image - no media storage found.");
+ return;
+ }
+
+ // If all this is true we shouldn't compress the image.
+ if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 &&
+ !this.correctOrientation) {
+ writeUncompressedImage(uri);
+
+ this.callbackContext.success(uri.toString());
+ } else {
+ bitmap = getScaledBitmap(FileHelper.stripFileProtocol(imageUri.toString()));
+
+ if (rotate != 0 && this.correctOrientation) {
+ bitmap = getRotatedBitmap(rotate, bitmap, exif);
+ }
+
+ // Add compressed version of captured image to returned media store Uri
+ OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
+ bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
+ os.close();
+
+ // Restore exif data to file
+ if (this.encodingType == JPEG) {
+ String exifPath;
+ if (this.saveToPhotoAlbum) {
+ exifPath = FileHelper.getRealPath(uri, this.cordova);
+ } else {
+ exifPath = uri.getPath();
+ }
+ exif.createOutFile(exifPath);
+ exif.writeExifData();
+ }
+ if (this.allowEdit) {
+ performCrop(uri);
+ } else {
+ // Send Uri back to JavaScript for viewing image
+ this.callbackContext.success(uri.toString());
+ }
+ }
+ } else {
+ throw new IllegalStateException();
+ }
+
+ this.cleanup(FILE_URI, this.imageUri, uri, bitmap);
+ bitmap = null;
+ }
+
+private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
+ // Create an ExifHelper to save the exif data that is lost during compression
+ String modifiedPath = getTempDirectoryPath() + "/modified.jpg";
+
+ OutputStream os = new FileOutputStream(modifiedPath);
+ bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
+ os.close();
+
+ // Some content: URIs do not map to file paths (e.g. picasa).
+ String realPath = FileHelper.getRealPath(uri, this.cordova);
+ ExifHelper exif = new ExifHelper();
+ if (realPath != null && this.encodingType == JPEG) {
+ try {
+ exif.createInFile(realPath);
+ exif.readExifData();
+ if (this.correctOrientation && this.orientationCorrected) {
+ exif.resetOrientation();
+ }
+ exif.createOutFile(modifiedPath);
+ exif.writeExifData();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return modifiedPath;
+ }
+
+/**
+ * Applies all needed transformation to the image received from the gallery.
+ *
+ * @param destType In which form should we return the image
+ * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
+ */
+ private void processResultFromGallery(int destType, Intent intent) {
+ Uri uri = intent.getData();
+ if (uri == null) {
+ if (croppedUri != null) {
+ uri = croppedUri;
+ } else {
+ this.failPicture("null data from photo library");
+ return;
+ }
+ }
+ int rotate = 0;
+
+ // If you ask for video or all media type you will automatically get back a file URI
+ // and there will be no attempt to resize any returned data
+ if (this.mediaType != PICTURE) {
+ this.callbackContext.success(uri.toString());
+ }
+ else {
+ // This is a special case to just return the path as no scaling,
+ // rotating, nor compressing needs to be done
+ if (this.targetHeight == -1 && this.targetWidth == -1 &&
+ (destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) {
+ this.callbackContext.success(uri.toString());
+ } else {
+ String uriString = uri.toString();
+ // Get the path to the image. Makes loading so much easier.
+ String mimeType = FileHelper.getMimeType(uriString, this.cordova);
+ // If we don't have a valid image so quit.
+ if (!("image/jpeg".equalsIgnoreCase(mimeType) || "image/png".equalsIgnoreCase(mimeType))) {
+ Log.d(LOG_TAG, "I either have a null image path or bitmap");
+ this.failPicture("Unable to retrieve path to picture!");
+ return;
+ }
+ Bitmap bitmap = null;
+ try {
+ bitmap = getScaledBitmap(uriString);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (bitmap == null) {
+ Log.d(LOG_TAG, "I either have a null image path or bitmap");
+ this.failPicture("Unable to create bitmap!");
+ return;
+ }
+
+ if (this.correctOrientation) {
+ rotate = getImageOrientation(uri);
+ if (rotate != 0) {
+ Matrix matrix = new Matrix();
+ matrix.setRotate(rotate);
+ try {
+ bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ this.orientationCorrected = true;
+ } catch (OutOfMemoryError oom) {
+ this.orientationCorrected = false;
+ }
+ }
+ }
+
+ // If sending base64 image back
+ if (destType == DATA_URL) {
+ this.processPicture(bitmap);
+ }
+
+ // If sending filename back
+ else if (destType == FILE_URI || destType == NATIVE_URI) {
+ // Did we modify the image?
+ if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
+ (this.correctOrientation && this.orientationCorrected) ) {
+ try {
+ String modifiedPath = this.ouputModifiedBitmap(bitmap, uri);
+ // The modified image is cached by the app in order to get around this and not have to delete you
+ // application cache I'm adding the current system time to the end of the file url.
+ this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis());
+ } catch (Exception e) {
+ e.printStackTrace();
+ this.failPicture("Error retrieving image.");
+ }
+ }
+ else {
+ this.callbackContext.success(uri.toString());
+ }
+ }
+ if (bitmap != null) {
+ bitmap.recycle();
+ bitmap = null;
+ }
+ System.gc();
+ }
+ }
+ }
+
+ /**
+ * Called when the camera view exits.
+ *
+ * @param requestCode The request code originally supplied to startActivityForResult(),
+ * allowing you to identify who this result came from.
+ * @param resultCode The integer result code returned by the child activity through its setResult().
+ * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
+ */
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+
+ // Get src and dest types from request code
+ int srcType = (requestCode / 16) - 1;
+ int destType = (requestCode % 16) - 1;
+ // if camera crop
+ if (requestCode == CROP_CAMERA) {
+ if (resultCode == Activity.RESULT_OK) {
+ // // Send Uri back to JavaScript for viewing image
+ this.callbackContext
+ .success(croppedUri.toString());
+ croppedUri = null;
+
+ }// If cancelled
+ else if (resultCode == Activity.RESULT_CANCELED) {
+ this.failPicture("Camera cancelled.");
+ }
+
+ // If something else
+ else {
+ this.failPicture("Did not complete!");
+ }
+
+ }
+ // If CAMERA
+ if (srcType == CAMERA) {
+ // If image available
+ if (resultCode == Activity.RESULT_OK) {
+ try {
+ this.processResultFromCamera(destType, intent);
+ } catch (IOException e) {
+ e.printStackTrace();
+ this.failPicture("Error capturing image.");
+ }
+ }
+
+ // If cancelled
+ else if (resultCode == Activity.RESULT_CANCELED) {
+ this.failPicture("Camera cancelled.");
+ }
+
+ // If something else
+ else {
+ this.failPicture("Did not complete!");
+ }
+ }
+
+ // If retrieving photo from library
+ else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
+ if (resultCode == Activity.RESULT_OK && intent != null) {
+ this.processResultFromGallery(destType, intent);
+ }
+ else if (resultCode == Activity.RESULT_CANCELED) {
+ this.failPicture("Selection cancelled.");
+ }
+ else {
+ this.failPicture("Selection did not complete!");
+ }
+ }
+ }
+
+ private int getImageOrientation(Uri uri) {
+ int rotate = 0;
+ String[] cols = { MediaStore.Images.Media.ORIENTATION };
+ try {
+ Cursor cursor = cordova.getActivity().getContentResolver().query(uri,
+ cols, null, null, null);
+ if (cursor != null) {
+ cursor.moveToPosition(0);
+ rotate = cursor.getInt(0);
+ cursor.close();
+ }
+ } catch (Exception e) {
+ // You can get an IllegalArgumentException if ContentProvider doesn't support querying for orientation.
+ }
+ return rotate;
+ }
+
+ /**
+ * Figure out if the bitmap should be rotated. For instance if the picture was taken in
+ * portrait mode
+ *
+ * @param rotate
+ * @param bitmap
+ * @return rotated bitmap
+ */
+ private Bitmap getRotatedBitmap(int rotate, Bitmap bitmap, ExifHelper exif) {
+ Matrix matrix = new Matrix();
+ if (rotate == 180) {
+ matrix.setRotate(rotate);
+ } else {
+ matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
+ }
+
+ try
+ {
+ bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ exif.resetOrientation();
+ }
+ catch (OutOfMemoryError oom)
+ {
+ // You can run out of memory if the image is very large:
+ // http://simonmacdonald.blogspot.ca/2012/07/change-to-camera-code-in-phonegap-190.html
+ // If this happens, simply do not rotate the image and return it unmodified.
+ // If you do not catch the OutOfMemoryError, the Android app crashes.
+ }
+
+ return bitmap;
+ }
+
+ /**
+ * In the special case where the default width, height and quality are unchanged
+ * we just write the file out to disk saving the expensive Bitmap.compress function.
+ *
+ * @param uri
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ private void writeUncompressedImage(Uri uri) throws FileNotFoundException,
+ IOException {
+ FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(imageUri.toString()));
+ OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
+ byte[] buffer = new byte[4096];
+ int len;
+ while ((len = fis.read(buffer)) != -1) {
+ os.write(buffer, 0, len);
+ }
+ os.flush();
+ os.close();
+ fis.close();
+ }
+
+ /**
+ * Create entry in media store for image
+ *
+ * @return uri
+ */
+ private Uri getUriFromMediaStore() {
+ ContentValues values = new ContentValues();
+ values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+ Uri uri;
+ try {
+ uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ } catch (RuntimeException e) {
+ LOG.d(LOG_TAG, "Can't write to external media storage.");
+ try {
+ uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
+ } catch (RuntimeException ex) {
+ LOG.d(LOG_TAG, "Can't write to internal media storage.");
+ return null;
+ }
+ }
+ return uri;
+ }
+
+ /**
+ * Return a scaled bitmap based on the target width and height
+ *
+ * @param imagePath
+ * @return
+ * @throws IOException
+ */
+ private Bitmap getScaledBitmap(String imageUrl) throws IOException {
+ // If no new width or height were specified return the original bitmap
+ if (this.targetWidth <= 0 && this.targetHeight <= 0) {
+ return BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova));
+ }
+
+ // figure out the original width and height of the image
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova), null, options);
+
+ //CB-2292: WTF? Why is the width null?
+ if(options.outWidth == 0 || options.outHeight == 0)
+ {
+ return null;
+ }
+
+ // determine the correct aspect ratio
+ int[] widthHeight = calculateAspectRatio(options.outWidth, options.outHeight);
+
+ // Load in the smallest bitmap possible that is closest to the size we want
+ options.inJustDecodeBounds = false;
+ options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight);
+ Bitmap unscaledBitmap = BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova), null, options);
+ if (unscaledBitmap == null) {
+ return null;
+ }
+
+ return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true);
+ }
+
+ /**
+ * Maintain the aspect ratio so the resulting image does not look smooshed
+ *
+ * @param origWidth
+ * @param origHeight
+ * @return
+ */
+ public int[] calculateAspectRatio(int origWidth, int origHeight) {
+ int newWidth = this.targetWidth;
+ int newHeight = this.targetHeight;
+
+ // If no new width or height were specified return the original bitmap
+ if (newWidth <= 0 && newHeight <= 0) {
+ newWidth = origWidth;
+ newHeight = origHeight;
+ }
+ // Only the width was specified
+ else if (newWidth > 0 && newHeight <= 0) {
+ newHeight = (newWidth * origHeight) / origWidth;
+ }
+ // only the height was specified
+ else if (newWidth <= 0 && newHeight > 0) {
+ newWidth = (newHeight * origWidth) / origHeight;
+ }
+ // If the user specified both a positive width and height
+ // (potentially different aspect ratio) then the width or height is
+ // scaled so that the image fits while maintaining aspect ratio.
+ // Alternatively, the specified width and height could have been
+ // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this
+ // would result in whitespace in the new image.
+ else {
+ double newRatio = newWidth / (double) newHeight;
+ double origRatio = origWidth / (double) origHeight;
+
+ if (origRatio > newRatio) {
+ newHeight = (newWidth * origHeight) / origWidth;
+ } else if (origRatio < newRatio) {
+ newWidth = (newHeight * origWidth) / origHeight;
+ }
+ }
+
+ int[] retval = new int[2];
+ retval[0] = newWidth;
+ retval[1] = newHeight;
+ return retval;
+ }
+
+ /**
+ * Figure out what ratio we can load our image into memory at while still being bigger than
+ * our desired width and height
+ *
+ * @param srcWidth
+ * @param srcHeight
+ * @param dstWidth
+ * @param dstHeight
+ * @return
+ */
+ public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
+ final float srcAspect = (float)srcWidth / (float)srcHeight;
+ final float dstAspect = (float)dstWidth / (float)dstHeight;
+
+ if (srcAspect > dstAspect) {
+ return srcWidth / dstWidth;
+ } else {
+ return srcHeight / dstHeight;
+ }
+ }
+
+ /**
+ * Creates a cursor that can be used to determine how many images we have.
+ *
+ * @return a cursor
+ */
+ private Cursor queryImgDB(Uri contentStore) {
+ return this.cordova.getActivity().getContentResolver().query(
+ contentStore,
+ new String[] { MediaStore.Images.Media._ID },
+ null,
+ null,
+ null);
+ }
+
+ /**
+ * Cleans up after picture taking. Checking for duplicates and that kind of stuff.
+ * @param newImage
+ */
+ private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
+
+ // Clean up initial camera-written image file.
+ (new File(FileHelper.stripFileProtocol(oldImage.toString()))).delete();
+
+ checkForDuplicateImage(imageType);
+ // Scan for the gallery to update pic refs in gallery
+ if (this.saveToPhotoAlbum && newImage != null) {
+ this.scanForGallery(newImage);
+ }
+
+ System.gc();
+ }
+
+ /**
+ * Used to find out if we are in a situation where the Camera Intent adds to images
+ * to the content store. If we are using a FILE_URI and the number of images in the DB
+ * increases by 2 we have a duplicate, when using a DATA_URL the number is 1.
+ *
+ * @param type FILE_URI or DATA_URL
+ */
+ private void checkForDuplicateImage(int type) {
+ int diff = 1;
+ Uri contentStore = whichContentStore();
+ Cursor cursor = queryImgDB(contentStore);
+ int currentNumOfImages = cursor.getCount();
+
+ if (type == FILE_URI && this.saveToPhotoAlbum) {
+ diff = 2;
+ }
+
+ // delete the duplicate file if the difference is 2 for file URI or 1 for Data URL
+ if ((currentNumOfImages - numPics) == diff) {
+ cursor.moveToLast();
+ int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
+ if (diff == 2) {
+ id--;
+ }
+ Uri uri = Uri.parse(contentStore + "/" + id);
+ this.cordova.getActivity().getContentResolver().delete(uri, null, null);
+ cursor.close();
+ }
+ }
+
+ /**
+ * Determine if we are storing the images in internal or external storage
+ * @return Uri
+ */
+ private Uri whichContentStore() {
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ return android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else {
+ return android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI;
+ }
+ }
+
+ /**
+ * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript.
+ *
+ * @param bitmap
+ */
+ public void processPicture(Bitmap bitmap) {
+ ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream();
+ try {
+ if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) {
+ byte[] code = jpeg_data.toByteArray();
+ byte[] output = Base64.encode(code, Base64.NO_WRAP);
+ String js_out = new String(output);
+ this.callbackContext.success(js_out);
+ js_out = null;
+ output = null;
+ code = null;
+ }
+ } catch (Exception e) {
+ this.failPicture("Error compressing image.");
+ }
+ jpeg_data = null;
+ }
+
+ /**
+ * Send error message to JavaScript.
+ *
+ * @param err
+ */
+ public void failPicture(String err) {
+ this.callbackContext.error(err);
+ }
+
+ private void scanForGallery(Uri newImage) {
+ this.scanMe = newImage;
+ if(this.conn != null) {
+ this.conn.disconnect();
+ }
+ this.conn = new MediaScannerConnection(this.cordova.getActivity().getApplicationContext(), this);
+ conn.connect();
+ }
+
+ public void onMediaScannerConnected() {
+ try{
+ this.conn.scanFile(this.scanMe.toString(), "image/*");
+ } catch (java.lang.IllegalStateException e){
+ LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture");
+ }
+
+ }
+
+ public void onScanCompleted(String path, Uri uri) {
+ this.conn.disconnect();
+ }
+}
diff --git a/plugins/org.apache.cordova.camera/src/android/ExifHelper.java b/plugins/org.apache.cordova.camera/src/android/ExifHelper.java
new file mode 100644
index 0000000..5160a2f
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/android/ExifHelper.java
@@ -0,0 +1,185 @@
+/*
+ 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.
+*/
+package org.apache.cordova.camera;
+
+import java.io.IOException;
+
+import android.media.ExifInterface;
+
+public class ExifHelper {
+ private String aperture = null;
+ private String datetime = null;
+ private String exposureTime = null;
+ private String flash = null;
+ private String focalLength = null;
+ private String gpsAltitude = null;
+ private String gpsAltitudeRef = null;
+ private String gpsDateStamp = null;
+ private String gpsLatitude = null;
+ private String gpsLatitudeRef = null;
+ private String gpsLongitude = null;
+ private String gpsLongitudeRef = null;
+ private String gpsProcessingMethod = null;
+ private String gpsTimestamp = null;
+ private String iso = null;
+ private String make = null;
+ private String model = null;
+ private String orientation = null;
+ private String whiteBalance = null;
+
+ private ExifInterface inFile = null;
+ private ExifInterface outFile = null;
+
+ /**
+ * The file before it is compressed
+ *
+ * @param filePath
+ * @throws IOException
+ */
+ public void createInFile(String filePath) throws IOException {
+ this.inFile = new ExifInterface(filePath);
+ }
+
+ /**
+ * The file after it has been compressed
+ *
+ * @param filePath
+ * @throws IOException
+ */
+ public void createOutFile(String filePath) throws IOException {
+ this.outFile = new ExifInterface(filePath);
+ }
+
+ /**
+ * Reads all the EXIF data from the input file.
+ */
+ public void readExifData() {
+ this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE);
+ this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
+ this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
+ this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
+ this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
+ this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
+ this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF);
+ this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
+ this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
+ this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
+ this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
+ this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
+ this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);
+ this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
+ this.iso = inFile.getAttribute(ExifInterface.TAG_ISO);
+ this.make = inFile.getAttribute(ExifInterface.TAG_MAKE);
+ this.model = inFile.getAttribute(ExifInterface.TAG_MODEL);
+ this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION);
+ this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
+ }
+
+ /**
+ * Writes the previously stored EXIF data to the output file.
+ *
+ * @throws IOException
+ */
+ public void writeExifData() throws IOException {
+ // Don't try to write to a null file
+ if (this.outFile == null) {
+ return;
+ }
+
+ if (this.aperture != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture);
+ }
+ if (this.datetime != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);
+ }
+ if (this.exposureTime != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime);
+ }
+ if (this.flash != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash);
+ }
+ if (this.focalLength != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength);
+ }
+ if (this.gpsAltitude != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude);
+ }
+ if (this.gpsAltitudeRef != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef);
+ }
+ if (this.gpsDateStamp != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp);
+ }
+ if (this.gpsLatitude != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude);
+ }
+ if (this.gpsLatitudeRef != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef);
+ }
+ if (this.gpsLongitude != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude);
+ }
+ if (this.gpsLongitudeRef != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef);
+ }
+ if (this.gpsProcessingMethod != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod);
+ }
+ if (this.gpsTimestamp != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp);
+ }
+ if (this.iso != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso);
+ }
+ if (this.make != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make);
+ }
+ if (this.model != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model);
+ }
+ if (this.orientation != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation);
+ }
+ if (this.whiteBalance != null) {
+ this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance);
+ }
+
+ this.outFile.saveAttributes();
+ }
+
+ public int getOrientation() {
+ int o = Integer.parseInt(this.orientation);
+
+ if (o == ExifInterface.ORIENTATION_NORMAL) {
+ return 0;
+ } else if (o == ExifInterface.ORIENTATION_ROTATE_90) {
+ return 90;
+ } else if (o == ExifInterface.ORIENTATION_ROTATE_180) {
+ return 180;
+ } else if (o == ExifInterface.ORIENTATION_ROTATE_270) {
+ return 270;
+ } else {
+ return 0;
+ }
+ }
+
+ public void resetOrientation() {
+ this.orientation = "" + ExifInterface.ORIENTATION_NORMAL;
+ }
+}
diff --git a/plugins/org.apache.cordova.camera/src/android/FileHelper.java b/plugins/org.apache.cordova.camera/src/android/FileHelper.java
new file mode 100644
index 0000000..24ced59
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/android/FileHelper.java
@@ -0,0 +1,158 @@
+/*
+ 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.
+ */
+package org.apache.cordova.camera;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.webkit.MimeTypeMap;
+
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.LOG;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+public class FileHelper {
+ private static final String LOG_TAG = "FileUtils";
+ private static final String _DATA = "_data";
+
+ /**
+ * Returns the real path of the given URI string.
+ * If the given URI string represents a content:// URI, the real path is retrieved from the media store.
+ *
+ * @param uriString the URI string of the audio/image/video
+ * @param cordova the current application context
+ * @return the full path to the file
+ */
+ @SuppressWarnings("deprecation")
+ public static String getRealPath(String uriString, CordovaInterface cordova) {
+ String realPath = null;
+
+ if (uriString.startsWith("content://")) {
+ String[] proj = { _DATA };
+ Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
+ int column_index = cursor.getColumnIndexOrThrow(_DATA);
+ cursor.moveToFirst();
+ realPath = cursor.getString(column_index);
+ if (realPath == null) {
+ LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
+ }
+ } else if (uriString.startsWith("file://")) {
+ realPath = uriString.substring(7);
+ if (realPath.startsWith("/android_asset/")) {
+ LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
+ realPath = null;
+ }
+ } else {
+ realPath = uriString;
+ }
+
+ return realPath;
+ }
+
+ /**
+ * Returns the real path of the given URI.
+ * If the given URI is a content:// URI, the real path is retrieved from the media store.
+ *
+ * @param uri the URI of the audio/image/video
+ * @param cordova the current application context
+ * @return the full path to the file
+ */
+ public static String getRealPath(Uri uri, CordovaInterface cordova) {
+ return FileHelper.getRealPath(uri.toString(), cordova);
+ }
+
+ /**
+ * Returns an input stream based on given URI string.
+ *
+ * @param uriString the URI string from which to obtain the input stream
+ * @param cordova the current application context
+ * @return an input stream into the data at the given URI or null if given an invalid URI string
+ * @throws IOException
+ */
+ public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
+ if (uriString.startsWith("content")) {
+ Uri uri = Uri.parse(uriString);
+ return cordova.getActivity().getContentResolver().openInputStream(uri);
+ } else if (uriString.startsWith("file://")) {
+ int question = uriString.indexOf("?");
+ if (question > -1) {
+ uriString = uriString.substring(0,question);
+ }
+ if (uriString.startsWith("file:///android_asset/")) {
+ Uri uri = Uri.parse(uriString);
+ String relativePath = uri.getPath().substring(15);
+ return cordova.getActivity().getAssets().open(relativePath);
+ } else {
+ return new FileInputStream(getRealPath(uriString, cordova));
+ }
+ } else {
+ return new FileInputStream(getRealPath(uriString, cordova));
+ }
+ }
+
+ /**
+ * Removes the "file://" prefix from the given URI string, if applicable.
+ * If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
+ *
+ * @param uriString the URI string to operate on
+ * @return a path without the "file://" prefix
+ */
+ public static String stripFileProtocol(String uriString) {
+ if (uriString.startsWith("file://")) {
+ uriString = uriString.substring(7);
+ }
+ return uriString;
+ }
+
+ public static String getMimeTypeForExtension(String path) {
+ String extension = path;
+ int lastDot = extension.lastIndexOf('.');
+ if (lastDot != -1) {
+ extension = extension.substring(lastDot + 1);
+ }
+ // Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
+ extension = extension.toLowerCase(Locale.getDefault());
+ if (extension.equals("3ga")) {
+ return "audio/3gpp";
+ }
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ }
+
+ /**
+ * Returns the mime type of the data specified by the given URI string.
+ *
+ * @param uriString the URI string of the data
+ * @return the mime type of the specified data
+ */
+ public static String getMimeType(String uriString, CordovaInterface cordova) {
+ String mimeType = null;
+
+ Uri uri = Uri.parse(uriString);
+ if (uriString.startsWith("content://")) {
+ mimeType = cordova.getActivity().getContentResolver().getType(uri);
+ } else {
+ mimeType = getMimeTypeForExtension(uri.getPath());
+ }
+
+ return mimeType;
+ }
+}
diff --git a/plugins/org.apache.cordova.camera/src/blackberry10/index.js b/plugins/org.apache.cordova.camera/src/blackberry10/index.js
new file mode 100644
index 0000000..74cb191
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/blackberry10/index.js
@@ -0,0 +1,224 @@
+/*
+ *
+ * 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.
+ *
+*/
+var PictureSourceType = {
+ PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
+ CAMERA : 1, // Take picture from camera
+ SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android)
+ },
+ DestinationType = {
+ DATA_URL: 0, // Return base64 encoded string
+ FILE_URI: 1, // Return file uri (content://media/external/images/media/2 for Android)
+ NATIVE_URI: 2 // Return native uri (eg. asset-library://... for iOS)
+ },
+ savePath = window.qnx.webplatform.getApplication().getEnv("HOME").replace('/data', '') + '/shared/camera/',
+ invokeAvailable = true;
+
+//check for camera card - it isn't currently availble in work perimeter
+window.qnx.webplatform.getApplication().invocation.queryTargets(
+ {
+ type: 'image/jpeg',
+ action: 'bb.action.CAPTURE',
+ target_type: 'CARD'
+ },
+ function (error, targets) {
+ invokeAvailable = !error && targets && targets instanceof Array &&
+ targets.filter(function (t) { return t.default === 'sys.camera.card' }).length > 0;
+ }
+);
+
+//open a webview with getUserMedia camera card implementation when camera card not available
+function showCameraDialog (done, cancel, fail) {
+ var wv = qnx.webplatform.createWebView(function () {
+ wv.url = 'local:///chrome/camera.html';
+ wv.allowQnxObject = true;
+ wv.allowRpc = true;
+ wv.zOrder = 1;
+ wv.setGeometry(0, 0, screen.width, screen.height);
+ wv.backgroundColor = 0x00000000;
+ wv.active = true;
+ wv.visible = true;
+ wv.on('UserMediaRequest', function (evt, args) {
+ wv.allowUserMedia(JSON.parse(args).id, 'CAMERA_UNIT_REAR');
+ });
+ wv.on('JavaScriptCallback', function (evt, data) {
+ var args = JSON.parse(data).args;
+ if (args[0] === 'org.apache.cordova.camera') {
+ if (args[1] === 'cancel') {
+ cancel('User canceled');
+ } else if (args[1] === 'error') {
+ fail(args[2]);
+ } else {
+ saveImage(args[1], done, fail);
+ }
+ wv.un('JavaScriptCallback', arguments.callee);
+ wv.visible = false;
+ wv.destroy();
+ qnx.webplatform.getApplication().unlockRotation();
+ }
+ });
+ wv.on('Destroyed', function () {
+ wv.delete();
+ });
+ qnx.webplatform.getApplication().lockRotation();
+ qnx.webplatform.getController().dispatchEvent('webview.initialized', [wv]);
+ });
+}
+
+//create unique name for saved file (same pattern as BB10 camera app)
+function imgName() {
+ var date = new Date(),
+ pad = function (n) { return n < 10 ? '0' + n : n };
+ return 'IMG_' + date.getFullYear() + pad(date.getMonth() + 1) + pad(date.getDate()) + '_' +
+ pad(date.getHours()) + pad(date.getMinutes()) + pad(date.getSeconds()) + '.png';
+}
+
+//convert dataURI to Blob
+function dataURItoBlob(dataURI) {
+ var byteString = atob(dataURI.split(',')[1]),
+ mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0],
+ arrayBuffer = new ArrayBuffer(byteString.length),
+ ia = new Uint8Array(arrayBuffer),
+ i;
+ for (i = 0; i < byteString.length; i++) {
+ ia[i] = byteString.charCodeAt(i);
+ }
+ return new Blob([new DataView(arrayBuffer)], { type: mimeString });
+}
+
+//save dataURI to file system and call success with path
+function saveImage(data, success, fail) {
+ var name = savePath + imgName();
+ require('lib/webview').setSandbox(false);
+ window.webkitRequestFileSystem(window.PERSISTENT, 0, function (fs) {
+ fs.root.getFile(name, { create: true }, function (entry) {
+ entry.createWriter(function (writer) {
+ writer.onwriteend = function () {
+ success(name);
+ };
+ writer.onerror = fail;
+ writer.write(dataURItoBlob(data));
+ });
+ }, fail);
+ }, fail);
+}
+
+function encodeBase64(filePath, callback) {
+ var sandbox = window.qnx.webplatform.getController().setFileSystemSandbox, // save original sandbox value
+ errorHandler = function (err) {
+ var msg = "An error occured: ";
+
+ switch (err.code) {
+ case FileError.NOT_FOUND_ERR:
+ msg += "File or directory not found";
+ break;
+
+ case FileError.NOT_READABLE_ERR:
+ msg += "File or directory not readable";
+ break;
+
+ case FileError.PATH_EXISTS_ERR:
+ msg += "File or directory already exists";
+ break;
+
+ case FileError.TYPE_MISMATCH_ERR:
+ msg += "Invalid file type";
+ break;
+
+ default:
+ msg += "Unknown Error";
+ break;
+ };
+
+ // set it back to original value
+ window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
+ callback(msg);
+ },
+ gotFile = function (fileEntry) {
+ fileEntry.file(function (file) {
+ var reader = new FileReader();
+
+ reader.onloadend = function (e) {
+ // set it back to original value
+ window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
+ callback(this.result);
+ };
+
+ reader.readAsDataURL(file);
+ }, errorHandler);
+ },
+ onInitFs = function (fs) {
+ window.qnx.webplatform.getController().setFileSystemSandbox = false;
+ fs.root.getFile(filePath, {create: false}, gotFile, errorHandler);
+ };
+
+ window.webkitRequestFileSystem(window.TEMPORARY, 10 * 1024 * 1024, onInitFs, errorHandler); // set size to 10MB max
+}
+
+module.exports = {
+ takePicture: function (success, fail, args, env) {
+ var destinationType = JSON.parse(decodeURIComponent(args[1])),
+ sourceType = JSON.parse(decodeURIComponent(args[2])),
+ result = new PluginResult(args, env),
+ done = function (data) {
+ if (destinationType === DestinationType.FILE_URI) {
+ data = "file://" + data;
+ result.callbackOk(data, false);
+ } else {
+ encodeBase64(data, function (data) {
+ if (/^data:/.test(data)) {
+ data = data.slice(data.indexOf(",") + 1);
+ result.callbackOk(data, false);
+ } else {
+ result.callbackError(data, false);
+ }
+ });
+ }
+ },
+ cancel = function (reason) {
+ result.callbackError(reason, false);
+ },
+ invoked = function (error) {
+ if (error) {
+ result.callbackError(error, false);
+ }
+ };
+
+ switch(sourceType) {
+ case PictureSourceType.CAMERA:
+ if (invokeAvailable) {
+ window.qnx.webplatform.getApplication().cards.camera.open("photo", done, cancel, invoked);
+ } else {
+ showCameraDialog(done, cancel, fail);
+ }
+ break;
+
+ case PictureSourceType.PHOTOLIBRARY:
+ case PictureSourceType.SAVEDPHOTOALBUM:
+ window.qnx.webplatform.getApplication().cards.filePicker.open({
+ mode: "Picker",
+ type: ["picture"]
+ }, done, cancel, invoked);
+ break;
+ }
+
+ result.noResult(true);
+ }
+};
diff --git a/plugins/org.apache.cordova.camera/src/browser/CameraProxy.js b/plugins/org.apache.cordova.camera/src/browser/CameraProxy.js
new file mode 100644
index 0000000..0944e58
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/browser/CameraProxy.js
@@ -0,0 +1,102 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+function takePicture(success, error, opts) {
+ if (opts && opts[2] === 1) {
+ capture(success, error);
+ } else {
+ var input = document.createElement('input');
+ input.type = 'file';
+ input.name = 'files[]';
+
+ input.onchange = function(inputEvent) {
+ var canvas = document.createElement('canvas');
+
+ var reader = new FileReader();
+ reader.onload = function(readerEvent) {
+ input.parentNode.removeChild(input);
+
+ var imageData = readerEvent.target.result;
+
+ return success(imageData.substr(imageData.indexOf(',') + 1));
+ }
+
+ reader.readAsDataURL(inputEvent.target.files[0]);
+ };
+
+ document.body.appendChild(input);
+ }
+}
+
+function capture(success, errorCallback) {
+ var localMediaStream;
+
+ var video = document.createElement('video');
+ var button = document.createElement('button');
+
+ video.width = 320;
+ video.height = 240;
+ button.innerHTML = 'Capture!';
+
+ button.onclick = function() {
+ // create a canvas and capture a frame from video stream
+ var canvas = document.createElement('canvas');
+ canvas.getContext('2d').drawImage(video, 0, 0, 320, 240);
+
+ // convert image stored in canvas to base64 encoded image
+ var imageData = canvas.toDataURL('img/png');
+ imageData = imageData.replace('data:image/png;base64,', '');
+
+ // stop video stream, remove video and button
+ localMediaStream.stop();
+ video.parentNode.removeChild(video);
+ button.parentNode.removeChild(button);
+
+ return success(imageData);
+ }
+
+ navigator.getUserMedia = navigator.getUserMedia ||
+ navigator.webkitGetUserMedia ||
+ navigator.mozGetUserMedia ||
+ navigator.msGetUserMedia;
+
+ var successCallback = function(stream) {
+ localMediaStream = stream;
+ video.src = window.URL.createObjectURL(localMediaStream);
+ video.play();
+
+ document.body.appendChild(video);
+ document.body.appendChild(button);
+ }
+
+ if (navigator.getUserMedia) {
+ navigator.getUserMedia({video: true, audio: true}, successCallback, errorCallback);
+ } else {
+ alert('Browser does not support camera :(');
+ }
+}
+
+module.exports = {
+ takePicture: takePicture,
+ cleanup: function(){}
+};
+
+require("cordova/exec/proxy").add("Camera",module.exports);
diff --git a/plugins/org.apache.cordova.camera/src/firefoxos/CameraProxy.js b/plugins/org.apache.cordova.camera/src/firefoxos/CameraProxy.js
new file mode 100644
index 0000000..872d0ad
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/firefoxos/CameraProxy.js
@@ -0,0 +1,51 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+function takePicture(success, error, opts) {
+ var pick = new MozActivity({
+ name: "pick",
+ data: {
+ type: ["image/*"]
+ }
+ });
+
+ pick.onerror = error || function() {};
+
+ pick.onsuccess = function() {
+ // image is returned as Blob in this.result.blob
+ // we need to call success with url or base64 encoded image
+ if (opts && opts.destinationType == 0) {
+ // TODO: base64
+ return;
+ }
+ if (!opts || !opts.destinationType || opts.destinationType > 0) {
+ // url
+ return success(window.URL.createObjectURL(this.result.blob));
+ }
+ };
+}
+
+module.exports = {
+ takePicture: takePicture,
+ cleanup: function(){}
+};
+
+require("cordova/exec/proxy").add("Camera", module.exports);
diff --git a/plugins/org.apache.cordova.camera/src/ios/CDVCamera.h b/plugins/org.apache.cordova.camera/src/ios/CDVCamera.h
new file mode 100644
index 0000000..f64f66c
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ios/CDVCamera.h
@@ -0,0 +1,116 @@
+/*
+ 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.
+ */
+
+#import
+#import
+#import
+#import
+
+enum CDVDestinationType {
+ DestinationTypeDataUrl = 0,
+ DestinationTypeFileUri,
+ DestinationTypeNativeUri
+};
+typedef NSUInteger CDVDestinationType;
+
+enum CDVEncodingType {
+ EncodingTypeJPEG = 0,
+ EncodingTypePNG
+};
+typedef NSUInteger CDVEncodingType;
+
+enum CDVMediaType {
+ MediaTypePicture = 0,
+ MediaTypeVideo,
+ MediaTypeAll
+};
+typedef NSUInteger CDVMediaType;
+
+@interface CDVPictureOptions : NSObject
+
+@property (strong) NSNumber* quality;
+@property (assign) CDVDestinationType destinationType;
+@property (assign) UIImagePickerControllerSourceType sourceType;
+@property (assign) CGSize targetSize;
+@property (assign) CDVEncodingType encodingType;
+@property (assign) CDVMediaType mediaType;
+@property (assign) BOOL allowsEditing;
+@property (assign) BOOL correctOrientation;
+@property (assign) BOOL saveToPhotoAlbum;
+@property (strong) NSDictionary* popoverOptions;
+@property (assign) UIImagePickerControllerCameraDevice cameraDirection;
+
+@property (assign) BOOL popoverSupported;
+@property (assign) BOOL usesGeolocation;
+@property (assign) BOOL cropToSize;
+
++ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command;
+
+@end
+
+@interface CDVCameraPicker : UIImagePickerController
+
+@property (strong) CDVPictureOptions* pictureOptions;
+
+@property (copy) NSString* callbackId;
+@property (copy) NSString* postUrl;
+@property (strong) UIPopoverController* pickerPopoverController;
+@property (assign) BOOL cropToSize;
+@property (strong) UIView* webView;
+
++ (instancetype) createFromPictureOptions:(CDVPictureOptions*)options;
+
+@end
+
+// ======================================================================= //
+
+@interface CDVCamera : CDVPlugin
+{}
+
+@property (strong) CDVCameraPicker* pickerController;
+@property (strong) NSMutableDictionary *metadata;
+@property (strong, nonatomic) CLLocationManager *locationManager;
+@property (strong) NSData* data;
+
+/*
+ * getPicture
+ *
+ * arguments:
+ * 1: this is the javascript function that will be called with the results, the first parameter passed to the
+ * javascript function is the picture as a Base64 encoded string
+ * 2: this is the javascript function to be called if there was an error
+ * options:
+ * quality: integer between 1 and 100
+ */
+- (void)takePicture:(CDVInvokedUrlCommand*)command;
+- (void)cleanup:(CDVInvokedUrlCommand*)command;
+- (void)repositionPopover:(CDVInvokedUrlCommand*)command;
+
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info;
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo;
+- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker;
+- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
+
+- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation;
+- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
+
+@end
diff --git a/plugins/org.apache.cordova.camera/src/ios/CDVCamera.m b/plugins/org.apache.cordova.camera/src/ios/CDVCamera.m
new file mode 100644
index 0000000..37bc716
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ios/CDVCamera.m
@@ -0,0 +1,680 @@
+/*
+ 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.
+ */
+
+#import "CDVCamera.h"
+#import "CDVJpegHeaderWriter.h"
+#import "UIImage+CropScaleOrientation.h"
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+#define CDV_PHOTO_PREFIX @"cdv_photo_"
+
+static NSSet* org_apache_cordova_validArrowDirections;
+
+static NSString* toBase64(NSData* data) {
+ SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
+ SEL s2 = NSSelectorFromString(@"base64EncodedString");
+ SEL realSel = [data respondsToSelector:s1] ? s1 : s2;
+ NSString* (*func)(id, SEL) = (void *)[data methodForSelector:realSel];
+ return func(data, realSel);
+}
+
+@implementation CDVPictureOptions
+
++ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command
+{
+ CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
+
+ pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
+ pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
+ pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(UIImagePickerControllerSourceTypeCamera)] unsignedIntegerValue];
+
+ NSNumber* targetWidth = [command argumentAtIndex:3 withDefault:nil];
+ NSNumber* targetHeight = [command argumentAtIndex:4 withDefault:nil];
+ pictureOptions.targetSize = CGSizeMake(0, 0);
+ if ((targetWidth != nil) && (targetHeight != nil)) {
+ pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
+ }
+
+ pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
+ pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
+ pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
+ pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
+ pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
+ pictureOptions.popoverOptions = [command argumentAtIndex:10 withDefault:nil];
+ pictureOptions.cameraDirection = [[command argumentAtIndex:11 withDefault:@(UIImagePickerControllerCameraDeviceRear)] unsignedIntegerValue];
+
+ pictureOptions.popoverSupported = NO;
+ pictureOptions.usesGeolocation = NO;
+
+ return pictureOptions;
+}
+
+@end
+
+
+@interface CDVCamera ()
+
+@property (readwrite, assign) BOOL hasPendingOperation;
+
+@end
+
+@implementation CDVCamera
+
++ (void)initialize
+{
+ org_apache_cordova_validArrowDirections = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:UIPopoverArrowDirectionUp], [NSNumber numberWithInt:UIPopoverArrowDirectionDown], [NSNumber numberWithInt:UIPopoverArrowDirectionLeft], [NSNumber numberWithInt:UIPopoverArrowDirectionRight], [NSNumber numberWithInt:UIPopoverArrowDirectionAny], nil];
+}
+
+@synthesize hasPendingOperation, pickerController, locationManager;
+
+- (NSURL*) urlTransformer:(NSURL*)url
+{
+ NSURL* urlToTransform = url;
+
+ // for backwards compatibility - we check if this property is there
+ SEL sel = NSSelectorFromString(@"urlTransformer");
+ if ([self.commandDelegate respondsToSelector:sel]) {
+ // grab the block from the commandDelegate
+ NSURL* (^urlTransformer)(NSURL*) = ((id(*)(id, SEL))objc_msgSend)(self.commandDelegate, sel);
+ // if block is not null, we call it
+ if (urlTransformer) {
+ urlToTransform = urlTransformer(url);
+ }
+ }
+
+ return urlToTransform;
+}
+
+- (BOOL)usesGeolocation
+{
+ id useGeo = [self.commandDelegate.settings objectForKey:[@"CameraUsesGeolocation" lowercaseString]];
+ return [(NSNumber*)useGeo boolValue];
+}
+
+- (BOOL)popoverSupported
+{
+ return (NSClassFromString(@"UIPopoverController") != nil) &&
+ (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
+}
+
+- (void)takePicture:(CDVInvokedUrlCommand*)command
+{
+ self.hasPendingOperation = YES;
+
+ __weak CDVCamera* weakSelf = self;
+
+ [self.commandDelegate runInBackground:^{
+
+ CDVPictureOptions* pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
+ pictureOptions.popoverSupported = [self popoverSupported];
+ pictureOptions.usesGeolocation = [self usesGeolocation];
+ pictureOptions.cropToSize = NO;
+
+ BOOL hasCamera = [UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType];
+ if (!hasCamera) {
+ NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)pictureOptions.sourceType);
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No camera available"];
+ [weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ // If a popover is already open, close it; we only want one at a time.
+ if (([[weakSelf pickerController] pickerPopoverController] != nil) && [[[weakSelf pickerController] pickerPopoverController] isPopoverVisible]) {
+ [[[weakSelf pickerController] pickerPopoverController] dismissPopoverAnimated:YES];
+ [[[weakSelf pickerController] pickerPopoverController] setDelegate:nil];
+ [[weakSelf pickerController] setPickerPopoverController:nil];
+ }
+
+ CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
+ weakSelf.pickerController = cameraPicker;
+
+ cameraPicker.delegate = weakSelf;
+ cameraPicker.callbackId = command.callbackId;
+ // we need to capture this state for memory warnings that dealloc this object
+ cameraPicker.webView = weakSelf.webView;
+
+ if ([weakSelf popoverSupported] && (pictureOptions.sourceType != UIImagePickerControllerSourceTypeCamera)) {
+ if (cameraPicker.pickerPopoverController == nil) {
+ cameraPicker.pickerPopoverController = [[NSClassFromString(@"UIPopoverController") alloc] initWithContentViewController:cameraPicker];
+ }
+ [weakSelf displayPopover:pictureOptions.popoverOptions];
+ weakSelf.hasPendingOperation = NO;
+
+ } else {
+ [weakSelf.viewController presentViewController:cameraPicker animated:YES completion:^{
+ weakSelf.hasPendingOperation = NO;
+ }];
+ }
+ }];
+}
+
+- (void)repositionPopover:(CDVInvokedUrlCommand*)command
+{
+ NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
+
+ [self displayPopover:options];
+}
+
+- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
+{
+ NSInteger value = defaultValue;
+
+ NSNumber* val = [self valueForKey:key]; // value is an NSNumber
+
+ if (val != nil) {
+ value = [val integerValue];
+ }
+ return value;
+}
+
+- (void)displayPopover:(NSDictionary*)options
+{
+ NSInteger x = 0;
+ NSInteger y = 32;
+ NSInteger width = 320;
+ NSInteger height = 480;
+ UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
+
+ if (options) {
+ x = [self integerValueForKey:options key:@"x" defaultValue:0];
+ y = [self integerValueForKey:options key:@"y" defaultValue:32];
+ width = [self integerValueForKey:options key:@"width" defaultValue:320];
+ height = [self integerValueForKey:options key:@"height" defaultValue:480];
+ arrowDirection = [self integerValueForKey:options key:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny];
+ if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithUnsignedInteger:arrowDirection]]) {
+ arrowDirection = UIPopoverArrowDirectionAny;
+ }
+ }
+
+ [[[self pickerController] pickerPopoverController] setDelegate:self];
+ [[[self pickerController] pickerPopoverController] presentPopoverFromRect:CGRectMake(x, y, width, height)
+ inView:[self.webView superview]
+ permittedArrowDirections:arrowDirection
+ animated:YES];
+}
+
+- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
+{
+ if([navigationController isKindOfClass:[UIImagePickerController class]]){
+ UIImagePickerController* cameraPicker = (UIImagePickerController*)navigationController;
+
+ if(![cameraPicker.mediaTypes containsObject:(NSString*)kUTTypeImage]){
+ [viewController.navigationItem setTitle:NSLocalizedString(@"Videos title", nil)];
+ }
+ }
+}
+
+- (void)cleanup:(CDVInvokedUrlCommand*)command
+{
+ // empty the tmp directory
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSError* err = nil;
+ BOOL hasErrors = NO;
+
+ // clear contents of NSTemporaryDirectory
+ NSString* tempDirectoryPath = NSTemporaryDirectory();
+ NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath];
+ NSString* fileName = nil;
+ BOOL result;
+
+ while ((fileName = [directoryEnumerator nextObject])) {
+ // only delete the files we created
+ if (![fileName hasPrefix:CDV_PHOTO_PREFIX]) {
+ continue;
+ }
+ NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
+ result = [fileMgr removeItemAtPath:filePath error:&err];
+ if (!result && err) {
+ NSLog(@"Failed to delete: %@ (error: %@)", filePath, err);
+ hasErrors = YES;
+ }
+ }
+
+ CDVPluginResult* pluginResult;
+ if (hasErrors) {
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"One or more files failed to be deleted."];
+ } else {
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ }
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+}
+
+- (void)popoverControllerDidDismissPopover:(id)popoverController
+{
+ UIPopoverController* pc = (UIPopoverController*)popoverController;
+
+ [pc dismissPopoverAnimated:YES];
+ pc.delegate = nil;
+ if (self.pickerController && self.pickerController.callbackId && self.pickerController.pickerPopoverController) {
+ self.pickerController.pickerPopoverController = nil;
+ NSString* callbackId = self.pickerController.callbackId;
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }
+ self.hasPendingOperation = NO;
+}
+
+- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
+{
+ NSData* data = nil;
+
+ switch (options.encodingType) {
+ case EncodingTypePNG:
+ data = UIImagePNGRepresentation(image);
+ break;
+ case EncodingTypeJPEG:
+ {
+ if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO)){
+ // use image unedited as requested , don't resize
+ data = UIImageJPEGRepresentation(image, 1.0);
+ } else {
+ if (options.usesGeolocation) {
+ NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
+ if (controllerMetadata) {
+ self.data = data;
+ self.metadata = [[NSMutableDictionary alloc] init];
+
+ NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
+ if (EXIFDictionary) {
+ [self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
+ }
+
+ if (IsAtLeastiOSVersion(@"8.0")) {
+ [[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
+ }
+ [[self locationManager] startUpdatingLocation];
+ }
+ } else {
+ data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ };
+
+ return data;
+}
+
+- (NSString*)tempFilePath:(NSString*)extension
+{
+ NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
+ NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by Apple (vs [NSFileManager defaultManager]) to be threadsafe
+ NSString* filePath;
+
+ // generate unique file name
+ int i = 1;
+ do {
+ filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, extension];
+ } while ([fileMgr fileExistsAtPath:filePath]);
+
+ return filePath;
+}
+
+- (UIImage*)retrieveImage:(NSDictionary*)info options:(CDVPictureOptions*)options
+{
+ // get the image
+ UIImage* image = nil;
+ if (options.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) {
+ image = [info objectForKey:UIImagePickerControllerEditedImage];
+ } else {
+ image = [info objectForKey:UIImagePickerControllerOriginalImage];
+ }
+
+ if (options.correctOrientation) {
+ image = [image imageCorrectedForCaptureOrientation];
+ }
+
+ UIImage* scaledImage = nil;
+
+ if ((options.targetSize.width > 0) && (options.targetSize.height > 0)) {
+ // if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping
+ if (options.cropToSize) {
+ scaledImage = [image imageByScalingAndCroppingForSize:options.targetSize];
+ } else {
+ scaledImage = [image imageByScalingNotCroppingForSize:options.targetSize];
+ }
+ }
+
+ return (scaledImage == nil ? image : scaledImage);
+}
+
+- (CDVPluginResult*)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info
+{
+ CDVPluginResult* result = nil;
+ BOOL saveToPhotoAlbum = options.saveToPhotoAlbum;
+ UIImage* image = nil;
+
+ switch (options.destinationType) {
+ case DestinationTypeNativeUri:
+ {
+ NSURL* url = (NSURL*)[info objectForKey:UIImagePickerControllerReferenceURL];
+ NSString* nativeUri = [[self urlTransformer:url] absoluteString];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
+ saveToPhotoAlbum = NO;
+ }
+ break;
+ case DestinationTypeFileUri:
+ {
+ image = [self retrieveImage:info options:options];
+ NSData* data = [self processImage:image info:info options:options];
+ if (data) {
+
+ NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
+ NSString* filePath = [self tempFilePath:extension];
+ NSError* err = nil;
+
+ // save file
+ if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
+ }
+ }
+ }
+ break;
+ case DestinationTypeDataUrl:
+ {
+ image = [self retrieveImage:info options:options];
+ NSData* data = [self processImage:image info:info options:options];
+
+ if (data) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(data)];
+ }
+ }
+ break;
+ default:
+ break;
+ };
+
+ if (saveToPhotoAlbum && image) {
+ ALAssetsLibrary* library = [ALAssetsLibrary new];
+ [library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:nil];
+ }
+
+ return result;
+}
+
+- (CDVPluginResult*)resultForVideo:(NSDictionary*)info
+{
+ NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
+}
+
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
+{
+ __weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
+ __weak CDVCamera* weakSelf = self;
+
+ dispatch_block_t invoke = ^(void) {
+ __block CDVPluginResult* result = nil;
+
+ NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
+ if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
+ result = [self resultForImage:cameraPicker.pictureOptions info:info];
+ }
+ else {
+ result = [self resultForVideo:info];
+ }
+
+ if (result) {
+ [weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
+ weakSelf.hasPendingOperation = NO;
+ weakSelf.pickerController = nil;
+ }
+ };
+
+ if (cameraPicker.pictureOptions.popoverSupported && (cameraPicker.pickerPopoverController != nil)) {
+ [cameraPicker.pickerPopoverController dismissPopoverAnimated:YES];
+ cameraPicker.pickerPopoverController.delegate = nil;
+ cameraPicker.pickerPopoverController = nil;
+ invoke();
+ } else {
+ [[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
+ }
+}
+
+// older api calls newer didFinishPickingMediaWithInfo
+- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo
+{
+ NSDictionary* imageInfo = [NSDictionary dictionaryWithObject:image forKey:UIImagePickerControllerOriginalImage];
+
+ [self imagePickerController:picker didFinishPickingMediaWithInfo:imageInfo];
+}
+
+- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker
+{
+ __weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
+ __weak CDVCamera* weakSelf = self;
+
+ dispatch_block_t invoke = ^ (void) {
+ CDVPluginResult* result;
+ if ([ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"];
+ }
+
+ [weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
+
+ weakSelf.hasPendingOperation = NO;
+ weakSelf.pickerController = nil;
+ };
+
+ [[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
+}
+
+- (CLLocationManager*)locationManager
+{
+ if (locationManager != nil) {
+ return locationManager;
+ }
+
+ locationManager = [[CLLocationManager alloc] init];
+ [locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
+ [locationManager setDelegate:self];
+
+ return locationManager;
+}
+
+- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
+{
+ if (locationManager == nil) {
+ return;
+ }
+
+ [self.locationManager stopUpdatingLocation];
+ self.locationManager = nil;
+
+ NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init];
+
+ CLLocationDegrees latitude = newLocation.coordinate.latitude;
+ CLLocationDegrees longitude = newLocation.coordinate.longitude;
+
+ // latitude
+ if (latitude < 0.0) {
+ latitude = latitude * -1.0f;
+ [GPSDictionary setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
+ } else {
+ [GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
+ }
+ [GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
+
+ // longitude
+ if (longitude < 0.0) {
+ longitude = longitude * -1.0f;
+ [GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
+ }
+ else {
+ [GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
+ }
+ [GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
+
+ // altitude
+ CGFloat altitude = newLocation.altitude;
+ if (!isnan(altitude)){
+ if (altitude < 0) {
+ altitude = -altitude;
+ [GPSDictionary setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
+ } else {
+ [GPSDictionary setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
+ }
+ [GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
+ }
+
+ // Time and date
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ [formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
+ [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
+ [GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
+ [formatter setDateFormat:@"yyyy:MM:dd"];
+ [GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
+
+ [self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
+ [self imagePickerControllerReturnImageResult];
+}
+
+- (void)locationManager:(CLLocationManager*)manager didFailWithError:(NSError*)error
+{
+ if (locationManager == nil) {
+ return;
+ }
+
+ [self.locationManager stopUpdatingLocation];
+ self.locationManager = nil;
+
+ [self imagePickerControllerReturnImageResult];
+}
+
+- (void)imagePickerControllerReturnImageResult
+{
+ CDVPictureOptions* options = self.pickerController.pictureOptions;
+ CDVPluginResult* result = nil;
+
+ if (self.metadata) {
+ CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)self.data, NULL);
+ CFStringRef sourceType = CGImageSourceGetType(sourceImage);
+
+ CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
+ CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
+ CGImageDestinationFinalize(destinationImage);
+
+ CFRelease(sourceImage);
+ CFRelease(destinationImage);
+ }
+
+ switch (options.destinationType) {
+ case DestinationTypeFileUri:
+ {
+ NSError* err = nil;
+ NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
+ NSString* filePath = [self tempFilePath:extension];
+
+ // save file
+ if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
+ }
+ else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
+ }
+ }
+ break;
+ case DestinationTypeDataUrl:
+ {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(self.data)];
+ }
+ break;
+ case DestinationTypeNativeUri:
+ default:
+ break;
+ };
+
+ if (result) {
+ [self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
+ }
+
+ self.hasPendingOperation = NO;
+ self.pickerController = nil;
+ self.data = nil;
+ self.metadata = nil;
+
+ if (options.saveToPhotoAlbum) {
+ ALAssetsLibrary *library = [ALAssetsLibrary new];
+ [library writeImageDataToSavedPhotosAlbum:self.data metadata:self.metadata completionBlock:nil];
+ }
+}
+
+@end
+
+@implementation CDVCameraPicker
+
+- (BOOL)prefersStatusBarHidden
+{
+ return YES;
+}
+
+- (UIViewController*)childViewControllerForStatusBarHidden
+{
+ return nil;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
+ if ([self respondsToSelector:sel]) {
+ [self performSelector:sel withObject:nil afterDelay:0];
+ }
+
+ [super viewWillAppear:animated];
+}
+
++ (instancetype) createFromPictureOptions:(CDVPictureOptions*)pictureOptions;
+{
+ CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init];
+ cameraPicker.pictureOptions = pictureOptions;
+ cameraPicker.sourceType = pictureOptions.sourceType;
+ cameraPicker.allowsEditing = pictureOptions.allowsEditing;
+
+ if (cameraPicker.sourceType == UIImagePickerControllerSourceTypeCamera) {
+ // We only allow taking pictures (no video) in this API.
+ cameraPicker.mediaTypes = @[(NSString*)kUTTypeImage];
+ // We can only set the camera device if we're actually using the camera.
+ cameraPicker.cameraDevice = pictureOptions.cameraDirection;
+ } else if (pictureOptions.mediaType == MediaTypeAll) {
+ cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:cameraPicker.sourceType];
+ } else {
+ NSArray* mediaArray = @[(NSString*)(pictureOptions.mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage)];
+ cameraPicker.mediaTypes = mediaArray;
+ }
+
+ return cameraPicker;
+}
+
+@end
diff --git a/plugins/org.apache.cordova.camera/src/ios/CDVExif.h b/plugins/org.apache.cordova.camera/src/ios/CDVExif.h
new file mode 100644
index 0000000..3e8adbd
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ios/CDVExif.h
@@ -0,0 +1,43 @@
+/*
+ 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.
+ */
+
+#ifndef CordovaLib_ExifData_h
+#define CordovaLib_ExifData_h
+
+// exif data types
+typedef enum exifDataTypes {
+ EDT_UBYTE = 1, // 8 bit unsigned integer
+ EDT_ASCII_STRING, // 8 bits containing 7 bit ASCII code, null terminated
+ EDT_USHORT, // 16 bit unsigned integer
+ EDT_ULONG, // 32 bit unsigned integer
+ EDT_URATIONAL, // 2 longs, first is numerator and second is denominator
+ EDT_SBYTE,
+ EDT_UNDEFINED, // 8 bits
+ EDT_SSHORT,
+ EDT_SLONG, // 32bit signed integer (2's complement)
+ EDT_SRATIONAL, // 2 SLONGS, first long is numerator, second is denominator
+ EDT_SINGLEFLOAT,
+ EDT_DOUBLEFLOAT
+} ExifDataTypes;
+
+// maps integer code for exif data types to width in bytes
+static const int DataTypeToWidth[] = {1,1,2,4,8,1,1,2,4,8,4,8};
+
+static const int RECURSE_HORIZON = 8;
+#endif
diff --git a/plugins/org.apache.cordova.camera/src/ios/CDVJpegHeaderWriter.h b/plugins/org.apache.cordova.camera/src/ios/CDVJpegHeaderWriter.h
new file mode 100644
index 0000000..3b43ef0
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ios/CDVJpegHeaderWriter.h
@@ -0,0 +1,62 @@
+/*
+ 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.
+ */
+
+#import
+
+@interface CDVJpegHeaderWriter : NSObject {
+ NSDictionary * SubIFDTagFormatDict;
+ NSDictionary * IFD0TagFormatDict;
+}
+
+- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata
+ withExifBlock: (NSString*) exifstr;
+- (NSString*) createExifAPP1 : (NSDictionary*) datadict;
+- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb
+ withPlaces: (NSNumber*) width;
+- (NSString*) formatNumberWithLeadingZeroes: (NSNumber*) numb
+ withPlaces: (NSNumber*) places;
+- (NSString*) decimalToUnsignedRational: (NSNumber*) numb
+ withResultNumerator: (NSNumber**) numerator
+ withResultDenominator: (NSNumber**) denominator;
+- (void) continuedFraction: (double) val
+ withFractionList: (NSMutableArray*) fractionlist
+ withHorizon: (int) horizon;
+//- (void) expandContinuedFraction: (NSArray*) fractionlist;
+- (void) splitDouble: (double) val
+ withIntComponent: (int*) rightside
+ withFloatRemainder: (double*) leftside;
+- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator
+ withDenominator: (NSNumber*) denominator
+ asSigned: (Boolean) signedFlag;
+- (NSString*) hexStringFromData : (NSData*) data;
+- (NSNumber*) numericFromHexString : (NSString *) hexstring;
+
+/*
+- (void) readExifMetaData : (NSData*) imgdata;
+- (void) spliceImageData : (NSData*) imgdata withExifData: (NSDictionary*) exifdata;
+- (void) locateExifMetaData : (NSData*) imgdata;
+- (NSString*) createExifAPP1 : (NSDictionary*) datadict;
+- (void) createExifDataString : (NSDictionary*) datadict;
+- (NSString*) createDataElement : (NSString*) element
+ withElementData: (NSString*) data
+ withExternalDataBlock: (NSDictionary*) memblock;
+- (NSString*) hexStringFromData : (NSData*) data;
+- (NSNumber*) numericFromHexString : (NSString *) hexstring;
+*/
+@end
diff --git a/plugins/org.apache.cordova.camera/src/ios/CDVJpegHeaderWriter.m b/plugins/org.apache.cordova.camera/src/ios/CDVJpegHeaderWriter.m
new file mode 100644
index 0000000..4d3ea24
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ios/CDVJpegHeaderWriter.m
@@ -0,0 +1,547 @@
+/*
+ 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.
+ */
+
+#import "CDVJpegHeaderWriter.h"
+#include "CDVExif.h"
+
+/* macros for tag info shorthand:
+ tagno : tag number
+ typecode : data type
+ components : number of components
+ appendString (TAGINF_W_APPEND only) : string to append to data
+ Exif date data format include an extra 0x00 to the end of the data
+ */
+#define TAGINF(tagno, typecode, components) [NSArray arrayWithObjects: tagno, typecode, components, nil]
+#define TAGINF_W_APPEND(tagno, typecode, components, appendString) [NSArray arrayWithObjects: tagno, typecode, components, appendString, nil]
+
+const uint mJpegId = 0xffd8; // JPEG format marker
+const uint mExifMarker = 0xffe1; // APP1 jpeg header marker
+const uint mExif = 0x45786966; // ASCII 'Exif', first characters of valid exif header after size
+const uint mMotorallaByteAlign = 0x4d4d; // 'MM', motorola byte align, msb first or 'sane'
+const uint mIntelByteAlgin = 0x4949; // 'II', Intel byte align, lsb first or 'batshit crazy reverso world'
+const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a(MM) or 0x2a00(II), tiff version number
+
+
+@implementation CDVJpegHeaderWriter
+
+- (id) init {
+ self = [super init];
+ // supported tags for exif IFD
+ IFD0TagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys:
+ // TAGINF(@"010e", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"ImageDescription",
+ TAGINF_W_APPEND(@"0132", [NSNumber numberWithInt:EDT_ASCII_STRING], @20, @"00"), @"DateTime",
+ TAGINF(@"010f", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Make",
+ TAGINF(@"0110", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Model",
+ TAGINF(@"0131", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Software",
+ TAGINF(@"011a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"XResolution",
+ TAGINF(@"011b", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"YResolution",
+ // currently supplied outside of Exif data block by UIImagePickerControllerMediaMetadata, this is set manually in CDVCamera.m
+ /* TAGINF(@"0112", [NSNumber numberWithInt:EDT_USHORT], @1), @"Orientation",
+
+ // rest of the tags are supported by exif spec, but are not specified by UIImagePickerControllerMediaMedadata
+ // should camera hardware supply these values in future versions, or if they can be derived, ImageHeaderWriter will include them gracefully
+ TAGINF(@"0128", [NSNumber numberWithInt:EDT_USHORT], @1), @"ResolutionUnit",
+ TAGINF(@"013e", [NSNumber numberWithInt:EDT_URATIONAL], @2), @"WhitePoint",
+ TAGINF(@"013f", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"PrimaryChromaticities",
+ TAGINF(@"0211", [NSNumber numberWithInt:EDT_URATIONAL], @3), @"YCbCrCoefficients",
+ TAGINF(@"0213", [NSNumber numberWithInt:EDT_USHORT], @1), @"YCbCrPositioning",
+ TAGINF(@"0214", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"ReferenceBlackWhite",
+ TAGINF(@"8298", [NSNumber numberWithInt:EDT_URATIONAL], @0), @"Copyright",
+
+ // offset to exif subifd, we determine this dynamically based on the size of the main exif IFD
+ TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",*/
+ nil];
+
+
+ // supported tages for exif subIFD
+ SubIFDTagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys:
+ //TAGINF(@"9000", [NSNumber numberWithInt:], @), @"ExifVersion",
+ //TAGINF(@"9202",[NSNumber numberWithInt:EDT_URATIONAL],@1), @"ApertureValue",
+ //TAGINF(@"9203",[NSNumber numberWithInt:EDT_SRATIONAL],@1), @"BrightnessValue",
+ TAGINF(@"a001",[NSNumber numberWithInt:EDT_USHORT],@1), @"ColorSpace",
+ TAGINF_W_APPEND(@"9004",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeDigitized",
+ TAGINF_W_APPEND(@"9003",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeOriginal",
+ TAGINF(@"a402", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureMode",
+ TAGINF(@"8822", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureProgram",
+ //TAGINF(@"829a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"ExposureTime",
+ //TAGINF(@"829d", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FNumber",
+ TAGINF(@"9209", [NSNumber numberWithInt:EDT_USHORT], @1), @"Flash",
+ // FocalLengthIn35mmFilm
+ TAGINF(@"a405", [NSNumber numberWithInt:EDT_USHORT], @1), @"FocalLenIn35mmFilm",
+ //TAGINF(@"920a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FocalLength",
+ //TAGINF(@"8827", [NSNumber numberWithInt:EDT_USHORT], @2), @"ISOSpeedRatings",
+ TAGINF(@"9207", [NSNumber numberWithInt:EDT_USHORT],@1), @"MeteringMode",
+ // specific to compressed data
+ TAGINF(@"a002", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelXDimension",
+ TAGINF(@"a003", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelYDimension",
+ // data type undefined, but this is a DSC camera, so value is always 1, treat as ushort
+ TAGINF(@"a301", [NSNumber numberWithInt:EDT_USHORT],@1), @"SceneType",
+ TAGINF(@"a217",[NSNumber numberWithInt:EDT_USHORT],@1), @"SensingMethod",
+ //TAGINF(@"9201", [NSNumber numberWithInt:EDT_SRATIONAL], @1), @"ShutterSpeedValue",
+ // specifies location of main subject in scene (x,y,wdith,height) expressed before rotation processing
+ //TAGINF(@"9214", [NSNumber numberWithInt:EDT_USHORT], @4), @"SubjectArea",
+ TAGINF(@"a403", [NSNumber numberWithInt:EDT_USHORT], @1), @"WhiteBalance",
+ nil];
+ return self;
+}
+
+- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata withExifBlock: (NSString*) exifstr {
+
+ CDVJpegHeaderWriter * exifWriter = [[CDVJpegHeaderWriter alloc] init];
+
+ NSMutableData * exifdata = [NSMutableData dataWithCapacity: [exifstr length]/2];
+ int idx;
+ for (idx = 0; idx+1 < [exifstr length]; idx+=2) {
+ NSRange range = NSMakeRange(idx, 2);
+ NSString* hexStr = [exifstr substringWithRange:range];
+ NSScanner* scanner = [NSScanner scannerWithString:hexStr];
+ unsigned int intValue;
+ [scanner scanHexInt:&intValue];
+ [exifdata appendBytes:&intValue length:1];
+ }
+
+ NSMutableData * ddata = [NSMutableData dataWithCapacity: [jpegdata length]];
+ NSMakeRange(0,4);
+ int loc = 0;
+ bool done = false;
+ // read the jpeg data until we encounter the app1==0xFFE1 marker
+ while (loc+1 < [jpegdata length]) {
+ NSData * blag = [jpegdata subdataWithRange: NSMakeRange(loc,2)];
+ if( [[blag description] isEqualToString : @""]) {
+ // read the APP1 block size bits
+ NSString * the = [exifWriter hexStringFromData:[jpegdata subdataWithRange: NSMakeRange(loc+2,2)]];
+ NSNumber * app1width = [exifWriter numericFromHexString:the];
+ //consume the original app1 block
+ [ddata appendData:exifdata];
+ // advance our loc marker past app1
+ loc += [app1width intValue] + 2;
+ done = true;
+ } else {
+ if(!done) {
+ [ddata appendData:blag];
+ loc += 2;
+ } else {
+ break;
+ }
+ }
+ }
+ // copy the remaining data
+ [ddata appendData:[jpegdata subdataWithRange: NSMakeRange(loc,[jpegdata length]-loc)]];
+ return ddata;
+}
+
+
+
+/**
+ * Create the Exif data block as a hex string
+ * jpeg uses Application Markers (APP's) as markers for application data
+ * APP1 is the application marker reserved for exif data
+ *
+ * (NSDictionary*) datadict - with subdictionaries marked '{TIFF}' and '{EXIF}' as returned by imagePickerController with a valid
+ * didFinishPickingMediaWithInfo data dict, under key @"UIImagePickerControllerMediaMetadata"
+ *
+ * the following constructs a hex string to Exif specifications, and is therefore brittle
+ * altering the order of arguments to the string constructors, modifying field sizes or formats,
+ * and any other minor change will likely prevent the exif data from being read
+ */
+- (NSString*) createExifAPP1 : (NSDictionary*) datadict {
+ NSMutableString * app1; // holds finalized product
+ NSString * exifIFD; // exif information file directory
+ NSString * subExifIFD; // subexif information file directory
+
+ // FFE1 is the hex APP1 marker code, and will allow client apps to read the data
+ NSString * app1marker = @"ffe1";
+ // SSSS size, to be determined
+ // EXIF ascii characters followed by 2bytes of zeros
+ NSString * exifmarker = @"457869660000";
+ // Tiff header: 4d4d is motorolla byte align (big endian), 002a is hex for 42
+ NSString * tiffheader = @"4d4d002a";
+ //first IFD offset from the Tiff header to IFD0. Since we are writing it, we know it's address 0x08
+ NSString * ifd0offset = @"00000008";
+ // current offset to next data area
+ int currentDataOffset = 0;
+
+ //data labeled as TIFF in UIImagePickerControllerMediaMetaData is part of the EXIF IFD0 portion of APP1
+ exifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{TIFF}"] withFormatDict: IFD0TagFormatDict isIFD0:YES currentDataOffset:¤tDataOffset];
+
+ //data labeled as EXIF in UIImagePickerControllerMediaMetaData is part of the EXIF Sub IFD portion of APP1
+ subExifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{Exif}"] withFormatDict: SubIFDTagFormatDict isIFD0:NO currentDataOffset:¤tDataOffset];
+ /*
+ NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",exifIFD,[exifIFD length]);
+
+ NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",subExifIFD,[subExifIFD length]);
+ */
+ // construct the complete app1 data block
+ app1 = [[NSMutableString alloc] initWithFormat: @"%@%04x%@%@%@%@%@",
+ app1marker,
+ (unsigned int)(16 + ([exifIFD length]/2) + ([subExifIFD length]/2)) /*16+[exifIFD length]/2*/,
+ exifmarker,
+ tiffheader,
+ ifd0offset,
+ exifIFD,
+ subExifIFD];
+
+ return app1;
+}
+
+// returns hex string representing a valid exif information file directory constructed from the datadict and formatdict
+- (NSString*) createExifIFDFromDict : (NSDictionary*) datadict
+ withFormatDict : (NSDictionary*) formatdict
+ isIFD0 : (BOOL) ifd0flag
+ currentDataOffset : (int*) dataoffset {
+ NSArray * datakeys = [datadict allKeys]; // all known data keys
+ NSArray * knownkeys = [formatdict allKeys]; // only keys in knowkeys are considered for entry in this IFD
+ NSMutableArray * ifdblock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // all ifd entries
+ NSMutableArray * ifddatablock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // data block entries
+ // ifd0flag = NO; // ifd0 requires a special flag and has offset to next ifd appended to end
+
+ // iterate through known provided data keys
+ for (int i = 0; i < [datakeys count]; i++) {
+ NSString * key = [datakeys objectAtIndex:i];
+ // don't muck about with unknown keys
+ if ([knownkeys indexOfObject: key] != NSNotFound) {
+ // create new IFD entry
+ NSString * entry = [self createIFDElement: key
+ withFormat: [formatdict objectForKey:key]
+ withElementData: [datadict objectForKey:key]];
+ // create the IFD entry's data block
+ NSString * data = [self createIFDElementDataWithFormat: [formatdict objectForKey:key]
+ withData: [datadict objectForKey:key]];
+ if (entry) {
+ [ifdblock addObject:entry];
+ if(!data) {
+ [ifdblock addObject:@""];
+ } else {
+ [ifddatablock addObject:data];
+ }
+ }
+ }
+ }
+
+ NSMutableString * exifstr = [[NSMutableString alloc] initWithCapacity: [ifdblock count] * 24];
+ NSMutableString * dbstr = [[NSMutableString alloc] initWithCapacity: 100];
+
+ int addr=*dataoffset; // current offset/address in datablock
+ if (ifd0flag) {
+ // calculate offset to datablock based on ifd file entry count
+ addr += 14+(12*([ifddatablock count]+1)); // +1 for tag 0x8769, exifsubifd offset
+ } else {
+ // current offset + numSubIFDs (2-bytes) + 12*numSubIFDs + endMarker (4-bytes)
+ addr += 2+(12*[ifddatablock count])+4;
+ }
+
+ for (int i = 0; i < [ifdblock count]; i++) {
+ NSString * entry = [ifdblock objectAtIndex:i];
+ NSString * data = [ifddatablock objectAtIndex:i];
+
+ // check if the data fits into 4 bytes
+ if( [data length] <= 8) {
+ // concatenate the entry and the (4byte) data entry into the final IFD entry and append to exif ifd string
+ [exifstr appendFormat : @"%@%@", entry, data];
+ } else {
+ [exifstr appendFormat : @"%@%08x", entry, addr];
+ [dbstr appendFormat: @"%@", data];
+ addr+= [data length] / 2;
+ /*
+ NSLog(@"=====data-length[%i]=======",[data length]);
+ NSLog(@"addr-offset[%i]",addr);
+ NSLog(@"entry[%@]",entry);
+ NSLog(@"data[%@]",data);
+ */
+ }
+ }
+
+ // calculate IFD0 terminal offset tags, currently ExifSubIFD
+ unsigned int entrycount = (unsigned int)[ifdblock count];
+ if (ifd0flag) {
+ // 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header
+ NSNumber * offset = [NSNumber numberWithUnsignedInteger:[exifstr length] / 2 + [dbstr length] / 2 + 18+8];
+
+ [self appendExifOffsetTagTo: exifstr
+ withOffset : offset];
+ entrycount++;
+ }
+ *dataoffset = addr;
+ return [[NSString alloc] initWithFormat: @"%04x%@%@%@",
+ entrycount,
+ exifstr,
+ @"00000000", // offset to next IFD, 0 since there is none
+ dbstr]; // lastly, the datablock
+}
+
+// Creates an exif formatted exif information file directory entry
+- (NSString*) createIFDElement: (NSString*) elementName withFormat: (NSArray*) formtemplate withElementData: (NSString*) data {
+ //NSArray * fielddata = [formatdict objectForKey: elementName];// format data of desired field
+ if (formtemplate) {
+ // format string @"%@%@%@%@", tag number, data format, components, value
+ NSNumber * dataformat = [formtemplate objectAtIndex:1];
+ NSNumber * components = [formtemplate objectAtIndex:2];
+ if([components intValue] == 0) {
+ components = [NSNumber numberWithUnsignedInteger:[data length] * DataTypeToWidth[[dataformat intValue]-1]];
+ }
+
+ return [[NSString alloc] initWithFormat: @"%@%@%08x",
+ [formtemplate objectAtIndex:0], // the field code
+ [self formatNumberWithLeadingZeroes: dataformat withPlaces: @4], // the data type code
+ [components intValue]]; // number of components
+ }
+ return NULL;
+}
+
+/**
+ * appends exif IFD0 tag 8769 "ExifOffset" to the string provided
+ * (NSMutableString*) str - string you wish to append the 8769 tag to: APP1 or IFD0 hex data string
+ * // TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",
+ */
+- (void) appendExifOffsetTagTo: (NSMutableString*) str withOffset : (NSNumber*) offset {
+ NSArray * format = TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1);
+
+ NSString * entry = [self createIFDElement: @"ExifOffset"
+ withFormat: format
+ withElementData: [offset stringValue]];
+
+ NSString * data = [self createIFDElementDataWithFormat: format
+ withData: [offset stringValue]];
+ [str appendFormat:@"%@%@", entry, data];
+}
+
+// formats the Information File Directory Data to exif format
+- (NSString*) createIFDElementDataWithFormat: (NSArray*) dataformat withData: (NSString*) data {
+ NSMutableString * datastr = nil;
+ NSNumber * tmp = nil;
+ NSNumber * formatcode = [dataformat objectAtIndex:1];
+ NSUInteger formatItemsCount = [dataformat count];
+ NSNumber * num = @0;
+ NSNumber * denom = @0;
+
+ switch ([formatcode intValue]) {
+ case EDT_UBYTE:
+ break;
+ case EDT_ASCII_STRING:
+ datastr = [[NSMutableString alloc] init];
+ for (int i = 0; i < [data length]; i++) {
+ [datastr appendFormat:@"%02x",[data characterAtIndex:i]];
+ }
+ if (formatItemsCount > 3) {
+ // We have additional data to append.
+ // currently used by Date format to append final 0x00 but can be used by other data types as well in the future
+ [datastr appendString:[dataformat objectAtIndex:3]];
+ }
+ if ([datastr length] < 8) {
+ NSString * format = [NSString stringWithFormat:@"%%0%dd", (int)(8 - [datastr length])];
+ [datastr appendFormat:format,0];
+ }
+ return datastr;
+ case EDT_USHORT:
+ return [[NSString alloc] initWithFormat : @"%@%@",
+ [self formattedHexStringFromDecimalNumber: [NSNumber numberWithInt: [data intValue]] withPlaces: @4],
+ @"0000"];
+ case EDT_ULONG:
+ tmp = [NSNumber numberWithUnsignedLong:[data intValue]];
+ return [NSString stringWithFormat : @"%@",
+ [self formattedHexStringFromDecimalNumber: tmp withPlaces: @8]];
+ case EDT_URATIONAL:
+ return [self decimalToUnsignedRational: [NSNumber numberWithDouble:[data doubleValue]]
+ withResultNumerator: &num
+ withResultDenominator: &denom];
+ case EDT_SBYTE:
+
+ break;
+ case EDT_UNDEFINED:
+ break; // 8 bits
+ case EDT_SSHORT:
+ break;
+ case EDT_SLONG:
+ break; // 32bit signed integer (2's complement)
+ case EDT_SRATIONAL:
+ break; // 2 SLONGS, first long is numerator, second is denominator
+ case EDT_SINGLEFLOAT:
+ break;
+ case EDT_DOUBLEFLOAT:
+ break;
+ }
+ return datastr;
+}
+
+//======================================================================================================================
+// Utility Methods
+//======================================================================================================================
+
+// creates a formatted little endian hex string from a number and width specifier
+- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb withPlaces: (NSNumber*) width {
+ NSMutableString * str = [[NSMutableString alloc] initWithCapacity:[width intValue]];
+ NSString * formatstr = [[NSString alloc] initWithFormat: @"%%%@%dx", @"0", [width intValue]];
+ [str appendFormat:formatstr, [numb intValue]];
+ return str;
+}
+
+// format number as string with leading 0's
+- (NSString*) formatNumberWithLeadingZeroes: (NSNumber *) numb withPlaces: (NSNumber *) places {
+ NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
+ NSString *formatstr = [@"" stringByPaddingToLength:[places unsignedIntegerValue] withString:@"0" startingAtIndex:0];
+ [formatter setPositiveFormat:formatstr];
+ return [formatter stringFromNumber:numb];
+}
+
+// approximate a decimal with a rational by method of continued fraction
+// can be collasped into decimalToUnsignedRational after testing
+- (void) decimalToRational: (NSNumber *) numb
+ withResultNumerator: (NSNumber**) numerator
+ withResultDenominator: (NSNumber**) denominator {
+ NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
+
+ [self continuedFraction: [numb doubleValue]
+ withFractionList: fractionlist
+ withHorizon: 8];
+
+ // simplify complex fraction represented by partial fraction list
+ [self expandContinuedFraction: fractionlist
+ withResultNumerator: numerator
+ withResultDenominator: denominator];
+
+}
+
+// approximate a decimal with an unsigned rational by method of continued fraction
+- (NSString*) decimalToUnsignedRational: (NSNumber *) numb
+ withResultNumerator: (NSNumber**) numerator
+ withResultDenominator: (NSNumber**) denominator {
+ NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
+
+ // generate partial fraction list
+ [self continuedFraction: [numb doubleValue]
+ withFractionList: fractionlist
+ withHorizon: 8];
+
+ // simplify complex fraction represented by partial fraction list
+ [self expandContinuedFraction: fractionlist
+ withResultNumerator: numerator
+ withResultDenominator: denominator];
+
+ return [self formatFractionList: fractionlist];
+}
+
+// recursive implementation of decimal approximation by continued fraction
+- (void) continuedFraction: (double) val
+ withFractionList: (NSMutableArray*) fractionlist
+ withHorizon: (int) horizon {
+ int whole;
+ double remainder;
+ // 1. split term
+ [self splitDouble: val withIntComponent: &whole withFloatRemainder: &remainder];
+ [fractionlist addObject: [NSNumber numberWithInt:whole]];
+
+ // 2. calculate reciprocal of remainder
+ if (!remainder) return; // early exit, exact fraction found, avoids recip/0
+ double recip = 1 / remainder;
+
+ // 3. exit condition
+ if ([fractionlist count] > horizon) {
+ return;
+ }
+
+ // 4. recurse
+ [self continuedFraction:recip withFractionList: fractionlist withHorizon: horizon];
+
+}
+
+// expand continued fraction list, creating a single level rational approximation
+-(void) expandContinuedFraction: (NSArray*) fractionlist
+ withResultNumerator: (NSNumber**) numerator
+ withResultDenominator: (NSNumber**) denominator {
+ NSUInteger i = 0;
+ int den = 0;
+ int num = 0;
+ if ([fractionlist count] == 1) {
+ *numerator = [NSNumber numberWithInt:[[fractionlist objectAtIndex:0] intValue]];
+ *denominator = @1;
+ return;
+ }
+
+ //begin at the end of the list
+ i = [fractionlist count] - 1;
+ num = 1;
+ den = [[fractionlist objectAtIndex:i] intValue];
+
+ while (i > 0) {
+ int t = [[fractionlist objectAtIndex: i-1] intValue];
+ num = t * den + num;
+ if (i==1) {
+ break;
+ } else {
+ t = num;
+ num = den;
+ den = t;
+ }
+ i--;
+ }
+ // set result parameters values
+ *numerator = [NSNumber numberWithInt: num];
+ *denominator = [NSNumber numberWithInt: den];
+}
+
+// formats expanded fraction list to string matching exif specification
+- (NSString*) formatFractionList: (NSArray *) fractionlist {
+ NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
+
+ if ([fractionlist count] == 1){
+ [str appendFormat: @"%08x00000001", [[fractionlist objectAtIndex:0] intValue]];
+ }
+ return str;
+}
+
+// format rational as
+- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator withDenominator: (NSNumber*) denominator asSigned: (Boolean) signedFlag {
+ NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
+ if (signedFlag) {
+ long num = [numerator longValue];
+ long den = [denominator longValue];
+ [str appendFormat: @"%08lx%08lx", num >= 0 ? num : ~ABS(num) + 1, num >= 0 ? den : ~ABS(den) + 1];
+ } else {
+ [str appendFormat: @"%08lx%08lx", [numerator unsignedLongValue], [denominator unsignedLongValue]];
+ }
+ return str;
+}
+
+// split a floating point number into two integer values representing the left and right side of the decimal
+- (void) splitDouble: (double) val withIntComponent: (int*) rightside withFloatRemainder: (double*) leftside {
+ *rightside = val; // convert numb to int representation, which truncates the decimal portion
+ *leftside = val - *rightside;
+}
+
+
+//
+- (NSString*) hexStringFromData : (NSData*) data {
+ //overflow detection
+ const unsigned char *dataBuffer = [data bytes];
+ return [[NSString alloc] initWithFormat: @"%02x%02x",
+ (unsigned char)dataBuffer[0],
+ (unsigned char)dataBuffer[1]];
+}
+
+// convert a hex string to a number
+- (NSNumber*) numericFromHexString : (NSString *) hexstring {
+ NSScanner * scan = NULL;
+ unsigned int numbuf= 0;
+
+ scan = [NSScanner scannerWithString:hexstring];
+ [scan scanHexInt:&numbuf];
+ return [NSNumber numberWithInt:numbuf];
+}
+
+@end
diff --git a/plugins/org.apache.cordova.camera/src/ios/UIImage+CropScaleOrientation.h b/plugins/org.apache.cordova.camera/src/ios/UIImage+CropScaleOrientation.h
new file mode 100644
index 0000000..31bc42f
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ios/UIImage+CropScaleOrientation.h
@@ -0,0 +1,29 @@
+/*
+ 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.
+ */
+
+#import
+
+@interface UIImage (CropScaleOrientation)
+
+- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize;
+- (UIImage*)imageCorrectedForCaptureOrientation;
+- (UIImage*)imageCorrectedForCaptureOrientation:(UIImageOrientation)imageOrientation;
+- (UIImage*)imageByScalingNotCroppingForSize:(CGSize)targetSize;
+
+@end
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/src/ios/UIImage+CropScaleOrientation.m b/plugins/org.apache.cordova.camera/src/ios/UIImage+CropScaleOrientation.m
new file mode 100644
index 0000000..a66a5d8
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ios/UIImage+CropScaleOrientation.m
@@ -0,0 +1,175 @@
+/*
+ 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.
+ */
+
+#import "UIImage+CropScaleOrientation.h"
+
+@implementation UIImage (CropScaleOrientation)
+
+- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize
+{
+ UIImage* sourceImage = self;
+ UIImage* newImage = nil;
+ CGSize imageSize = sourceImage.size;
+ CGFloat width = imageSize.width;
+ CGFloat height = imageSize.height;
+ CGFloat targetWidth = targetSize.width;
+ CGFloat targetHeight = targetSize.height;
+ CGFloat scaleFactor = 0.0;
+ CGFloat scaledWidth = targetWidth;
+ CGFloat scaledHeight = targetHeight;
+ CGPoint thumbnailPoint = CGPointMake(0.0, 0.0);
+
+ if (CGSizeEqualToSize(imageSize, targetSize) == NO) {
+ CGFloat widthFactor = targetWidth / width;
+ CGFloat heightFactor = targetHeight / height;
+
+ if (widthFactor > heightFactor) {
+ scaleFactor = widthFactor; // scale to fit height
+ } else {
+ scaleFactor = heightFactor; // scale to fit width
+ }
+ scaledWidth = width * scaleFactor;
+ scaledHeight = height * scaleFactor;
+
+ // center the image
+ if (widthFactor > heightFactor) {
+ thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
+ } else if (widthFactor < heightFactor) {
+ thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
+ }
+ }
+
+ UIGraphicsBeginImageContext(targetSize); // this will crop
+
+ CGRect thumbnailRect = CGRectZero;
+ thumbnailRect.origin = thumbnailPoint;
+ thumbnailRect.size.width = scaledWidth;
+ thumbnailRect.size.height = scaledHeight;
+
+ [sourceImage drawInRect:thumbnailRect];
+
+ newImage = UIGraphicsGetImageFromCurrentImageContext();
+ if (newImage == nil) {
+ NSLog(@"could not scale image");
+ }
+
+ // pop the context to get back to the default
+ UIGraphicsEndImageContext();
+ return newImage;
+}
+
+- (UIImage*)imageCorrectedForCaptureOrientation:(UIImageOrientation)imageOrientation
+{
+ float rotation_radians = 0;
+ bool perpendicular = false;
+
+ switch (imageOrientation) {
+ case UIImageOrientationUp :
+ rotation_radians = 0.0;
+ break;
+
+ case UIImageOrientationDown:
+ rotation_radians = M_PI; // don't be scared of radians, if you're reading this, you're good at math
+ break;
+
+ case UIImageOrientationRight:
+ rotation_radians = M_PI_2;
+ perpendicular = true;
+ break;
+
+ case UIImageOrientationLeft:
+ rotation_radians = -M_PI_2;
+ perpendicular = true;
+ break;
+
+ default:
+ break;
+ }
+
+ UIGraphicsBeginImageContext(CGSizeMake(self.size.width, self.size.height));
+ CGContextRef context = UIGraphicsGetCurrentContext();
+
+ // Rotate around the center point
+ CGContextTranslateCTM(context, self.size.width / 2, self.size.height / 2);
+ CGContextRotateCTM(context, rotation_radians);
+
+ CGContextScaleCTM(context, 1.0, -1.0);
+ float width = perpendicular ? self.size.height : self.size.width;
+ float height = perpendicular ? self.size.width : self.size.height;
+ CGContextDrawImage(context, CGRectMake(-width / 2, -height / 2, width, height), [self CGImage]);
+
+ // Move the origin back since the rotation might've change it (if its 90 degrees)
+ if (perpendicular) {
+ CGContextTranslateCTM(context, -self.size.height / 2, -self.size.width / 2);
+ }
+
+ UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ return newImage;
+}
+
+- (UIImage*)imageCorrectedForCaptureOrientation
+{
+ return [self imageCorrectedForCaptureOrientation:[self imageOrientation]];
+}
+
+- (UIImage*)imageByScalingNotCroppingForSize:(CGSize)targetSize
+{
+ UIImage* sourceImage = self;
+ UIImage* newImage = nil;
+ CGSize imageSize = sourceImage.size;
+ CGFloat width = imageSize.width;
+ CGFloat height = imageSize.height;
+ CGFloat targetWidth = targetSize.width;
+ CGFloat targetHeight = targetSize.height;
+ CGFloat scaleFactor = 0.0;
+ CGSize scaledSize = targetSize;
+
+ if (CGSizeEqualToSize(imageSize, targetSize) == NO) {
+ CGFloat widthFactor = targetWidth / width;
+ CGFloat heightFactor = targetHeight / height;
+
+ // opposite comparison to imageByScalingAndCroppingForSize in order to contain the image within the given bounds
+ if (widthFactor > heightFactor) {
+ scaleFactor = heightFactor; // scale to fit height
+ } else {
+ scaleFactor = widthFactor; // scale to fit width
+ }
+ scaledSize = CGSizeMake(MIN(width * scaleFactor, targetWidth), MIN(height * scaleFactor, targetHeight));
+ }
+
+ // If the pixels are floats, it causes a white line in iOS8 and probably other versions too
+ scaledSize.width = (int)scaledSize.width;
+ scaledSize.height = (int)scaledSize.height;
+
+ UIGraphicsBeginImageContext(scaledSize); // this will resize
+
+ [sourceImage drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
+
+ newImage = UIGraphicsGetImageFromCurrentImageContext();
+ if (newImage == nil) {
+ NSLog(@"could not scale image");
+ }
+
+ // pop the context to get back to the default
+ UIGraphicsEndImageContext();
+ return newImage;
+}
+
+@end
diff --git a/plugins/org.apache.cordova.camera/src/ubuntu/CaptureWidget.qml b/plugins/org.apache.cordova.camera/src/ubuntu/CaptureWidget.qml
new file mode 100644
index 0000000..0a332e2
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ubuntu/CaptureWidget.qml
@@ -0,0 +1,118 @@
+/*
+ *
+ * Copyright 2013 Canonical Ltd.
+ *
+ * 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.
+ *
+*/
+import QtQuick 2.0
+import QtMultimedia 5.0
+
+Rectangle {
+ property string shootImagePath: "shoot.png"
+ function isSuffix(str, suffix) {
+ return String(str).substr(String(str).length - suffix.length) == suffix
+ }
+
+ id: ui
+ color: "#252423"
+ anchors.fill: parent
+
+ Camera {
+ objectName: "camera"
+ id: camera
+ onError: {
+ console.log(errorString);
+ }
+ videoRecorder.audioBitRate: 128000
+ imageCapture {
+ onImageSaved: {
+ root.exec("Camera", "onImageSaved", [path]);
+ ui.destroy();
+ }
+ }
+ }
+ VideoOutput {
+ id: output
+ source: camera
+ width: parent.width
+ height: parent.height
+ }
+
+ Item {
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: shootButton.height
+ BorderImage {
+ id: leftBackground
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: middle.left
+ anchors.topMargin: units.dp(2)
+ anchors.bottomMargin: units.dp(2)
+ source: "toolbar-left.png"
+ Image {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: parent.iconSpacing
+ source: "back.png"
+ width: units.gu(6)
+ height: units.gu(5)
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ root.exec("Camera", "cancel");
+ }
+ }
+ }
+ }
+ BorderImage {
+ id: middle
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: shootButton.height + units.gu(1)
+ width: shootButton.width
+ source: "toolbar-middle.png"
+ Image {
+ id: shootButton
+ width: units.gu(8)
+ height: width
+ anchors.horizontalCenter: parent.horizontalCenter
+ source: shootImagePath
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ camera.imageCapture.captureToLocation(ui.parent.plugin('Camera').generateLocation("jpg"));
+ }
+ }
+ }
+ }
+ BorderImage {
+ id: rightBackground
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.left: middle.right
+ anchors.topMargin: units.dp(2)
+ anchors.bottomMargin: units.dp(2)
+ source: "toolbar-right.png"
+ }
+ }
+}
diff --git a/plugins/org.apache.cordova.camera/src/ubuntu/back.png b/plugins/org.apache.cordova.camera/src/ubuntu/back.png
new file mode 100644
index 0000000..af78faa
Binary files /dev/null and b/plugins/org.apache.cordova.camera/src/ubuntu/back.png differ
diff --git a/plugins/org.apache.cordova.camera/src/ubuntu/camera.cpp b/plugins/org.apache.cordova.camera/src/ubuntu/camera.cpp
new file mode 100644
index 0000000..c58af32
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ubuntu/camera.cpp
@@ -0,0 +1,140 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+#include "camera.h"
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+const char code[] = "\
+var component, object; \
+function createObject() { \
+ component = Qt.createComponent(%1); \
+ if (component.status == Component.Ready) \
+ finishCreation(); \
+ else \
+ component.statusChanged.connect(finishCreation); \
+} \
+function finishCreation() { \
+ CordovaWrapper.global.cameraPluginWidget = component.createObject(root, \
+ {root: root, cordova: cordova}); \
+} \
+createObject()";
+
+
+Camera::Camera(Cordova *cordova):
+ CPlugin(cordova),
+ _lastScId(0),
+ _lastEcId(0) {
+}
+
+bool Camera::preprocessImage(QString &path) {
+ bool convertToPNG = (*_options.find("encodingType")).toInt() == Camera::PNG;
+ int quality = (*_options.find("quality")).toInt();
+ int width = (*_options.find("targetWidth")).toInt();
+ int height = (*_options.find("targetHeight")).toInt();
+
+ QImage image(path);
+ if (width <= 0)
+ width = image.width();
+ if (height <= 0)
+ height = image.height();
+ image = image.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+
+ QFile oldImage(path);
+ QTemporaryFile newImage;
+
+ const char *type;
+ if (convertToPNG) {
+ path = generateLocation("png");
+ type = "png";
+ } else {
+ path = generateLocation("jpg");
+ type = "jpg";
+ }
+
+ image.save(path, type, quality);
+
+ oldImage.remove();
+
+ return true;
+}
+
+void Camera::onImageSaved(QString path) {
+ bool dataURL = _options.find("destinationType")->toInt() == Camera::DATA_URL;
+
+ QString cbParams;
+ if (preprocessImage(path)) {
+ QString absolutePath = QFileInfo(path).absoluteFilePath();
+ if (dataURL) {
+ QFile image(absolutePath);
+ image.open(QIODevice::ReadOnly);
+ QByteArray content = image.readAll().toBase64();
+ cbParams = QString("\"%1\"").arg(content.data());
+ image.remove();
+ } else {
+ cbParams = CordovaInternal::format(QString("file://localhost") + absolutePath);
+ }
+ }
+
+ this->callback(_lastScId, cbParams);
+
+ _lastEcId = _lastScId = 0;
+}
+
+void Camera::takePicture(int scId, int ecId, int quality, int destinationType, int/*sourceType*/, int targetWidth, int targetHeight, int encodingType,
+ int/*mediaType*/, bool/*allowEdit*/, bool/*correctOrientation*/, bool/*saveToPhotoAlbum*/, const QVariantMap &/*popoverOptions*/, int/*cameraDirection*/) {
+ if (_camera.isNull()) {
+ _camera = QSharedPointer(new QCamera());
+ }
+
+ if (((_lastScId || _lastEcId) && (_lastScId != scId && _lastEcId != ecId)) || !_camera->isAvailable() || _camera->lockStatus() != QCamera::Unlocked) {
+ this->cb(_lastEcId, "Device is busy");
+ return;
+ }
+
+ _options.clear();
+ _options.insert("quality", quality);
+ _options.insert("destinationType", destinationType);
+ _options.insert("targetWidth", targetWidth);
+ _options.insert("targetHeight", targetHeight);
+ _options.insert("encodingType", encodingType);
+
+ _lastScId = scId;
+ _lastEcId = ecId;
+
+ QString path = m_cordova->get_app_dir() + "/../qml/CaptureWidget.qml";
+
+ // TODO: relative url
+ QString qml = QString(code).arg(CordovaInternal::format(path));
+ m_cordova->execQML(qml);
+}
+
+void Camera::cancel() {
+ m_cordova->execQML("CordovaWrapper.global.cameraPluginWidget.destroy()");
+ this->cb(_lastEcId, "canceled");
+
+ _lastEcId = _lastScId = 0;
+}
diff --git a/plugins/org.apache.cordova.camera/src/ubuntu/camera.h b/plugins/org.apache.cordova.camera/src/ubuntu/camera.h
new file mode 100644
index 0000000..6d96038
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/ubuntu/camera.h
@@ -0,0 +1,86 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+#ifndef CAMERA_H
+#define CAMERA_H
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+class Camera: public CPlugin {
+ Q_OBJECT
+public:
+ explicit Camera(Cordova *cordova);
+
+ virtual const QString fullName() override {
+ return Camera::fullID();
+ }
+
+ virtual const QString shortName() override {
+ return "Camera";
+ }
+
+ static const QString fullID() {
+ return "Camera";
+ }
+
+public slots:
+ void takePicture(int scId, int ecId, int quality, int destinationType, int/*sourceType*/, int targetWidth, int targetHeight, int encodingType,
+ int/*mediaType*/, bool/*allowEdit*/, bool/*correctOrientation*/, bool/*saveToPhotoAlbum*/, const QVariantMap &popoverOptions, int cameraDirection);
+ void cancel();
+
+ void onImageSaved(QString path);
+
+ QString generateLocation(const QString &extension) {
+ int i = 1;
+ for (;;++i) {
+ QString path = QString("%1/.local/share/%2/persistent/%3.%4").arg(QDir::homePath())
+ .arg(QCoreApplication::applicationName()).arg(i).arg(extension);
+
+ if (!QFileInfo(path).exists())
+ return path;
+ }
+ }
+private:
+ bool preprocessImage(QString &path);
+
+ int _lastScId;
+ int _lastEcId;
+ QSharedPointer _camera;
+
+ QVariantMap _options;
+protected:
+ enum DestinationType {
+ DATA_URL = 0,
+ FILE_URI = 1
+ };
+ enum EncodingType {
+ JPEG = 0,
+ PNG = 1
+ };
+};
+
+#endif // CAMERA_H
diff --git a/plugins/org.apache.cordova.camera/src/ubuntu/shoot.png b/plugins/org.apache.cordova.camera/src/ubuntu/shoot.png
new file mode 100644
index 0000000..c093b63
Binary files /dev/null and b/plugins/org.apache.cordova.camera/src/ubuntu/shoot.png differ
diff --git a/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-left.png b/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-left.png
new file mode 100644
index 0000000..720d7f6
Binary files /dev/null and b/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-left.png differ
diff --git a/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-middle.png b/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-middle.png
new file mode 100644
index 0000000..77595bb
Binary files /dev/null and b/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-middle.png differ
diff --git a/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-right.png b/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-right.png
new file mode 100644
index 0000000..e4e6aa6
Binary files /dev/null and b/plugins/org.apache.cordova.camera/src/ubuntu/toolbar-right.png differ
diff --git a/plugins/org.apache.cordova.camera/src/windows/CameraProxy.js b/plugins/org.apache.cordova.camera/src/windows/CameraProxy.js
new file mode 100644
index 0000000..0924e5c
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/windows/CameraProxy.js
@@ -0,0 +1,441 @@
+/*
+ *
+ * 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:true, URL:true */
+
+
+ var cordova = require('cordova'),
+ Camera = require('./Camera');
+
+module.exports = {
+
+ // args will contain :
+ // ... it is an array, so be careful
+ // 0 quality:50,
+ // 1 destinationType:Camera.DestinationType.FILE_URI,
+ // 2 sourceType:Camera.PictureSourceType.CAMERA,
+ // 3 targetWidth:-1,
+ // 4 targetHeight:-1,
+ // 5 encodingType:Camera.EncodingType.JPEG,
+ // 6 mediaType:Camera.MediaType.PICTURE,
+ // 7 allowEdit:false,
+ // 8 correctOrientation:false,
+ // 9 saveToPhotoAlbum:false,
+ // 10 popoverOptions:null
+ // 11 cameraDirection:0
+
+ takePicture: function (successCallback, errorCallback, args) {
+ var encodingType = args[5];
+ var targetWidth = args[3];
+ var targetHeight = args[4];
+ var sourceType = args[2];
+ var destinationType = args[1];
+ var mediaType = args[6];
+ var allowCrop = !!args[7];
+ var saveToPhotoAlbum = args[9];
+ var cameraDirection = args[11];
+
+ // resize method :)
+ var resizeImage = function (file) {
+ var tempPhotoFileName = "";
+ if (encodingType == Camera.EncodingType.PNG) {
+ tempPhotoFileName = "camera_cordova_temp_return.png";
+ } else {
+ tempPhotoFileName = "camera_cordova_temp_return.jpg";
+ }
+
+ var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
+ file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).then(function (storageFile) {
+ Windows.Storage.FileIO.readBufferAsync(storageFile).then(function(buffer) {
+ var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
+ var imageData = "data:" + file.contentType + ";base64," + strBase64;
+ var image = new Image();
+ image.src = imageData;
+ image.onload = function() {
+ var imageWidth = targetWidth,
+ imageHeight = targetHeight;
+ var canvas = document.createElement('canvas');
+
+ canvas.width = imageWidth;
+ canvas.height = imageHeight;
+
+ canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);
+
+ var fileContent = canvas.toDataURL(file.contentType).split(',')[1];
+
+ var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
+
+ storageFolder.createFileAsync(tempPhotoFileName, Windows.Storage.CreationCollisionOption.generateUniqueName).done(function (storagefile) {
+ var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
+ Windows.Storage.FileIO.writeBufferAsync(storagefile, content).then(function () {
+ successCallback("ms-appdata:///local/" + storagefile.name);
+ }, function () {
+ errorCallback("Resize picture error.");
+ });
+ });
+ };
+ });
+ }, function () {
+ errorCallback("Can't access localStorage folder");
+ });
+
+ };
+
+ // because of asynchronous method, so let the successCallback be called in it.
+ var resizeImageBase64 = function (file) {
+
+ Windows.Storage.FileIO.readBufferAsync(file).done( function(buffer) {
+ var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
+ var imageData = "data:" + file.contentType + ";base64," + strBase64;
+
+ var image = new Image();
+ image.src = imageData;
+
+ image.onload = function() {
+ var imageWidth = targetWidth,
+ imageHeight = targetHeight;
+ var canvas = document.createElement('canvas');
+
+ canvas.width = imageWidth;
+ canvas.height = imageHeight;
+
+ var ctx = canvas.getContext("2d");
+ ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
+
+ // The resized file ready for upload
+ var finalFile = canvas.toDataURL(file.contentType);
+
+ // Remove the prefix such as "data:" + contentType + ";base64," , in order to meet the Cordova API.
+ var arr = finalFile.split(",");
+ var newStr = finalFile.substr(arr[0].length + 1);
+ successCallback(newStr);
+ };
+ });
+ };
+
+ if (sourceType != Camera.PictureSourceType.CAMERA) {
+
+ // TODO: Add WP8.1 support
+ // WP8.1 doesn't allow to use of pickSingleFileAsync method
+ // see http://msdn.microsoft.com/en-us/library/windows/apps/br207852.aspx for details
+ // replacement of pickSingleFileAsync - pickSingleFileAndContinue method
+ // will take application to suspended state and this require additional logic to wake application up
+ if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0 ) {
+ errorCallback('Not supported');
+ return;
+ }
+
+ var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
+ fileOpenPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
+ if (mediaType == Camera.MediaType.PICTURE) {
+ fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
+ }
+ else if (mediaType == Camera.MediaType.VIDEO) {
+ fileOpenPicker.fileTypeFilter.replaceAll([".avi", ".flv", ".asx", ".asf", ".mov", ".mp4", ".mpg", ".rm", ".srt", ".swf", ".wmv", ".vob"]);
+ }
+ else {
+ fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
+ }
+
+ fileOpenPicker.pickSingleFileAsync().done(function (file) {
+ if (file) {
+ if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
+ if (targetHeight > 0 && targetWidth > 0) {
+ resizeImage(file);
+ }
+ else {
+
+ var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
+ file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).then(function (storageFile) {
+ successCallback(URL.createObjectURL(storageFile));
+ }, function () {
+ errorCallback("Can't access localStorage folder.");
+ });
+
+ }
+ }
+ else {
+ if (targetHeight > 0 && targetWidth > 0) {
+ resizeImageBase64(file);
+ } else {
+ Windows.Storage.FileIO.readBufferAsync(file).done(function (buffer) {
+ var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
+ successCallback(strBase64);
+ });
+ }
+
+ }
+
+ } else {
+ errorCallback("User didn't choose a file.");
+ }
+ }, function () {
+ errorCallback("User didn't choose a file.");
+ });
+
+ } else {
+
+ var CaptureNS = Windows.Media.Capture;
+
+ // Check if necessary API available
+ if (!CaptureNS.CameraCaptureUI) {
+ // We are running on WP8.1 which lacks CameraCaptureUI class
+ // so we need to use MediaCapture class instead and implement custom UI for camera
+
+ var capturePreview = null,
+ captureCancelButton = null,
+ capture = null,
+ captureSettings = null;
+
+ var createCameraUI = function () {
+
+ // Create fullscreen preview
+ capturePreview = document.createElement("video");
+
+ // z-order style element for capturePreview and captureCancelButton elts
+ // is necessary to avoid overriding by another page elements, -1 sometimes is not enough
+ capturePreview.style.cssText = "position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-order: 999";
+
+ // Create cancel button
+ captureCancelButton = document.createElement("button");
+ captureCancelButton.innerText = "Cancel";
+ captureCancelButton.style.cssText = "position: fixed; right: 0; bottom: 0; display: block; margin: 20px; z-order: 1000";
+
+ capture = new CaptureNS.MediaCapture();
+
+ captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
+ captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video;
+ };
+
+ var startCameraPreview = function () {
+
+ // Search for available camera devices
+ // This is necessary to detect which camera (front or back) we should use
+ var expectedPanel = cameraDirection === 1 ? Windows.Devices.Enumeration.Panel.front : Windows.Devices.Enumeration.Panel.back;
+ Windows.Devices.Enumeration.DeviceInformation.findAllAsync(Windows.Devices.Enumeration.DeviceClass.videoCapture)
+ .done(function (devices) {
+ if (devices.length > 0) {
+ devices.forEach(function(currDev) {
+ if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel == expectedPanel) {
+ captureSettings.videoDeviceId = currDev.id;
+ }
+ });
+
+ capture.initializeAsync(captureSettings).done(function () {
+ // This is necessary since WP8.1 MediaCapture outputs video stream rotated 90 degrees CCW
+ // TODO: This can be not consistent across devices, need additional testing on various devices
+ capture.setPreviewRotation(Windows.Media.Capture.VideoRotation.clockwise90Degrees);
+ // msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
+ capturePreview.msZoom = true;
+ capturePreview.src = URL.createObjectURL(capture);
+ capturePreview.play();
+
+ // Insert preview frame and controls into page
+ document.body.appendChild(capturePreview);
+ document.body.appendChild(captureCancelButton);
+
+ // Bind events to controls
+ capturePreview.addEventListener('click', captureAction);
+ captureCancelButton.addEventListener('click', function () {
+ destroyCameraPreview();
+ errorCallback('Cancelled');
+ }, false);
+ }, function (err) {
+ destroyCameraPreview();
+ errorCallback('Camera intitialization error ' + err);
+ });
+ } else {
+ destroyCameraPreview();
+ errorCallback('Camera not found');
+ }
+ });
+ };
+
+ var destroyCameraPreview = function () {
+ capturePreview.pause();
+ capturePreview.src = null;
+ [capturePreview, captureCancelButton].forEach(function(elem) {
+ if (elem /* && elem in document.body.childNodes */) {
+ document.body.removeChild(elem);
+ }
+ });
+ if (capture) {
+ capture.stopRecordAsync();
+ capture = null;
+ }
+ };
+
+ var captureAction = function () {
+
+ var encodingProperties,
+ fileName,
+ generateUniqueCollisionOption = Windows.Storage.CreationCollisionOption.generateUniqueName,
+ tempFolder = Windows.Storage.ApplicationData.current.temporaryFolder;
+
+ if (encodingType == Camera.EncodingType.PNG) {
+ fileName = 'photo.png';
+ encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createPng();
+ } else {
+ fileName = 'photo.jpg';
+ encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg();
+ }
+
+ tempFolder.createFileAsync(fileName, generateUniqueCollisionOption).done(function(capturedFile) {
+ capture.capturePhotoToStorageFileAsync(encodingProperties, capturedFile).done(function() {
+
+ destroyCameraPreview();
+
+ // success callback for capture operation
+ var success = function(capturedfile) {
+ if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
+ if (targetHeight > 0 && targetWidth > 0) {
+ resizeImage(capturedfile);
+ } else {
+ capturedfile.copyAsync(Windows.Storage.ApplicationData.current.localFolder, capturedfile.name, generateUniqueCollisionOption).done(function(copiedfile) {
+ successCallback("ms-appdata:///local/" + copiedfile.name);
+ }, errorCallback);
+ }
+ } else {
+ if (targetHeight > 0 && targetWidth > 0) {
+ resizeImageBase64(capturedfile);
+ } else {
+ Windows.Storage.FileIO.readBufferAsync(capturedfile).done(function(buffer) {
+ var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
+ capturedfile.deleteAsync().done(function() {
+ successCallback(strBase64);
+ }, function(err) {
+ console.error(err);
+ successCallback(strBase64);
+ });
+ }, errorCallback);
+ }
+ }
+ };
+
+ if (saveToPhotoAlbum) {
+ capturedFile.copyAsync(Windows.Storage.KnownFolders.picturesLibrary, capturedFile.name, generateUniqueCollisionOption)
+ .done(function() {
+ success(capturedFile);
+ }, errorCallback);
+ } else {
+ success(capturedFile);
+ }
+
+
+ }, function(err) {
+ destroyCameraPreview();
+ errorCallback(err);
+ });
+ }, function(err) {
+ destroyCameraPreview();
+ errorCallback(err);
+ });
+ };
+
+ try {
+ createCameraUI();
+ startCameraPreview();
+ } catch (ex) {
+ errorCallback(ex);
+ }
+
+ } else {
+
+ var cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI();
+ cameraCaptureUI.photoSettings.allowCropping = allowCrop;
+
+ if (encodingType == Camera.EncodingType.PNG) {
+ cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.png;
+ } else {
+ cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.jpeg;
+ }
+
+ // decide which max pixels should be supported by targetWidth or targetHeight.
+ if (targetWidth >= 1280 || targetHeight >= 960) {
+ cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.large3M;
+ } else if (targetWidth >= 1024 || targetHeight >= 768) {
+ cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.mediumXga;
+ } else if (targetWidth >= 800 || targetHeight >= 600) {
+ cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.mediumXga;
+ } else if (targetWidth >= 640 || targetHeight >= 480) {
+ cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.smallVga;
+ } else if (targetWidth >= 320 || targetHeight >= 240) {
+ cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.verySmallQvga;
+ } else {
+ cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.highestAvailable;
+ }
+
+ cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).then(function(picture) {
+ if (picture) {
+ // save to photo album successCallback
+ var success = function() {
+ if (destinationType == Camera.DestinationType.FILE_URI) {
+ if (targetHeight > 0 && targetWidth > 0) {
+ resizeImage(picture);
+ } else {
+
+ var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
+ picture.copyAsync(storageFolder, picture.name, Windows.Storage.NameCollisionOption.replaceExisting).then(function(storageFile) {
+ successCallback("ms-appdata:///local/" + storageFile.name);
+ }, function() {
+ errorCallback("Can't access localStorage folder.");
+ });
+ }
+ } else {
+ if (targetHeight > 0 && targetWidth > 0) {
+ resizeImageBase64(picture);
+ } else {
+ Windows.Storage.FileIO.readBufferAsync(picture).done(function(buffer) {
+ var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
+ successCallback(strBase64);
+ });
+ }
+ }
+ };
+ // save to photo album errorCallback
+ var fail = function() {
+ //errorCallback("FileError, code:" + fileError.code);
+ errorCallback("Save fail.");
+ };
+
+ if (saveToPhotoAlbum) {
+ Windows.Storage.StorageFile.getFileFromPathAsync(picture.path).then(function(storageFile) {
+ storageFile.copyAsync(Windows.Storage.KnownFolders.picturesLibrary, picture.name, Windows.Storage.NameCollisionOption.generateUniqueName).then(function(storageFile) {
+ success();
+ }, function() {
+ fail();
+ });
+ });
+ } else {
+ success();
+ }
+ } else {
+ errorCallback("User didn't capture a photo.");
+ }
+ }, function() {
+ errorCallback("Fail to capture a photo.");
+ });
+ }
+ }
+ }
+};
+
+require("cordova/exec/proxy").add("Camera",module.exports);
diff --git a/plugins/org.apache.cordova.camera/src/wp/Camera.cs b/plugins/org.apache.cordova.camera/src/wp/Camera.cs
new file mode 100644
index 0000000..30bb5e8
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/src/wp/Camera.cs
@@ -0,0 +1,516 @@
+/*
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System;
+using System.Net;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Collections.Generic;
+using Microsoft.Phone.Tasks;
+using System.Runtime.Serialization;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Windows.Media.Imaging;
+using Microsoft.Phone;
+using Microsoft.Xna.Framework.Media;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+ public class Camera : BaseCommand
+ {
+
+ ///
+ /// Return base64 encoded string
+ ///
+ private const int DATA_URL = 0;
+
+ ///
+ /// Return file uri
+ ///
+ private const int FILE_URI = 1;
+
+ ///
+ /// Choose image from picture library
+ ///
+ private const int PHOTOLIBRARY = 0;
+
+ ///
+ /// Take picture from camera
+ ///
+
+ private const int CAMERA = 1;
+
+ ///
+ /// Choose image from picture library
+ ///
+ private const int SAVEDPHOTOALBUM = 2;
+
+ ///
+ /// Take a picture of type JPEG
+ ///
+ private const int JPEG = 0;
+
+ ///
+ /// Take a picture of type PNG
+ ///
+ private const int PNG = 1;
+
+ ///
+ /// Folder to store captured images
+ ///
+ private const string isoFolder = "CapturedImagesCache";
+
+ ///
+ /// Represents captureImage action options.
+ ///
+ [DataContract]
+ public class CameraOptions
+ {
+ ///
+ /// Source to getPicture from.
+ ///
+ [DataMember(IsRequired = false, Name = "sourceType")]
+ public int PictureSourceType { get; set; }
+
+ ///
+ /// Format of image that returned from getPicture.
+ ///
+ [DataMember(IsRequired = false, Name = "destinationType")]
+ public int DestinationType { get; set; }
+
+ ///
+ /// Quality of saved image
+ ///
+ [DataMember(IsRequired = false, Name = "quality")]
+ public int Quality { get; set; }
+
+ ///
+ /// Controls whether or not the image is also added to the device photo album.
+ ///
+ [DataMember(IsRequired = false, Name = "saveToPhotoAlbum")]
+ public bool SaveToPhotoAlbum { get; set; }
+
+ ///
+ /// Ignored
+ ///
+ [DataMember(IsRequired = false, Name = "correctOrientation")]
+ public bool CorrectOrientation { get; set; }
+
+ ///
+ /// Ignored
+ ///
+ [DataMember(IsRequired = false, Name = "allowEdit")]
+ public bool AllowEdit { get; set; }
+
+ ///
+ /// Height in pixels to scale image
+ ///
+ [DataMember(IsRequired = false, Name = "encodingType")]
+ public int EncodingType { get; set; }
+
+ ///
+ /// Height in pixels to scale image
+ ///
+ [DataMember(IsRequired = false, Name = "mediaType")]
+ public int MediaType { get; set; }
+
+
+ ///
+ /// Height in pixels to scale image
+ ///
+ [DataMember(IsRequired = false, Name = "targetHeight")]
+ public int TargetHeight { get; set; }
+
+
+ ///
+ /// Width in pixels to scale image
+ ///
+ [DataMember(IsRequired = false, Name = "targetWidth")]
+ public int TargetWidth { get; set; }
+
+ ///
+ /// Creates options object with default parameters
+ ///
+ public CameraOptions()
+ {
+ this.SetDefaultValues(new StreamingContext());
+ }
+
+ ///
+ /// Initializes default values for class fields.
+ /// Implemented in separate method because default constructor is not invoked during deserialization.
+ ///
+ ///
+ [OnDeserializing()]
+ public void SetDefaultValues(StreamingContext context)
+ {
+ PictureSourceType = CAMERA;
+ DestinationType = FILE_URI;
+ Quality = 80;
+ TargetHeight = -1;
+ TargetWidth = -1;
+ SaveToPhotoAlbum = false;
+ CorrectOrientation = true;
+ AllowEdit = false;
+ MediaType = -1;
+ EncodingType = -1;
+ }
+ }
+
+ ///
+ /// Camera options
+ ///
+ CameraOptions cameraOptions;
+
+ public void takePicture(string options)
+ {
+ try
+ {
+ string[] args = JSON.JsonHelper.Deserialize(options);
+ // ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType",
+ // "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ]
+ cameraOptions = new CameraOptions();
+ cameraOptions.Quality = int.Parse(args[0]);
+ cameraOptions.DestinationType = int.Parse(args[1]);
+ cameraOptions.PictureSourceType = int.Parse(args[2]);
+ cameraOptions.TargetWidth = int.Parse(args[3]);
+ cameraOptions.TargetHeight = int.Parse(args[4]);
+ cameraOptions.EncodingType = int.Parse(args[5]);
+ cameraOptions.MediaType = int.Parse(args[6]);
+ cameraOptions.AllowEdit = bool.Parse(args[7]);
+ cameraOptions.CorrectOrientation = bool.Parse(args[8]);
+ cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]);
+
+ // a very large number will force the other value to be the bound
+ if (cameraOptions.TargetWidth > -1 && cameraOptions.TargetHeight == -1)
+ {
+ cameraOptions.TargetHeight = 100000;
+ }
+ else if (cameraOptions.TargetHeight > -1 && cameraOptions.TargetWidth == -1)
+ {
+ cameraOptions.TargetWidth = 100000;
+ }
+ }
+ catch (Exception ex)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ if(cameraOptions.DestinationType != Camera.FILE_URI && cameraOptions.DestinationType != Camera.DATA_URL )
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType"));
+ return;
+ }
+
+ ChooserBase chooserTask = null;
+ if (cameraOptions.PictureSourceType == CAMERA)
+ {
+ chooserTask = new CameraCaptureTask();
+ }
+ else if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM))
+ {
+ chooserTask = new PhotoChooserTask();
+ }
+ // if chooserTask is still null, then PictureSourceType was invalid
+ if (chooserTask != null)
+ {
+ chooserTask.Completed += onTaskCompleted;
+ chooserTask.Show();
+ }
+ else
+ {
+ Debug.WriteLine("Unrecognized PictureSourceType :: " + cameraOptions.PictureSourceType.ToString());
+ DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT));
+ }
+ }
+
+ public void onTaskCompleted(object sender, PhotoResult e)
+ {
+ var task = sender as ChooserBase;
+ if (task != null)
+ {
+ task.Completed -= onTaskCompleted;
+ }
+
+ if (e.Error != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+ return;
+ }
+
+ switch (e.TaskResult)
+ {
+ case TaskResult.OK:
+ try
+ {
+ string imagePathOrContent = string.Empty;
+
+ // Save image back to media library
+ // only save to photoalbum if it didn't come from there ...
+ if (cameraOptions.PictureSourceType == CAMERA && cameraOptions.SaveToPhotoAlbum)
+ {
+ MediaLibrary library = new MediaLibrary();
+ Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ...
+ }
+
+ int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto);
+ int newAngle = 0;
+ switch (orient)
+ {
+ case ImageExifOrientation.LandscapeLeft:
+ newAngle = 90;
+ break;
+ case ImageExifOrientation.PortraitUpsideDown:
+ newAngle = 180;
+ break;
+ case ImageExifOrientation.LandscapeRight:
+ newAngle = 270;
+ break;
+ case ImageExifOrientation.Portrait:
+ default: break; // 0 default already set
+ }
+
+ if (newAngle != 0)
+ {
+ using (Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle))
+ {
+ // we should reset stream position after saving stream to media library
+ rotImageStream.Seek(0, SeekOrigin.Begin);
+ if (cameraOptions.DestinationType == DATA_URL)
+ {
+ imagePathOrContent = GetImageContent(rotImageStream);
+ }
+ else // FILE_URL
+ {
+ imagePathOrContent = SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName));
+ }
+ }
+ }
+ else // no need to reorient
+ {
+ if (cameraOptions.DestinationType == DATA_URL)
+ {
+ imagePathOrContent = GetImageContent(e.ChosenPhoto);
+ }
+ else // FILE_URL
+ {
+ imagePathOrContent = SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName));
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent));
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image."));
+ }
+ break;
+ case TaskResult.Cancel:
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled."));
+ break;
+ default:
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!"));
+ break;
+ }
+ }
+
+ ///
+ /// Returns image content in a form of base64 string
+ ///
+ /// Image stream
+ /// Base64 representation of the image
+ private string GetImageContent(Stream stream)
+ {
+ byte[] imageContent = null;
+
+ try
+ {
+ // Resize photo and convert to JPEG
+ imageContent = ResizePhoto(stream);
+ }
+ finally
+ {
+ stream.Dispose();
+ }
+
+ return Convert.ToBase64String(imageContent);
+ }
+
+ ///
+ /// Resize image
+ ///
+ /// Image stream
+ /// resized image
+ private byte[] ResizePhoto(Stream stream)
+ {
+ //output
+ byte[] resizedFile;
+
+ BitmapImage objBitmap = new BitmapImage();
+ objBitmap.SetSource(stream);
+ objBitmap.CreateOptions = BitmapCreateOptions.None;
+
+ WriteableBitmap objWB = new WriteableBitmap(objBitmap);
+ objBitmap.UriSource = null;
+
+ // Calculate resultant image size
+ int width, height;
+ if (cameraOptions.TargetWidth >= 0 && cameraOptions.TargetHeight >= 0)
+ {
+ // Keep proportionally
+ double ratio = Math.Min(
+ (double)cameraOptions.TargetWidth / objWB.PixelWidth,
+ (double)cameraOptions.TargetHeight / objWB.PixelHeight);
+ width = Convert.ToInt32(ratio * objWB.PixelWidth);
+ height = Convert.ToInt32(ratio * objWB.PixelHeight);
+ }
+ else
+ {
+ width = objWB.PixelWidth;
+ height = objWB.PixelHeight;
+ }
+
+ //Hold the result stream
+ using (MemoryStream objBitmapStreamResized = new MemoryStream())
+ {
+
+ try
+ {
+ // resize the photo with user defined TargetWidth & TargetHeight
+ Extensions.SaveJpeg(objWB, objBitmapStreamResized, width, height, 0, cameraOptions.Quality);
+ }
+ finally
+ {
+ //Dispose bitmaps immediately, they are memory expensive
+ DisposeImage(objBitmap);
+ DisposeImage(objWB);
+ GC.Collect();
+ }
+
+ //Convert the resized stream to a byte array.
+ int streamLength = (int)objBitmapStreamResized.Length;
+ resizedFile = new Byte[streamLength]; //-1
+ objBitmapStreamResized.Position = 0;
+
+ //for some reason we have to set Position to zero, but we don't have to earlier when we get the bytes from the chosen photo...
+ objBitmapStreamResized.Read(resizedFile, 0, streamLength);
+ }
+
+ return resizedFile;
+ }
+
+ ///
+ /// Util: Dispose a bitmap resource
+ ///
+ /// BitmapSource subclass to dispose
+ private void DisposeImage(BitmapSource image)
+ {
+ if (image != null)
+ {
+ try
+ {
+ using (var ms = new MemoryStream(new byte[] { 0x0 }))
+ {
+ image.SetSource(ms);
+ }
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+
+ ///
+ /// Saves captured image in isolated storage
+ ///
+ /// image file name
+ /// Image path
+ private string SaveImageToLocalStorage(Stream stream, string imageFileName)
+ {
+
+ if (stream == null)
+ {
+ throw new ArgumentNullException("imageBytes");
+ }
+ try
+ {
+ var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
+
+ if (!isoFile.DirectoryExists(isoFolder))
+ {
+ isoFile.CreateDirectory(isoFolder);
+ }
+
+ string filePath = System.IO.Path.Combine("///" + isoFolder + "/", imageFileName);
+
+ using (IsolatedStorageFileStream outputStream = isoFile.CreateFile(filePath))
+ {
+ BitmapImage objBitmap = new BitmapImage();
+ objBitmap.SetSource(stream);
+ objBitmap.CreateOptions = BitmapCreateOptions.None;
+
+ WriteableBitmap objWB = new WriteableBitmap(objBitmap);
+ objBitmap.UriSource = null;
+
+ try
+ {
+
+ //use photo's actual width & height if user doesn't provide width & height
+ if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0)
+ {
+ objWB.SaveJpeg(outputStream, objWB.PixelWidth, objWB.PixelHeight, 0, cameraOptions.Quality);
+ }
+ else
+ {
+ //Resize
+ //Keep proportionally
+ double ratio = Math.Min((double)cameraOptions.TargetWidth / objWB.PixelWidth, (double)cameraOptions.TargetHeight / objWB.PixelHeight);
+ int width = Convert.ToInt32(ratio * objWB.PixelWidth);
+ int height = Convert.ToInt32(ratio * objWB.PixelHeight);
+
+ // resize the photo with user defined TargetWidth & TargetHeight
+ objWB.SaveJpeg(outputStream, width, height, 0, cameraOptions.Quality);
+ }
+ }
+ finally
+ {
+ //Dispose bitmaps immediately, they are memory expensive
+ DisposeImage(objBitmap);
+ DisposeImage(objWB);
+ GC.Collect();
+ }
+ }
+
+ return new Uri(filePath, UriKind.Relative).ToString();
+ }
+ catch (Exception)
+ {
+ //TODO: log or do something else
+ throw;
+ }
+ finally
+ {
+ stream.Dispose();
+ }
+ }
+
+ }
+}
diff --git a/plugins/org.apache.cordova.camera/tests/ios/.npmignore b/plugins/org.apache.cordova.camera/tests/ios/.npmignore
new file mode 100644
index 0000000..b512c09
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/.npmignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/contents.xcworkspacedata b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..22fcf41
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/xcshareddata/CDVCameraTest.xccheckout b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/xcshareddata/CDVCameraTest.xccheckout
new file mode 100644
index 0000000..c8a5605
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/xcshareddata/CDVCameraTest.xccheckout
@@ -0,0 +1,41 @@
+
+
+
+
+ IDESourceControlProjectFavoriteDictionaryKey
+
+ IDESourceControlProjectIdentifier
+ 6BE9AD73-1B9F-4362-98D7-DC631BEC6185
+ IDESourceControlProjectName
+ CDVCameraTest
+ IDESourceControlProjectOriginsDictionary
+
+ 729B5706E7BAF4E9EE7AEE3C003A08107411AB7C
+ github.com:shazron/cordova-plugin-camera.git
+
+ IDESourceControlProjectPath
+ tests/ios/CDVCameraTest.xcworkspace
+ IDESourceControlProjectRelativeInstallPathDictionary
+
+ 729B5706E7BAF4E9EE7AEE3C003A08107411AB7C
+ ../../..
+
+ IDESourceControlProjectURL
+ github.com:shazron/cordova-plugin-camera.git
+ IDESourceControlProjectVersion
+ 111
+ IDESourceControlProjectWCCIdentifier
+ 729B5706E7BAF4E9EE7AEE3C003A08107411AB7C
+ IDESourceControlProjectWCConfigurations
+
+
+ IDESourceControlRepositoryExtensionIdentifierKey
+ public.vcs.git
+ IDESourceControlWCCIdentifierKey
+ 729B5706E7BAF4E9EE7AEE3C003A08107411AB7C
+ IDESourceControlWCCName
+ cordova-plugin-camera
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/xcshareddata/xcschemes/CordovaLib.xcscheme b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/xcshareddata/xcschemes/CordovaLib.xcscheme
new file mode 100644
index 0000000..3e8cd2c
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest.xcworkspace/xcshareddata/xcschemes/CordovaLib.xcscheme
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraLibTests/CameraTest.m b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraLibTests/CameraTest.m
new file mode 100644
index 0000000..d0e8b4b
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraLibTests/CameraTest.m
@@ -0,0 +1,498 @@
+/*
+ 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.
+ */
+
+#import
+#import
+#import "CDVCamera.h"
+#import "UIImage+CropScaleOrientation.h"
+#import
+#import
+#import
+#import
+
+
+@interface CameraTest : XCTestCase
+
+@property (nonatomic, strong) CDVCamera* plugin;
+
+@end
+
+@interface CDVCamera ()
+
+// expose private interface
+- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options;
+- (UIImage*)retrieveImage:(NSDictionary*)info options:(CDVPictureOptions*)options;
+- (CDVPluginResult*)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info;
+- (CDVPluginResult*)resultForVideo:(NSDictionary*)info;
+
+@end
+
+@implementation CameraTest
+
+- (void)setUp {
+ [super setUp];
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ self.plugin = [[CDVCamera alloc] init];
+}
+
+- (void)tearDown {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ [super tearDown];
+}
+
+- (void) testPictureOptionsCreate
+{
+ NSArray* args;
+ CDVPictureOptions* options;
+ NSDictionary* popoverOptions;
+
+ // No arguments, check whether the defaults are set
+ args = @[];
+ options = [CDVPictureOptions createFromTakePictureArguments:args];
+
+ XCTAssertEqual([options.quality intValue], 50);
+ XCTAssertEqual(options.destinationType, (int)DestinationTypeFileUri);
+ XCTAssertEqual(options.sourceType, (int)UIImagePickerControllerSourceTypeCamera);
+ XCTAssertEqual(options.targetSize.width, 0);
+ XCTAssertEqual(options.targetSize.height, 0);
+ XCTAssertEqual(options.encodingType, (int)EncodingTypeJPEG);
+ XCTAssertEqual(options.mediaType, (int)MediaTypePicture);
+ XCTAssertEqual(options.allowsEditing, NO);
+ XCTAssertEqual(options.correctOrientation, NO);
+ XCTAssertEqual(options.saveToPhotoAlbum, NO);
+ XCTAssertEqualObjects(options.popoverOptions, nil);
+ XCTAssertEqual(options.cameraDirection, (int)UIImagePickerControllerCameraDeviceRear);
+ XCTAssertEqual(options.popoverSupported, NO);
+ XCTAssertEqual(options.usesGeolocation, NO);
+
+ // Set each argument, check whether they are set. different from defaults
+ popoverOptions = @{ @"x" : @1, @"y" : @2, @"width" : @3, @"height" : @4 };
+
+ args = @[
+ @(49),
+ @(DestinationTypeDataUrl),
+ @(UIImagePickerControllerSourceTypePhotoLibrary),
+ @(120),
+ @(240),
+ @(EncodingTypePNG),
+ @(MediaTypeVideo),
+ @YES,
+ @YES,
+ @YES,
+ popoverOptions,
+ @(UIImagePickerControllerCameraDeviceFront),
+ ];
+ options = [CDVPictureOptions createFromTakePictureArguments:args];
+
+ XCTAssertEqual([options.quality intValue], 49);
+ XCTAssertEqual(options.destinationType, (int)DestinationTypeDataUrl);
+ XCTAssertEqual(options.sourceType, (int)UIImagePickerControllerSourceTypePhotoLibrary);
+ XCTAssertEqual(options.targetSize.width, 120);
+ XCTAssertEqual(options.targetSize.height, 240);
+ XCTAssertEqual(options.encodingType, (int)EncodingTypePNG);
+ XCTAssertEqual(options.mediaType, (int)MediaTypeVideo);
+ XCTAssertEqual(options.allowsEditing, YES);
+ XCTAssertEqual(options.correctOrientation, YES);
+ XCTAssertEqual(options.saveToPhotoAlbum, YES);
+ XCTAssertEqualObjects(options.popoverOptions, popoverOptions);
+ XCTAssertEqual(options.cameraDirection, (int)UIImagePickerControllerCameraDeviceFront);
+ XCTAssertEqual(options.popoverSupported, NO);
+ XCTAssertEqual(options.usesGeolocation, NO);
+}
+
+- (void) testCameraPickerCreate
+{
+ NSDictionary* popoverOptions;
+ NSArray* args;
+ CDVPictureOptions* pictureOptions;
+ CDVCameraPicker* picker;
+
+ // Souce is Camera, and image type
+
+ popoverOptions = @{ @"x" : @1, @"y" : @2, @"width" : @3, @"height" : @4 };
+ args = @[
+ @(49),
+ @(DestinationTypeDataUrl),
+ @(UIImagePickerControllerSourceTypeCamera),
+ @(120),
+ @(240),
+ @(EncodingTypePNG),
+ @(MediaTypeAll),
+ @YES,
+ @YES,
+ @YES,
+ popoverOptions,
+ @(UIImagePickerControllerCameraDeviceFront),
+ ];
+ pictureOptions = [CDVPictureOptions createFromTakePictureArguments:args];
+
+ if ([UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType]) {
+ picker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
+
+ XCTAssertEqualObjects(picker.pictureOptions, pictureOptions);
+
+ XCTAssertEqual(picker.sourceType, pictureOptions.sourceType);
+ XCTAssertEqual(picker.allowsEditing, pictureOptions.allowsEditing);
+ XCTAssertEqualObjects(picker.mediaTypes, @[(NSString*)kUTTypeImage]);
+ XCTAssertEqual(picker.cameraDevice, pictureOptions.cameraDirection);
+ }
+
+ // Souce is not Camera, and all media types
+
+ args = @[
+ @(49),
+ @(DestinationTypeDataUrl),
+ @(UIImagePickerControllerSourceTypePhotoLibrary),
+ @(120),
+ @(240),
+ @(EncodingTypePNG),
+ @(MediaTypeAll),
+ @YES,
+ @YES,
+ @YES,
+ popoverOptions,
+ @(UIImagePickerControllerCameraDeviceFront),
+ ];
+ pictureOptions = [CDVPictureOptions createFromTakePictureArguments:args];
+
+ if ([UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType]) {
+ picker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
+
+ XCTAssertEqualObjects(picker.pictureOptions, pictureOptions);
+
+ XCTAssertEqual(picker.sourceType, pictureOptions.sourceType);
+ XCTAssertEqual(picker.allowsEditing, pictureOptions.allowsEditing);
+ XCTAssertEqualObjects(picker.mediaTypes, [UIImagePickerController availableMediaTypesForSourceType:picker.sourceType]);
+ }
+
+ // Souce is not Camera, and either Image or Movie media type
+
+ args = @[
+ @(49),
+ @(DestinationTypeDataUrl),
+ @(UIImagePickerControllerSourceTypePhotoLibrary),
+ @(120),
+ @(240),
+ @(EncodingTypePNG),
+ @(MediaTypeVideo),
+ @YES,
+ @YES,
+ @YES,
+ popoverOptions,
+ @(UIImagePickerControllerCameraDeviceFront),
+ ];
+ pictureOptions = [CDVPictureOptions createFromTakePictureArguments:args];
+
+ if ([UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType]) {
+ picker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
+
+ XCTAssertEqualObjects(picker.pictureOptions, pictureOptions);
+
+ XCTAssertEqual(picker.sourceType, pictureOptions.sourceType);
+ XCTAssertEqual(picker.allowsEditing, pictureOptions.allowsEditing);
+ XCTAssertEqualObjects(picker.mediaTypes, @[(NSString*)kUTTypeMovie]);
+ }
+}
+
+- (UIImage*) createImage:(CGRect)rect orientation:(UIImageOrientation)imageOrientation {
+ UIGraphicsBeginImageContext(rect.size);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+
+ CGContextSetFillColorWithColor(context, [[UIColor greenColor] CGColor]);
+ CGContextFillRect(context, rect);
+
+ CGImageRef result = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
+ UIImage* image = [UIImage imageWithCGImage:result scale:1.0f orientation:imageOrientation];
+
+ UIGraphicsEndImageContext();
+
+ return image;
+}
+
+- (void) testImageScaleCropForSize {
+
+ UIImage *sourceImagePortrait, *sourceImageLandscape, *targetImage;
+ CGSize targetSize = CGSizeZero;
+
+ sourceImagePortrait = [self createImage:CGRectMake(0, 0, 2448, 3264) orientation:UIImageOrientationUp];
+ sourceImageLandscape = [self createImage:CGRectMake(0, 0, 3264, 2448) orientation:UIImageOrientationUp];
+
+ // test 640x480
+
+ targetSize = CGSizeMake(640, 480);
+
+ targetImage = [sourceImagePortrait imageByScalingAndCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageByScalingAndCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+
+ // test 800x600
+
+ targetSize = CGSizeMake(800, 600);
+
+ targetImage = [sourceImagePortrait imageByScalingAndCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageByScalingAndCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ // test 1024x768
+
+ targetSize = CGSizeMake(1024, 768);
+
+ targetImage = [sourceImagePortrait imageByScalingAndCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageByScalingAndCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+}
+
+- (void) testImageScaleNoCropForSize {
+ UIImage *sourceImagePortrait, *sourceImageLandscape, *targetImage;
+ CGSize targetSize = CGSizeZero;
+
+ sourceImagePortrait = [self createImage:CGRectMake(0, 0, 2448, 3264) orientation:UIImageOrientationUp];
+ sourceImageLandscape = [self createImage:CGRectMake(0, 0, 3264, 2448) orientation:UIImageOrientationUp];
+
+ // test 640x480
+
+ targetSize = CGSizeMake(640, 480);
+
+ targetImage = [sourceImagePortrait imageByScalingNotCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageByScalingNotCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+
+ // test 800x600
+
+ targetSize = CGSizeMake(800, 600);
+
+ targetImage = [sourceImagePortrait imageByScalingNotCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageByScalingNotCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ // test 1024x768
+
+ targetSize = CGSizeMake(1024, 768);
+
+ targetImage = [sourceImagePortrait imageByScalingNotCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageByScalingNotCroppingForSize:targetSize];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+}
+
+- (void) testImageCorrectedForOrientation {
+ UIImage *sourceImagePortrait, *sourceImageLandscape, *targetImage;
+ CGSize targetSize = CGSizeZero;
+
+ sourceImagePortrait = [self createImage:CGRectMake(0, 0, 2448, 3264) orientation:UIImageOrientationDown];
+ sourceImageLandscape = [self createImage:CGRectMake(0, 0, 3264, 2448) orientation:UIImageOrientationDown];
+
+ // PORTRAIT - image size should be unchanged
+
+ targetSize = CGSizeMake(2448, 3264);
+
+ targetImage = [sourceImagePortrait imageCorrectedForCaptureOrientation:UIImageOrientationUp];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+ XCTAssertEqual(targetImage.imageOrientation, UIImageOrientationUp);
+
+ targetImage = [sourceImagePortrait imageCorrectedForCaptureOrientation:UIImageOrientationDown];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+ XCTAssertEqual(targetImage.imageOrientation, UIImageOrientationUp);
+
+ targetImage = [sourceImagePortrait imageCorrectedForCaptureOrientation:UIImageOrientationRight];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+ XCTAssertEqual(targetImage.imageOrientation, UIImageOrientationUp);
+
+ targetImage = [sourceImagePortrait imageCorrectedForCaptureOrientation:UIImageOrientationLeft];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+ XCTAssertEqual(targetImage.imageOrientation, UIImageOrientationUp);
+
+ // LANDSCAPE - image size should be unchanged
+
+ targetSize = CGSizeMake(3264, 2448);
+
+ targetImage = [sourceImageLandscape imageCorrectedForCaptureOrientation:UIImageOrientationUp];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageCorrectedForCaptureOrientation:UIImageOrientationDown];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageCorrectedForCaptureOrientation:UIImageOrientationRight];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+
+ targetImage = [sourceImageLandscape imageCorrectedForCaptureOrientation:UIImageOrientationLeft];
+ XCTAssertEqual(targetImage.size.width, targetSize.width);
+ XCTAssertEqual(targetImage.size.height, targetSize.height);
+}
+
+
+- (void) testRetrieveImage
+{
+ CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
+ NSDictionary *infoDict1, *infoDict2;
+ UIImage* resultImage;
+
+ UIImage* originalImage = [self createImage:CGRectMake(0, 0, 1024, 768) orientation:UIImageOrientationDown];
+ UIImage* originalCorrectedForOrientation = [originalImage imageCorrectedForCaptureOrientation];
+
+ UIImage* editedImage = [self createImage:CGRectMake(0, 0, 800, 600) orientation:UIImageOrientationDown];
+ UIImage* scaledImageWithCrop = [originalImage imageByScalingAndCroppingForSize:CGSizeMake(640, 480)];
+ UIImage* scaledImageNoCrop = [originalImage imageByScalingNotCroppingForSize:CGSizeMake(640, 480)];
+
+ infoDict1 = @{
+ UIImagePickerControllerOriginalImage : originalImage
+ };
+
+ infoDict2 = @{
+ UIImagePickerControllerOriginalImage : originalImage,
+ UIImagePickerControllerEditedImage : editedImage
+ };
+
+ // Original with no options
+
+ pictureOptions.allowsEditing = YES;
+ pictureOptions.targetSize = CGSizeZero;
+ pictureOptions.cropToSize = NO;
+ pictureOptions.correctOrientation = NO;
+
+ resultImage = [self.plugin retrieveImage:infoDict1 options:pictureOptions];
+ XCTAssertEqualObjects(resultImage, originalImage);
+
+ // Original with no options
+
+ pictureOptions.allowsEditing = YES;
+ pictureOptions.targetSize = CGSizeZero;
+ pictureOptions.cropToSize = NO;
+ pictureOptions.correctOrientation = NO;
+
+ resultImage = [self.plugin retrieveImage:infoDict2 options:pictureOptions];
+ XCTAssertEqualObjects(resultImage, editedImage);
+
+ // Original with corrected orientation
+
+ pictureOptions.allowsEditing = YES;
+ pictureOptions.targetSize = CGSizeZero;
+ pictureOptions.cropToSize = NO;
+ pictureOptions.correctOrientation = YES;
+
+ resultImage = [self.plugin retrieveImage:infoDict1 options:pictureOptions];
+ XCTAssertNotEqual(resultImage.imageOrientation, originalImage.imageOrientation);
+ XCTAssertEqual(resultImage.imageOrientation, originalCorrectedForOrientation.imageOrientation);
+ XCTAssertEqual(resultImage.size.width, originalCorrectedForOrientation.size.width);
+ XCTAssertEqual(resultImage.size.height, originalCorrectedForOrientation.size.height);
+
+ // Original with targetSize, no crop
+
+ pictureOptions.allowsEditing = YES;
+ pictureOptions.targetSize = CGSizeMake(640, 480);
+ pictureOptions.cropToSize = NO;
+ pictureOptions.correctOrientation = NO;
+
+ resultImage = [self.plugin retrieveImage:infoDict1 options:pictureOptions];
+ XCTAssertEqual(resultImage.size.width, scaledImageNoCrop.size.width);
+ XCTAssertEqual(resultImage.size.height, scaledImageNoCrop.size.height);
+
+ // Original with targetSize, plus crop
+
+ pictureOptions.allowsEditing = YES;
+ pictureOptions.targetSize = CGSizeMake(640, 480);
+ pictureOptions.cropToSize = YES;
+ pictureOptions.correctOrientation = NO;
+
+ resultImage = [self.plugin retrieveImage:infoDict1 options:pictureOptions];
+ XCTAssertEqual(resultImage.size.width, scaledImageWithCrop.size.width);
+ XCTAssertEqual(resultImage.size.height, scaledImageWithCrop.size.height);
+}
+
+- (void) testProcessImage
+{
+ CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
+ NSData* resultData;
+
+ UIImage* originalImage = [self createImage:CGRectMake(0, 0, 1024, 768) orientation:UIImageOrientationDown];
+ NSData* originalImageDataPNG = UIImagePNGRepresentation(originalImage);
+ NSData* originalImageDataJPEG = UIImageJPEGRepresentation(originalImage, 1.0);
+
+ // Original, PNG
+
+ pictureOptions.allowsEditing = YES;
+ pictureOptions.targetSize = CGSizeZero;
+ pictureOptions.cropToSize = NO;
+ pictureOptions.correctOrientation = NO;
+ pictureOptions.encodingType = EncodingTypePNG;
+
+ resultData = [self.plugin processImage:originalImage info:@{} options:pictureOptions];
+ XCTAssertEqualObjects([resultData base64EncodedString], [originalImageDataPNG base64EncodedString]);
+
+ // Original, JPEG, full quality
+
+ pictureOptions.allowsEditing = NO;
+ pictureOptions.targetSize = CGSizeZero;
+ pictureOptions.cropToSize = NO;
+ pictureOptions.correctOrientation = NO;
+ pictureOptions.encodingType = EncodingTypeJPEG;
+
+ resultData = [self.plugin processImage:originalImage info:@{} options:pictureOptions];
+ XCTAssertEqualObjects([resultData base64EncodedString], [originalImageDataJPEG base64EncodedString]);
+
+ // Original, JPEG, with quality value
+
+ pictureOptions.allowsEditing = YES;
+ pictureOptions.targetSize = CGSizeZero;
+ pictureOptions.cropToSize = NO;
+ pictureOptions.correctOrientation = NO;
+ pictureOptions.encodingType = EncodingTypeJPEG;
+ pictureOptions.quality = @(57);
+
+ NSData* originalImageDataJPEGWithQuality = UIImageJPEGRepresentation(originalImage, [pictureOptions.quality floatValue]/ 100.f);
+ resultData = [self.plugin processImage:originalImage info:@{} options:pictureOptions];
+ XCTAssertEqualObjects([resultData base64EncodedString], [originalImageDataJPEGWithQuality base64EncodedString]);
+
+ // TODO: usesGeolocation is not tested
+}
+
+@end
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraLibTests/Info.plist b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraLibTests/Info.plist
new file mode 100644
index 0000000..a949946
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraLibTests/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ org.apache.cordova.$(PRODUCT_NAME:rfc1034identifier)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.pbxproj b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..3e5a4c0
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.pbxproj
@@ -0,0 +1,561 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 30486FEB1A40DC350065C233 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30486FEA1A40DC350065C233 /* UIKit.framework */; };
+ 30486FED1A40DC3B0065C233 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30486FEC1A40DC3A0065C233 /* Foundation.framework */; };
+ 30486FF91A40DCC70065C233 /* CDVCamera.m in Sources */ = {isa = PBXBuildFile; fileRef = 30486FF31A40DCC70065C233 /* CDVCamera.m */; };
+ 30486FFA1A40DCC70065C233 /* CDVJpegHeaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 30486FF61A40DCC70065C233 /* CDVJpegHeaderWriter.m */; };
+ 30486FFB1A40DCC70065C233 /* UIImage+CropScaleOrientation.m in Sources */ = {isa = PBXBuildFile; fileRef = 30486FF81A40DCC70065C233 /* UIImage+CropScaleOrientation.m */; };
+ 304870011A40DD620065C233 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30486FFE1A40DD180065C233 /* CoreGraphics.framework */; };
+ 304870021A40DD860065C233 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30486FFE1A40DD180065C233 /* CoreGraphics.framework */; };
+ 304870031A40DD8C0065C233 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30486FEA1A40DC350065C233 /* UIKit.framework */; };
+ 304870051A40DD9A0065C233 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 304870041A40DD9A0065C233 /* MobileCoreServices.framework */; };
+ 304870071A40DDAC0065C233 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 304870061A40DDAC0065C233 /* AssetsLibrary.framework */; };
+ 304870091A40DDB90065C233 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 304870081A40DDB90065C233 /* CoreLocation.framework */; };
+ 3048700B1A40DDF30065C233 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3048700A1A40DDF30065C233 /* ImageIO.framework */; };
+ 308F59B11A4228730031A4D4 /* libCordova.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E9F519019DA0F8300DA31AC /* libCordova.a */; };
+ 7E9F51B119DA114400DA31AC /* CameraTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E9F51B019DA114400DA31AC /* CameraTest.m */; };
+ 7E9F51B919DA1B1600DA31AC /* libCDVCameraLib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E9F519519DA102000DA31AC /* libCDVCameraLib.a */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 30486FFC1A40DCE80065C233 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 7E9F518B19DA0F8300DA31AC /* CordovaLib.xcodeproj */;
+ proxyType = 1;
+ remoteGlobalIDString = D2AAC07D0554694100DB518D;
+ remoteInfo = CordovaLib;
+ };
+ 7E9F518F19DA0F8300DA31AC /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 7E9F518B19DA0F8300DA31AC /* CordovaLib.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 68A32D7114102E1C006B237C;
+ remoteInfo = CordovaLib;
+ };
+ 7E9F51AC19DA10DE00DA31AC /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 7E9F517219DA09CE00DA31AC /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 7E9F519419DA102000DA31AC;
+ remoteInfo = CDVCameraLib;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 7E9F519319DA102000DA31AC /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "include/$(PRODUCT_NAME)";
+ dstSubfolderSpec = 16;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 30486FEA1A40DC350065C233 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
+ 30486FEC1A40DC3A0065C233 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
+ 30486FF21A40DCC70065C233 /* CDVCamera.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVCamera.h; sourceTree = ""; };
+ 30486FF31A40DCC70065C233 /* CDVCamera.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVCamera.m; sourceTree = ""; };
+ 30486FF41A40DCC70065C233 /* CDVExif.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVExif.h; sourceTree = ""; };
+ 30486FF51A40DCC70065C233 /* CDVJpegHeaderWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVJpegHeaderWriter.h; sourceTree = ""; };
+ 30486FF61A40DCC70065C233 /* CDVJpegHeaderWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVJpegHeaderWriter.m; sourceTree = ""; };
+ 30486FF71A40DCC70065C233 /* UIImage+CropScaleOrientation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+CropScaleOrientation.h"; sourceTree = ""; };
+ 30486FF81A40DCC70065C233 /* UIImage+CropScaleOrientation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+CropScaleOrientation.m"; sourceTree = ""; };
+ 30486FFE1A40DD180065C233 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
+ 304870041A40DD9A0065C233 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
+ 304870061A40DDAC0065C233 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/AssetsLibrary.framework; sourceTree = DEVELOPER_DIR; };
+ 304870081A40DDB90065C233 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; };
+ 3048700A1A40DDF30065C233 /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
+ 7E9F518B19DA0F8300DA31AC /* CordovaLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CordovaLib.xcodeproj; path = "../node_modules/cordova-ios/CordovaLib/CordovaLib.xcodeproj"; sourceTree = ""; };
+ 7E9F519519DA102000DA31AC /* libCDVCameraLib.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCDVCameraLib.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7E9F519F19DA102000DA31AC /* CDVCameraLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CDVCameraLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7E9F51A219DA102000DA31AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 7E9F51B019DA114400DA31AC /* CameraTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraTest.m; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 7E9F519219DA102000DA31AC /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 308F59B11A4228730031A4D4 /* libCordova.a in Frameworks */,
+ 304870011A40DD620065C233 /* CoreGraphics.framework in Frameworks */,
+ 30486FED1A40DC3B0065C233 /* Foundation.framework in Frameworks */,
+ 30486FEB1A40DC350065C233 /* UIKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7E9F519C19DA102000DA31AC /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 3048700B1A40DDF30065C233 /* ImageIO.framework in Frameworks */,
+ 304870091A40DDB90065C233 /* CoreLocation.framework in Frameworks */,
+ 304870071A40DDAC0065C233 /* AssetsLibrary.framework in Frameworks */,
+ 304870051A40DD9A0065C233 /* MobileCoreServices.framework in Frameworks */,
+ 304870031A40DD8C0065C233 /* UIKit.framework in Frameworks */,
+ 304870021A40DD860065C233 /* CoreGraphics.framework in Frameworks */,
+ 7E9F51B919DA1B1600DA31AC /* libCDVCameraLib.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 30486FF11A40DCC70065C233 /* CDVCameraLib */ = {
+ isa = PBXGroup;
+ children = (
+ 30486FF21A40DCC70065C233 /* CDVCamera.h */,
+ 30486FF31A40DCC70065C233 /* CDVCamera.m */,
+ 30486FF41A40DCC70065C233 /* CDVExif.h */,
+ 30486FF51A40DCC70065C233 /* CDVJpegHeaderWriter.h */,
+ 30486FF61A40DCC70065C233 /* CDVJpegHeaderWriter.m */,
+ 30486FF71A40DCC70065C233 /* UIImage+CropScaleOrientation.h */,
+ 30486FF81A40DCC70065C233 /* UIImage+CropScaleOrientation.m */,
+ );
+ name = CDVCameraLib;
+ path = ../../../src/ios;
+ sourceTree = "";
+ };
+ 308F59B01A4227A60031A4D4 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 3048700A1A40DDF30065C233 /* ImageIO.framework */,
+ 304870081A40DDB90065C233 /* CoreLocation.framework */,
+ 304870061A40DDAC0065C233 /* AssetsLibrary.framework */,
+ 304870041A40DD9A0065C233 /* MobileCoreServices.framework */,
+ 30486FFE1A40DD180065C233 /* CoreGraphics.framework */,
+ 30486FEC1A40DC3A0065C233 /* Foundation.framework */,
+ 30486FEA1A40DC350065C233 /* UIKit.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 7E9F517119DA09CE00DA31AC = {
+ isa = PBXGroup;
+ children = (
+ 7E9F518B19DA0F8300DA31AC /* CordovaLib.xcodeproj */,
+ 308F59B01A4227A60031A4D4 /* Frameworks */,
+ 30486FF11A40DCC70065C233 /* CDVCameraLib */,
+ 7E9F51A019DA102000DA31AC /* CDVCameraLibTests */,
+ 7E9F517D19DA0A0A00DA31AC /* Products */,
+ );
+ sourceTree = "";
+ };
+ 7E9F517D19DA0A0A00DA31AC /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 7E9F519519DA102000DA31AC /* libCDVCameraLib.a */,
+ 7E9F519F19DA102000DA31AC /* CDVCameraLibTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 7E9F518C19DA0F8300DA31AC /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 7E9F519019DA0F8300DA31AC /* libCordova.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 7E9F51A019DA102000DA31AC /* CDVCameraLibTests */ = {
+ isa = PBXGroup;
+ children = (
+ 7E9F51A119DA102000DA31AC /* Supporting Files */,
+ 7E9F51B019DA114400DA31AC /* CameraTest.m */,
+ );
+ path = CDVCameraLibTests;
+ sourceTree = "";
+ };
+ 7E9F51A119DA102000DA31AC /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 7E9F51A219DA102000DA31AC /* Info.plist */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 7E9F519419DA102000DA31AC /* CDVCameraLib */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7E9F51A319DA102000DA31AC /* Build configuration list for PBXNativeTarget "CDVCameraLib" */;
+ buildPhases = (
+ 7E9F519119DA102000DA31AC /* Sources */,
+ 7E9F519219DA102000DA31AC /* Frameworks */,
+ 7E9F519319DA102000DA31AC /* CopyFiles */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 30486FFD1A40DCE80065C233 /* PBXTargetDependency */,
+ );
+ name = CDVCameraLib;
+ productName = CDVCameraLib;
+ productReference = 7E9F519519DA102000DA31AC /* libCDVCameraLib.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+ 7E9F519E19DA102000DA31AC /* CDVCameraLibTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7E9F51A619DA102000DA31AC /* Build configuration list for PBXNativeTarget "CDVCameraLibTests" */;
+ buildPhases = (
+ 7E9F519B19DA102000DA31AC /* Sources */,
+ 7E9F519C19DA102000DA31AC /* Frameworks */,
+ 7E9F519D19DA102000DA31AC /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 7E9F51AD19DA10DE00DA31AC /* PBXTargetDependency */,
+ );
+ name = CDVCameraLibTests;
+ productName = CDVCameraLibTests;
+ productReference = 7E9F519F19DA102000DA31AC /* CDVCameraLibTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 7E9F517219DA09CE00DA31AC /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0610;
+ TargetAttributes = {
+ 7E9F519419DA102000DA31AC = {
+ CreatedOnToolsVersion = 6.0;
+ };
+ 7E9F519E19DA102000DA31AC = {
+ CreatedOnToolsVersion = 6.0;
+ };
+ };
+ };
+ buildConfigurationList = 7E9F517519DA09CE00DA31AC /* Build configuration list for PBXProject "CDVCameraTest" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 7E9F517119DA09CE00DA31AC;
+ productRefGroup = 7E9F517D19DA0A0A00DA31AC /* Products */;
+ projectDirPath = "";
+ projectReferences = (
+ {
+ ProductGroup = 7E9F518C19DA0F8300DA31AC /* Products */;
+ ProjectRef = 7E9F518B19DA0F8300DA31AC /* CordovaLib.xcodeproj */;
+ },
+ );
+ projectRoot = "";
+ targets = (
+ 7E9F519419DA102000DA31AC /* CDVCameraLib */,
+ 7E9F519E19DA102000DA31AC /* CDVCameraLibTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXReferenceProxy section */
+ 7E9F519019DA0F8300DA31AC /* libCordova.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libCordova.a;
+ remoteRef = 7E9F518F19DA0F8300DA31AC /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+/* End PBXReferenceProxy section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 7E9F519D19DA102000DA31AC /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 7E9F519119DA102000DA31AC /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 30486FF91A40DCC70065C233 /* CDVCamera.m in Sources */,
+ 30486FFB1A40DCC70065C233 /* UIImage+CropScaleOrientation.m in Sources */,
+ 30486FFA1A40DCC70065C233 /* CDVJpegHeaderWriter.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7E9F519B19DA102000DA31AC /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7E9F51B119DA114400DA31AC /* CameraTest.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 30486FFD1A40DCE80065C233 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = CordovaLib;
+ targetProxy = 30486FFC1A40DCE80065C233 /* PBXContainerItemProxy */;
+ };
+ 7E9F51AD19DA10DE00DA31AC /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 7E9F519419DA102000DA31AC /* CDVCameraLib */;
+ targetProxy = 7E9F51AC19DA10DE00DA31AC /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 7E9F517619DA09CE00DA31AC /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ONLY_ACTIVE_ARCH = YES;
+ };
+ name = Debug;
+ };
+ 7E9F517719DA09CE00DA31AC /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ };
+ name = Release;
+ };
+ 7E9F51A419DA102000DA31AC /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(TARGET_BUILD_DIR)/usr/local/lib/include\"",
+ "\"$(OBJROOT)/UninstalledProducts/include\"",
+ "\"$(BUILT_PRODUCTS_DIR)\"",
+ );
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ };
+ name = Debug;
+ };
+ 7E9F51A519DA102000DA31AC /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(TARGET_BUILD_DIR)/usr/local/lib/include\"",
+ "\n\"$(OBJROOT)/UninstalledProducts/include\"\n\"$(BUILT_PRODUCTS_DIR)\"",
+ );
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 7E9F51A719DA102000DA31AC /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ );
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INFOPLIST_FILE = CDVCameraLibTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-framework",
+ XCTest,
+ "-all_load",
+ "-ObjC",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ 7E9F51A819DA102000DA31AC /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ );
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INFOPLIST_FILE = CDVCameraLibTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-framework",
+ XCTest,
+ "-all_load",
+ "-ObjC",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 7E9F517519DA09CE00DA31AC /* Build configuration list for PBXProject "CDVCameraTest" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7E9F517619DA09CE00DA31AC /* Debug */,
+ 7E9F517719DA09CE00DA31AC /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 7E9F51A319DA102000DA31AC /* Build configuration list for PBXNativeTarget "CDVCameraLib" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7E9F51A419DA102000DA31AC /* Debug */,
+ 7E9F51A519DA102000DA31AC /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 7E9F51A619DA102000DA31AC /* Build configuration list for PBXNativeTarget "CDVCameraLibTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7E9F51A719DA102000DA31AC /* Debug */,
+ 7E9F51A819DA102000DA31AC /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 7E9F517219DA09CE00DA31AC /* Project object */;
+}
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..98498f8
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.xcworkspace/xcshareddata/CDVCameraTest.xccheckout b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.xcworkspace/xcshareddata/CDVCameraTest.xccheckout
new file mode 100644
index 0000000..2a654cb
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/project.xcworkspace/xcshareddata/CDVCameraTest.xccheckout
@@ -0,0 +1,41 @@
+
+
+
+
+ IDESourceControlProjectFavoriteDictionaryKey
+
+ IDESourceControlProjectIdentifier
+ 6BE9AD73-1B9F-4362-98D7-DC631BEC6185
+ IDESourceControlProjectName
+ CDVCameraTest
+ IDESourceControlProjectOriginsDictionary
+
+ BEF5A5D0FF64801E558286389440357A9233D7DB
+ https://git-wip-us.apache.org/repos/asf/cordova-plugin-camera.git
+
+ IDESourceControlProjectPath
+ tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj
+ IDESourceControlProjectRelativeInstallPathDictionary
+
+ BEF5A5D0FF64801E558286389440357A9233D7DB
+ ../../../../..
+
+ IDESourceControlProjectURL
+ https://git-wip-us.apache.org/repos/asf/cordova-plugin-camera.git
+ IDESourceControlProjectVersion
+ 111
+ IDESourceControlProjectWCCIdentifier
+ BEF5A5D0FF64801E558286389440357A9233D7DB
+ IDESourceControlProjectWCConfigurations
+
+
+ IDESourceControlRepositoryExtensionIdentifierKey
+ public.vcs.git
+ IDESourceControlWCCIdentifierKey
+ BEF5A5D0FF64801E558286389440357A9233D7DB
+ IDESourceControlWCCName
+ cordova-plugin-camera
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/xcshareddata/xcschemes/CDVCameraLib.xcscheme b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/xcshareddata/xcschemes/CDVCameraLib.xcscheme
new file mode 100644
index 0000000..f0a8304
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/xcshareddata/xcschemes/CDVCameraLib.xcscheme
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/xcshareddata/xcschemes/CDVCameraLibTests.xcscheme b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/xcshareddata/xcschemes/CDVCameraLibTests.xcscheme
new file mode 100644
index 0000000..b5d2603
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/CDVCameraTest/CDVCameraTest.xcodeproj/xcshareddata/xcschemes/CDVCameraLibTests.xcscheme
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/ios/README.md b/plugins/org.apache.cordova.camera/tests/ios/README.md
new file mode 100644
index 0000000..631ae2c
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/README.md
@@ -0,0 +1,21 @@
+# iOS Tests for CDVCamera
+
+You need to install `node.js` to pull in `cordova-ios`.
+
+First install cordova-ios:
+
+ npm install
+
+... in the current folder.
+
+
+# Testing from Xcode
+
+1. Launch the `CDVCameraTest.xcworkspace` file.
+2. Choose "CDVCameraLibTests" from the scheme drop-down menu
+3. Click and hold on the `Play` button, and choose the `Wrench` icon to run the tests
+
+
+# Testing from the command line
+
+ npm test
diff --git a/plugins/org.apache.cordova.camera/tests/ios/package.json b/plugins/org.apache.cordova.camera/tests/ios/package.json
new file mode 100644
index 0000000..84bd9ec
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/ios/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "cordova-plugin-camera-test-ios",
+ "version": "1.0.0",
+ "description": "iOS Unit Tests for Camera Plugin",
+ "author": "Apache Software Foundation",
+ "license": "Apache Version 2.0",
+ "dependencies": {
+ "cordova-ios": "^3.7.0"
+ },
+ "scripts": {
+ "test": "xcodebuild -scheme CordovaLib && xcodebuild test -scheme CDVCameraLibTests -destination 'platform=iOS Simulator,name=iPhone 5'"
+ }
+}
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.camera/tests/plugin.xml b/plugins/org.apache.cordova.camera/tests/plugin.xml
new file mode 100644
index 0000000..2ba7ace
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/plugin.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ Cordova Camera Plugin Tests
+ Apache 2.0
+
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/tests/tests.js b/plugins/org.apache.cordova.camera/tests/tests.js
new file mode 100644
index 0000000..fd97ec2
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/tests/tests.js
@@ -0,0 +1,504 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+exports.defineAutoTests = function () {
+ describe('Camera (navigator.camera)', function () {
+ it("should exist", function () {
+ expect(navigator.camera).toBeDefined();
+ });
+
+ it("should contain a getPicture function", function () {
+ expect(navigator.camera.getPicture).toBeDefined();
+ expect(typeof navigator.camera.getPicture == 'function').toBe(true);
+ });
+ });
+
+ describe('Camera Constants (window.Camera + navigator.camera)', function () {
+ it("camera.spec.1 window.Camera should exist", function () {
+ expect(window.Camera).toBeDefined();
+ });
+
+ it("camera.spec.2 should contain three DestinationType constants", function () {
+ expect(Camera.DestinationType.DATA_URL).toBe(0);
+ expect(Camera.DestinationType.FILE_URI).toBe(1);
+ expect(Camera.DestinationType.NATIVE_URI).toBe(2);
+ expect(navigator.camera.DestinationType.DATA_URL).toBe(0);
+ expect(navigator.camera.DestinationType.FILE_URI).toBe(1);
+ expect(navigator.camera.DestinationType.NATIVE_URI).toBe(2);
+ });
+
+ it("camera.spec.3 should contain two EncodingType constants", function () {
+ expect(Camera.EncodingType.JPEG).toBe(0);
+ expect(Camera.EncodingType.PNG).toBe(1);
+ expect(navigator.camera.EncodingType.JPEG).toBe(0);
+ expect(navigator.camera.EncodingType.PNG).toBe(1);
+ });
+
+ it("camera.spec.4 should contain three MediaType constants", function () {
+ expect(Camera.MediaType.PICTURE).toBe(0);
+ expect(Camera.MediaType.VIDEO).toBe(1);
+ expect(Camera.MediaType.ALLMEDIA).toBe(2);
+ expect(navigator.camera.MediaType.PICTURE).toBe(0);
+ expect(navigator.camera.MediaType.VIDEO).toBe(1);
+ expect(navigator.camera.MediaType.ALLMEDIA).toBe(2);
+ });
+
+ it("camera.spec.5 should contain three PictureSourceType constants", function () {
+ expect(Camera.PictureSourceType.PHOTOLIBRARY).toBe(0);
+ expect(Camera.PictureSourceType.CAMERA).toBe(1);
+ expect(Camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2);
+ expect(navigator.camera.PictureSourceType.PHOTOLIBRARY).toBe(0);
+ expect(navigator.camera.PictureSourceType.CAMERA).toBe(1);
+ expect(navigator.camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2);
+ });
+ });
+};
+
+
+/******************************************************************************/
+/******************************************************************************/
+/******************************************************************************/
+
+exports.defineManualTests = function (contentEl, createActionButton) {
+ var platformId = cordova.require('cordova/platform').id;
+ var pictureUrl = null;
+ var fileObj = null;
+ var fileEntry = null;
+ var pageStartTime = +new Date();
+
+ //default camera options
+ var camQualityDefault = ['50', 50];
+ var camDestinationTypeDefault = ['FILE_URI', 1];
+ var camPictureSourceTypeDefault = ['CAMERA', 1];
+ var camAllowEditDefault = ['allowEdit', false];
+ var camEncodingTypeDefault = ['JPEG', 0];
+ var camMediaTypeDefault = ['mediaType', 0];
+ var camCorrectOrientationDefault = ['correctOrientation', false];
+ var camSaveToPhotoAlbumDefault = ['saveToPhotoAlbum', true];
+
+ var clearLog = function () {
+ var log = document.getElementById('info');
+ log.innerHTML = "";
+ }
+
+ function log(value) {
+ console.log(value);
+ document.getElementById('camera_status').textContent += (new Date() - pageStartTime) / 1000 + ': ' + value + '\n';
+ }
+
+ function clearStatus() {
+ document.getElementById('camera_status').innerHTML = '';
+ document.getElementById('camera_image').src = 'about:blank';
+ var canvas = document.getElementById('canvas');
+ canvas.width = canvas.height = 1;
+ pictureUrl = null;
+ fileObj = null;
+ fileEntry = null;
+ }
+
+ function setPicture(url, callback) {
+ try {
+ window.atob(url);
+ // if we got here it is a base64 string (DATA_URL)
+ url = "data:image/jpeg;base64," + url;
+ } catch (e) {
+ // not DATA_URL
+ log('URL: ' + url.slice(0, 100));
+ }
+
+ pictureUrl = url;
+ var img = document.getElementById('camera_image');
+ var startTime = new Date();
+ img.src = url;
+ img.onloadend = function () {
+ log('Image tag load time: ' + (new Date() - startTime));
+ callback && callback();
+ };
+ }
+
+ function onGetPictureError(e) {
+ log('Error getting picture: ' + (e.code || e));
+ }
+
+ function getPictureWin(data) {
+ setPicture(data);
+ // TODO: Fix resolveLocalFileSystemURI to work with native-uri.
+ if (pictureUrl.indexOf('file:') == 0 || pictureUrl.indexOf('content:') == 0 || pictureUrl.indexOf('ms-appdata:') === 0) {
+ resolveLocalFileSystemURI(data, function (e) {
+ fileEntry = e;
+ logCallback('resolveLocalFileSystemURI()', true)(e.toURL());
+ }, logCallback('resolveLocalFileSystemURI()', false));
+ } else if (pictureUrl.indexOf('data:image/jpeg;base64') == 0) {
+ // do nothing
+ } else {
+ var path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' ');
+ fileEntry = new FileEntry('image_name.png', path);
+ }
+ }
+
+ function getPicture() {
+ clearStatus();
+ var options = extractOptions();
+ log('Getting picture with options: ' + JSON.stringify(options));
+ var popoverHandle = navigator.camera.getPicture(getPictureWin, onGetPictureError, options);
+
+ // Reposition the popover if the orientation changes.
+ window.onorientationchange = function () {
+ var newPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0);
+ popoverHandle.setPosition(newPopoverOptions);
+ }
+ }
+
+ function uploadImage() {
+ var ft = new FileTransfer(),
+ uploadcomplete = 0,
+ progress = 0,
+ options = new FileUploadOptions();
+ options.fileKey = "photo";
+ options.fileName = 'test.jpg';
+ options.mimeType = "image/jpeg";
+ ft.onprogress = function (progressEvent) {
+ console.log('progress: ' + progressEvent.loaded + ' of ' + progressEvent.total);
+ };
+ var server = "http://cordova-filetransfer.jitsu.com";
+
+ ft.upload(pictureUrl, server + '/upload', win, fail, options);
+ function win(information_back) {
+ log('upload complete');
+ }
+ function fail(message) {
+ log('upload failed: ' + JSON.stringify(message));
+ }
+ }
+
+ function logCallback(apiName, success) {
+ return function () {
+ log('Call to ' + apiName + (success ? ' success: ' : ' failed: ') + JSON.stringify([].slice.call(arguments)));
+ };
+ }
+
+ /**
+ * Select image from library using a NATIVE_URI destination type
+ * This calls FileEntry.getMetadata, FileEntry.setMetadata, FileEntry.getParent, FileEntry.file, and FileReader.readAsDataURL.
+ */
+ function readFile() {
+ function onFileReadAsDataURL(evt) {
+ var img = document.getElementById('camera_image');
+ img.style.visibility = "visible";
+ img.style.display = "block";
+ img.src = evt.target.result;
+ log("FileReader.readAsDataURL success");
+ };
+
+ function onFileReceived(file) {
+ log('Got file: ' + JSON.stringify(file));
+ fileObj = file;
+
+ var reader = new FileReader();
+ reader.onload = function () {
+ log('FileReader.readAsDataURL() - length = ' + reader.result.length);
+ };
+ reader.onerror = logCallback('FileReader.readAsDataURL', false);
+ reader.readAsDataURL(file);
+ };
+ // Test out onFileReceived when the file object was set via a native elements.
+ if (fileObj) {
+ onFileReceived(fileObj);
+ } else {
+ fileEntry.file(onFileReceived, logCallback('FileEntry.file', false));
+ }
+ }
+ function getFileInfo() {
+ // Test FileEntry API here.
+ fileEntry.getMetadata(logCallback('FileEntry.getMetadata', true), logCallback('FileEntry.getMetadata', false));
+ fileEntry.setMetadata(logCallback('FileEntry.setMetadata', true), logCallback('FileEntry.setMetadata', false), { "com.apple.MobileBackup": 1 });
+ fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false));
+ fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false));
+ };
+
+ /**
+ * Copy image from library using a NATIVE_URI destination type
+ * This calls FileEntry.copyTo and FileEntry.moveTo.
+ */
+ function copyImage() {
+ var onFileSystemReceived = function (fileSystem) {
+ var destDirEntry = fileSystem.root;
+ var origName = fileEntry.name;
+
+ // Test FileEntry API here.
+ fileEntry.copyTo(destDirEntry, 'copied_file.png', logCallback('FileEntry.copyTo', true), logCallback('FileEntry.copyTo', false));
+ fileEntry.moveTo(destDirEntry, 'moved_file.png', logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false));
+
+ //cleanup
+ //rename moved file back to original name so other tests can reference image
+ resolveLocalFileSystemURI(destDirEntry.nativeURL+'moved_file.png', function(fileEntry) {
+ fileEntry.moveTo(destDirEntry, origName, logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false));
+ console.log('Cleanup: successfully renamed file back to original name');
+ }, function () {
+ console.log('Cleanup: failed to rename file back to original name');
+ });
+
+ //remove copied file
+ resolveLocalFileSystemURI(destDirEntry.nativeURL+'copied_file.png', function(fileEntry) {
+ fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
+ console.log('Cleanup: successfully removed copied file');
+ }, function () {
+ console.log('Cleanup: failed to remove copied file');
+ });
+ };
+
+ window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, onFileSystemReceived, null);
+ };
+
+ /**
+ * Write image to library using a NATIVE_URI destination type
+ * This calls FileEntry.createWriter, FileWriter.write, and FileWriter.truncate.
+ */
+ function writeImage() {
+ var onFileWriterReceived = function (fileWriter) {
+ fileWriter.onwrite = logCallback('FileWriter.write', true);
+ fileWriter.onerror = logCallback('FileWriter.write', false);
+ fileWriter.write("some text!");
+ };
+
+ var onFileTruncateWriterReceived = function (fileWriter) {
+ fileWriter.onwrite = logCallback('FileWriter.truncate', true);
+ fileWriter.onerror = logCallback('FileWriter.truncate', false);
+ fileWriter.truncate(10);
+ };
+
+ fileEntry.createWriter(onFileWriterReceived, logCallback('FileEntry.createWriter', false));
+ fileEntry.createWriter(onFileTruncateWriterReceived, null);
+ };
+
+ function displayImageUsingCanvas() {
+ var canvas = document.getElementById('canvas');
+ var img = document.getElementById('camera_image');
+ var w = img.width;
+ var h = img.height;
+ h = 100 / w * h;
+ w = 100;
+ canvas.width = w;
+ canvas.height = h;
+ var context = canvas.getContext('2d');
+ context.drawImage(img, 0, 0, w, h);
+ };
+
+ /**
+ * Remove image from library using a NATIVE_URI destination type
+ * This calls FileEntry.remove.
+ */
+ function removeImage() {
+ fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
+ };
+
+ function testInputTag(inputEl) {
+ clearStatus();
+ // iOS 6 likes to dead-lock in the onchange context if you
+ // do any alerts or try to remote-debug.
+ window.setTimeout(function () {
+ testNativeFile2(inputEl);
+ }, 0);
+ };
+
+ function testNativeFile2(inputEl) {
+ if (!inputEl.value) {
+ alert('No file selected.');
+ return;
+ }
+ fileObj = inputEl.files[0];
+ if (!fileObj) {
+ alert('Got value but no file.');
+ return;
+ }
+ var URLApi = window.URL || window.webkitURL;
+ if (URLApi) {
+ var blobURL = URLApi.createObjectURL(fileObj);
+ if (blobURL) {
+ setPicture(blobURL, function () {
+ URLApi.revokeObjectURL(blobURL);
+ });
+ } else {
+ log('URL.createObjectURL returned null');
+ }
+ } else {
+ log('URL.createObjectURL() not supported.');
+ }
+ }
+
+ function extractOptions() {
+ var els = document.querySelectorAll('#image-options select');
+ var ret = {};
+ for (var i = 0, el; el = els[i]; ++i) {
+ var value = el.value;
+ if (value === '') continue;
+ if (el.isBool) {
+ ret[el.getAttribute("name")] = !!+value;
+ } else {
+ ret[el.getAttribute("name")] = +value;
+ }
+ }
+ return ret;
+ }
+
+ function createOptionsEl(name, values, selectionDefault) {
+ var openDiv = '
' +
+ 'Options not specified should be the default value' +
+ ' Status box should update with image and info whenever an image is taken or selected from library' +
+ '
' +
+ '
All default options. Should be able to edit once picture is taken and will be saved to library.
' +
+ '
sourceType=PHOTOLIBRARY Should be able to see picture that was just taken in previous test and edit when selected
' +
+ '
sourceType=Camera allowEdit=false saveToPhotoAlbum=false Should not be able to edit when taken and will not save to library
' +
+ '
encodingType=PNG allowEdit=true saveToPhotoAlbum=true cameraDirection=FRONT Should bring up front camera. Verify in status box info URL that image is encoded as PNG.
' +
+ '
sourceType=SAVEDPHOTOALBUM mediaType=VIDEO Should only be able to select a video
' +
+ '
sourceType=SAVEDPHOTOALBUM mediaType=PICTURE allowEdit=false Should only be able to select a picture and not edit
' +
+ '
sourceType=PHOTOLIBRARY mediaType=ALLMEDIA allowEdit=true Should be able to select pics and videos and edit picture if selected
' +
+ '
sourceType=CAMERA targetWidth & targetHeight=50 allowEdit=false Do Get File Metadata test below and take note of size Repeat test but with width and height=800. Size should be significantly larger.
' +
+ '
quality=0 targetWidth & targetHeight=default allowEdit=false Do Get File Metadata test below and take note of size Repeat test but with quality=80. Size should be significantly larger.
' +
+ '
',
+ inputs_div = '
Native File Inputs
' +
+ 'For the following tests, status box should update with file selected' +
+ '
input type=file
' +
+ '
capture=camera
' +
+ '
capture=camcorder
' +
+ '
capture=microphone
',
+ actions_div = '
Actions
' +
+ 'For the following tests, ensure that an image is set in status box' +
+ '' +
+ 'Expected result: Get metadata about file selected. Status box will show, along with the metadata, "Call to FileEntry.getMetadata success, Call to FileEntry.setMetadata success, Call to FileEntry.getParent success"' +
+ '' +
+ 'Expected result: Read contents of file. Status box will show "Got file: {some metadata}, FileReader.readAsDataURL() - length = someNumber"' +
+ '' +
+ 'Expected result: Copy image to new location and move file to different location. Status box will show "Call to FileEntry.copyTo success:{some metadata}, Call to FileEntry.moveTo success:{some metadata}"' +
+ '' +
+ 'Expected result: Write image to library. Status box will show "Call to FileWriter.write success:{some metadata}, Call to FileWriter.truncate success:{some metadata}"' +
+ '' +
+ 'Expected result: Upload image to server. Status box may print out progress. Once finished will show "upload complete"' +
+ '' +
+ 'Expected result: Display image using canvas. Image will be displayed in status box under "canvas:"' +
+ '' +
+ 'Expected result: Remove image from library. Status box will show "FileEntry.remove success:["OK"]';
+
+ // We need to wrap this code due to Windows security restrictions
+ // see http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences for details
+ if (window.MSApp && window.MSApp.execUnsafeLocalFunction) {
+ MSApp.execUnsafeLocalFunction(function() {
+ contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
+ });
+ } else {
+ contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
+ }
+
+ var elements = document.getElementsByClassName("testInputTag");
+ var listener = function (e) {
+ testInputTag(e.target);
+ }
+ for (var i = 0; i < elements.length; ++i) {
+ var item = elements[i];
+ item.addEventListener("change", listener, false);
+ }
+
+ createActionButton('Get picture', function () {
+ getPicture();
+ }, 'getpicture');
+
+ createActionButton('Clear Status', function () {
+ clearStatus();
+ }, 'getpicture');
+
+ createActionButton('Get File Metadata', function () {
+ getFileInfo();
+ }, 'metadata');
+
+ createActionButton('Read with FileReader', function () {
+ readFile();
+ }, 'reader');
+
+ createActionButton('Copy Image', function () {
+ copyImage();
+ }, 'copy');
+
+ createActionButton('Write Image', function () {
+ writeImage();
+ }, 'write');
+
+ createActionButton('Upload Image', function () {
+ uploadImage();
+ }, 'upload');
+
+ createActionButton('Draw Using Canvas', function () {
+ displayImageUsingCanvas();
+ }, 'draw_canvas');
+
+ createActionButton('Remove Image', function () {
+ removeImage();
+ }, 'remove');
+};
diff --git a/plugins/org.apache.cordova.camera/www/Camera.js b/plugins/org.apache.cordova.camera/www/Camera.js
new file mode 100644
index 0000000..555bb5e
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/www/Camera.js
@@ -0,0 +1,75 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var argscheck = require('cordova/argscheck'),
+ exec = require('cordova/exec'),
+ Camera = require('./Camera');
+ // XXX: commented out
+ //CameraPopoverHandle = require('./CameraPopoverHandle');
+
+var cameraExport = {};
+
+// Tack on the Camera Constants to the base camera plugin.
+for (var key in Camera) {
+ cameraExport[key] = Camera[key];
+}
+
+/**
+ * Gets a picture from source defined by "options.sourceType", and returns the
+ * image as defined by the "options.destinationType" option.
+
+ * The defaults are sourceType=CAMERA and destinationType=FILE_URI.
+ *
+ * @param {Function} successCallback
+ * @param {Function} errorCallback
+ * @param {Object} options
+ */
+cameraExport.getPicture = function(successCallback, errorCallback, options) {
+ argscheck.checkArgs('fFO', 'Camera.getPicture', arguments);
+ options = options || {};
+ var getValue = argscheck.getValue;
+
+ var quality = getValue(options.quality, 50);
+ var destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI);
+ var sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA);
+ var targetWidth = getValue(options.targetWidth, -1);
+ var targetHeight = getValue(options.targetHeight, -1);
+ var encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG);
+ var mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE);
+ var allowEdit = !!options.allowEdit;
+ var correctOrientation = !!options.correctOrientation;
+ var saveToPhotoAlbum = !!options.saveToPhotoAlbum;
+ var popoverOptions = getValue(options.popoverOptions, null);
+ var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK);
+
+ var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
+ mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
+
+ exec(successCallback, errorCallback, "Camera", "takePicture", args);
+ // XXX: commented out
+ //return new CameraPopoverHandle();
+};
+
+cameraExport.cleanup = function(successCallback, errorCallback) {
+ exec(successCallback, errorCallback, "Camera", "cleanup", []);
+};
+
+module.exports = cameraExport;
diff --git a/plugins/org.apache.cordova.camera/www/CameraConstants.js b/plugins/org.apache.cordova.camera/www/CameraConstants.js
new file mode 100644
index 0000000..ae4e534
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/www/CameraConstants.js
@@ -0,0 +1,53 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+module.exports = {
+ DestinationType:{
+ DATA_URL: 0, // Return base64 encoded string
+ FILE_URI: 1, // Return file uri (content://media/external/images/media/2 for Android)
+ NATIVE_URI: 2 // Return native uri (eg. asset-library://... for iOS)
+ },
+ EncodingType:{
+ JPEG: 0, // Return JPEG encoded image
+ PNG: 1 // Return PNG encoded image
+ },
+ MediaType:{
+ PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
+ VIDEO: 1, // allow selection of video only, ONLY RETURNS URL
+ ALLMEDIA : 2 // allow selection from all media types
+ },
+ PictureSourceType:{
+ PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
+ CAMERA : 1, // Take picture from camera
+ SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android)
+ },
+ PopoverArrowDirection:{
+ ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants to specify arrow location on popover
+ ARROW_DOWN : 2,
+ ARROW_LEFT : 4,
+ ARROW_RIGHT : 8,
+ ARROW_ANY : 15
+ },
+ Direction:{
+ BACK: 0,
+ FRONT: 1
+ }
+};
diff --git a/plugins/org.apache.cordova.camera/www/CameraPopoverHandle.js b/plugins/org.apache.cordova.camera/www/CameraPopoverHandle.js
new file mode 100644
index 0000000..7a4c32d
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/www/CameraPopoverHandle.js
@@ -0,0 +1,33 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var exec = require('cordova/exec');
+
+/**
+ * A handle to an image picker popover.
+ */
+var CameraPopoverHandle = function() {
+ this.setPosition = function(popoverOptions) {
+ console.log('CameraPopoverHandle.setPosition is only supported on iOS.');
+ };
+};
+
+module.exports = CameraPopoverHandle;
diff --git a/plugins/org.apache.cordova.camera/www/CameraPopoverOptions.js b/plugins/org.apache.cordova.camera/www/CameraPopoverOptions.js
new file mode 100644
index 0000000..c024984
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/www/CameraPopoverOptions.js
@@ -0,0 +1,37 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var Camera = require('./Camera');
+
+/**
+ * Encapsulates options for iOS Popover image picker
+ */
+var CameraPopoverOptions = function(x,y,width,height,arrowDir){
+ // information of rectangle that popover should be anchored to
+ this.x = x || 0;
+ this.y = y || 32;
+ this.width = width || 320;
+ this.height = height || 480;
+ // The direction of the popover arrow
+ this.arrowDir = arrowDir || Camera.PopoverArrowDirection.ARROW_ANY;
+};
+
+module.exports = CameraPopoverOptions;
diff --git a/plugins/org.apache.cordova.camera/www/blackberry10/assets/camera.html b/plugins/org.apache.cordova.camera/www/blackberry10/assets/camera.html
new file mode 100644
index 0000000..2ebd9d1
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/www/blackberry10/assets/camera.html
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.camera/www/blackberry10/assets/camera.js b/plugins/org.apache.cordova.camera/www/blackberry10/assets/camera.js
new file mode 100644
index 0000000..6d163d0
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/www/blackberry10/assets/camera.js
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ *
+*/
+
+document.addEventListener('DOMContentLoaded', function () {
+ document.getElementById('back').onclick = function () {
+ window.qnx.callExtensionMethod('org.apache.cordova.camera', 'cancel');
+ };
+ window.navigator.webkitGetUserMedia(
+ { video: true },
+ function (stream) {
+ var video = document.getElementById('v'),
+ canvas = document.getElementById('c'),
+ camera = document.getElementById('camera');
+ video.autoplay = true;
+ video.width = window.innerWidth;
+ video.height = window.innerHeight - 100;
+ video.src = window.webkitURL.createObjectURL(stream);
+ camera.onclick = function () {
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+ canvas.getContext('2d').drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
+ window.qnx.callExtensionMethod('org.apache.cordova.camera', canvas.toDataURL('img/png'));
+ };
+ },
+ function () {
+ window.qnx.callExtensionMethod('org.apache.cordova.camera', 'error', 'getUserMedia failed');
+ }
+ );
+});
diff --git a/plugins/org.apache.cordova.camera/www/ios/CameraPopoverHandle.js b/plugins/org.apache.cordova.camera/www/ios/CameraPopoverHandle.js
new file mode 100644
index 0000000..fc48c11
--- /dev/null
+++ b/plugins/org.apache.cordova.camera/www/ios/CameraPopoverHandle.js
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var exec = require('cordova/exec');
+
+/**
+ * A handle to an image picker popover.
+ */
+var CameraPopoverHandle = function() {
+ this.setPosition = function(popoverOptions) {
+ var args = [ popoverOptions ];
+ exec(null, null, "Camera", "repositionPopover", args);
+ };
+};
+
+module.exports = CameraPopoverHandle;
diff --git a/plugins/org.apache.cordova.file-transfer/.fetch.json b/plugins/org.apache.cordova.file-transfer/.fetch.json
new file mode 100644
index 0000000..c27e29f
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/.fetch.json
@@ -0,0 +1 @@
+{"source":{"type":"registry","id":"org.apache.cordova.file-transfer"}}
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/CONTRIBUTING.md b/plugins/org.apache.cordova.file-transfer/CONTRIBUTING.md
new file mode 100644
index 0000000..f7dbcab
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/CONTRIBUTING.md
@@ -0,0 +1,37 @@
+
+
+# Contributing to Apache Cordova
+
+Anyone can contribute to Cordova. And we need your contributions.
+
+There are multiple ways to contribute: report bugs, improve the docs, and
+contribute code.
+
+For instructions on this, start with the
+[contribution overview](http://cordova.apache.org/#contribute).
+
+The details are explained there, but the important items are:
+ - Sign and submit an Apache ICLA (Contributor License Agreement).
+ - Have a Jira issue open that corresponds to your contribution.
+ - Run the tests so your patch doesn't break existing functionality.
+
+We look forward to your contributions!
diff --git a/plugins/org.apache.cordova.file-transfer/LICENSE b/plugins/org.apache.cordova.file-transfer/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/NOTICE b/plugins/org.apache.cordova.file-transfer/NOTICE
new file mode 100644
index 0000000..46fed23
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/NOTICE
@@ -0,0 +1,8 @@
+Apache Cordova
+Copyright 2012 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes a copy of OkHttp from:
+https://github.com/square/okhttp
diff --git a/plugins/org.apache.cordova.file-transfer/README.md b/plugins/org.apache.cordova.file-transfer/README.md
new file mode 100644
index 0000000..9d5c69d
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/README.md
@@ -0,0 +1,22 @@
+
+
+# org.apache.cordova.file-transfer
+
+Plugin documentation: [doc/index.md](doc/index.md)
diff --git a/plugins/org.apache.cordova.file-transfer/RELEASENOTES.md b/plugins/org.apache.cordova.file-transfer/RELEASENOTES.md
new file mode 100644
index 0000000..40a4ccf
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/RELEASENOTES.md
@@ -0,0 +1,159 @@
+
+# Release Notes
+
+### 0.3.2 (Sept 25, 2013)
+* CB-4889 bumping&resetting version
+* [windows8] commandProxy was moved
+* CB-4889 updating core references
+* CB-4889 renaming org.apache.cordova.core.file-transfer to org.apache.cordova.file-transfer and updating dependency
+* Rename CHANGELOG.md -> RELEASENOTES.md
+
+### 0.3.3 (Oct 9, 2013)
+* removed un-needed undef check
+* Fix missing headers in Windows 8 upload proxy
+* Fix missing headers in Windows 8 Proxy
+* Fix Windows 8 HTMLAnchorElement return host:80 which force Basic Auth Header to replace options Auth Header
+* [CB-4915] Incremented plugin version on dev branch.
+
+ ### 0.3.4 (Oct 28, 2013)
+* CB-5128: added repo + issue tag to plugin.xml for file transfer plugin
+* [CB-5010] Incremented plugin version on dev branch.
+
+### 0.4.0 (Dec 4, 2013)
+* CB-5466: Partial revert; we're not ready yet for FS urls
+* add ubuntu platform
+* CB-5466: Minor version bump
+* CB-5466: Update FileTransfer plugin to accept filesystem urls
+* Added amazon-fireos platform. Change to use amazon-fireos as the platform if the user agen string contains 'cordova-amazon-fireos'
+
+### 0.4.1 (Feb 05, 2014)
+* CB-5365 Remove unused exception var to prevent warnings?
+* CB-2421 explicitly write the bytesSent,responseCode,result to the FileUploadResult pending release of cordova-plugin-file dependency, added some sanity checks for callbacks
+* iOS: Update for new file plugin api
+* CB-5631 Removed SimpleTrackingInputStream.read(byte[] buffer)
+* CB-5762 android: Fix lengthComputable set wrong for gzip downloads
+* CB-4899 [BlackBerry10] Improve binary file transfer download
+* Delete stale test/ directory
+* CB-5722 [BlackBerry10] Update upload function to use native file object
+* CB-5658 Delete stale snapshot of plugin docs
+* Remove @1 designation from file plugin dependency until pushed to npm
+* CB-5466: Update to work with filesystem URLs
+
+### 0.4.2 (Feb 28, 2014)
+* CB-6106 Ensure that nativeURL is used by file transfer download
+* iOS: Fix default value for trustAllHosts on iOS (YES->NO)
+* CB-6059 iOS: Stop FileTransfer.download doing IO on the UI thread.
+* CB-5588 iOS: Add response headers to upload result
+* CB-2190 iOS: Make backgroundTaskId apply to downloads as well. Move backgroundTaskId to the delegate.
+* CB-6050 Android: Use instance method on actual file plugin object to get FileEntry to return on download
+* CB-6000 Android: Nginx rejects Content-Type without a space before "boundary".
+* CB-4907 Android: Close stream when we're finished with it
+* CB-6022 Add backwards-compatibility notes to doc
+
+### 0.4.3 (Apr 17, 2014)
+* CB-6422 [windows8] use cordova/exec/proxy
+* iOS: Fix error where files were not removed on abort
+* CB-5175: [ios] CDVFileTransfer asynchronous download (Fixes #24)
+* [ios] Cast id references to NSURL to avoid compiler warnings (Fixes: apache/cordova-plugin-file-transfer#18)
+* CB-6212: [iOS] fix warnings compiled under arm64 64-bit
+* CB-5762: [FireOS] android: Fix lengthComputable set wrong for gzip downloads
+* CB-5631: [FireOS] Removed SimpleTrackingInputStream.read(byte[] buffer)
+* CB-4907: [FireOS] Close stream when we're finished with it
+* CB-6000: [FireOS] Nginx rejects Content-Type without a space before "boundary".
+* CB-6050: [FireOS] Use instance method on actual file plugin object to get FileEntry to return on download
+* CB-6460: Update license headers
+
+### 0.4.4 (Jun 05, 2014)
+* CB-6127 Spanish and French Translations added. Github close #21
+* ubuntu: support 'cdvfile' URI
+* CB-6802 Add license
+* Upload progress now works also for second file
+* CB-6706: Relax dependency on file plugin
+* CB-3440 [BlackBerry10] Update implementation to use modules from file plugin
+* CB-6378 Use connection.disconnect() instead of stream.close() for thread-safety
+* CB-6491 add CONTRIBUTING.md
+* CB-6466 Auto-create directories in download
+* CB-6494 android: Fix upload of KitKat content URIs
+* Upleveled from android port with following commits: 3c1ff16 Andrew Grieve - CB-5762 android: Fix lengthComputable set wrong for gzip downloads 8374b3d Colin Mahoney - CB-5631 Removed SimpleTrackingInputStream.read(byte[] buffer) 6f91ac3 Bas Bosman - CB-4907 Close stream when we're finished with it 651460f Christoph Neumann - CB-6000 Nginx rejects Content-Type without a space before "boundary". 35f80e4 Ian Clelland - CB-6050: Use instance method on actual file plugin object to get FileEntry to return on download
+* CB-5980 Updated version and RELEASENOTES.md for release 0.4.1
+
+### 0.4.5
+
+### 0.4.5 (Aug 06, 2014)
+* Upload parameters out of order
+* **FirefoxOS** initial implementation
+* CB-6781: Expose FileTransferError.exception to application
+* CB-6928: Add new error code to documentation
+* CB-6928: Handle 304 status code
+* CB-6928: Open output stream only if it's necessary.
+* [BlackBerry10] Minor doc correction
+* CB-6127 Updated translations for docs
+* [Windows8] upload uses the provided fileName or the actual fileName
+* CB-2420 [Windows8] honor fileKey and param options. This closes #15
+* CB-6781: Update new docs to match AlexNennker's changes in PR30
+* CB-6781: Continue previous commit with one new instance (This closes #30)
+* CB-6781: add the exception text to the error object
+* CB-6890: Fix pluginManager access for 4.0.x branch
+
+### 0.4.6 (Sep 17, 2014)
+* CB-7471 cordova-plugin-file-transfer documentation translation
+* CB-7249 cordova-plugin-file-transfer documentation translation
+* CB-7423 fix spec28,29 lastProgressEvent not visible to afterEach function
+* Amazon related changes.
+* Remove dupe file windows+windows8 both use the same one
+* CB-7316 Updates docs with actual information.
+* CB-7316 Adds support for Windows platform, moves \*Proxy files to proper directory.
+* CB-7316 Improves current specs compatibility:
+* added documentation for new test
+* CB-6466 Fix failing test due to recent url change
+* CB-6466 created mobile-spec test
+* Renamed test dir, added nested plugin.xml and test
+* Fixed failing spec.19 on wp8
+* added documentation to manual tests
+* CB-6961 port file-transfer tests to framework
+
+### 0.4.7 (Oct 03, 2014)
+* Construct proper FileEntry with nativeURL property set
+* CB-7532 Handle non-existent download dirs properly
+* CB-7529 Adds support for 'ms-appdata' URIs for windows
+
+### 0.4.8 (Dec 02, 2014)
+* CB-8021 - adds documentation for `httpMethod` to `doc/index.md`. However, translations still need to be addressed.
+* CB-7223 spec.27 marked pending for **wp8**
+* CB-6900 fixed `spec.7` for **wp8**
+* CB-7944 Pended unsupported auto tests for *Windows*
+* CB-7977 Mention `deviceready` in plugin docs
+* CB-7700 cordova-plugin-file-transfer documentation translation: cordova-plugin-file-transfer
+
+### 0.5.0 (Feb 04, 2015)
+* CB-8407 windows: Fix download of `ms-appdata:///` URIs
+* CB-8095 windows: Rewrite upload method to support progress events properly
+* CB-5059 android: Add a CookieManager abstraction for pluggable webviews
+* ios: Fix compile warning about implicity int conversion
+* CB-8351 ios: Use argumentForIndex rather than NSArray extension
+* CB-8351 ios: Use a local copy of DLog macro rather than CordovaLib version
+* CB-8296 ios: Fix crash when upload fails and file is not yet created (close #57)
+* Document "body" property on FileTransferError
+* CB-7912 ios, android: Update to work with whitelist plugins in Cordova 4.x
+* Error callback should always be called with the FileTransferError object, and not just the code
+* windows: alias appData to Windows.Storage.ApplicationData.current
+* CB-8093 Fixes incorrect FileTransferError returned in case of download failure
diff --git a/plugins/org.apache.cordova.file-transfer/doc/de/index.md b/plugins/org.apache.cordova.file-transfer/doc/de/index.md
new file mode 100644
index 0000000..4311761
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/de/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+Dieses Plugin ermöglicht Ihnen zum Hochladen und Herunterladen von Dateien.
+
+## Installation
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## Unterstützte Plattformen
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Firefox OS **
+* iOS
+* Windows Phone 7 und 8 *
+* Windows 8 ***|
+* Windows ***|
+
+* *Unterstützen nicht `onprogress` noch `abort()` *
+
+** *Unterstützen keine `onprogress` *
+
+Partielle Unterstützung von `onprogress` für upload-Methode. `onprogress` wird aufgerufen, mit leeren Progress-Ereignis durch Windows Limitations_
+
+# FileTransfer
+
+Das `FileTransfer` Objekt bietet eine Möglichkeit zum Hochladen von Dateien, die mithilfe einer HTTP-Anforderung für mehrteiligen POST sowie Informationen zum Herunterladen von Dateien sowie.
+
+## Eigenschaften
+
+* **OnProgress**: aufgerufen, wobei ein `ProgressEvent` wann wird eine neue Datenmenge übertragen. *(Funktion)*
+
+## Methoden
+
+* **Upload**: sendet eine Datei an einen Server.
+
+* **Download**: lädt eine Datei vom Server.
+
+* **Abbrechen**: Abbruch eine Übertragung in Bearbeitung.
+
+## Upload
+
+**Parameter**:
+
+* **FileURL**: Dateisystem-URL, das die Datei auf dem Gerät. Für rückwärts Kompatibilität, dies kann auch der vollständige Pfad der Datei auf dem Gerät sein. (Siehe [rückwärts Kompatibilität Notes] unten)
+
+* **Server**: URL des Servers, die Datei zu empfangen, wie kodiert`encodeURI()`.
+
+* **SuccessCallback**: ein Rückruf, der übergeben wird ein `Metadata` Objekt. *(Funktion)*
+
+* **ErrorCallback**: ein Rückruf, der ausgeführt wird, tritt ein Fehler beim Abrufen der `Metadata` . Aufgerufene mit einem `FileTransferError` Objekt. *(Funktion)*
+
+* **Optionen**: optionale Parameter *(Objekt)*. Gültige Schlüssel:
+
+ * **FileKey**: der Name des Form-Elements. Wird standardmäßig auf `file` . (DOM-String und enthält)
+ * **Dateiname**: der Dateiname beim Speichern der Datei auf dem Server verwendet. Wird standardmäßig auf `image.jpg` . (DOM-String und enthält)
+ * **MimeType**: den Mime-Typ der Daten hochzuladen. Wird standardmäßig auf `image/jpeg` . (DOM-String und enthält)
+ * **Params**: eine Reihe von optionalen Schlüssel/Wert-Paaren in der HTTP-Anforderung übergeben. (Objekt)
+ * **ChunkedMode**: ob die Daten in "Chunked" streaming-Modus hochladen. Wird standardmäßig auf `true` . (Boolean)
+ * **Header**: eine Karte von Header-Name-Header-Werte. Verwenden Sie ein Array, um mehr als einen Wert anzugeben. (Objekt)
+
+* **TrustAllHosts**: Optionaler Parameter, wird standardmäßig auf `false` . Wenn legen Sie auf `true` , es akzeptiert alle Sicherheitszertifikate. Dies ist nützlich, da Android selbstsignierte Zertifikate ablehnt. Nicht für den produktiven Einsatz empfohlen. Auf Android und iOS unterstützt. *(Boolean)*
+
+### Beispiel
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### Beispiel mit hochladen Kopf- und Progress-Ereignisse (Android und iOS nur)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+A `FileUploadResult` -Objekt wird an den Erfolg-Rückruf des übergeben die `FileTransfer` des Objekts `upload()` Methode.
+
+### Eigenschaften
+
+* **BytesSent**: die Anzahl der Bytes, die als Teil des Uploads an den Server gesendet. (lange)
+
+* **ResponseCode**: die HTTP-Response-Code vom Server zurückgegeben. (lange)
+
+* **Antwort**: der HTTP-Antwort vom Server zurückgegeben. (DOM-String und enthält)
+
+* **Header**: die HTTP-Response-Header vom Server. (Objekt)
+
+ * Derzeit unterstützt auf iOS nur.
+
+### iOS Macken
+
+* Unterstützt keine `responseCode` oder`bytesSent`.
+
+## Download
+
+**Parameter**:
+
+* **Quelle**: URL des Servers, um die Datei herunterzuladen, wie kodiert`encodeURI()`.
+
+* **Ziel**: Dateisystem-Url, das die Datei auf dem Gerät. Für rückwärts Kompatibilität, dies kann auch der vollständige Pfad der Datei auf dem Gerät sein. (Siehe [rückwärts Kompatibilität Notes] unten)
+
+* **SuccessCallback**: ein Rückruf, der übergeben wird ein `FileEntry` Objekt. *(Funktion)*
+
+* **ErrorCallback**: ein Rückruf, der ausgeführt wird, tritt ein Fehler beim Abrufen der `Metadata` . Aufgerufene mit einem `FileTransferError` Objekt. *(Funktion)*
+
+* **TrustAllHosts**: Optionaler Parameter, wird standardmäßig auf `false` . Wenn legen Sie auf `true` , es akzeptiert alle Sicherheitszertifikate. Dies ist nützlich, da Android selbstsignierte Zertifikate ablehnt. Nicht für den produktiven Einsatz empfohlen. Auf Android und iOS unterstützt. *(Boolean)*
+
+* **Optionen**: optionale Parameter, derzeit nur unterstützt Kopfzeilen (z. B. Autorisierung (Standardauthentifizierung), etc.).
+
+### Beispiel
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## Abbruch
+
+Bricht einen in-Progress-Transfer. Der Onerror-Rückruf wird ein FileTransferError-Objekt übergeben, die einen Fehlercode FileTransferError.ABORT_ERR hat.
+
+### Beispiel
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` Objekt wird an eine Fehler-Callback übergeben, wenn ein Fehler auftritt.
+
+### Eigenschaften
+
+* **Code**: einer der vordefinierten Fehlercodes aufgeführt. (Anzahl)
+
+* **Quelle**: URL der Quelle. (String)
+
+* **Ziel**: URL zum Ziel. (String)
+
+* **HTTP_STATUS**: HTTP-Statuscode. Dieses Attribut ist nur verfügbar, wenn ein Response-Code aus der HTTP-Verbindung eingeht. (Anzahl)
+
+* **Ausnahme**: entweder e.getMessage oder e.toString (String)
+
+### Konstanten
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## Hinweise rückwärts Kompatibilität
+
+Frühere Versionen des Plugins würde nur Gerät-Absolute-Dateipfade als Quelle für Uploads oder als Ziel für Downloads übernehmen. Diese Pfade wäre in der Regel der form
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Für rückwärts Kompatibilität, diese Pfade noch akzeptiert werden, und wenn Ihre Anwendung Pfade wie diese im permanenten Speicher aufgezeichnet hat, dann sie können weiter verwendet werden.
+
+Diese Pfade waren zuvor ausgesetzt, der `fullPath` -Eigenschaft des `FileEntry` und `DirectoryEntry` Objekte, die durch das Plugin Datei zurückgegeben. Neue Versionen der die Datei-Erweiterung, jedoch nicht länger werden diese Pfade zu JavaScript.
+
+Wenn Sie ein auf eine neue Upgrade (1.0.0 oder neuere) Version der Datei und Sie zuvor verwendet haben `entry.fullPath` als Argumente für `download()` oder `upload()` , dann du den Code musst, um die Dateisystem-URLs verwenden zu ändern.
+
+`FileEntry.toURL()`und `DirectoryEntry.toURL()` zurück, eine Dateisystem-URL in der Form
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+die benutzt werden kann, anstelle der absoluten Dateipfad in beiden `download()` und `upload()` Methoden.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/doc/es/index.md b/plugins/org.apache.cordova.file-transfer/doc/es/index.md
new file mode 100644
index 0000000..d8ef0b4
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/es/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+Este plugin te permite cargar y descargar archivos.
+
+## Instalación
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## Plataformas soportadas
+
+* Amazon fire OS
+* Android
+* BlackBerry 10
+* Firefox OS **
+* iOS
+* Windows Phone 7 y 8 *
+* Windows 8 ***|
+* Windows ***|
+
+* *No son compatibles con `onprogress` ni `abort()` *
+
+** *No son compatibles con `onprogress` *
+
+Apoyo parcial de `onprogress` para subir método `onprogress` se llama con el evento progress vacía debido a Windows limitations_
+
+# FileTransfer
+
+El `FileTransfer` objeto proporciona una manera de subir archivos mediante una solicitud HTTP de POST varias parte y para descargar archivos.
+
+## Propiedades
+
+* **OnProgress**: llama con un `ProgressEvent` cuando se transfiere un nuevo paquete de datos. *(Función)*
+
+## Métodos
+
+* **cargar**: envía un archivo a un servidor.
+
+* **Descargar**: descarga un archivo del servidor.
+
+* **abortar**: aborta una transferencia en curso.
+
+## subir
+
+**Parámetros**:
+
+* **fileURL**: URL de Filesystem que representa el archivo en el dispositivo. Para atrás compatibilidad, esto también puede ser la ruta de acceso completa del archivo en el dispositivo. (Ver [hacia atrás compatibilidad notas] debajo)
+
+* **servidor**: dirección URL del servidor para recibir el archivo, como codificada por`encodeURI()`.
+
+* **successCallback**: una devolución de llamada que se pasa un `Metadata` objeto. *(Función)*
+
+* **errorCallback**: una devolución de llamada que se ejecuta si se produce un error recuperar la `Metadata` . Invocado con un `FileTransferError` objeto. *(Función)*
+
+* **Opciones**: parámetros opcionales *(objeto)*. Teclas válidas:
+
+ * **fileKey**: el nombre del elemento de formulario. Por defecto es `file` . (DOMString)
+ * **nombre de archivo**: el nombre del archivo a utilizar al guardar el archivo en el servidor. Por defecto es `image.jpg` . (DOMString)
+ * **mimeType**: el tipo mime de los datos para cargar. Por defecto es `image/jpeg` . (DOMString)
+ * **params**: un conjunto de pares clave/valor opcional para pasar en la petición HTTP. (Objeto)
+ * **chunkedMode**: Si desea cargar los datos en modo de transmisión fragmentado. Por defecto es `true` . (Boolean)
+ * **cabeceras**: un mapa de valores de encabezado nombre/cabecera. Utilice una matriz para especificar más de un valor. (Objeto)
+
+* **trustAllHosts**: parámetro opcional, por defecto es `false` . Si establece en `true` , acepta todos los certificados de seguridad. Esto es útil ya que Android rechaza certificados autofirmados seguridad. No se recomienda para uso productivo. Compatible con iOS y Android. *(boolean)*
+
+### Ejemplo
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### Ejemplo con cabeceras de subir y eventos de progreso (Android y iOS solamente)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+A `FileUploadResult` objeto se pasa a la devolución del éxito de la `FileTransfer` del objeto `upload()` método.
+
+### Propiedades
+
+* **bytesSent**: el número de bytes enviados al servidor como parte de la carga. (largo)
+
+* **responseCode**: código de respuesta HTTP el devuelto por el servidor. (largo)
+
+* **respuesta**: respuesta el HTTP devuelto por el servidor. (DOMString)
+
+* **cabeceras**: cabeceras de respuesta HTTP el por el servidor. (Objeto)
+
+ * Actualmente compatible con iOS solamente.
+
+### iOS rarezas
+
+* No es compatible con `responseCode` o`bytesSent`.
+
+## descargar
+
+**Parámetros**:
+
+* **fuente**: dirección URL del servidor para descargar el archivo, como codificada por`encodeURI()`.
+
+* **objetivo**: Filesystem url que representa el archivo en el dispositivo. Para atrás compatibilidad, esto también puede ser la ruta de acceso completa del archivo en el dispositivo. (Ver [hacia atrás compatibilidad notas] debajo)
+
+* **successCallback**: una devolución de llamada que se pasa un `FileEntry` objeto. *(Función)*
+
+* **errorCallback**: una devolución de llamada que se ejecuta si se produce un error al recuperar los `Metadata` . Invocado con un `FileTransferError` objeto. *(Función)*
+
+* **trustAllHosts**: parámetro opcional, por defecto es `false` . Si establece en `true` , acepta todos los certificados de seguridad. Esto es útil porque Android rechaza certificados autofirmados seguridad. No se recomienda para uso productivo. Compatible con iOS y Android. *(boolean)*
+
+* **Opciones**: parámetros opcionales, actualmente sólo soporta cabeceras (como autorización (autenticación básica), etc.).
+
+### Ejemplo
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## abortar
+
+Aborta a una transferencia en curso. El callback onerror se pasa un objeto FileTransferError que tiene un código de error de FileTransferError.ABORT_ERR.
+
+### Ejemplo
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` objeto se pasa a un callback de error cuando se produce un error.
+
+### Propiedades
+
+* **código**: uno de los códigos de error predefinido enumerados a continuación. (Número)
+
+* **fuente**: URL a la fuente. (String)
+
+* **objetivo**: URL a la meta. (String)
+
+* **HTTP_STATUS**: código de estado HTTP. Este atributo sólo está disponible cuando se recibe un código de respuesta de la conexión HTTP. (Número)
+
+* **excepción**: cualquier e.getMessage o e.toString (String)
+
+### Constantes
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## Al revés notas de compatibilidad
+
+Versiones anteriores de este plugin sólo aceptaría dispositivo-absoluto-archivo-rutas como la fuente de carga, o como destino para las descargas. Estos caminos normalmente sería de la forma
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Para atrás compatibilidad, estos caminos son aceptados todavía, y si su solicitud ha grabado caminos como éstos en almacenamiento persistente, entonces pueden seguir utilizarse.
+
+Estos caminos fueron expuestos anteriormente en el `fullPath` propiedad de `FileEntry` y `DirectoryEntry` objetos devueltos por el plugin de archivo. Las nuevas versiones del archivo plugin, sin embargo, ya no exponen estos caminos a JavaScript.
+
+Si va a actualizar a una nueva (1.0.0 o más reciente) versión del archivo y previamente han estado utilizando `entry.fullPath` como argumentos para `download()` o `upload()` , entonces tendrá que cambiar su código para usar URLs de sistema de archivos en su lugar.
+
+`FileEntry.toURL()`y `DirectoryEntry.toURL()` devolver un filesystem dirección URL de la forma
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+que puede ser utilizado en lugar de la ruta del archivo absoluta tanto en `download()` y `upload()` los métodos.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/doc/fr/index.md b/plugins/org.apache.cordova.file-transfer/doc/fr/index.md
new file mode 100644
index 0000000..6144648
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/fr/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+Ce plugin vous permet de télécharger des fichiers.
+
+## Installation
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## Plates-formes prises en charge
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Firefox OS **
+* iOS
+* Windows Phone 7 et 8 *
+* Windows 8 ***|
+* Windows ***|
+
+* *Ne supportent pas `onprogress` ni `abort()` *
+
+** *Ne prennent pas en charge `onprogress` *
+
+Prise en charge partielle des `onprogress` pour télécharger méthode. `onprogress` est appelée avec l'événement de progression vide à cause de Windows limitations_
+
+# Transfert de fichiers
+
+Le `FileTransfer` objet fournit un moyen de télécharger des fichiers à l'aide d'une requête HTTP de la poste plusieurs partie et pour télécharger des fichiers aussi bien.
+
+## Propriétés
+
+* **onprogress** : fonction appelée avec un `ProgressEvent` à chaque fois qu'un nouveau segment de données est transféré. *(Function)*
+
+## Méthodes
+
+* **upload** : envoie un fichier à un serveur.
+
+* **download** : télécharge un fichier depuis un serveur.
+
+* **abort** : annule le transfert en cours.
+
+## upload
+
+**Paramètres**:
+
+* **fileURL** : système de fichiers URL représentant le fichier sur le périphérique. Pour la compatibilité ascendante, cela peut aussi être le chemin complet du fichier sur le périphérique. (Voir [Backwards Compatibility Notes] ci-dessous)
+
+* **server** : l'URL du serveur destiné à recevoir le fichier, encodée via `encodeURI()`.
+
+* **successCallback**: un callback passé à un objet `Metadata`. *(Fonction)*
+
+* **errorCallback** : callback d'erreur s'exécutant si une erreur survient lors de la récupération de l'objet `Metadata` . Appelée avec un objet `FileTransferError`. *(Function)*
+
+* **options**: paramètres facultatifs *(objet)*. Clés valides :
+
+ * **fileKey**: le nom de l'élément form. Valeur par défaut est `file` . (DOMString)
+ * **fileName**: le nom de fichier à utiliser lorsque vous enregistrez le fichier sur le serveur. Valeur par défaut est `image.jpg` . (DOMString)
+ * **type MIME**: le type mime des données à télécharger. Valeur par défaut est `image/jpeg` . (DOMString)
+ * **params**: un ensemble de paires clé/valeur facultative pour passer dans la requête HTTP. (Objet)
+ * **chunkedMode**: s'il faut télécharger les données en mode streaming mémorisé en bloc. Valeur par défaut est `true` . (Boolean)
+ * **en-têtes**: une carte des valeurs d'en-tête en-tête/nom. Un tableau permet de spécifier plusieurs valeurs. (Objet)
+
+* **trustAllHosts**: paramètre facultatif, valeur par défaut est `false` . Si la valeur `true` , il accepte tous les certificats de sécurité. Ceci est utile car Android rejette des certificats auto-signés. Non recommandé pour une utilisation de production. Supporté sur Android et iOS. *(boolean)*
+
+### Exemple
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### Exemple avec téléchargement du Header et des Progress Events (Android et iOS uniquement)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+A `FileUploadResult` objet est passé au rappel de succès la `FileTransfer` de l'objet `upload()` méthode.
+
+### Propriétés
+
+* **bytesSent** : le nombre d'octets envoyés au serveur dans le cadre du téléchargement. (long)
+
+* **responseCode** : le code de réponse HTTP retourné par le serveur. (long)
+
+* **response** : la réponse HTTP renvoyée par le serveur. (DOMString)
+
+* **en-têtes** : en-têtes de réponse HTTP par le serveur. (Objet)
+
+ * Actuellement pris en charge sur iOS seulement.
+
+### iOS Remarques
+
+* Ne prend pas en charge les propriétés `responseCode` et `bytesSent`.
+
+## download
+
+**Paramètres**:
+
+* **source** : l'URL du serveur depuis lequel télécharger le fichier, encodée via `encodeURI()`.
+
+* **target** : système de fichiers url représentant le fichier sur le périphérique. Pour vers l'arrière la compatibilité, cela peut aussi être le chemin d'accès complet du fichier sur le périphérique. (Voir [vers l'arrière compatibilité note] ci-dessous)
+
+* **successCallback** : une callback de succès à laquelle est passée un objet `FileEntry`. *(Function)*
+
+* **errorCallback** : une callback d'erreur s'exécutant si une erreur se produit lors de la récupération de l'objet `Metadata`. Appelée avec un objet `FileTransferError`. *(Function)*
+
+* **trustAllHosts**: paramètre facultatif, valeur par défaut est `false` . Si la valeur est `true` , il accepte tous les certificats de sécurité. Ceci peut être utile car Android rejette les certificats auto-signés. N'est pas recommandé pour une utilisation en production. Supporté sur Android et iOS. *(booléen)*
+
+* **options** : paramètres facultatifs, seules les en-têtes sont actuellement supportées (par exemple l'autorisation (authentification basique), etc.).
+
+### Exemple
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## abort
+
+Abandonne un transfert en cours. Le rappel onerror est passé à un objet FileTransferError qui a un code d'erreur de FileTransferError.ABORT_ERR.
+
+### Exemple
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` objet est passé à un rappel d'erreur lorsqu'une erreur survient.
+
+### Propriétés
+
+* **code** : l'un des codes d'erreur prédéfinis énumérés ci-dessous. (Number)
+
+* **source** : l'URI de la source. (String)
+
+* **target**: l'URI de la destination. (String)
+
+* **http_status** : code d'état HTTP. Cet attribut n'est disponible que lorsqu'un code de réponse est fourni via la connexion HTTP. (Number)
+
+* **exception**: soit e.getMessage ou e.toString (String)
+
+### Constantes
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## Backwards Compatibility Notes
+
+Les versions précédentes de ce plugin n'accepterait périphérique--fichier-chemins d'accès absolus comme source pour les téléchargements, ou comme cible pour les téléchargements. Ces chemins seraient généralement de la forme
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Pour vers l'arrière la compatibilité, ces chemins sont toujours acceptés, et si votre application a enregistré des chemins comme celles-ci dans un stockage persistant, alors ils peuvent continuer à être utilisé.
+
+Ces chemins ont été précédemment exposés dans le `fullPath` propriété de `FileEntry` et `DirectoryEntry` les objets retournés par le fichier plugin. Nouvelles versions du fichier plugin, cependant, ne plus exposent ces chemins à JavaScript.
+
+Si vous migrez vers une nouvelle (1.0.0 ou plus récent) version de fichier et vous avez précédemment utilisé `entry.fullPath` comme arguments à `download()` ou `upload()` , alors vous aurez besoin de modifier votre code pour utiliser le système de fichiers URL au lieu de cela.
+
+`FileEntry.toURL()`et `DirectoryEntry.toURL()` retournent une URL de système de fichiers du formulaire
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+qui peut être utilisé à la place le chemin d'accès absolu au fichier dans les deux `download()` et `upload()` méthodes.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/doc/index.md b/plugins/org.apache.cordova.file-transfer/doc/index.md
new file mode 100644
index 0000000..81710b1
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/index.md
@@ -0,0 +1,296 @@
+
+
+# org.apache.cordova.file-transfer
+
+This plugin allows you to upload and download files.
+
+This plugin defines global `FileTransfer`, `FileUploadOptions` Constructors.
+
+Although in the global scope, they are not available until after the `deviceready` event.
+
+ document.addEventListener("deviceready", onDeviceReady, false);
+ function onDeviceReady() {
+ console.log(FileTransfer);
+ }
+
+## Installation
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+## Supported Platforms
+
+- Amazon Fire OS
+- Android
+- BlackBerry 10
+- Firefox OS**
+- iOS
+- Windows Phone 7 and 8*
+- Windows 8
+- Windows
+
+\* _Do not support `onprogress` nor `abort()`_
+
+\** _Do not support `onprogress`_
+
+# FileTransfer
+
+The `FileTransfer` object provides a way to upload files using an HTTP
+multi-part POST request, and to download files as well.
+
+## Properties
+
+- __onprogress__: Called with a `ProgressEvent` whenever a new chunk of data is transferred. _(Function)_
+
+## Methods
+
+- __upload__: sends a file to a server.
+
+- __download__: downloads a file from server.
+
+- __abort__: Aborts an in-progress transfer.
+
+
+## upload
+
+__Parameters__:
+
+- __fileURL__: Filesystem URL representing the file on the device. For backwards compatibility, this can also be the full path of the file on the device. (See [Backwards Compatibility Notes] below)
+
+- __server__: URL of the server to receive the file, as encoded by `encodeURI()`.
+
+- __successCallback__: A callback that is passed a `FileUploadResult` object. _(Function)_
+
+- __errorCallback__: A callback that executes if an error occurs retrieving the `FileUploadResult`. Invoked with a `FileTransferError` object. _(Function)_
+
+- __options__: Optional parameters _(Object)_. Valid keys:
+ - __fileKey__: The name of the form element. Defaults to `file`. (DOMString)
+ - __fileName__: The file name to use when saving the file on the server. Defaults to `image.jpg`. (DOMString)
+ - __httpMethod__: The HTTP method to use - either `PUT` or `POST`. Defaults to `POST`. (DOMString)
+ - __mimeType__: The mime type of the data to upload. Defaults to `image/jpeg`. (DOMString)
+ - __params__: A set of optional key/value pairs to pass in the HTTP request. (Object)
+ - __chunkedMode__: Whether to upload the data in chunked streaming mode. Defaults to `true`. (Boolean)
+ - __headers__: A map of header name/header values. Use an array to specify more than one value. (Object)
+
+- __trustAllHosts__: Optional parameter, defaults to `false`. If set to `true`, it accepts all security certificates. This is useful since Android rejects self-signed security certificates. Not recommended for production use. Supported on Android and iOS. _(boolean)_
+
+### Example
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+### Example with Upload Headers and Progress Events (Android and iOS only)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+## FileUploadResult
+
+A `FileUploadResult` object is passed to the success callback of the
+`FileTransfer` object's `upload()` method.
+
+### Properties
+
+- __bytesSent__: The number of bytes sent to the server as part of the upload. (long)
+
+- __responseCode__: The HTTP response code returned by the server. (long)
+
+- __response__: The HTTP response returned by the server. (DOMString)
+
+- __headers__: The HTTP response headers by the server. (Object)
+ - Currently supported on iOS only.
+
+### iOS Quirks
+
+- Does not support `responseCode` or `bytesSent`.
+
+
+## download
+
+__Parameters__:
+
+- __source__: URL of the server to download the file, as encoded by `encodeURI()`.
+
+- __target__: Filesystem url representing the file on the device. For backwards compatibility, this can also be the full path of the file on the device. (See [Backwards Compatibility Notes] below)
+
+- __successCallback__: A callback that is passed a `FileEntry` object. _(Function)_
+
+- __errorCallback__: A callback that executes if an error occurs when retrieving the `FileEntry`. Invoked with a `FileTransferError` object. _(Function)_
+
+- __trustAllHosts__: Optional parameter, defaults to `false`. If set to `true`, it accepts all security certificates. This is useful because Android rejects self-signed security certificates. Not recommended for production use. Supported on Android and iOS. _(boolean)_
+
+- __options__: Optional parameters, currently only supports headers (such as Authorization (Basic Authentication), etc).
+
+### Example
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+## abort
+
+Aborts an in-progress transfer. The onerror callback is passed a FileTransferError object which has an error code of FileTransferError.ABORT_ERR.
+
+### Example
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` object is passed to an error callback when an error occurs.
+
+### Properties
+
+- __code__: One of the predefined error codes listed below. (Number)
+
+- __source__: URL to the source. (String)
+
+- __target__: URL to the target. (String)
+
+- __http_status__: HTTP status code. This attribute is only available when a response code is received from the HTTP connection. (Number)
+
+- __body__ Response body. This attribute is only available when a response is received from the HTTP connection. (String)
+
+- __exception__: Either e.getMessage or e.toString (String)
+
+### Constants
+
+- 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+- 2 = `FileTransferError.INVALID_URL_ERR`
+- 3 = `FileTransferError.CONNECTION_ERR`
+- 4 = `FileTransferError.ABORT_ERR`
+- 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## Backwards Compatibility Notes
+
+Previous versions of this plugin would only accept device-absolute-file-paths as the source for uploads, or as the target for downloads. These paths would typically be of the form
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+For backwards compatibility, these paths are still accepted, and if your application has recorded paths like these in persistent storage, then they can continue to be used.
+
+These paths were previously exposed in the `fullPath` property of `FileEntry` and `DirectoryEntry` objects returned by the File plugin. New versions of the File plugin, however, no longer expose these paths to JavaScript.
+
+If you are upgrading to a new (1.0.0 or newer) version of File, and you have previously been using `entry.fullPath` as arguments to `download()` or `upload()`, then you will need to change your code to use filesystem URLs instead.
+
+`FileEntry.toURL()` and `DirectoryEntry.toURL()` return a filesystem URL of the form
+
+ cdvfile://localhost/persistent/path/to/file
+
+which can be used in place of the absolute file path in both `download()` and `upload()` methods.
diff --git a/plugins/org.apache.cordova.file-transfer/doc/it/index.md b/plugins/org.apache.cordova.file-transfer/doc/it/index.md
new file mode 100644
index 0000000..dbd2625
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/it/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+Questo plugin permette di caricare e scaricare file.
+
+## Installazione
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## Piattaforme supportate
+
+* Amazon fuoco OS
+* Android
+* BlackBerry 10
+* Firefox OS * *
+* iOS
+* Windows Phone 7 e 8 *
+* Windows 8 * * *|
+* Windows * * *|
+
+* *Non supportano `onprogress` né `abort()` *
+
+* * *Non supportano `onprogress` *
+
+Supporto parziale di `onprogress` per caricare metodo. `onprogress` viene chiamato con evento progress vuota a causa di Windows limitations_
+
+# FileTransfer
+
+Il `FileTransfer` oggetto fornisce un modo per caricare i file utilizzando una richiesta HTTP di POST più parte e scaricare file pure.
+
+## Proprietà
+
+* **OnProgress**: chiamata con un `ProgressEvent` ogni volta che un nuovo blocco di dati viene trasferito. *(Funzione)*
+
+## Metodi
+
+* **caricare**: invia un file a un server.
+
+* **Scarica**: Scarica un file dal server.
+
+* **Abort**: interrompe un trasferimento in corso.
+
+## caricare
+
+**Parametri**:
+
+* **fileURL**: Filesystem URL che rappresenta il file nel dispositivo. Per indietro la compatibilità, questo può anche essere il percorso completo del file sul dispositivo. (Vedere [indietro compatibilità rileva] qui sotto)
+
+* **server**: URL del server per ricevere il file, come codificato dal`encodeURI()`.
+
+* **successCallback**: un callback passato un `Metadata` oggetto. *(Funzione)*
+
+* **errorCallback**: un callback che viene eseguito se si verifica un errore recuperando il `Metadata` . Invocato con un `FileTransferError` oggetto. *(Funzione)*
+
+* **opzioni**: parametri facoltativi *(oggetto)*. Chiavi valide:
+
+ * **fileKey**: il nome dell'elemento form. Valore predefinito è `file` . (DOMString)
+ * **nome file**: il nome del file da utilizzare quando si salva il file sul server. Valore predefinito è `image.jpg` . (DOMString)
+ * **mimeType**: il tipo mime dei dati da caricare. Valore predefinito è `image/jpeg` . (DOMString)
+ * **params**: un insieme di coppie chiave/valore opzionale per passare nella richiesta HTTP. (Oggetto)
+ * **chunkedMode**: se a caricare i dati in modalità streaming chunked. Valore predefinito è `true` . (Boolean)
+ * **intestazioni**: mappa di valori nome/intestazione intestazione. Utilizzare una matrice per specificare più valori. (Oggetto)
+
+* **trustAllHosts**: parametro opzionale, valore predefinito è `false` . Se impostata su `true` , accetta tutti i certificati di sicurezza. Questo è utile poiché Android respinge i certificati autofirmati sicurezza. Non raccomandato per uso in produzione. Supportato su Android e iOS. *(boolean)*
+
+### Esempio
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### Esempio con intestazioni di caricare ed eventi Progress (Android e iOS solo)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+A `FileUploadResult` oggetto viene passato al metodo di callback di successo il `FileTransfer` dell'oggetto `upload()` metodo.
+
+### Proprietà
+
+* **bytesSent**: il numero di byte inviati al server come parte dell'upload. (lungo)
+
+* **responseCode**: codice di risposta HTTP restituito dal server. (lungo)
+
+* **risposta**: risposta HTTP restituito dal server. (DOMString)
+
+* **intestazioni**: intestazioni di risposta HTTP dal server. (Oggetto)
+
+ * Attualmente supportato solo iOS.
+
+### iOS stranezze
+
+* Non supporta `responseCode` o`bytesSent`.
+
+## Scarica
+
+**Parametri**:
+
+* **fonte**: URL del server per scaricare il file, come codificato dal`encodeURI()`.
+
+* **destinazione**: Filesystem url che rappresenta il file nel dispositivo. Per indietro la compatibilità, questo può anche essere il percorso completo del file sul dispositivo. (Vedere [indietro compatibilità rileva] qui sotto)
+
+* **successCallback**: un callback passato un `FileEntry` oggetto. *(Funzione)*
+
+* **errorCallback**: un callback che viene eseguito se si verifica un errore durante il recupero del `Metadata` . Invocato con un `FileTransferError` oggetto. *(Funzione)*
+
+* **trustAllHosts**: parametro opzionale, valore predefinito è `false` . Se impostata su `true` , accetta tutti i certificati di sicurezza. Questo è utile perché Android respinge i certificati autofirmati sicurezza. Non raccomandato per uso in produzione. Supportato su Android e iOS. *(boolean)*
+
+* **opzioni**: parametri facoltativi, attualmente solo supporti intestazioni (ad esempio autorizzazione (autenticazione di base), ecc.).
+
+### Esempio
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## Abort
+
+Interrompe un trasferimento in corso. Il callback onerror viene passato un oggetto FileTransferError che presenta un codice di errore di FileTransferError.ABORT_ERR.
+
+### Esempio
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` oggetto viene passato a un callback di errore quando si verifica un errore.
+
+### Proprietà
+
+* **codice**: uno dei codici di errore predefiniti elencati di seguito. (Numero)
+
+* **fonte**: URL all'origine. (String)
+
+* **destinazione**: URL di destinazione. (String)
+
+* **http_status**: codice di stato HTTP. Questo attributo è disponibile solo quando viene ricevuto un codice di risposta della connessione HTTP. (Numero)
+
+* **eccezione**: O e.getMessage o e.toString (String)
+
+### Costanti
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## Note di compatibilità all'indietro
+
+Versioni precedenti di questo plugin accetterebbe solo dispositivo-assoluto-percorsi di file come origine per upload, o come destinazione per il download. Questi percorsi si sarebbero generalmente di forma
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Per indietro compatibilità, questi percorsi sono ancora accettati, e se l'applicazione ha registrato percorsi come questi in un archivio permanente, quindi possono continuare a essere utilizzato.
+
+Questi percorsi sono state precedentemente esposte nella `fullPath` proprietà di `FileEntry` e `DirectoryEntry` oggetti restituiti dal File plugin. Nuove versioni del File plugin, tuttavia, non è più espongono questi percorsi a JavaScript.
+
+Se si esegue l'aggiornamento a una nuova (1.0.0 o più recente) precedentemente utilizzano la versione del File e si `entry.fullPath` come argomenti a `download()` o `upload()` , sarà necessario modificare il codice per utilizzare gli URL filesystem invece.
+
+`FileEntry.toURL()`e `DirectoryEntry.toURL()` restituiscono un filesystem URL del modulo
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+che può essere utilizzato al posto del percorso assoluto in entrambi `download()` e `upload()` metodi.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/doc/ja/index.md b/plugins/org.apache.cordova.file-transfer/doc/ja/index.md
new file mode 100644
index 0000000..49cd44e
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/ja/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+このプラグインは、アップロードし、ファイルをダウンロードすることができます。
+
+## インストール
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## サポートされているプラットフォーム
+
+* アマゾン火 OS
+* アンドロイド
+* ブラックベリー 10
+* Firefox の OS * *
+* iOS
+* Windows Phone 7 と 8 *
+* Windows 8 * * *|
+* Windows * * *|
+
+**サポートしていない `onprogress` も `abort()` *
+
+* **サポートしていない `onprogress` *
+
+部分的なサポート `onprogress` のアップロード方法 `onprogress` は Windows limitations_ のための空のプログレスのイベントで呼び出されます。
+
+# ファイル転送
+
+`FileTransfer`オブジェクトはマルチパートのポスト、HTTP 要求を使用してファイルをアップロードして同様にファイルをダウンロードする方法を提供します。
+
+## プロパティ
+
+* **onprogress**: と呼ばれる、 `ProgressEvent` データの新しいチャンクが転送されるたびに。*(機能)*
+
+## メソッド
+
+* **アップロード**: サーバーにファイルを送信します。
+
+* **ダウンロード**: サーバーからファイルをダウンロードします。
+
+* **中止**: 進行中の転送を中止します。
+
+## アップロード
+
+**パラメーター**:
+
+* **fileURL**: デバイス上のファイルを表すファイルシステム URL。 下位互換性は、このことも、デバイス上のファイルの完全パスであります。 (参照してください [後方互換性メモ] の下)
+
+* **サーバー**: によって符号化されるように、ファイルを受信するサーバーの URL`encodeURI()`.
+
+* **successCallback**: 渡されたコールバックを `Metadata` オブジェクト。*(機能)*
+
+* **解り**: エラー取得が発生した場合に実行されるコールバック、 `Metadata` 。呼び出されると、 `FileTransferError` オブジェクト。*(機能)*
+
+* **オプション**: 省略可能なパラメーター *(オブジェクト)*。有効なキー:
+
+ * **fileKey**: フォーム要素の名前。既定値は `file` です。(,)
+ * **ファイル名**: ファイル名、サーバー上のファイルを保存するときに使用します。既定値は `image.jpg` です。(,)
+ * **mime タイプ**: アップロードするデータの mime タイプ。既定値は `image/jpeg` です。(,)
+ * **params**: HTTP リクエストに渡すために任意のキー/値ペアのセット。(オブジェクト)
+ * **chunkedMode**: チャンク ストリーミング モードでデータをアップロードするかどうか。既定値は `true` です。(ブール値)
+ * **ヘッダー**: ヘッダーの名前/ヘッダー値のマップ。1 つ以上の値を指定するには、配列を使用します。(オブジェクト)
+
+* **trustAllHosts**: 省略可能なパラメーターは、デフォルト `false` 。 場合設定 `true` 、セキュリティ証明書をすべて受け付けます。 これは Android の自己署名入りセキュリティ証明書を拒否するので便利です。 運用環境で使用しないでください。 Android と iOS でサポートされています。 *(ブール値)*
+
+### 例
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### サンプルのアップロード ヘッダーと進行状況のイベント (Android と iOS のみ)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+A `FileUploadResult` オブジェクトの成功時のコールバックに渡される、 `FileTransfer` オブジェクトの `upload()` メソッド。
+
+### プロパティ
+
+* **bytesSent**: アップロードの一部としてサーバーに送信されたバイト数。(ロング)
+
+* **記述**: サーバーによって返される HTTP 応答コード。(ロング)
+
+* **応答**: サーバーによって返される HTTP 応答。(,)
+
+* **ヘッダー**: HTTP 応答ヘッダー サーバーによって。(オブジェクト)
+
+ * 現在 iOS のみでサポートされます。
+
+### iOS の癖
+
+* サポートしていない `responseCode` または`bytesSent`.
+
+## ダウンロード
+
+**パラメーター**:
+
+* **ソース**: によって符号化されるように、ファイルをダウンロードするサーバーの URL`encodeURI()`.
+
+* **ターゲット**: デバイス上のファイルを表すファイルシステム url。 下位互換性は、このことも、デバイス上のファイルの完全パスであります。 (参照してください [後方互換性メモ] の下)
+
+* **successCallback**: 渡されたコールバックを `FileEntry` オブジェクト。*(機能)*
+
+* **解り**: コールバックを取得するときにエラーが発生した場合に実行される、 `Metadata` 。呼び出されると、 `FileTransferError` オブジェクト。*(機能)*
+
+* **trustAllHosts**: 省略可能なパラメーターは、デフォルト `false` 。 場合設定 `true` 、セキュリティ証明書をすべて受け付けます。 Android は、自己署名入りセキュリティ証明書を拒否しますので便利です。 運用環境で使用しないでください。 Android と iOS でサポートされています。 *(ブール値)*
+
+* **オプション**: 省略可能なパラメーターは、現在サポートするヘッダーのみ (認証 (基本認証) など)。
+
+### 例
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## 中止
+
+進行中の転送を中止します。Onerror コールバックが FileTransferError.ABORT_ERR のエラー コードを持っている FileTransferError オブジェクトに渡されます。
+
+### 例
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` オブジェクトは、エラーが発生エラー コールバックに渡されます。
+
+### プロパティ
+
+* **コード**: 次のいずれかの定義済みのエラー コード。(数)
+
+* **ソース**: ソースの URL。(文字列)
+
+* **ターゲット**: 先の URL。(文字列)
+
+* **http_status**: HTTP ステータス コード。この属性は、HTTP 接続から応答コードを受信したときにのみ使用できます。(数)
+
+* **例外**: どちらか e.getMessage または e.toString (文字列)
+
+### 定数
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## 後方互換性をノートします。
+
+このプラグインの以前のバージョンまたはダウンロードのターゲットとして、アップロードのソースとしてのみデバイス絶対ファイル パスを受け入れるでしょう。これらのパスの形式は、通常
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+下位互換性、これらのパスを使用しても、アプリケーションは、永続的なストレージでこのようなパスを記録している場合、し彼らが引き続き使用されます。
+
+これらのパスに公開されていなかった、 `fullPath` のプロパティ `FileEntry` および `DirectoryEntry` ファイル プラグインによって返されるオブジェクト。 新しいプラグインのバージョン、ファイル、ただし、もはや java スクリプトの設定をこれらのパスを公開します。
+
+新しいにアップグレードする場合 (1.0.0 以降) ファイルのバージョンが以前を使用して `entry.fullPath` への引数として `download()` または `upload()` 、ファイルシステムの Url を代わりに使用するコードを変更する必要があります。
+
+`FileEntry.toURL()``DirectoryEntry.toURL()`フォームのファイルシステムの URL を返す
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+両方のファイルの絶対パスの代わりに使用できる `download()` および `upload()` メソッド。
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/doc/ko/index.md b/plugins/org.apache.cordova.file-transfer/doc/ko/index.md
new file mode 100644
index 0000000..1289567
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/ko/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+이 플러그인을 사용 하면 업로드 및 다운로드 파일 수 있습니다.
+
+## 설치
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## 지원 되는 플랫폼
+
+* 아마존 화재 운영 체제
+* 안 드 로이드
+* 블랙베리 10
+* 파이어 폭스 OS * *
+* iOS
+* Windows Phone 7과 8 *
+* 윈도우 8 * * *|
+* 윈도 즈 * * *|
+
+* *를 지원 하지 않는 `onprogress` 도 `abort()` *
+
+* * *를 지원 하지 않는 `onprogress` *
+
+부분적인 지원 `onprogress` 업로드 방법. `onprogress` Windows limitations_로 인해 빈 진행률 이벤트 호출
+
+# FileTransfer
+
+`FileTransfer`개체는 HTTP 다중 파트 POST 요청을 사용 하 여 파일을 업로드 하 고 뿐만 아니라 파일을 다운로드 하는 방법을 제공 합니다.
+
+## 속성
+
+* **onprogress**:로 불리는 `ProgressEvent` 새로운 양의 데이터를 전송 하는 때마다. *(기능)*
+
+## 메서드
+
+* **업로드**: 파일을 서버에 보냅니다.
+
+* **다운로드**: 서버에서 파일을 다운로드 합니다.
+
+* **중단**: 진행 중인 전송 중단.
+
+## 업로드
+
+**매개 변수**:
+
+* **fileURL**: 장치에 파일을 나타내는 파일 시스템 URL. 에 대 한 이전 버전과 호환성을이 수도 장치에 있는 파일의 전체 경로 수. (참조 [거꾸로 호환성 노트] 아래)
+
+* **서버**: 인코딩 파일 수신 서버의 URL`encodeURI()`.
+
+* **successCallback**: 콜백 전달 되는 `Metadata` 개체. *(기능)*
+
+* **errorCallback**: 콜백 검색에 오류가 발생 하면 실행 되는 `Metadata` . 로 호출을 `FileTransferError` 개체. *(기능)*
+
+* **옵션**: 선택적 매개 변수 *(개체)*. 유효한 키:
+
+ * **fileKey**: form 요소의 이름. 기본값은 `file` . (DOMString)
+ * **파일 이름**: 파일 이름을 서버에 파일을 저장할 때 사용 합니다. 기본값은 `image.jpg` . (DOMString)
+ * **mimeType**: 업로드 데이터의 mime 형식을. 기본값은 `image/jpeg` . (DOMString)
+ * **params**: HTTP 요청에 전달할 선택적 키/값 쌍의 집합. (개체)
+ * **chunkedMode**: 청크 스트리밍 모드에서 데이터 업로드를 합니다. 기본값은 `true` . (부울)
+ * **헤더**: 헤더 이름/헤더 값의 지도. 배열을 사용 하 여 하나 이상의 값을 지정 합니다. (개체)
+
+* **trustAllHosts**: 선택적 매개 변수는 기본적으로 `false` . 만약 설정 `true` , 그것은 모든 보안 인증서를 허용 합니다. 이 안 드 로이드 자체 서명 된 보안 인증서를 거부 하기 때문에 유용 합니다. 프로덕션 환경에서 사용 권장 되지 않습니다. 안 드 로이드와 iOS에서 지원. *(부울)*
+
+### 예를 들어
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### 예를 들어 헤더 업로드 및 진행 이벤트 (안 드 로이드와 iOS만)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+A `FileUploadResult` 개체의 성공 콜백에 전달 되는 `FileTransfer` 개체의 `upload()` 메서드.
+
+### 속성
+
+* **bytesSent**: 업로드의 일부로 서버에 보낸 바이트 수. (긴)
+
+* **responseCode**: 서버에서 반환 된 HTTP 응답 코드. (긴)
+
+* **응답**: 서버에서 반환 되는 HTTP 응답. (DOMString)
+
+* **머리글**: 서버에서 HTTP 응답 헤더. (개체)
+
+ * 현재 ios만 지원 합니다.
+
+### iOS 단점
+
+* 지원 하지 않는 `responseCode` 또는`bytesSent`.
+
+## 다운로드
+
+**매개 변수**:
+
+* **소스**: URL로 인코딩된 파일, 다운로드 서버`encodeURI()`.
+
+* **대상**: 장치에 파일을 나타내는 파일 시스템 url. 에 대 한 이전 버전과 호환성을이 수도 장치에 있는 파일의 전체 경로 수. (참조 [거꾸로 호환성 노트] 아래)
+
+* **successCallback**: 콜백 전달 되는 `FileEntry` 개체. *(기능)*
+
+* **errorCallback**: 콜백 검색할 때 오류가 발생 하면 실행 되는 `Metadata` . 로 호출을 `FileTransferError` 개체. *(기능)*
+
+* **trustAllHosts**: 선택적 매개 변수는 기본적으로 `false` . 만약 설정 `true` , 그것은 모든 보안 인증서를 허용 합니다. 안 드 로이드 자체 서명 된 보안 인증서를 거부 하기 때문에 유용 합니다. 프로덕션 환경에서 사용 권장 되지 않습니다. 안 드 로이드와 iOS에서 지원. *(부울)*
+
+* **옵션**: 선택적 매개 변수를 현재 지 원하는 머리글만 (예: 인증 (기본 인증), 등).
+
+### 예를 들어
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## 중단
+
+진행 중인 전송을 중단합니다. Onerror 콜백 FileTransferError.ABORT_ERR의 오류 코드는 FileTransferError 개체를 전달 합니다.
+
+### 예를 들어
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` 오류가 발생 하면 오류 콜백 개체 전달 됩니다.
+
+### 속성
+
+* **코드**: 미리 정의 된 오류 코드 중 하나가 아래에 나열 된. (수)
+
+* **소스**: 소스 URL. (문자열)
+
+* **대상**: 대상 URL. (문자열)
+
+* **http_status**: HTTP 상태 코드. 이 특성은 응답 코드를 HTTP 연결에서 수신에 사용할 수 있습니다. (수)
+
+* **예외**: 어느 e.getMessage 또는 e.toString (문자열)
+
+### 상수
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## 이전 버전과 호환성 노트
+
+이 플러그인의 이전 버전만 업로드에 대 한 소스 또는 다운로드에 대 한 대상 장치 절대 파일 경로 받아들일 것 이다. 이러한 경로 일반적으로 폼의 것
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+대 한 뒤 호환성, 이러한 경로 여전히 허용, 그리고 응용 프로그램이 영구 저장소에서 이와 같은 경로 기록 했다, 그때 그들은 계속할 수 있다면 사용할 수.
+
+이 경로에 노출 되었던는 `fullPath` 속성의 `FileEntry` 및 `DirectoryEntry` 파일 플러그인에 의해 반환 된 개체. 그러나 파일 플러그인의,, 더 이상 새로운 버전 자바 스크립트이 경로 노출.
+
+새로 업그레이드 하는 경우 (1.0.0 이상) 파일의 버전을 사용 하고있다 이전 `entry.fullPath` 인수로 `download()` 또는 `upload()` , 다음 대신 파일 시스템 Url을 사용 하 여 코드를 변경 해야 합니다.
+
+`FileEntry.toURL()`그리고 `DirectoryEntry.toURL()` 폼의 파일 URL을 반환
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+둘 다에서 절대 파일 경로 대신 사용할 수 있는 `download()` 및 `upload()` 메서드.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/doc/pl/index.md b/plugins/org.apache.cordova.file-transfer/doc/pl/index.md
new file mode 100644
index 0000000..644a418
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/pl/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+Plugin pozwala na przesyłanie i pobieranie plików.
+
+## Instalacja
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## Obsługiwane platformy
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Firefox OS **
+* iOS
+* Windows Phone 7 i 8 *
+* Windows 8 ***|
+* Windows ***|
+
+* *Nie obsługują `onprogress` ani `abort()` *
+
+** *Nie obsługują `onprogress` *
+
+Częściowe wsparcie `onprogress` dla przesłać Metoda. `onprogress` jest wywoływana z zdarzenie progress pusty z powodu Windows limitations_
+
+# FileTransfer
+
+`FileTransfer`Obiekt zapewnia sposób wgrać pliki przy użyciu żądania HTTP wieloczęściowe POST i pobierania plików, jak również.
+
+## Właściwości
+
+* **OnProgress**: o nazwie `ProgressEvent` gdy nowy kawałek danych jest przenoszona. *(Funkcja)*
+
+## Metody
+
+* **wgraj**: wysyła plik na serwer.
+
+* **do pobrania**: pliki do pobrania pliku z serwera.
+
+* **przerwać**: przerywa w toku transferu.
+
+## upload
+
+**Parametry**:
+
+* **fileURL**: URL plików reprezentujących pliku na urządzenie. Dla wstecznej kompatybilności, to może również być pełną ścieżkę pliku na urządzenie. (Zobacz [wstecz zgodności zauważa] poniżej)
+
+* **serwer**: adres URL serwera, aby otrzymać plik, jak kodowane przez`encodeURI()`.
+
+* **successCallback**: wywołania zwrotnego, który jest przekazywany `Metadata` obiektu. *(Funkcja)*
+
+* **errorCallback**: wywołanie zwrotne, które wykonuje, jeżeli wystąpi błąd pobierania `Metadata` . Wywołany z `FileTransferError` obiektu. *(Funkcja)*
+
+* **Opcje**: parametry opcjonalne *(obiektu)*. Ważne klucze:
+
+ * **fileKey**: nazwa elementu form. Domyślnie `file` . (DOMString)
+ * **Nazwa pliku**: nazwy pliku, aby użyć podczas zapisywania pliku na serwerze. Domyślnie `image.jpg` . (DOMString)
+ * **mimeType**: Typ mime danych do przesłania. Domyślnie `image/jpeg` . (DOMString)
+ * **Parametry**: zestaw par opcjonalny klucz/wartość w żądaniu HTTP. (Obiekt)
+ * **chunkedMode**: czy przekazać dane w trybie pakietowego przesyłania strumieniowego. Domyślnie `true` . (Wartość logiczna)
+ * **nagłówki**: Mapa wartości Nazwa/nagłówka nagłówek. Aby określić więcej niż jedną wartość, należy użyć tablicę. (Obiekt)
+
+* **trustAllHosts**: parametr opcjonalny, domyślnie `false` . Jeśli zestaw `true` , to akceptuje wszystkie certyfikaty bezpieczeństwa. Jest to przydatne, ponieważ Android odrzuca Certyfikaty samopodpisane. Nie zaleca się do użytku produkcyjnego. Obsługiwane na Androida i iOS. *(wartość logiczna)*
+
+### Przykład
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### Przykład z Prześlij nagłówki i zdarzeń postępu (Android i iOS tylko)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+A `FileUploadResult` obiekt jest przekazywany do funkcji callback sukces z `FileTransfer` obiektu `upload()` Metoda.
+
+### Właściwości
+
+* **bytesSent**: liczba bajtów wysłanych do serwera jako część upload. (długie)
+
+* **responseCode**: kod odpowiedzi HTTP, zwracane przez serwer. (długie)
+
+* **odpowiedź**: HTTP odpowiedzi zwracane przez serwer. (DOMString)
+
+* **nagłówki**: nagłówki HTTP odpowiedzi przez serwer. (Obiekt)
+
+ * Obecnie obsługiwane na iOS tylko.
+
+### Dziwactwa iOS
+
+* Nie obsługuje `responseCode` lub`bytesSent`.
+
+## Pobierz za darmo
+
+**Parametry**:
+
+* **Źródło**: adres URL serwera, aby pobrać plik, jak kodowane przez`encodeURI()`.
+
+* **cel**: url plików reprezentujących pliku na urządzenie. Dla wstecznej kompatybilności, to może również być pełną ścieżkę pliku na urządzenie. (Zobacz [wstecz zgodności zauważa] poniżej)
+
+* **successCallback**: wywołania zwrotnego, który jest przekazywany `FileEntry` obiektu. *(Funkcja)*
+
+* **errorCallback**: wywołanie zwrotne, które wykonuje, jeśli wystąpi błąd podczas pobierania `Metadata` . Wywołany z `FileTransferError` obiektu. *(Funkcja)*
+
+* **trustAllHosts**: parametr opcjonalny, domyślnie `false` . Jeśli zestaw `true` , to akceptuje wszystkie certyfikaty bezpieczeństwa. Jest to przydatne, ponieważ Android odrzuca Certyfikaty samopodpisane. Nie zaleca się do użytku produkcyjnego. Obsługiwane na Androida i iOS. *(wartość logiczna)*
+
+* **Opcje**: parametry opcjonalne, obecnie tylko obsługuje nagłówki (takie jak autoryzacja (uwierzytelnianie podstawowe), itp.).
+
+### Przykład
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## przerwanie
+
+Przerywa w toku transferu. Onerror callback jest przekazywany obiekt FileTransferError, który kod błędu z FileTransferError.ABORT_ERR.
+
+### Przykład
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` obiekt jest przekazywany do wywołania zwrotnego błąd, gdy wystąpi błąd.
+
+### Właściwości
+
+* **Kod**: jeden z kodów błędów wstępnie zdefiniowanych poniżej. (Liczba)
+
+* **Źródło**: URL do źródła. (String)
+
+* **cel**: adres URL do docelowego. (String)
+
+* **HTTP_STATUS**: kod stanu HTTP. Ten atrybut jest dostępna tylko po otrzymaniu kodu odpowiedzi z połączenia HTTP. (Liczba)
+
+* **wyjątek**: albo e.getMessage lub e.toString (String)
+
+### Stałe
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## Do tyłu zgodności stwierdza
+
+Poprzednie wersje tego pluginu tylko zaakceptować urządzenia bezwzględnych ścieżek jako źródło dla przekazywania, lub w celu pobrania. Te ścieżki będzie zazwyczaj formy
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Do tyłu zgodności, akceptowane są jeszcze te ścieżki, i jeśli aplikacja nagrał ścieżki, jak te w trwałej pamięci, następnie można nadal stosować.
+
+Te ścieżki były wcześniej wystawione w `fullPath` właściwości `FileEntry` i `DirectoryEntry` obiektów zwróconych przez wtyczki pliku. Nowe wersje pliku plugin, jednak już wystawiać te ścieżki do JavaScript.
+
+Jeśli uaktualniasz nowy (1.0.0 lub nowsza) wersji pliku, a wcześniej za pomocą `entry.fullPath` jako argumenty do `download()` lub `upload()` , a następnie trzeba będzie zmienić kod aby używać adresów URL plików zamiast.
+
+`FileEntry.toURL()`i `DirectoryEntry.toURL()` zwraca adres URL plików formularza
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+które mogą być używane zamiast bezwzględna ścieżka w obu `download()` i `upload()` metody.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/doc/ru/index.md b/plugins/org.apache.cordova.file-transfer/doc/ru/index.md
new file mode 100644
index 0000000..3d75d98
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/ru/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+Этот плагин позволяет вам загружать и скачивать файлы.
+
+## Установка
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## Поддерживаемые платформы
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Firefox OS **
+* iOS
+* Windows Phone 7 и 8 *
+* Windows 8 ***|
+* Windows ***|
+
+* *Не поддерживают `onprogress` , ни `abort()` *
+
+** *Не поддерживает `onprogress` *
+
+Частичная поддержка `onprogress` для закачки метод. `onprogress` вызывается с пустой ход событий благодаря Windows limitations_
+
+# FileTransfer
+
+`FileTransfer`Объект предоставляет способ для загрузки файлов с помощью нескольких частей запроса POST HTTP и для загрузки файлов, а также.
+
+## Параметры
+
+* **OnProgress**: называется с `ProgressEvent` всякий раз, когда новый фрагмент данных передается. *(Функция)*
+
+## Методы
+
+* **добавлено**: отправляет файл на сервер.
+
+* **скачать**: Скачать файл с сервера.
+
+* **прервать**: прерывает передачу в прогресс.
+
+## загрузить
+
+**Параметры**:
+
+* **fileURL**: файловой системы URL-адрес, представляющий файл на устройстве. Для обратной совместимости, это также может быть полный путь к файлу на устройстве. (См. [обратной совместимости отмечает] ниже)
+
+* **сервер**: URL-адрес сервера, чтобы получить файл, как закодированные`encodeURI()`.
+
+* **successCallback**: обратного вызова, передаваемого `Metadata` объект. *(Функция)*
+
+* **errorCallback**: обратного вызова, который выполняется в случае получения ошибки `Metadata` . Вызываемый с `FileTransferError` объект. *(Функция)*
+
+* **опции**: необязательные параметры *(объект)*. Допустимые ключи:
+
+ * **fileKey**: имя элемента form. По умолчанию `file` . (DOMString)
+ * **имя файла**: имя файла для использования при сохранении файла на сервере. По умолчанию `image.jpg` . (DOMString)
+ * **mimeType**: mime-тип данных для загрузки. По умолчанию `image/jpeg` . (DOMString)
+ * **params**: набор пар дополнительный ключ/значение для передачи в HTTP-запросе. (Объект)
+ * **chunkedMode**: следует ли загружать данные в фрагментарности потоковом режиме. По умолчанию `true` . (Логическое значение)
+ * **заголовки**: Карта значений заголовок имя заголовка. Используйте массив для указания более одного значения. (Объект)
+
+* **trustAllHosts**: необязательный параметр, по умолчанию `false` . Если значение `true` , она принимает все сертификаты безопасности. Это полезно, поскольку Android отвергает самозаверяющие сертификаты. Не рекомендуется для использования в производстве. Поддерживается на Android и iOS. *(логическое значение)*
+
+### Пример
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### Пример с загружать заголовки и события Progress (Android и iOS только)
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+Объект `FileUploadResult` передается на успех обратного вызова метода `upload()` объекта `FileTransfer`.
+
+### Параметры
+
+* **bytesSent**: количество байт, отправленных на сервер как часть загрузки. (длинная)
+
+* **responseCode**: код ответа HTTP, возвращаемых сервером. (длинная)
+
+* **ответ**: ответ HTTP, возвращаемых сервером. (DOMString)
+
+* **заголовки**: заголовки ответов HTTP-сервером. (Объект)
+
+ * В настоящее время поддерживается только для iOS.
+
+### Особенности iOS
+
+* Не поддерживает `responseCode` или`bytesSent`.
+
+## Скачать
+
+**Параметры**:
+
+* **источник**: URL-адрес сервера для загрузки файла, как закодированные`encodeURI()`.
+
+* **Цель**: файловой системы URL-адрес, представляющий файл на устройстве. Для обратной совместимости, это также может быть полный путь к файлу на устройстве. (См. [обратной совместимости отмечает] ниже)
+
+* **successCallback**: обратного вызова, передаваемого `FileEntry` объект. *(Функция)*
+
+* **errorCallback**: обратного вызова, который выполняется, если возникает ошибка при получении `Metadata` . Вызываемый с `FileTransferError` объект. *(Функция)*
+
+* **trustAllHosts**: необязательный параметр, по умолчанию `false` . Если значение `true` , она принимает все сертификаты безопасности. Это полезно, потому что Android отвергает самозаверяющие сертификаты. Не рекомендуется для использования в производстве. Поддерживается на Android и iOS. *(логическое значение)*
+
+* **опции**: необязательные параметры, в настоящее время только поддерживает заголовки (например авторизации (базовая аутентификация) и т.д.).
+
+### Пример
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## прервать
+
+Прерывает передачу в прогресс. Onerror обратного вызова передается объект FileTransferError, который имеет код ошибки FileTransferError.ABORT_ERR.
+
+### Пример
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` при ошибке обратного вызова передается объект, при возникновении ошибки.
+
+### Параметры
+
+* **код**: один из кодов стандартных ошибок, перечисленные ниже. (Число)
+
+* **источник**: URL-адрес источника. (Строка)
+
+* **Цель**: URL-адрес к целевому объекту. (Строка)
+
+* **http_status**: код состояния HTTP. Этот атрибут доступен только при код ответа от HTTP-соединения. (Число)
+
+* **исключение**: либо e.getMessage или e.toString (строка)
+
+### Константы
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## Обратной совместимости отмечает
+
+Предыдущие версии этого плагина будет принимать только устройства Абсолют файлам как источник для закачки, или как целевых для загрузок. Обычно эти пути бы формы
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Для обратной совместимости, по-прежнему принимаются эти пути, и если ваше приложение зарегистрировано пути как в постоянное хранилище, то они могут продолжать использоваться.
+
+Эти пути ранее были видны в `fullPath` свойства `FileEntry` и `DirectoryEntry` объекты, возвращаемые файл плагина. Новые версии файла плагина, однако, не подвергать эти пути в JavaScript.
+
+Если вы переходите на новый (1.0.0 или новее) версию файла и вы ранее использовали `entry.fullPath` в качестве аргументов `download()` или `upload()` , то вам необходимо будет изменить код для использования файловой системы URL вместо.
+
+`FileEntry.toURL()`и `DirectoryEntry.toURL()` возвращает URL-адрес формы файловой системы
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+которые могут быть использованы вместо абсолютного пути в обоих `download()` и `upload()` методы.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/doc/zh/index.md b/plugins/org.apache.cordova.file-transfer/doc/zh/index.md
new file mode 100644
index 0000000..6c5aac8
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/doc/zh/index.md
@@ -0,0 +1,290 @@
+
+
+# org.apache.cordova.file-transfer
+
+這個外掛程式允許你上傳和下載檔案。
+
+## 安裝
+
+ cordova plugin add org.apache.cordova.file-transfer
+
+
+## 支援的平臺
+
+* 亞馬遜火 OS
+* Android 系統
+* 黑莓 10
+* 火狐瀏覽器作業系統 * *
+* iOS
+* Windows Phone 7 和 8 *
+* Windows 8 * * *|
+* Windows * * *|
+
+**不支援 `onprogress` ,也不 `abort()` *
+
+* **不支援 `onprogress` *
+
+部分支援的 `onprogress` 為上傳方法。 `onprogress` 被稱為與 Windows limitations_ 空白進度事件
+
+# 檔案傳輸
+
+`FileTransfer`物件提供一種方法使用 HTTP 多部分 POST 請求的檔上傳和下載檔案,以及。
+
+## 屬性
+
+* **onprogress**: 使用調用 `ProgressEvent` 每當一塊新的資料傳輸。*(函數)*
+
+## 方法
+
+* **上傳**: 將檔發送到伺服器。
+
+* **下載**: 從伺服器上下載檔案。
+
+* **中止**: 中止正在進行轉讓。
+
+## 上傳
+
+**參數**:
+
+* **fileURL**: 表示檔在設備上的檔案系統 URL。 為向後相容性,這也可以將設備上的檔的完整路徑。 (請參見 [向後相容性注意到] 下面)
+
+* **伺服器**: 伺服器以接收該檔,由編碼的 URL`encodeURI()`.
+
+* **successCallback**: 傳遞一個回檔 `Metadata` 物件。*(函數)*
+
+* **errorCallback**: 回檔的執行如果出現檢索錯誤 `Metadata` 。調用與 `FileTransferError` 物件。*(函數)*
+
+* **選項**: 可選參數*(物件)*。有效的金鑰:
+
+ * **fileKey**: 表單元素的名稱。預設值為 `file` 。() DOMString
+ * **檔案名**: 要保存在伺服器上的檔時使用的檔案名稱。預設值為 `image.jpg` 。() DOMString
+ * **mimeType**: 要上傳的資料的 mime 類型。預設值為 `image/jpeg` 。() DOMString
+ * **params**: 一組可選的鍵/值對在 HTTP 要求中傳遞。(物件)
+ * **chunkedMode**: 是否要分塊流式處理模式中的資料上載。預設值為 `true` 。(布林值)
+ * **標題**: 地圖的標頭名稱/標頭值。使用陣列來指定多個值。(物件)
+
+* **trustAllHosts**: 可選參數,預設值為 `false` 。 如果設置為 `true` ,它接受的所有安全證書。 這是有用的因為 android 系統拒絕自簽名的安全證書。 不建議供生產使用。 支援 Android 和 iOS。 *(布林值)*
+
+### 示例
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function (r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ var fail = function (error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey = "file";
+ options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
+ options.mimeType = "text/plain";
+
+ var params = {};
+ params.value1 = "test";
+ params.value2 = "param";
+
+ options.params = params;
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+
+
+### 與上傳的標頭和進度事件 (Android 和 iOS 只) 的示例
+
+ function win(r) {
+ console.log("Code = " + r.responseCode);
+ console.log("Response = " + r.response);
+ console.log("Sent = " + r.bytesSent);
+ }
+
+ function fail(error) {
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var uri = encodeURI("http://some.server.com/upload.php");
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);
+ options.mimeType="text/plain";
+
+ var headers={'headerParam':'headerValue'};
+
+ options.headers = headers;
+
+ var ft = new FileTransfer();
+ ft.onprogress = function(progressEvent) {
+ if (progressEvent.lengthComputable) {
+ loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
+ } else {
+ loadingStatus.increment();
+ }
+ };
+ ft.upload(fileURL, uri, win, fail, options);
+
+
+## FileUploadResult
+
+A `FileUploadResult` 物件傳遞給成功回檔的 `FileTransfer` 物件的 `upload()` 方法。
+
+### 屬性
+
+* **位元組發送**: 作為上載的一部分發送到伺服器的位元組數。(長)
+
+* **responseCode**: 由伺服器返回的 HTTP 回應代碼。(長)
+
+* **回應**: 由伺服器返回的 HTTP 回應。() DOMString
+
+* **標題**: 由伺服器的 HTTP 回應標頭。(物件)
+
+ * 目前支援的 iOS 只。
+
+### iOS 的怪癖
+
+* 不支援 `responseCode` 或`bytesSent`.
+
+## 下載
+
+**參數**:
+
+* **來源**: 要下載的檔,如由編碼的伺服器的 URL`encodeURI()`.
+
+* **目標**: 表示檔在設備上的檔案系統 url。 為向後相容性,這也可以將設備上的檔的完整路徑。 (請參見 [向後相容性注意到] 下面)
+
+* **successCallback**: 傳遞一個回檔 `FileEntry` 物件。*(函數)*
+
+* **errorCallback**: 如果錯誤發生在檢索時將執行的回檔 `Metadata` 。調用與 `FileTransferError` 物件。*(函數)*
+
+* **trustAllHosts**: 可選參數,預設值為 `false` 。 如果設置為 `true` ,它可以接受的所有安全證書。 這是有用的因為 Android 拒絕自行簽署式安全證書。 不建議供生產使用。 在 Android 和 iOS 上受支援。 *(布林值)*
+
+* **選項**: 可選參數,目前只支援標題 (如授權 (基本驗證) 等)。
+
+### 示例
+
+ // !! Assumes variable fileURL contains a valid URL to a path on the device,
+ // for example, cdvfile://localhost/persistent/path/to/downloads/
+
+ var fileTransfer = new FileTransfer();
+ var uri = encodeURI("http://some.server.com/download.php");
+
+ fileTransfer.download(
+ uri,
+ fileURL,
+ function(entry) {
+ console.log("download complete: " + entry.toURL());
+ },
+ function(error) {
+ console.log("download error source " + error.source);
+ console.log("download error target " + error.target);
+ console.log("upload error code" + error.code);
+ },
+ false,
+ {
+ headers: {
+ "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
+ }
+ }
+ );
+
+
+## 中止
+
+中止正在進行轉讓。Onerror 回檔傳遞的錯誤代碼為 FileTransferError.ABORT_ERR 的 FileTransferError 物件。
+
+### 示例
+
+ // !! Assumes variable fileURL contains a valid URL to a text file on the device,
+ // for example, cdvfile://localhost/persistent/path/to/file.txt
+
+ var win = function(r) {
+ console.log("Should not be called.");
+ }
+
+ var fail = function(error) {
+ // error.code == FileTransferError.ABORT_ERR
+ alert("An error has occurred: Code = " + error.code);
+ console.log("upload error source " + error.source);
+ console.log("upload error target " + error.target);
+ }
+
+ var options = new FileUploadOptions();
+ options.fileKey="file";
+ options.fileName="myphoto.jpg";
+ options.mimeType="image/jpeg";
+
+ var ft = new FileTransfer();
+ ft.upload(fileURL, encodeURI("http://some.server.com/upload.php"), win, fail, options);
+ ft.abort();
+
+
+## FileTransferError
+
+A `FileTransferError` 物件被傳遞給一個錯誤回呼函數時出現錯誤。
+
+### 屬性
+
+* **代碼**: 下面列出的預定義的錯誤代碼之一。(人數)
+
+* **源**: 源的 URL。(字串)
+
+* **目標**: 到目標 URL。(字串)
+
+* **HTTP_status**: HTTP 狀態碼。從 HTTP 連接收到一個回應代碼時,此屬性才可用。(人數)
+
+* **例外**: 要麼 e.getMessage 或 e.toString (字串)
+
+### 常量
+
+* 1 = `FileTransferError.FILE_NOT_FOUND_ERR`
+* 2 = `FileTransferError.INVALID_URL_ERR`
+* 3 = `FileTransferError.CONNECTION_ERR`
+* 4 = `FileTransferError.ABORT_ERR`
+* 5 = `FileTransferError.NOT_MODIFIED_ERR`
+
+## 向後相容性注意到
+
+以前版本的這個外掛程式才會接受設備-絕對檔路徑的源上傳,或作為下載的目標。這些路徑通常會在表單
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+為向後相容性,這些路徑仍被接受,和如果您的應用程式記錄了像這些在持久性存儲的路徑,然後他們可以繼續使用。
+
+這些路徑被以前暴露在 `fullPath` 屬性的 `FileEntry` 和 `DirectoryEntry` 由檔外掛程式返回的物件。 新版本的檔的外掛程式,不過,不再公開這些 JavaScript 的路徑。
+
+如果您升級到一個新 (1.0.0 或更高版本) 版本的檔,和你以前一直在使用 `entry.fullPath` 作為的參數 `download()` 或 `upload()` ,那麼你將需要更改代碼以使用檔案系統的 Url 來代替。
+
+`FileEntry.toURL()`和 `DirectoryEntry.toURL()` 返回的表單檔案系統 URL
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+其中可代替的絕對檔路徑在兩個 `download()` 和 `upload()` 的方法。
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/package.json b/plugins/org.apache.cordova.file-transfer/package.json
new file mode 100644
index 0000000..7ca3b99
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/package.json
@@ -0,0 +1,28 @@
+{
+ "version": "0.5.0",
+ "name": "org.apache.cordova.file-transfer",
+ "cordova_name": "File Transfer",
+ "description": "Cordova File Transfer Plugin",
+ "license": "Apache 2.0",
+ "repo": "https://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer.git",
+ "issue": "https://issues.apache.org/jira/browse/CB/component/12320650",
+ "keywords": [
+ "cordova",
+ "file",
+ "transfer"
+ ],
+ "platforms": [
+ "android",
+ "amazon-fireos",
+ "ubuntu",
+ "blackberry10",
+ "ios",
+ "wp7",
+ "wp8",
+ "windows8",
+ "windows",
+ "firefoxos"
+ ],
+ "engines": [],
+ "englishdoc": "\n\n# org.apache.cordova.file-transfer\n\nThis plugin allows you to upload and download files.\n\nThis plugin defines global `FileTransfer`, `FileUploadOptions` Constructors.\n\nAlthough in the global scope, they are not available until after the `deviceready` event.\n\n document.addEventListener(\"deviceready\", onDeviceReady, false);\n function onDeviceReady() {\n console.log(FileTransfer);\n }\n\n## Installation\n\n cordova plugin add org.apache.cordova.file-transfer\n\n## Supported Platforms\n\n- Amazon Fire OS\n- Android\n- BlackBerry 10\n- Firefox OS**\n- iOS\n- Windows Phone 7 and 8*\n- Windows 8\n- Windows\n\n\\* _Do not support `onprogress` nor `abort()`_\n\n\\** _Do not support `onprogress`_\n\n# FileTransfer\n\nThe `FileTransfer` object provides a way to upload files using an HTTP\nmulti-part POST request, and to download files as well.\n\n## Properties\n\n- __onprogress__: Called with a `ProgressEvent` whenever a new chunk of data is transferred. _(Function)_\n\n## Methods\n\n- __upload__: sends a file to a server.\n\n- __download__: downloads a file from server.\n\n- __abort__: Aborts an in-progress transfer.\n\n\n## upload\n\n__Parameters__:\n\n- __fileURL__: Filesystem URL representing the file on the device. For backwards compatibility, this can also be the full path of the file on the device. (See [Backwards Compatibility Notes] below)\n\n- __server__: URL of the server to receive the file, as encoded by `encodeURI()`.\n\n- __successCallback__: A callback that is passed a `FileUploadResult` object. _(Function)_\n\n- __errorCallback__: A callback that executes if an error occurs retrieving the `FileUploadResult`. Invoked with a `FileTransferError` object. _(Function)_\n\n- __options__: Optional parameters _(Object)_. Valid keys:\n - __fileKey__: The name of the form element. Defaults to `file`. (DOMString)\n - __fileName__: The file name to use when saving the file on the server. Defaults to `image.jpg`. (DOMString)\n - __httpMethod__: The HTTP method to use - either `PUT` or `POST`. Defaults to `POST`. (DOMString)\n - __mimeType__: The mime type of the data to upload. Defaults to `image/jpeg`. (DOMString)\n - __params__: A set of optional key/value pairs to pass in the HTTP request. (Object)\n - __chunkedMode__: Whether to upload the data in chunked streaming mode. Defaults to `true`. (Boolean)\n - __headers__: A map of header name/header values. Use an array to specify more than one value. (Object)\n \n- __trustAllHosts__: Optional parameter, defaults to `false`. If set to `true`, it accepts all security certificates. This is useful since Android rejects self-signed security certificates. Not recommended for production use. Supported on Android and iOS. _(boolean)_\n\n### Example\n\n // !! Assumes variable fileURL contains a valid URL to a text file on the device,\n // for example, cdvfile://localhost/persistent/path/to/file.txt\n\n var win = function (r) {\n console.log(\"Code = \" + r.responseCode);\n console.log(\"Response = \" + r.response);\n console.log(\"Sent = \" + r.bytesSent);\n }\n\n var fail = function (error) {\n alert(\"An error has occurred: Code = \" + error.code);\n console.log(\"upload error source \" + error.source);\n console.log(\"upload error target \" + error.target);\n }\n\n var options = new FileUploadOptions();\n options.fileKey = \"file\";\n options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);\n options.mimeType = \"text/plain\";\n\n var params = {};\n params.value1 = \"test\";\n params.value2 = \"param\";\n\n options.params = params;\n\n var ft = new FileTransfer();\n ft.upload(fileURL, encodeURI(\"http://some.server.com/upload.php\"), win, fail, options);\n\n### Example with Upload Headers and Progress Events (Android and iOS only)\n\n function win(r) {\n console.log(\"Code = \" + r.responseCode);\n console.log(\"Response = \" + r.response);\n console.log(\"Sent = \" + r.bytesSent);\n }\n\n function fail(error) {\n alert(\"An error has occurred: Code = \" + error.code);\n console.log(\"upload error source \" + error.source);\n console.log(\"upload error target \" + error.target);\n }\n\n var uri = encodeURI(\"http://some.server.com/upload.php\");\n\n var options = new FileUploadOptions();\n options.fileKey=\"file\";\n options.fileName=fileURL.substr(fileURL.lastIndexOf('/')+1);\n options.mimeType=\"text/plain\";\n\n var headers={'headerParam':'headerValue'};\n\n options.headers = headers;\n\n var ft = new FileTransfer();\n ft.onprogress = function(progressEvent) {\n if (progressEvent.lengthComputable) {\n loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);\n } else {\n loadingStatus.increment();\n }\n };\n ft.upload(fileURL, uri, win, fail, options);\n\n## FileUploadResult\n\nA `FileUploadResult` object is passed to the success callback of the\n`FileTransfer` object's `upload()` method.\n\n### Properties\n\n- __bytesSent__: The number of bytes sent to the server as part of the upload. (long)\n\n- __responseCode__: The HTTP response code returned by the server. (long)\n\n- __response__: The HTTP response returned by the server. (DOMString)\n\n- __headers__: The HTTP response headers by the server. (Object)\n - Currently supported on iOS only.\n\n### iOS Quirks\n\n- Does not support `responseCode` or `bytesSent`.\n\n\n## download\n\n__Parameters__:\n\n- __source__: URL of the server to download the file, as encoded by `encodeURI()`.\n\n- __target__: Filesystem url representing the file on the device. For backwards compatibility, this can also be the full path of the file on the device. (See [Backwards Compatibility Notes] below)\n\n- __successCallback__: A callback that is passed a `FileEntry` object. _(Function)_\n\n- __errorCallback__: A callback that executes if an error occurs when retrieving the `FileEntry`. Invoked with a `FileTransferError` object. _(Function)_\n\n- __trustAllHosts__: Optional parameter, defaults to `false`. If set to `true`, it accepts all security certificates. This is useful because Android rejects self-signed security certificates. Not recommended for production use. Supported on Android and iOS. _(boolean)_\n\n- __options__: Optional parameters, currently only supports headers (such as Authorization (Basic Authentication), etc).\n\n### Example\n\n // !! Assumes variable fileURL contains a valid URL to a path on the device,\n // for example, cdvfile://localhost/persistent/path/to/downloads/\n\n var fileTransfer = new FileTransfer();\n var uri = encodeURI(\"http://some.server.com/download.php\");\n\n fileTransfer.download(\n uri,\n fileURL,\n function(entry) {\n console.log(\"download complete: \" + entry.toURL());\n },\n function(error) {\n console.log(\"download error source \" + error.source);\n console.log(\"download error target \" + error.target);\n console.log(\"upload error code\" + error.code);\n },\n false,\n {\n headers: {\n \"Authorization\": \"Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA==\"\n }\n }\n );\n\n## abort\n\nAborts an in-progress transfer. The onerror callback is passed a FileTransferError object which has an error code of FileTransferError.ABORT_ERR.\n\n### Example\n\n // !! Assumes variable fileURL contains a valid URL to a text file on the device,\n // for example, cdvfile://localhost/persistent/path/to/file.txt\n\n var win = function(r) {\n console.log(\"Should not be called.\");\n }\n\n var fail = function(error) {\n // error.code == FileTransferError.ABORT_ERR\n alert(\"An error has occurred: Code = \" + error.code);\n console.log(\"upload error source \" + error.source);\n console.log(\"upload error target \" + error.target);\n }\n\n var options = new FileUploadOptions();\n options.fileKey=\"file\";\n options.fileName=\"myphoto.jpg\";\n options.mimeType=\"image/jpeg\";\n\n var ft = new FileTransfer();\n ft.upload(fileURL, encodeURI(\"http://some.server.com/upload.php\"), win, fail, options);\n ft.abort();\n\n\n## FileTransferError\n\nA `FileTransferError` object is passed to an error callback when an error occurs.\n\n### Properties\n\n- __code__: One of the predefined error codes listed below. (Number)\n\n- __source__: URL to the source. (String)\n\n- __target__: URL to the target. (String)\n\n- __http_status__: HTTP status code. This attribute is only available when a response code is received from the HTTP connection. (Number)\n\n- __body__ Response body. This attribute is only available when a response is received from the HTTP connection. (String)\n\n- __exception__: Either e.getMessage or e.toString (String)\n\n### Constants\n\n- 1 = `FileTransferError.FILE_NOT_FOUND_ERR`\n- 2 = `FileTransferError.INVALID_URL_ERR`\n- 3 = `FileTransferError.CONNECTION_ERR`\n- 4 = `FileTransferError.ABORT_ERR`\n- 5 = `FileTransferError.NOT_MODIFIED_ERR`\n\n## Backwards Compatibility Notes\n\nPrevious versions of this plugin would only accept device-absolute-file-paths as the source for uploads, or as the target for downloads. These paths would typically be of the form\n\n /var/mobile/Applications//Documents/path/to/file (iOS)\n /storage/emulated/0/path/to/file (Android)\n\nFor backwards compatibility, these paths are still accepted, and if your application has recorded paths like these in persistent storage, then they can continue to be used.\n\nThese paths were previously exposed in the `fullPath` property of `FileEntry` and `DirectoryEntry` objects returned by the File plugin. New versions of the File plugin, however, no longer expose these paths to JavaScript.\n\nIf you are upgrading to a new (1.0.0 or newer) version of File, and you have previously been using `entry.fullPath` as arguments to `download()` or `upload()`, then you will need to change your code to use filesystem URLs instead.\n\n`FileEntry.toURL()` and `DirectoryEntry.toURL()` return a filesystem URL of the form\n\n cdvfile://localhost/persistent/path/to/file\n\nwhich can be used in place of the absolute file path in both `download()` and `upload()` methods.\n"
+}
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file-transfer/plugin.xml b/plugins/org.apache.cordova.file-transfer/plugin.xml
new file mode 100644
index 0000000..efb7cc4
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/plugin.xml
@@ -0,0 +1,164 @@
+
+
+
+
+ File Transfer
+ Cordova File Transfer Plugin
+ Apache 2.0
+ cordova,file,transfer
+ https://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer.git
+ https://issues.apache.org/jira/browse/CB/component/12320650
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.file-transfer/src/amazon/FileTransfer.java b/plugins/org.apache.cordova.file-transfer/src/amazon/FileTransfer.java
new file mode 100644
index 0000000..513400e
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/amazon/FileTransfer.java
@@ -0,0 +1,888 @@
+/*
+ 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.
+*/
+package org.apache.cordova.filetransfer;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.Inflater;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.cordova.Config;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaResourceApi;
+import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
+import org.apache.cordova.PluginResult;
+import org.apache.cordova.file.FileUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+import com.amazon.android.webkit.AmazonCookieManager;
+
+public class FileTransfer extends CordovaPlugin {
+
+ private static final String LOG_TAG = "FileTransfer";
+ private static final String LINE_START = "--";
+ private static final String LINE_END = "\r\n";
+ private static final String BOUNDARY = "+++++";
+
+ public static int FILE_NOT_FOUND_ERR = 1;
+ public static int INVALID_URL_ERR = 2;
+ public static int CONNECTION_ERR = 3;
+ public static int ABORTED_ERR = 4;
+
+ private static HashMap activeRequests = new HashMap();
+ private static final int MAX_BUFFER_SIZE = 16 * 1024;
+
+ private static final class RequestContext {
+ String source;
+ String target;
+ File targetFile;
+ CallbackContext callbackContext;
+ InputStream currentInputStream;
+ OutputStream currentOutputStream;
+ boolean aborted;
+ RequestContext(String source, String target, CallbackContext callbackContext) {
+ this.source = source;
+ this.target = target;
+ this.callbackContext = callbackContext;
+ }
+ void sendPluginResult(PluginResult pluginResult) {
+ synchronized (this) {
+ if (!aborted) {
+ callbackContext.sendPluginResult(pluginResult);
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds an interface method to an InputStream to return the number of bytes
+ * read from the raw stream. This is used to track total progress against
+ * the HTTP Content-Length header value from the server.
+ */
+ private static abstract class TrackingInputStream extends FilterInputStream {
+ public TrackingInputStream(final InputStream in) {
+ super(in);
+ }
+ public abstract long getTotalRawBytesRead();
+ }
+
+ private static class ExposedGZIPInputStream extends GZIPInputStream {
+ public ExposedGZIPInputStream(final InputStream in) throws IOException {
+ super(in);
+ }
+ public Inflater getInflater() {
+ return inf;
+ }
+ }
+
+ /**
+ * Provides raw bytes-read tracking for a GZIP input stream. Reports the
+ * total number of compressed bytes read from the input, rather than the
+ * number of uncompressed bytes.
+ */
+ private static class TrackingGZIPInputStream extends TrackingInputStream {
+ private ExposedGZIPInputStream gzin;
+ public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException {
+ super(gzin);
+ this.gzin = gzin;
+ }
+ public long getTotalRawBytesRead() {
+ return gzin.getInflater().getBytesRead();
+ }
+ }
+
+ /**
+ * Provides simple total-bytes-read tracking for an existing InputStream
+ */
+ private static class SimpleTrackingInputStream extends TrackingInputStream {
+ private long bytesRead = 0;
+ public SimpleTrackingInputStream(InputStream stream) {
+ super(stream);
+ }
+
+ private int updateBytesRead(int newBytesRead) {
+ if (newBytesRead != -1) {
+ bytesRead += newBytesRead;
+ }
+ return newBytesRead;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return updateBytesRead(super.read());
+ }
+
+ // Note: FilterInputStream delegates read(byte[] bytes) to the below method,
+ // so we don't override it or else double count (CB-5631).
+ @Override
+ public int read(byte[] bytes, int offset, int count) throws IOException {
+ return updateBytesRead(super.read(bytes, offset, count));
+ }
+
+ public long getTotalRawBytesRead() {
+ return bytesRead;
+ }
+ }
+
+ @Override
+ public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ if (action.equals("upload") || action.equals("download")) {
+ String source = args.getString(0);
+ String target = args.getString(1);
+
+ if (action.equals("upload")) {
+ try {
+ source = URLDecoder.decode(source, "UTF-8");
+ upload(source, target, args, callbackContext);
+ } catch (UnsupportedEncodingException e) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.MALFORMED_URL_EXCEPTION, "UTF-8 error."));
+ }
+ } else {
+ download(source, target, args, callbackContext);
+ }
+ return true;
+ } else if (action.equals("abort")) {
+ String objectId = args.getString(0);
+ abort(objectId);
+ callbackContext.success();
+ return true;
+ }
+ return false;
+ }
+
+ private static void addHeadersToRequest(URLConnection connection, JSONObject headers) {
+ try {
+ for (Iterator> iter = headers.keys(); iter.hasNext(); ) {
+ String headerKey = iter.next().toString();
+ JSONArray headerValues = headers.optJSONArray(headerKey);
+ if (headerValues == null) {
+ headerValues = new JSONArray();
+ headerValues.put(headers.getString(headerKey));
+ }
+ connection.setRequestProperty(headerKey, headerValues.getString(0));
+ for (int i = 1; i < headerValues.length(); ++i) {
+ connection.addRequestProperty(headerKey, headerValues.getString(i));
+ }
+ }
+ } catch (JSONException e1) {
+ // No headers to be manipulated!
+ }
+ }
+
+ /**
+ * Uploads the specified file to the server URL provided using an HTTP multipart request.
+ * @param source Full path of the file on the file system
+ * @param target URL of the server to receive the file
+ * @param args JSON Array of args
+ * @param callbackContext callback id for optional progress reports
+ *
+ * args[2] fileKey Name of file request parameter
+ * args[3] fileName File name to be used on server
+ * args[4] mimeType Describes file content type
+ * args[5] params key:value pairs of user-defined parameters
+ * @return FileUploadResult containing result of upload request
+ */
+ private void upload(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ Log.d(LOG_TAG, "upload " + source + " to " + target);
+
+ // Setup the options
+ final String fileKey = getArgument(args, 2, "file");
+ final String fileName = getArgument(args, 3, "image.jpg");
+ final String mimeType = getArgument(args, 4, "image/jpeg");
+ final JSONObject params = args.optJSONObject(5) == null ? new JSONObject() : args.optJSONObject(5);
+ final boolean trustEveryone = args.optBoolean(6);
+ // Always use chunked mode unless set to false as per API
+ final boolean chunkedMode = args.optBoolean(7) || args.isNull(7);
+ // Look for headers on the params map for backwards compatibility with older Cordova versions.
+ final JSONObject headers = args.optJSONObject(8) == null ? params.optJSONObject("headers") : args.optJSONObject(8);
+ final String objectId = args.getString(9);
+ final String httpMethod = getArgument(args, 10, "POST");
+
+ final CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ Log.d(LOG_TAG, "fileKey: " + fileKey);
+ Log.d(LOG_TAG, "fileName: " + fileName);
+ Log.d(LOG_TAG, "mimeType: " + mimeType);
+ Log.d(LOG_TAG, "params: " + params);
+ Log.d(LOG_TAG, "trustEveryone: " + trustEveryone);
+ Log.d(LOG_TAG, "chunkedMode: " + chunkedMode);
+ Log.d(LOG_TAG, "headers: " + headers);
+ Log.d(LOG_TAG, "objectId: " + objectId);
+ Log.d(LOG_TAG, "httpMethod: " + httpMethod);
+
+ final Uri targetUri = resourceApi.remapUri(Uri.parse(target));
+ // Accept a path or a URI for the source.
+ Uri tmpSrc = Uri.parse(source);
+ final Uri sourceUri = resourceApi.remapUri(
+ tmpSrc.getScheme() != null ? tmpSrc : Uri.fromFile(new File(source)));
+
+ int uriType = CordovaResourceApi.getUriType(targetUri);
+ final boolean useHttps = uriType == CordovaResourceApi.URI_TYPE_HTTPS;
+ if (uriType != CordovaResourceApi.URI_TYPE_HTTP && !useHttps) {
+ JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0);
+ Log.e(LOG_TAG, "Unsupported URI: " + targetUri);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ return;
+ }
+
+ final RequestContext context = new RequestContext(source, target, callbackContext);
+ synchronized (activeRequests) {
+ activeRequests.put(objectId, context);
+ }
+
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ if (context.aborted) {
+ return;
+ }
+ HttpURLConnection conn = null;
+ HostnameVerifier oldHostnameVerifier = null;
+ SSLSocketFactory oldSocketFactory = null;
+ int totalBytes = 0;
+ int fixedLength = -1;
+ try {
+ // Create return object
+ FileUploadResult result = new FileUploadResult();
+ FileProgressResult progress = new FileProgressResult();
+
+ //------------------ CLIENT REQUEST
+ // Open a HTTP connection to the URL based on protocol
+ conn = resourceApi.createHttpConnection(targetUri);
+ if (useHttps && trustEveryone) {
+ // Setup the HTTPS connection class to trust everyone
+ HttpsURLConnection https = (HttpsURLConnection)conn;
+ oldSocketFactory = trustAllHosts(https);
+ // Save the current hostnameVerifier
+ oldHostnameVerifier = https.getHostnameVerifier();
+ // Setup the connection not to verify hostnames
+ https.setHostnameVerifier(DO_NOT_VERIFY);
+ }
+
+ // Allow Inputs
+ conn.setDoInput(true);
+
+ // Allow Outputs
+ conn.setDoOutput(true);
+
+ // Don't use a cached copy.
+ conn.setUseCaches(false);
+
+ // Use a post method.
+ conn.setRequestMethod(httpMethod);
+ conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
+
+ // Set the cookies on the response
+ String cookie = AmazonCookieManager.getInstance().getCookie(target);
+ if (cookie != null) {
+ conn.setRequestProperty("Cookie", cookie);
+ }
+
+ // Handle the other headers
+ if (headers != null) {
+ addHeadersToRequest(conn, headers);
+ }
+
+ /*
+ * Store the non-file portions of the multipart data as a string, so that we can add it
+ * to the contentSize, since it is part of the body of the HTTP request.
+ */
+ StringBuilder beforeData = new StringBuilder();
+ try {
+ for (Iterator> iter = params.keys(); iter.hasNext();) {
+ Object key = iter.next();
+ if(!String.valueOf(key).equals("headers"))
+ {
+ beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);
+ beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append('"');
+ beforeData.append(LINE_END).append(LINE_END);
+ beforeData.append(params.getString(key.toString()));
+ beforeData.append(LINE_END);
+ }
+ }
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ }
+
+ beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);
+ beforeData.append("Content-Disposition: form-data; name=\"").append(fileKey).append("\";");
+ beforeData.append(" filename=\"").append(fileName).append('"').append(LINE_END);
+ beforeData.append("Content-Type: ").append(mimeType).append(LINE_END).append(LINE_END);
+ byte[] beforeDataBytes = beforeData.toString().getBytes("UTF-8");
+ byte[] tailParamsBytes = (LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END).getBytes("UTF-8");
+
+
+ // Get a input stream of the file on the phone
+ OpenForReadResult readResult = resourceApi.openForRead(sourceUri);
+
+ int stringLength = beforeDataBytes.length + tailParamsBytes.length;
+ if (readResult.length >= 0) {
+ fixedLength = (int)readResult.length + stringLength;
+ progress.setLengthComputable(true);
+ progress.setTotal(fixedLength);
+ }
+ Log.d(LOG_TAG, "Content Length: " + fixedLength);
+ // setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices.
+ // http://code.google.com/p/android/issues/detail?id=3164
+ // It also causes OOM if HTTPS is used, even on newer devices.
+ boolean useChunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps);
+ useChunkedMode = useChunkedMode || (fixedLength == -1);
+
+ if (useChunkedMode) {
+ conn.setChunkedStreamingMode(MAX_BUFFER_SIZE);
+ // Although setChunkedStreamingMode sets this header, setting it explicitly here works
+ // around an OutOfMemoryException when using https.
+ conn.setRequestProperty("Transfer-Encoding", "chunked");
+ } else {
+ conn.setFixedLengthStreamingMode(fixedLength);
+ }
+
+ conn.connect();
+
+ OutputStream sendStream = null;
+ try {
+ sendStream = conn.getOutputStream();
+ synchronized (context) {
+ if (context.aborted) {
+ return;
+ }
+ context.currentOutputStream = sendStream;
+ }
+ //We don't want to change encoding, we just want this to write for all Unicode.
+ sendStream.write(beforeDataBytes);
+ totalBytes += beforeDataBytes.length;
+
+ // create a buffer of maximum size
+ int bytesAvailable = readResult.inputStream.available();
+ int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
+ byte[] buffer = new byte[bufferSize];
+
+ // read file and write it into form...
+ int bytesRead = readResult.inputStream.read(buffer, 0, bufferSize);
+
+ long prevBytesRead = 0;
+ while (bytesRead > 0) {
+ result.setBytesSent(totalBytes);
+ sendStream.write(buffer, 0, bytesRead);
+ totalBytes += bytesRead;
+ if (totalBytes > prevBytesRead + 102400) {
+ prevBytesRead = totalBytes;
+ Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes");
+ }
+ bytesAvailable = readResult.inputStream.available();
+ bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
+ bytesRead = readResult.inputStream.read(buffer, 0, bufferSize);
+
+ // Send a progress event.
+ progress.setLoaded(totalBytes);
+ PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
+ progressResult.setKeepCallback(true);
+ context.sendPluginResult(progressResult);
+ }
+
+ // send multipart form data necessary after file data...
+ sendStream.write(tailParamsBytes);
+ totalBytes += tailParamsBytes.length;
+ sendStream.flush();
+ } finally {
+ safeClose(readResult.inputStream);
+ safeClose(sendStream);
+ }
+ context.currentOutputStream = null;
+ Log.d(LOG_TAG, "Sent " + totalBytes + " of " + fixedLength);
+
+ //------------------ read the SERVER RESPONSE
+ String responseString;
+ int responseCode = conn.getResponseCode();
+ Log.d(LOG_TAG, "response code: " + responseCode);
+ Log.d(LOG_TAG, "response headers: " + conn.getHeaderFields());
+ TrackingInputStream inStream = null;
+ try {
+ inStream = getInputStream(conn);
+ synchronized (context) {
+ if (context.aborted) {
+ return;
+ }
+ context.currentInputStream = inStream;
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, conn.getContentLength()));
+ byte[] buffer = new byte[1024];
+ int bytesRead = 0;
+ // write bytes to file
+ while ((bytesRead = inStream.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ }
+ responseString = out.toString("UTF-8");
+ } finally {
+ context.currentInputStream = null;
+ safeClose(inStream);
+ }
+
+ Log.d(LOG_TAG, "got response from server");
+ Log.d(LOG_TAG, responseString.substring(0, Math.min(256, responseString.length())));
+
+ // send request and retrieve response
+ result.setResponseCode(responseCode);
+ result.setResponse(responseString);
+
+ context.sendPluginResult(new PluginResult(PluginResult.Status.OK, result.toJSONObject()));
+ } catch (FileNotFoundException e) {
+ JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn);
+ Log.e(LOG_TAG, error.toString(), e);
+ context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ } catch (IOException e) {
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
+ Log.e(LOG_TAG, error.toString(), e);
+ Log.e(LOG_TAG, "Failed after uploading " + totalBytes + " of " + fixedLength + " bytes.");
+ context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ context.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ } catch (Throwable t) {
+ // Shouldn't happen, but will
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
+ Log.e(LOG_TAG, error.toString(), t);
+ context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ } finally {
+ synchronized (activeRequests) {
+ activeRequests.remove(objectId);
+ }
+
+ if (conn != null) {
+ // Revert back to the proper verifier and socket factories
+ // Revert back to the proper verifier and socket factories
+ if (trustEveryone && useHttps) {
+ HttpsURLConnection https = (HttpsURLConnection) conn;
+ https.setHostnameVerifier(oldHostnameVerifier);
+ https.setSSLSocketFactory(oldSocketFactory);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ private static void safeClose(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ private static TrackingInputStream getInputStream(URLConnection conn) throws IOException {
+ String encoding = conn.getContentEncoding();
+ if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
+ return new TrackingGZIPInputStream(new ExposedGZIPInputStream(conn.getInputStream()));
+ }
+ return new SimpleTrackingInputStream(conn.getInputStream());
+ }
+
+ // always verify the host - don't check for certificate
+ private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ };
+ // Create a trust manager that does not validate certificate chains
+ private static final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return new java.security.cert.X509Certificate[] {};
+ }
+
+ public void checkClientTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+ } };
+
+ /**
+ * This function will install a trust manager that will blindly trust all SSL
+ * certificates. The reason this code is being added is to enable developers
+ * to do development using self signed SSL certificates on their web server.
+ *
+ * The standard HttpsURLConnection class will throw an exception on self
+ * signed certificates if this code is not run.
+ */
+ private static SSLSocketFactory trustAllHosts(HttpsURLConnection connection) {
+ // Install the all-trusting trust manager
+ SSLSocketFactory oldFactory = connection.getSSLSocketFactory();
+ try {
+ // Install our all trusting manager
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ SSLSocketFactory newFactory = sc.getSocketFactory();
+ connection.setSSLSocketFactory(newFactory);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ }
+ return oldFactory;
+ }
+
+ private static JSONObject createFileTransferError(int errorCode, String source, String target, URLConnection connection) {
+
+ int httpStatus = 0;
+ StringBuilder bodyBuilder = new StringBuilder();
+ String body = null;
+ if (connection != null) {
+ try {
+ if (connection instanceof HttpURLConnection) {
+ httpStatus = ((HttpURLConnection)connection).getResponseCode();
+ InputStream err = ((HttpURLConnection) connection).getErrorStream();
+ if(err != null)
+ {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(err, "UTF-8"));
+ try {
+ String line = reader.readLine();
+ while(line != null) {
+ bodyBuilder.append(line);
+ line = reader.readLine();
+ if(line != null) {
+ bodyBuilder.append('\n');
+ }
+ }
+ body = bodyBuilder.toString();
+ } finally {
+ reader.close();
+ }
+ }
+ }
+ // IOException can leave connection object in a bad state, so catch all exceptions.
+ } catch (Throwable e) {
+ Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e);
+ }
+ }
+
+ return createFileTransferError(errorCode, source, target, body, httpStatus);
+ }
+
+ /**
+ * Create an error object based on the passed in errorCode
+ * @param errorCode the error
+ * @return JSONObject containing the error
+ */
+ private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus) {
+ JSONObject error = null;
+ try {
+ error = new JSONObject();
+ error.put("code", errorCode);
+ error.put("source", source);
+ error.put("target", target);
+ if(body != null)
+ {
+ error.put("body", body);
+ }
+ if (httpStatus != null) {
+ error.put("http_status", httpStatus);
+ }
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ }
+ return error;
+ }
+
+ /**
+ * Convenience method to read a parameter from the list of JSON args.
+ * @param args the args passed to the Plugin
+ * @param position the position to retrieve the arg from
+ * @param defaultString the default to be used if the arg does not exist
+ * @return String with the retrieved value
+ */
+ private static String getArgument(JSONArray args, int position, String defaultString) {
+ String arg = defaultString;
+ if (args.length() > position) {
+ arg = args.optString(position);
+ if (arg == null || "null".equals(arg)) {
+ arg = defaultString;
+ }
+ }
+ return arg;
+ }
+
+ /**
+ * Downloads a file form a given URL and saves it to the specified directory.
+ *
+ * @param source URL of the server to receive the file
+ * @param target Full path of the file on the file system
+ */
+ private void download(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ Log.d(LOG_TAG, "download " + source + " to " + target);
+
+ final CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ final boolean trustEveryone = args.optBoolean(2);
+ final String objectId = args.getString(3);
+ final JSONObject headers = args.optJSONObject(4);
+
+ final Uri sourceUri = resourceApi.remapUri(Uri.parse(source));
+ // Accept a path or a URI for the source.
+ Uri tmpTarget = Uri.parse(target);
+ final Uri targetUri = resourceApi.remapUri(
+ tmpTarget.getScheme() != null ? tmpTarget : Uri.fromFile(new File(target)));
+
+ int uriType = CordovaResourceApi.getUriType(sourceUri);
+ final boolean useHttps = uriType == CordovaResourceApi.URI_TYPE_HTTPS;
+ final boolean isLocalTransfer = !useHttps && uriType != CordovaResourceApi.URI_TYPE_HTTP;
+ if (uriType == CordovaResourceApi.URI_TYPE_UNKNOWN) {
+ JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0);
+ Log.e(LOG_TAG, "Unsupported URI: " + targetUri);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ return;
+ }
+
+ // TODO: refactor to also allow resources & content:
+ if (!isLocalTransfer && !Config.isUrlWhiteListed(source)) {
+ Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ return;
+ }
+
+
+ final RequestContext context = new RequestContext(source, target, callbackContext);
+ synchronized (activeRequests) {
+ activeRequests.put(objectId, context);
+ }
+
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ if (context.aborted) {
+ return;
+ }
+ HttpURLConnection connection = null;
+ HostnameVerifier oldHostnameVerifier = null;
+ SSLSocketFactory oldSocketFactory = null;
+ File file = null;
+ PluginResult result = null;
+ TrackingInputStream inputStream = null;
+
+ OutputStream outputStream = null;
+ try {
+ OpenForReadResult readResult = null;
+ outputStream = resourceApi.openOutputStream(targetUri);
+
+ file = resourceApi.mapUriToFile(targetUri);
+ context.targetFile = file;
+
+ Log.d(LOG_TAG, "Download file:" + sourceUri);
+
+ FileProgressResult progress = new FileProgressResult();
+
+ if (isLocalTransfer) {
+ readResult = resourceApi.openForRead(sourceUri);
+ if (readResult.length != -1) {
+ progress.setLengthComputable(true);
+ progress.setTotal(readResult.length);
+ }
+ inputStream = new SimpleTrackingInputStream(readResult.inputStream);
+ } else {
+ // connect to server
+ // Open a HTTP connection to the URL based on protocol
+ connection = resourceApi.createHttpConnection(sourceUri);
+ if (useHttps && trustEveryone) {
+ // Setup the HTTPS connection class to trust everyone
+ HttpsURLConnection https = (HttpsURLConnection)connection;
+ oldSocketFactory = trustAllHosts(https);
+ // Save the current hostnameVerifier
+ oldHostnameVerifier = https.getHostnameVerifier();
+ // Setup the connection not to verify hostnames
+ https.setHostnameVerifier(DO_NOT_VERIFY);
+ }
+
+ connection.setRequestMethod("GET");
+
+ // TODO: Make OkHttp use this AmazonCookieManager by default.
+ String cookie = AmazonCookieManager.getInstance().getCookie(sourceUri.toString());
+ if(cookie != null)
+ {
+ connection.setRequestProperty("cookie", cookie);
+ }
+
+ // This must be explicitly set for gzip progress tracking to work.
+ connection.setRequestProperty("Accept-Encoding", "gzip");
+
+ // Handle the other headers
+ if (headers != null) {
+ addHeadersToRequest(connection, headers);
+ }
+
+ connection.connect();
+
+ if (connection.getContentEncoding() == null || connection.getContentEncoding().equalsIgnoreCase("gzip")) {
+ // Only trust content-length header if we understand
+ // the encoding -- identity or gzip
+ if (connection.getContentLength() != -1) {
+ progress.setLengthComputable(true);
+ progress.setTotal(connection.getContentLength());
+ }
+ }
+ inputStream = getInputStream(connection);
+ }
+
+ try {
+ synchronized (context) {
+ if (context.aborted) {
+ return;
+ }
+ context.currentInputStream = inputStream;
+ }
+
+ // write bytes to file
+ byte[] buffer = new byte[MAX_BUFFER_SIZE];
+ int bytesRead = 0;
+ while ((bytesRead = inputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, bytesRead);
+ // Send a progress event.
+ progress.setLoaded(inputStream.getTotalRawBytesRead());
+ PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
+ progressResult.setKeepCallback(true);
+ context.sendPluginResult(progressResult);
+ }
+ } finally {
+ context.currentInputStream = null;
+ safeClose(inputStream);
+ safeClose(outputStream);
+ }
+
+ Log.d(LOG_TAG, "Saved file: " + target);
+
+ // create FileEntry object
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+ if (filePlugin != null) {
+ JSONObject fileEntry = filePlugin.getEntryForFile(file);
+ if (fileEntry != null) {
+ result = new PluginResult(PluginResult.Status.OK, fileEntry);
+ } else {
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
+ Log.e(LOG_TAG, "File plugin cannot represent download path");
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ }
+ } else {
+ Log.e(LOG_TAG, "File plugin not found; cannot save downloaded file");
+ result = new PluginResult(PluginResult.Status.ERROR, "File plugin not found; cannot save downloaded file");
+ }
+
+ } catch (FileNotFoundException e) {
+ JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection);
+ Log.e(LOG_TAG, error.toString(), e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } catch (IOException e) {
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
+ Log.e(LOG_TAG, error.toString(), e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ result = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+ } catch (Throwable e) {
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
+ Log.e(LOG_TAG, error.toString(), e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } finally {
+ safeClose(outputStream);
+ synchronized (activeRequests) {
+ activeRequests.remove(objectId);
+ }
+
+ if (connection != null) {
+ // Revert back to the proper verifier and socket factories
+ if (trustEveryone && useHttps) {
+ HttpsURLConnection https = (HttpsURLConnection) connection;
+ https.setHostnameVerifier(oldHostnameVerifier);
+ https.setSSLSocketFactory(oldSocketFactory);
+ }
+ }
+
+ if (result == null) {
+ result = new PluginResult(PluginResult.Status.ERROR, createFileTransferError(CONNECTION_ERR, source, target, connection));
+ }
+ // Remove incomplete download.
+ if (result.getStatus() != PluginResult.Status.OK.ordinal() && file != null) {
+ file.delete();
+ }
+ context.sendPluginResult(result);
+ }
+ }
+ });
+ }
+
+ /**
+ * Abort an ongoing upload or download.
+ */
+ private void abort(String objectId) {
+ final RequestContext context;
+ synchronized (activeRequests) {
+ context = activeRequests.remove(objectId);
+ }
+ if (context != null) {
+ File file = context.targetFile;
+ if (file != null) {
+ file.delete();
+ }
+ // Trigger the abort callback immediately to minimize latency between it and abort() being called.
+ JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, null, -1);
+ synchronized (context) {
+ context.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error));
+ context.aborted = true;
+ }
+ // Closing the streams can block, so execute on a background thread.
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ synchronized (context) {
+ safeClose(context.currentInputStream);
+ safeClose(context.currentOutputStream);
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/plugins/org.apache.cordova.file-transfer/src/android/FileProgressResult.java b/plugins/org.apache.cordova.file-transfer/src/android/FileProgressResult.java
new file mode 100644
index 0000000..76a7b13
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/android/FileProgressResult.java
@@ -0,0 +1,63 @@
+/*
+ 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.
+*/
+package org.apache.cordova.filetransfer;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Encapsulates in-progress status of uploading or downloading a file to a remote server.
+ */
+public class FileProgressResult {
+
+ private boolean lengthComputable = false; // declares whether total is known
+ private long loaded = 0; // bytes sent so far
+ private long total = 0; // bytes total, if known
+
+ public boolean getLengthComputable() {
+ return lengthComputable;
+ }
+
+ public void setLengthComputable(boolean computable) {
+ this.lengthComputable = computable;
+ }
+
+ public long getLoaded() {
+ return loaded;
+ }
+
+ public void setLoaded(long bytes) {
+ this.loaded = bytes;
+ }
+
+ public long getTotal() {
+ return total;
+ }
+
+ public void setTotal(long bytes) {
+ this.total = bytes;
+ }
+
+ public JSONObject toJSONObject() throws JSONException {
+ return new JSONObject(
+ "{loaded:" + loaded +
+ ",total:" + total +
+ ",lengthComputable:" + (lengthComputable ? "true" : "false") + "}");
+ }
+}
diff --git a/plugins/org.apache.cordova.file-transfer/src/android/FileTransfer.java b/plugins/org.apache.cordova.file-transfer/src/android/FileTransfer.java
new file mode 100644
index 0000000..3ec49f9
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/android/FileTransfer.java
@@ -0,0 +1,987 @@
+/*
+ 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.
+*/
+package org.apache.cordova.filetransfer;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.Inflater;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.cordova.Config;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaResourceApi;
+import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
+import org.apache.cordova.PluginManager;
+import org.apache.cordova.PluginResult;
+import org.apache.cordova.Whitelist;
+import org.apache.cordova.file.FileUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+import android.webkit.CookieManager;
+
+public class FileTransfer extends CordovaPlugin {
+
+ private static final String LOG_TAG = "FileTransfer";
+ private static final String LINE_START = "--";
+ private static final String LINE_END = "\r\n";
+ private static final String BOUNDARY = "+++++";
+
+ public static int FILE_NOT_FOUND_ERR = 1;
+ public static int INVALID_URL_ERR = 2;
+ public static int CONNECTION_ERR = 3;
+ public static int ABORTED_ERR = 4;
+ public static int NOT_MODIFIED_ERR = 5;
+
+ private static HashMap activeRequests = new HashMap();
+ private static final int MAX_BUFFER_SIZE = 16 * 1024;
+
+ private static final class RequestContext {
+ String source;
+ String target;
+ File targetFile;
+ CallbackContext callbackContext;
+ HttpURLConnection connection;
+ boolean aborted;
+ RequestContext(String source, String target, CallbackContext callbackContext) {
+ this.source = source;
+ this.target = target;
+ this.callbackContext = callbackContext;
+ }
+ void sendPluginResult(PluginResult pluginResult) {
+ synchronized (this) {
+ if (!aborted) {
+ callbackContext.sendPluginResult(pluginResult);
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds an interface method to an InputStream to return the number of bytes
+ * read from the raw stream. This is used to track total progress against
+ * the HTTP Content-Length header value from the server.
+ */
+ private static abstract class TrackingInputStream extends FilterInputStream {
+ public TrackingInputStream(final InputStream in) {
+ super(in);
+ }
+ public abstract long getTotalRawBytesRead();
+ }
+
+ private static class ExposedGZIPInputStream extends GZIPInputStream {
+ public ExposedGZIPInputStream(final InputStream in) throws IOException {
+ super(in);
+ }
+ public Inflater getInflater() {
+ return inf;
+ }
+ }
+
+ /**
+ * Provides raw bytes-read tracking for a GZIP input stream. Reports the
+ * total number of compressed bytes read from the input, rather than the
+ * number of uncompressed bytes.
+ */
+ private static class TrackingGZIPInputStream extends TrackingInputStream {
+ private ExposedGZIPInputStream gzin;
+ public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException {
+ super(gzin);
+ this.gzin = gzin;
+ }
+ public long getTotalRawBytesRead() {
+ return gzin.getInflater().getBytesRead();
+ }
+ }
+
+ /**
+ * Provides simple total-bytes-read tracking for an existing InputStream
+ */
+ private static class SimpleTrackingInputStream extends TrackingInputStream {
+ private long bytesRead = 0;
+ public SimpleTrackingInputStream(InputStream stream) {
+ super(stream);
+ }
+
+ private int updateBytesRead(int newBytesRead) {
+ if (newBytesRead != -1) {
+ bytesRead += newBytesRead;
+ }
+ return newBytesRead;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return updateBytesRead(super.read());
+ }
+
+ // Note: FilterInputStream delegates read(byte[] bytes) to the below method,
+ // so we don't override it or else double count (CB-5631).
+ @Override
+ public int read(byte[] bytes, int offset, int count) throws IOException {
+ return updateBytesRead(super.read(bytes, offset, count));
+ }
+
+ public long getTotalRawBytesRead() {
+ return bytesRead;
+ }
+ }
+
+ @Override
+ public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ if (action.equals("upload") || action.equals("download")) {
+ String source = args.getString(0);
+ String target = args.getString(1);
+
+ if (action.equals("upload")) {
+ upload(source, target, args, callbackContext);
+ } else {
+ download(source, target, args, callbackContext);
+ }
+ return true;
+ } else if (action.equals("abort")) {
+ String objectId = args.getString(0);
+ abort(objectId);
+ callbackContext.success();
+ return true;
+ }
+ return false;
+ }
+
+ private static void addHeadersToRequest(URLConnection connection, JSONObject headers) {
+ try {
+ for (Iterator> iter = headers.keys(); iter.hasNext(); ) {
+ String headerKey = iter.next().toString();
+ JSONArray headerValues = headers.optJSONArray(headerKey);
+ if (headerValues == null) {
+ headerValues = new JSONArray();
+ headerValues.put(headers.getString(headerKey));
+ }
+ connection.setRequestProperty(headerKey, headerValues.getString(0));
+ for (int i = 1; i < headerValues.length(); ++i) {
+ connection.addRequestProperty(headerKey, headerValues.getString(i));
+ }
+ }
+ } catch (JSONException e1) {
+ // No headers to be manipulated!
+ }
+ }
+
+ private String getCookies(final String target) {
+ boolean gotCookie = false;
+ String cookie = null;
+ Class webViewClass = webView.getClass();
+ try {
+ Method gcmMethod = webViewClass.getMethod("getCookieManager");
+ Class iccmClass = gcmMethod.getReturnType();
+ Method gcMethod = iccmClass.getMethod("getCookie");
+
+ cookie = (String)gcMethod.invoke(
+ iccmClass.cast(
+ gcmMethod.invoke(webView)
+ ), target);
+
+ gotCookie = true;
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ } catch (ClassCastException e) {
+ }
+
+ if (!gotCookie) {
+ cookie = CookieManager.getInstance().getCookie(target);
+ }
+
+ return cookie;
+ }
+
+ /**
+ * Uploads the specified file to the server URL provided using an HTTP multipart request.
+ * @param source Full path of the file on the file system
+ * @param target URL of the server to receive the file
+ * @param args JSON Array of args
+ * @param callbackContext callback id for optional progress reports
+ *
+ * args[2] fileKey Name of file request parameter
+ * args[3] fileName File name to be used on server
+ * args[4] mimeType Describes file content type
+ * args[5] params key:value pairs of user-defined parameters
+ * @return FileUploadResult containing result of upload request
+ */
+ private void upload(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ Log.d(LOG_TAG, "upload " + source + " to " + target);
+
+ // Setup the options
+ final String fileKey = getArgument(args, 2, "file");
+ final String fileName = getArgument(args, 3, "image.jpg");
+ final String mimeType = getArgument(args, 4, "image/jpeg");
+ final JSONObject params = args.optJSONObject(5) == null ? new JSONObject() : args.optJSONObject(5);
+ final boolean trustEveryone = args.optBoolean(6);
+ // Always use chunked mode unless set to false as per API
+ final boolean chunkedMode = args.optBoolean(7) || args.isNull(7);
+ // Look for headers on the params map for backwards compatibility with older Cordova versions.
+ final JSONObject headers = args.optJSONObject(8) == null ? params.optJSONObject("headers") : args.optJSONObject(8);
+ final String objectId = args.getString(9);
+ final String httpMethod = getArgument(args, 10, "POST");
+
+ final CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ Log.d(LOG_TAG, "fileKey: " + fileKey);
+ Log.d(LOG_TAG, "fileName: " + fileName);
+ Log.d(LOG_TAG, "mimeType: " + mimeType);
+ Log.d(LOG_TAG, "params: " + params);
+ Log.d(LOG_TAG, "trustEveryone: " + trustEveryone);
+ Log.d(LOG_TAG, "chunkedMode: " + chunkedMode);
+ Log.d(LOG_TAG, "headers: " + headers);
+ Log.d(LOG_TAG, "objectId: " + objectId);
+ Log.d(LOG_TAG, "httpMethod: " + httpMethod);
+
+ final Uri targetUri = resourceApi.remapUri(Uri.parse(target));
+ // Accept a path or a URI for the source.
+ Uri tmpSrc = Uri.parse(source);
+ final Uri sourceUri = resourceApi.remapUri(
+ tmpSrc.getScheme() != null ? tmpSrc : Uri.fromFile(new File(source)));
+
+ int uriType = CordovaResourceApi.getUriType(targetUri);
+ final boolean useHttps = uriType == CordovaResourceApi.URI_TYPE_HTTPS;
+ if (uriType != CordovaResourceApi.URI_TYPE_HTTP && !useHttps) {
+ JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0, null);
+ Log.e(LOG_TAG, "Unsupported URI: " + targetUri);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ return;
+ }
+
+ final RequestContext context = new RequestContext(source, target, callbackContext);
+ synchronized (activeRequests) {
+ activeRequests.put(objectId, context);
+ }
+
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ if (context.aborted) {
+ return;
+ }
+ HttpURLConnection conn = null;
+ HostnameVerifier oldHostnameVerifier = null;
+ SSLSocketFactory oldSocketFactory = null;
+ int totalBytes = 0;
+ int fixedLength = -1;
+ try {
+ // Create return object
+ FileUploadResult result = new FileUploadResult();
+ FileProgressResult progress = new FileProgressResult();
+
+ //------------------ CLIENT REQUEST
+ // Open a HTTP connection to the URL based on protocol
+ conn = resourceApi.createHttpConnection(targetUri);
+ if (useHttps && trustEveryone) {
+ // Setup the HTTPS connection class to trust everyone
+ HttpsURLConnection https = (HttpsURLConnection)conn;
+ oldSocketFactory = trustAllHosts(https);
+ // Save the current hostnameVerifier
+ oldHostnameVerifier = https.getHostnameVerifier();
+ // Setup the connection not to verify hostnames
+ https.setHostnameVerifier(DO_NOT_VERIFY);
+ }
+
+ // Allow Inputs
+ conn.setDoInput(true);
+
+ // Allow Outputs
+ conn.setDoOutput(true);
+
+ // Don't use a cached copy.
+ conn.setUseCaches(false);
+
+ // Use a post method.
+ conn.setRequestMethod(httpMethod);
+ conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
+
+ // Set the cookies on the response
+ String cookie = getCookies(target);
+
+ if (cookie != null) {
+ conn.setRequestProperty("Cookie", cookie);
+ }
+
+ // Handle the other headers
+ if (headers != null) {
+ addHeadersToRequest(conn, headers);
+ }
+
+ /*
+ * Store the non-file portions of the multipart data as a string, so that we can add it
+ * to the contentSize, since it is part of the body of the HTTP request.
+ */
+ StringBuilder beforeData = new StringBuilder();
+ try {
+ for (Iterator> iter = params.keys(); iter.hasNext();) {
+ Object key = iter.next();
+ if(!String.valueOf(key).equals("headers"))
+ {
+ beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);
+ beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append('"');
+ beforeData.append(LINE_END).append(LINE_END);
+ beforeData.append(params.getString(key.toString()));
+ beforeData.append(LINE_END);
+ }
+ }
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ }
+
+ beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);
+ beforeData.append("Content-Disposition: form-data; name=\"").append(fileKey).append("\";");
+ beforeData.append(" filename=\"").append(fileName).append('"').append(LINE_END);
+ beforeData.append("Content-Type: ").append(mimeType).append(LINE_END).append(LINE_END);
+ byte[] beforeDataBytes = beforeData.toString().getBytes("UTF-8");
+ byte[] tailParamsBytes = (LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END).getBytes("UTF-8");
+
+
+ // Get a input stream of the file on the phone
+ OpenForReadResult readResult = resourceApi.openForRead(sourceUri);
+
+ int stringLength = beforeDataBytes.length + tailParamsBytes.length;
+ if (readResult.length >= 0) {
+ fixedLength = (int)readResult.length + stringLength;
+ progress.setLengthComputable(true);
+ progress.setTotal(fixedLength);
+ }
+ Log.d(LOG_TAG, "Content Length: " + fixedLength);
+ // setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices.
+ // http://code.google.com/p/android/issues/detail?id=3164
+ // It also causes OOM if HTTPS is used, even on newer devices.
+ boolean useChunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps);
+ useChunkedMode = useChunkedMode || (fixedLength == -1);
+
+ if (useChunkedMode) {
+ conn.setChunkedStreamingMode(MAX_BUFFER_SIZE);
+ // Although setChunkedStreamingMode sets this header, setting it explicitly here works
+ // around an OutOfMemoryException when using https.
+ conn.setRequestProperty("Transfer-Encoding", "chunked");
+ } else {
+ conn.setFixedLengthStreamingMode(fixedLength);
+ }
+
+ conn.connect();
+
+ OutputStream sendStream = null;
+ try {
+ sendStream = conn.getOutputStream();
+ synchronized (context) {
+ if (context.aborted) {
+ return;
+ }
+ context.connection = conn;
+ }
+ //We don't want to change encoding, we just want this to write for all Unicode.
+ sendStream.write(beforeDataBytes);
+ totalBytes += beforeDataBytes.length;
+
+ // create a buffer of maximum size
+ int bytesAvailable = readResult.inputStream.available();
+ int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
+ byte[] buffer = new byte[bufferSize];
+
+ // read file and write it into form...
+ int bytesRead = readResult.inputStream.read(buffer, 0, bufferSize);
+
+ long prevBytesRead = 0;
+ while (bytesRead > 0) {
+ result.setBytesSent(totalBytes);
+ sendStream.write(buffer, 0, bytesRead);
+ totalBytes += bytesRead;
+ if (totalBytes > prevBytesRead + 102400) {
+ prevBytesRead = totalBytes;
+ Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes");
+ }
+ bytesAvailable = readResult.inputStream.available();
+ bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
+ bytesRead = readResult.inputStream.read(buffer, 0, bufferSize);
+
+ // Send a progress event.
+ progress.setLoaded(totalBytes);
+ PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
+ progressResult.setKeepCallback(true);
+ context.sendPluginResult(progressResult);
+ }
+
+ // send multipart form data necessary after file data...
+ sendStream.write(tailParamsBytes);
+ totalBytes += tailParamsBytes.length;
+ sendStream.flush();
+ } finally {
+ safeClose(readResult.inputStream);
+ safeClose(sendStream);
+ }
+ synchronized (context) {
+ context.connection = null;
+ }
+ Log.d(LOG_TAG, "Sent " + totalBytes + " of " + fixedLength);
+
+ //------------------ read the SERVER RESPONSE
+ String responseString;
+ int responseCode = conn.getResponseCode();
+ Log.d(LOG_TAG, "response code: " + responseCode);
+ Log.d(LOG_TAG, "response headers: " + conn.getHeaderFields());
+ TrackingInputStream inStream = null;
+ try {
+ inStream = getInputStream(conn);
+ synchronized (context) {
+ if (context.aborted) {
+ return;
+ }
+ context.connection = conn;
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, conn.getContentLength()));
+ byte[] buffer = new byte[1024];
+ int bytesRead = 0;
+ // write bytes to file
+ while ((bytesRead = inStream.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ }
+ responseString = out.toString("UTF-8");
+ } finally {
+ synchronized (context) {
+ context.connection = null;
+ }
+ safeClose(inStream);
+ }
+
+ Log.d(LOG_TAG, "got response from server");
+ Log.d(LOG_TAG, responseString.substring(0, Math.min(256, responseString.length())));
+
+ // send request and retrieve response
+ result.setResponseCode(responseCode);
+ result.setResponse(responseString);
+
+ context.sendPluginResult(new PluginResult(PluginResult.Status.OK, result.toJSONObject()));
+ } catch (FileNotFoundException e) {
+ JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn, e);
+ Log.e(LOG_TAG, error.toString(), e);
+ context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ } catch (IOException e) {
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn, e);
+ Log.e(LOG_TAG, error.toString(), e);
+ Log.e(LOG_TAG, "Failed after uploading " + totalBytes + " of " + fixedLength + " bytes.");
+ context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ context.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ } catch (Throwable t) {
+ // Shouldn't happen, but will
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn, t);
+ Log.e(LOG_TAG, error.toString(), t);
+ context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ } finally {
+ synchronized (activeRequests) {
+ activeRequests.remove(objectId);
+ }
+
+ if (conn != null) {
+ // Revert back to the proper verifier and socket factories
+ // Revert back to the proper verifier and socket factories
+ if (trustEveryone && useHttps) {
+ HttpsURLConnection https = (HttpsURLConnection) conn;
+ https.setHostnameVerifier(oldHostnameVerifier);
+ https.setSSLSocketFactory(oldSocketFactory);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ private static void safeClose(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ private static TrackingInputStream getInputStream(URLConnection conn) throws IOException {
+ String encoding = conn.getContentEncoding();
+ if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
+ return new TrackingGZIPInputStream(new ExposedGZIPInputStream(conn.getInputStream()));
+ }
+ return new SimpleTrackingInputStream(conn.getInputStream());
+ }
+
+ // always verify the host - don't check for certificate
+ private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ };
+ // Create a trust manager that does not validate certificate chains
+ private static final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return new java.security.cert.X509Certificate[] {};
+ }
+
+ public void checkClientTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+ } };
+
+ /**
+ * This function will install a trust manager that will blindly trust all SSL
+ * certificates. The reason this code is being added is to enable developers
+ * to do development using self signed SSL certificates on their web server.
+ *
+ * The standard HttpsURLConnection class will throw an exception on self
+ * signed certificates if this code is not run.
+ */
+ private static SSLSocketFactory trustAllHosts(HttpsURLConnection connection) {
+ // Install the all-trusting trust manager
+ SSLSocketFactory oldFactory = connection.getSSLSocketFactory();
+ try {
+ // Install our all trusting manager
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ SSLSocketFactory newFactory = sc.getSocketFactory();
+ connection.setSSLSocketFactory(newFactory);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ }
+ return oldFactory;
+ }
+
+ private static JSONObject createFileTransferError(int errorCode, String source, String target, URLConnection connection, Throwable throwable) {
+
+ int httpStatus = 0;
+ StringBuilder bodyBuilder = new StringBuilder();
+ String body = null;
+ if (connection != null) {
+ try {
+ if (connection instanceof HttpURLConnection) {
+ httpStatus = ((HttpURLConnection)connection).getResponseCode();
+ InputStream err = ((HttpURLConnection) connection).getErrorStream();
+ if(err != null)
+ {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(err, "UTF-8"));
+ try {
+ String line = reader.readLine();
+ while(line != null) {
+ bodyBuilder.append(line);
+ line = reader.readLine();
+ if(line != null) {
+ bodyBuilder.append('\n');
+ }
+ }
+ body = bodyBuilder.toString();
+ } finally {
+ reader.close();
+ }
+ }
+ }
+ // IOException can leave connection object in a bad state, so catch all exceptions.
+ } catch (Throwable e) {
+ Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e);
+ }
+ }
+
+ return createFileTransferError(errorCode, source, target, body, httpStatus, throwable);
+ }
+
+ /**
+ * Create an error object based on the passed in errorCode
+ * @param errorCode the error
+ * @return JSONObject containing the error
+ */
+ private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus, Throwable throwable) {
+ JSONObject error = null;
+ try {
+ error = new JSONObject();
+ error.put("code", errorCode);
+ error.put("source", source);
+ error.put("target", target);
+ if(body != null)
+ {
+ error.put("body", body);
+ }
+ if (httpStatus != null) {
+ error.put("http_status", httpStatus);
+ }
+ if (throwable != null) {
+ String msg = throwable.getMessage();
+ if (msg == null || "".equals(msg)) {
+ msg = throwable.toString();
+ }
+ error.put("exception", msg);
+ }
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ }
+ return error;
+ }
+
+ /**
+ * Convenience method to read a parameter from the list of JSON args.
+ * @param args the args passed to the Plugin
+ * @param position the position to retrieve the arg from
+ * @param defaultString the default to be used if the arg does not exist
+ * @return String with the retrieved value
+ */
+ private static String getArgument(JSONArray args, int position, String defaultString) {
+ String arg = defaultString;
+ if (args.length() > position) {
+ arg = args.optString(position);
+ if (arg == null || "null".equals(arg)) {
+ arg = defaultString;
+ }
+ }
+ return arg;
+ }
+
+ /**
+ * Downloads a file form a given URL and saves it to the specified directory.
+ *
+ * @param source URL of the server to receive the file
+ * @param target Full path of the file on the file system
+ */
+ private void download(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ Log.d(LOG_TAG, "download " + source + " to " + target);
+
+ final CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ final boolean trustEveryone = args.optBoolean(2);
+ final String objectId = args.getString(3);
+ final JSONObject headers = args.optJSONObject(4);
+
+ final Uri sourceUri = resourceApi.remapUri(Uri.parse(source));
+ // Accept a path or a URI for the source.
+ Uri tmpTarget = Uri.parse(target);
+ final Uri targetUri = resourceApi.remapUri(
+ tmpTarget.getScheme() != null ? tmpTarget : Uri.fromFile(new File(target)));
+
+ int uriType = CordovaResourceApi.getUriType(sourceUri);
+ final boolean useHttps = uriType == CordovaResourceApi.URI_TYPE_HTTPS;
+ final boolean isLocalTransfer = !useHttps && uriType != CordovaResourceApi.URI_TYPE_HTTP;
+ if (uriType == CordovaResourceApi.URI_TYPE_UNKNOWN) {
+ JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0, null);
+ Log.e(LOG_TAG, "Unsupported URI: " + targetUri);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ return;
+ }
+
+ /* This code exists for compatibility between 3.x and 4.x versions of Cordova.
+ * Previously the CordovaWebView class had a method, getWhitelist, which would
+ * return a Whitelist object. Since the fixed whitelist is removed in Cordova 4.x,
+ * the correct call now is to shouldAllowRequest from the plugin manager.
+ */
+ Boolean shouldAllowRequest = null;
+ if (isLocalTransfer) {
+ shouldAllowRequest = true;
+ }
+ if (shouldAllowRequest == null) {
+ try {
+ Method gwl = webView.getClass().getMethod("getWhitelist");
+ Whitelist whitelist = (Whitelist)gwl.invoke(webView);
+ shouldAllowRequest = whitelist.isUrlWhiteListed(source);
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ }
+ if (shouldAllowRequest == null) {
+ try {
+ Method gpm = webView.getClass().getMethod("getPluginManager");
+ PluginManager pm = (PluginManager)gpm.invoke(webView);
+ Method san = pm.getClass().getMethod("shouldAllowRequest", String.class);
+ shouldAllowRequest = (Boolean)san.invoke(pm, source);
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ }
+
+ if (!Boolean.TRUE.equals(shouldAllowRequest)) {
+ Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401, null);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
+ return;
+ }
+
+
+ final RequestContext context = new RequestContext(source, target, callbackContext);
+ synchronized (activeRequests) {
+ activeRequests.put(objectId, context);
+ }
+
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ if (context.aborted) {
+ return;
+ }
+ HttpURLConnection connection = null;
+ HostnameVerifier oldHostnameVerifier = null;
+ SSLSocketFactory oldSocketFactory = null;
+ File file = null;
+ PluginResult result = null;
+ TrackingInputStream inputStream = null;
+ boolean cached = false;
+
+ OutputStream outputStream = null;
+ try {
+ OpenForReadResult readResult = null;
+
+ file = resourceApi.mapUriToFile(targetUri);
+ context.targetFile = file;
+
+ Log.d(LOG_TAG, "Download file:" + sourceUri);
+
+ FileProgressResult progress = new FileProgressResult();
+
+ if (isLocalTransfer) {
+ readResult = resourceApi.openForRead(sourceUri);
+ if (readResult.length != -1) {
+ progress.setLengthComputable(true);
+ progress.setTotal(readResult.length);
+ }
+ inputStream = new SimpleTrackingInputStream(readResult.inputStream);
+ } else {
+ // connect to server
+ // Open a HTTP connection to the URL based on protocol
+ connection = resourceApi.createHttpConnection(sourceUri);
+ if (useHttps && trustEveryone) {
+ // Setup the HTTPS connection class to trust everyone
+ HttpsURLConnection https = (HttpsURLConnection)connection;
+ oldSocketFactory = trustAllHosts(https);
+ // Save the current hostnameVerifier
+ oldHostnameVerifier = https.getHostnameVerifier();
+ // Setup the connection not to verify hostnames
+ https.setHostnameVerifier(DO_NOT_VERIFY);
+ }
+
+ connection.setRequestMethod("GET");
+
+ // TODO: Make OkHttp use this CookieManager by default.
+ String cookie = getCookies(sourceUri.toString());
+
+ if(cookie != null)
+ {
+ connection.setRequestProperty("cookie", cookie);
+ }
+
+ // This must be explicitly set for gzip progress tracking to work.
+ connection.setRequestProperty("Accept-Encoding", "gzip");
+
+ // Handle the other headers
+ if (headers != null) {
+ addHeadersToRequest(connection, headers);
+ }
+
+ connection.connect();
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
+ cached = true;
+ connection.disconnect();
+ Log.d(LOG_TAG, "Resource not modified: " + source);
+ JSONObject error = createFileTransferError(NOT_MODIFIED_ERR, source, target, connection, null);
+ result = new PluginResult(PluginResult.Status.ERROR, error);
+ } else {
+ if (connection.getContentEncoding() == null || connection.getContentEncoding().equalsIgnoreCase("gzip")) {
+ // Only trust content-length header if we understand
+ // the encoding -- identity or gzip
+ if (connection.getContentLength() != -1) {
+ progress.setLengthComputable(true);
+ progress.setTotal(connection.getContentLength());
+ }
+ }
+ inputStream = getInputStream(connection);
+ }
+ }
+
+ if (!cached) {
+ try {
+ synchronized (context) {
+ if (context.aborted) {
+ return;
+ }
+ context.connection = connection;
+ }
+
+ // write bytes to file
+ byte[] buffer = new byte[MAX_BUFFER_SIZE];
+ int bytesRead = 0;
+ outputStream = resourceApi.openOutputStream(targetUri);
+ while ((bytesRead = inputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, bytesRead);
+ // Send a progress event.
+ progress.setLoaded(inputStream.getTotalRawBytesRead());
+ PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
+ progressResult.setKeepCallback(true);
+ context.sendPluginResult(progressResult);
+ }
+ } finally {
+ synchronized (context) {
+ context.connection = null;
+ }
+ safeClose(inputStream);
+ safeClose(outputStream);
+ }
+
+ Log.d(LOG_TAG, "Saved file: " + target);
+
+
+ // create FileEntry object
+ Class webViewClass = webView.getClass();
+ PluginManager pm = null;
+ try {
+ Method gpm = webViewClass.getMethod("getPluginManager");
+ pm = (PluginManager) gpm.invoke(webView);
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ if (pm == null) {
+ try {
+ Field pmf = webViewClass.getField("pluginManager");
+ pm = (PluginManager)pmf.get(webView);
+ } catch (NoSuchFieldException e) {
+ } catch (IllegalAccessException e) {
+ }
+ }
+ file = resourceApi.mapUriToFile(targetUri);
+ context.targetFile = file;
+ FileUtils filePlugin = (FileUtils) pm.getPlugin("File");
+ if (filePlugin != null) {
+ JSONObject fileEntry = filePlugin.getEntryForFile(file);
+ if (fileEntry != null) {
+ result = new PluginResult(PluginResult.Status.OK, fileEntry);
+ } else {
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection, null);
+ Log.e(LOG_TAG, "File plugin cannot represent download path");
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ }
+ } else {
+ Log.e(LOG_TAG, "File plugin not found; cannot save downloaded file");
+ result = new PluginResult(PluginResult.Status.ERROR, "File plugin not found; cannot save downloaded file");
+ }
+ }
+
+ } catch (FileNotFoundException e) {
+ JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection, e);
+ Log.e(LOG_TAG, error.toString(), e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } catch (IOException e) {
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection, e);
+ Log.e(LOG_TAG, error.toString(), e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ result = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+ } catch (Throwable e) {
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection, e);
+ Log.e(LOG_TAG, error.toString(), e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } finally {
+ synchronized (activeRequests) {
+ activeRequests.remove(objectId);
+ }
+
+ if (connection != null) {
+ // Revert back to the proper verifier and socket factories
+ if (trustEveryone && useHttps) {
+ HttpsURLConnection https = (HttpsURLConnection) connection;
+ https.setHostnameVerifier(oldHostnameVerifier);
+ https.setSSLSocketFactory(oldSocketFactory);
+ }
+ }
+
+ if (result == null) {
+ result = new PluginResult(PluginResult.Status.ERROR, createFileTransferError(CONNECTION_ERR, source, target, connection, null));
+ }
+ // Remove incomplete download.
+ if (!cached && result.getStatus() != PluginResult.Status.OK.ordinal() && file != null) {
+ file.delete();
+ }
+ context.sendPluginResult(result);
+ }
+ }
+ });
+ }
+
+ /**
+ * Abort an ongoing upload or download.
+ */
+ private void abort(String objectId) {
+ final RequestContext context;
+ synchronized (activeRequests) {
+ context = activeRequests.remove(objectId);
+ }
+ if (context != null) {
+ // Closing the streams can block, so execute on a background thread.
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ synchronized (context) {
+ File file = context.targetFile;
+ if (file != null) {
+ file.delete();
+ }
+ // Trigger the abort callback immediately to minimize latency between it and abort() being called.
+ JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, null, -1, null);
+ context.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error));
+ context.aborted = true;
+ if (context.connection != null) {
+ context.connection.disconnect();
+ }
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/plugins/org.apache.cordova.file-transfer/src/android/FileUploadResult.java b/plugins/org.apache.cordova.file-transfer/src/android/FileUploadResult.java
new file mode 100644
index 0000000..c24ea78
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/android/FileUploadResult.java
@@ -0,0 +1,73 @@
+/*
+ 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.
+*/
+package org.apache.cordova.filetransfer;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Encapsulates the result and/or status of uploading a file to a remote server.
+ */
+public class FileUploadResult {
+
+ private long bytesSent = 0; // bytes sent
+ private int responseCode = -1; // HTTP response code
+ private String response = null; // HTTP response
+ private String objectId = null; // FileTransfer object id
+
+ public long getBytesSent() {
+ return bytesSent;
+ }
+
+ public void setBytesSent(long bytes) {
+ this.bytesSent = bytes;
+ }
+
+ public int getResponseCode() {
+ return responseCode;
+ }
+
+ public void setResponseCode(int responseCode) {
+ this.responseCode = responseCode;
+ }
+
+ public String getResponse() {
+ return response;
+ }
+
+ public void setResponse(String response) {
+ this.response = response;
+ }
+
+ public String getObjectId() {
+ return objectId;
+ }
+
+ public void setObjectId(String objectId) {
+ this.objectId = objectId;
+ }
+
+ public JSONObject toJSONObject() throws JSONException {
+ return new JSONObject(
+ "{bytesSent:" + bytesSent +
+ ",responseCode:" + responseCode +
+ ",response:" + JSONObject.quote(response) +
+ ",objectId:" + JSONObject.quote(objectId) + "}");
+ }
+}
diff --git a/plugins/org.apache.cordova.file-transfer/src/ios/CDVFileTransfer.h b/plugins/org.apache.cordova.file-transfer/src/ios/CDVFileTransfer.h
new file mode 100644
index 0000000..9c4bcf1
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/ios/CDVFileTransfer.h
@@ -0,0 +1,87 @@
+/*
+ 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.
+ */
+
+#import
+#import
+#import "CDVFile.h"
+
+enum CDVFileTransferError {
+ FILE_NOT_FOUND_ERR = 1,
+ INVALID_URL_ERR = 2,
+ CONNECTION_ERR = 3,
+ CONNECTION_ABORTED = 4
+};
+typedef int CDVFileTransferError;
+
+enum CDVFileTransferDirection {
+ CDV_TRANSFER_UPLOAD = 1,
+ CDV_TRANSFER_DOWNLOAD = 2,
+};
+typedef int CDVFileTransferDirection;
+
+// Magic value within the options dict used to set a cookie.
+extern NSString* const kOptionsKeyCookie;
+
+@interface CDVFileTransfer : CDVPlugin {}
+
+- (void)upload:(CDVInvokedUrlCommand*)command;
+- (void)download:(CDVInvokedUrlCommand*)command;
+- (NSString*)escapePathComponentForUrlString:(NSString*)urlString;
+
+// Visible for testing.
+- (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData:(NSData*)fileData;
+- (NSMutableDictionary*)createFileTransferError:(int)code AndSource:(NSString*)source AndTarget:(NSString*)target;
+
+- (NSMutableDictionary*)createFileTransferError:(int)code
+ AndSource:(NSString*)source
+ AndTarget:(NSString*)target
+ AndHttpStatus:(int)httpStatus
+ AndBody:(NSString*)body;
+@property (nonatomic, strong) NSOperationQueue* queue;
+@property (readonly) NSMutableDictionary* activeTransfers;
+@end
+
+@class CDVFileTransferEntityLengthRequest;
+
+@interface CDVFileTransferDelegate : NSObject {}
+
+- (void)updateBytesExpected:(long long)newBytesExpected;
+- (void)cancelTransfer:(NSURLConnection*)connection;
+
+@property (strong) NSMutableData* responseData; // atomic
+@property (nonatomic, strong) NSDictionary* responseHeaders;
+@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskID;
+@property (nonatomic, strong) CDVFileTransfer* command;
+@property (nonatomic, assign) CDVFileTransferDirection direction;
+@property (nonatomic, strong) NSURLConnection* connection;
+@property (nonatomic, copy) NSString* callbackId;
+@property (nonatomic, copy) NSString* objectId;
+@property (nonatomic, copy) NSString* source;
+@property (nonatomic, copy) NSString* target;
+@property (nonatomic, copy) NSURL* targetURL;
+@property (nonatomic, copy) NSString* mimeType;
+@property (assign) int responseCode; // atomic
+@property (nonatomic, assign) long long bytesTransfered;
+@property (nonatomic, assign) long long bytesExpected;
+@property (nonatomic, assign) BOOL trustAllHosts;
+@property (strong) NSFileHandle* targetFileHandle;
+@property (nonatomic, strong) CDVFileTransferEntityLengthRequest* entityLengthRequest;
+@property (nonatomic, strong) CDVFile *filePlugin;
+
+@end
diff --git a/plugins/org.apache.cordova.file-transfer/src/ios/CDVFileTransfer.m b/plugins/org.apache.cordova.file-transfer/src/ios/CDVFileTransfer.m
new file mode 100644
index 0000000..3031159
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/ios/CDVFileTransfer.m
@@ -0,0 +1,790 @@
+/*
+ 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.
+ */
+
+#import
+#import "CDVFileTransfer.h"
+#import "CDVLocalFilesystem.h"
+
+#import
+#import
+#import
+#import
+
+#ifndef DLog
+#ifdef DEBUG
+ #define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
+#else
+ #define DLog(...)
+#endif
+#endif
+
+@interface CDVFileTransfer ()
+// Sets the requests headers for the request.
+- (void)applyRequestHeaders:(NSDictionary*)headers toRequest:(NSMutableURLRequest*)req;
+// Creates a delegate to handle an upload.
+- (CDVFileTransferDelegate*)delegateForUploadCommand:(CDVInvokedUrlCommand*)command;
+// Creates an NSData* for the file for the given upload arguments.
+- (void)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command;
+@end
+
+// Buffer size to use for streaming uploads.
+static const NSUInteger kStreamBufferSize = 32768;
+// Magic value within the options dict used to set a cookie.
+NSString* const kOptionsKeyCookie = @"__cookie";
+// Form boundary for multi-part requests.
+NSString* const kFormBoundary = @"+++++org.apache.cordova.formBoundary";
+
+// Writes the given data to the stream in a blocking way.
+// If successful, returns bytesToWrite.
+// If the stream was closed on the other end, returns 0.
+// If there was an error, returns -1.
+static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
+{
+ UInt8* bytes = (UInt8*)[data bytes];
+ long long bytesToWrite = [data length];
+ long long totalBytesWritten = 0;
+
+ while (totalBytesWritten < bytesToWrite) {
+ CFIndex result = CFWriteStreamWrite(stream,
+ bytes + totalBytesWritten,
+ bytesToWrite - totalBytesWritten);
+ if (result < 0) {
+ CFStreamError error = CFWriteStreamGetError(stream);
+ NSLog(@"WriteStreamError domain: %ld error: %ld", error.domain, (long)error.error);
+ return result;
+ } else if (result == 0) {
+ return result;
+ }
+ totalBytesWritten += result;
+ }
+
+ return totalBytesWritten;
+}
+
+@implementation CDVFileTransfer
+@synthesize activeTransfers;
+
+- (void)pluginInitialize {
+ activeTransfers = [[NSMutableDictionary alloc] init];
+}
+
+- (NSString*)escapePathComponentForUrlString:(NSString*)urlString
+{
+ NSRange schemeAndHostRange = [urlString rangeOfString:@"://.*?/" options:NSRegularExpressionSearch];
+
+ if (schemeAndHostRange.length == 0) {
+ return urlString;
+ }
+
+ NSInteger schemeAndHostEndIndex = NSMaxRange(schemeAndHostRange);
+ NSString* schemeAndHost = [urlString substringToIndex:schemeAndHostEndIndex];
+ NSString* pathComponent = [urlString substringFromIndex:schemeAndHostEndIndex];
+ pathComponent = [pathComponent stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+
+ return [schemeAndHost stringByAppendingString:pathComponent];
+}
+
+- (void)applyRequestHeaders:(NSDictionary*)headers toRequest:(NSMutableURLRequest*)req
+{
+ [req setValue:@"XMLHttpRequest" forHTTPHeaderField:@"X-Requested-With"];
+
+ NSString* userAgent = [self.commandDelegate userAgent];
+ if (userAgent) {
+ [req setValue:userAgent forHTTPHeaderField:@"User-Agent"];
+ }
+
+ for (NSString* headerName in headers) {
+ id value = [headers objectForKey:headerName];
+ if (!value || (value == [NSNull null])) {
+ value = @"null";
+ }
+
+ // First, remove an existing header if one exists.
+ [req setValue:nil forHTTPHeaderField:headerName];
+
+ if (![value isKindOfClass:[NSArray class]]) {
+ value = [NSArray arrayWithObject:value];
+ }
+
+ // Then, append all header values.
+ for (id __strong subValue in value) {
+ // Convert from an NSNumber -> NSString.
+ if ([subValue respondsToSelector:@selector(stringValue)]) {
+ subValue = [subValue stringValue];
+ }
+ if ([subValue isKindOfClass:[NSString class]]) {
+ [req addValue:subValue forHTTPHeaderField:headerName];
+ }
+ }
+ }
+}
+
+- (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData:(NSData*)fileData
+{
+ // arguments order from js: [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode]
+ // however, params is a JavaScript object and during marshalling is put into the options dict,
+ // thus debug and chunkedMode are the 6th and 7th arguments
+ NSString* target = [command argumentAtIndex:0];
+ NSString* server = [command argumentAtIndex:1];
+ NSString* fileKey = [command argumentAtIndex:2 withDefault:@"file"];
+ NSString* fileName = [command argumentAtIndex:3 withDefault:@"no-filename"];
+ NSString* mimeType = [command argumentAtIndex:4 withDefault:nil];
+ NSDictionary* options = [command argumentAtIndex:5 withDefault:nil];
+ // BOOL trustAllHosts = [[command argumentAtIndex:6 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs
+ BOOL chunkedMode = [[command argumentAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue];
+ NSDictionary* headers = [command argumentAtIndex:8 withDefault:nil];
+ // Allow alternative http method, default to POST. JS side checks
+ // for allowed methods, currently PUT or POST (forces POST for
+ // unrecognised values)
+ NSString* httpMethod = [command argumentAtIndex:10 withDefault:@"POST"];
+ CDVPluginResult* result = nil;
+ CDVFileTransferError errorCode = 0;
+
+ // NSURL does not accepts URLs with spaces in the path. We escape the path in order
+ // to be more lenient.
+ NSURL* url = [NSURL URLWithString:server];
+
+ if (!url) {
+ errorCode = INVALID_URL_ERR;
+ NSLog(@"File Transfer Error: Invalid server URL %@", server);
+ } else if (!fileData) {
+ errorCode = FILE_NOT_FOUND_ERR;
+ }
+
+ if (errorCode > 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:errorCode AndSource:target AndTarget:server]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return nil;
+ }
+
+ NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
+
+ [req setHTTPMethod:httpMethod];
+
+ // Magic value to set a cookie
+ if ([options objectForKey:kOptionsKeyCookie]) {
+ [req setValue:[options objectForKey:kOptionsKeyCookie] forHTTPHeaderField:@"Cookie"];
+ [req setHTTPShouldHandleCookies:NO];
+ }
+
+ NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kFormBoundary];
+ [req setValue:contentType forHTTPHeaderField:@"Content-Type"];
+ [self applyRequestHeaders:headers toRequest:req];
+
+ NSData* formBoundaryData = [[NSString stringWithFormat:@"--%@\r\n", kFormBoundary] dataUsingEncoding:NSUTF8StringEncoding];
+ NSMutableData* postBodyBeforeFile = [NSMutableData data];
+
+ for (NSString* key in options) {
+ id val = [options objectForKey:key];
+ if (!val || (val == [NSNull null]) || [key isEqualToString:kOptionsKeyCookie]) {
+ continue;
+ }
+ // if it responds to stringValue selector (eg NSNumber) get the NSString
+ if ([val respondsToSelector:@selector(stringValue)]) {
+ val = [val stringValue];
+ }
+ // finally, check whether it is a NSString (for dataUsingEncoding selector below)
+ if (![val isKindOfClass:[NSString class]]) {
+ continue;
+ }
+
+ [postBodyBeforeFile appendData:formBoundaryData];
+ [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
+ [postBodyBeforeFile appendData:[val dataUsingEncoding:NSUTF8StringEncoding]];
+ [postBodyBeforeFile appendData:[@"\r\n" dataUsingEncoding : NSUTF8StringEncoding]];
+ }
+
+ [postBodyBeforeFile appendData:formBoundaryData];
+ [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fileKey, fileName] dataUsingEncoding:NSUTF8StringEncoding]];
+ if (mimeType != nil) {
+ [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n", mimeType] dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+ [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Length: %ld\r\n\r\n", (long)[fileData length]] dataUsingEncoding:NSUTF8StringEncoding]];
+
+ DLog(@"fileData length: %d", [fileData length]);
+ NSData* postBodyAfterFile = [[NSString stringWithFormat:@"\r\n--%@--\r\n", kFormBoundary] dataUsingEncoding:NSUTF8StringEncoding];
+
+ long long totalPayloadLength = [postBodyBeforeFile length] + [fileData length] + [postBodyAfterFile length];
+ [req setValue:[[NSNumber numberWithLongLong:totalPayloadLength] stringValue] forHTTPHeaderField:@"Content-Length"];
+
+ if (chunkedMode) {
+ CFReadStreamRef readStream = NULL;
+ CFWriteStreamRef writeStream = NULL;
+ CFStreamCreateBoundPair(NULL, &readStream, &writeStream, kStreamBufferSize);
+ [req setHTTPBodyStream:CFBridgingRelease(readStream)];
+
+ [self.commandDelegate runInBackground:^{
+ if (CFWriteStreamOpen(writeStream)) {
+ NSData* chunks[] = {postBodyBeforeFile, fileData, postBodyAfterFile};
+ int numChunks = sizeof(chunks) / sizeof(chunks[0]);
+
+ for (int i = 0; i < numChunks; ++i) {
+ CFIndex result = WriteDataToStream(chunks[i], writeStream);
+ if (result <= 0) {
+ break;
+ }
+ }
+ } else {
+ NSLog(@"FileTransfer: Failed to open writeStream");
+ }
+ CFWriteStreamClose(writeStream);
+ CFRelease(writeStream);
+ }];
+ } else {
+ [postBodyBeforeFile appendData:fileData];
+ [postBodyBeforeFile appendData:postBodyAfterFile];
+ [req setHTTPBody:postBodyBeforeFile];
+ }
+ return req;
+}
+
+- (CDVFileTransferDelegate*)delegateForUploadCommand:(CDVInvokedUrlCommand*)command
+{
+ NSString* source = [command argumentAtIndex:0];
+ NSString* server = [command argumentAtIndex:1];
+ BOOL trustAllHosts = [[command argumentAtIndex:6 withDefault:[NSNumber numberWithBool:NO]] boolValue]; // allow self-signed certs
+ NSString* objectId = [command argumentAtIndex:9];
+
+ CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init];
+
+ delegate.command = self;
+ delegate.callbackId = command.callbackId;
+ delegate.direction = CDV_TRANSFER_UPLOAD;
+ delegate.objectId = objectId;
+ delegate.source = source;
+ delegate.target = server;
+ delegate.trustAllHosts = trustAllHosts;
+ delegate.filePlugin = [self.commandDelegate getCommandInstance:@"File"];
+
+ return delegate;
+}
+
+- (void)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command
+{
+ NSString* source = (NSString*)[command argumentAtIndex:0];
+ NSString* server = [command argumentAtIndex:1];
+ NSError* __autoreleasing err = nil;
+
+ CDVFilesystemURL *sourceURL = [CDVFilesystemURL fileSystemURLWithString:source];
+ NSObject *fs;
+ if (sourceURL) {
+ // Try to get a CDVFileSystem which will handle this file.
+ // This requires talking to the current CDVFile plugin.
+ fs = [[self.commandDelegate getCommandInstance:@"File"] filesystemForURL:sourceURL];
+ }
+ if (fs) {
+ [fs readFileAtURL:sourceURL start:0 end:-1 callback:^(NSData *fileData, NSString *mimeType, CDVFileError err) {
+ if (err) {
+ // We couldn't find the asset. Send the appropriate error.
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:NOT_FOUND_ERR AndSource:source AndTarget:server]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ } else {
+ [self uploadData:fileData command:command];
+ }
+ }];
+ return;
+ } else {
+ // Extract the path part out of a file: URL.
+ NSString* filePath = [source hasPrefix:@"/"] ? [source copy] : [(NSURL *)[NSURL URLWithString:source] path];
+ if (filePath == nil) {
+ // We couldn't find the asset. Send the appropriate error.
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:NOT_FOUND_ERR AndSource:source AndTarget:server]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ // Memory map the file so that it can be read efficiently even if it is large.
+ NSData* fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&err];
+
+ if (err != nil) {
+ NSLog(@"Error opening file %@: %@", source, err);
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:NOT_FOUND_ERR AndSource:source AndTarget:server]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ } else {
+ [self uploadData:fileData command:command];
+ }
+ }
+}
+
+- (void)upload:(CDVInvokedUrlCommand*)command
+{
+ // fileData and req are split into helper functions to ease the unit testing of delegateForUpload.
+ // First, get the file data. This method will call `uploadData:command`.
+ [self fileDataForUploadCommand:command];
+}
+
+- (void)uploadData:(NSData*)fileData command:(CDVInvokedUrlCommand*)command
+{
+ NSURLRequest* req = [self requestForUploadCommand:command fileData:fileData];
+
+ if (req == nil) {
+ return;
+ }
+ CDVFileTransferDelegate* delegate = [self delegateForUploadCommand:command];
+ delegate.connection = [[NSURLConnection alloc] initWithRequest:req delegate:delegate startImmediately:NO];
+ if (self.queue == nil) {
+ self.queue = [[NSOperationQueue alloc] init];
+ }
+ [delegate.connection setDelegateQueue:self.queue];
+
+ // sets a background task ID for the transfer object.
+ delegate.backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
+ [delegate cancelTransfer:delegate.connection];
+ }];
+
+ @synchronized (activeTransfers) {
+ activeTransfers[delegate.objectId] = delegate;
+ }
+ [delegate.connection start];
+}
+
+- (void)abort:(CDVInvokedUrlCommand*)command
+{
+ NSString* objectId = [command argumentAtIndex:0];
+
+ @synchronized (activeTransfers) {
+ CDVFileTransferDelegate* delegate = activeTransfers[objectId];
+ if (delegate != nil) {
+ [delegate cancelTransfer:delegate.connection];
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:CONNECTION_ABORTED AndSource:delegate.source AndTarget:delegate.target]];
+ [self.commandDelegate sendPluginResult:result callbackId:delegate.callbackId];
+ }
+ }
+}
+
+- (void)download:(CDVInvokedUrlCommand*)command
+{
+ DLog(@"File Transfer downloading file...");
+ NSString* source = [command argumentAtIndex:0];
+ NSString* target = [command argumentAtIndex:1];
+ BOOL trustAllHosts = [[command argumentAtIndex:2 withDefault:[NSNumber numberWithBool:NO]] boolValue]; // allow self-signed certs
+ NSString* objectId = [command argumentAtIndex:3];
+ NSDictionary* headers = [command argumentAtIndex:4 withDefault:nil];
+
+ CDVPluginResult* result = nil;
+ CDVFileTransferError errorCode = 0;
+
+ NSURL* targetURL;
+
+ if ([target hasPrefix:@"/"]) {
+ /* Backwards-compatibility:
+ * Check here to see if it looks like the user passed in a raw filesystem path. (Perhaps they had the path saved, and were previously using it with the old version of File). If so, normalize it by removing empty path segments, and check with File to see if any of the installed filesystems will handle it. If so, then we will end up with a filesystem url to use for the remainder of this operation.
+ */
+ target = [target stringByReplacingOccurrencesOfString:@"//" withString:@"/"];
+ targetURL = [[self.commandDelegate getCommandInstance:@"File"] fileSystemURLforLocalPath:target].url;
+ } else {
+ targetURL = [NSURL URLWithString:target];
+ }
+
+ NSURL* sourceURL = [NSURL URLWithString:source];
+
+ if (!sourceURL) {
+ errorCode = INVALID_URL_ERR;
+ NSLog(@"File Transfer Error: Invalid server URL %@", source);
+ } else if (![targetURL isFileURL]) {
+ CDVFilesystemURL *fsURL = [CDVFilesystemURL fileSystemURLWithString:target];
+ if (!fsURL) {
+ errorCode = FILE_NOT_FOUND_ERR;
+ NSLog(@"File Transfer Error: Invalid file path or URL %@", target);
+ }
+ }
+
+ if (errorCode > 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:errorCode AndSource:source AndTarget:target]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:sourceURL];
+ [self applyRequestHeaders:headers toRequest:req];
+
+ CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init];
+ delegate.command = self;
+ delegate.direction = CDV_TRANSFER_DOWNLOAD;
+ delegate.callbackId = command.callbackId;
+ delegate.objectId = objectId;
+ delegate.source = source;
+ delegate.target = [targetURL absoluteString];
+ delegate.targetURL = targetURL;
+ delegate.trustAllHosts = trustAllHosts;
+ delegate.filePlugin = [self.commandDelegate getCommandInstance:@"File"];
+ delegate.backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
+ [delegate cancelTransfer:delegate.connection];
+ }];
+
+ delegate.connection = [[NSURLConnection alloc] initWithRequest:req delegate:delegate startImmediately:NO];
+
+ if (self.queue == nil) {
+ self.queue = [[NSOperationQueue alloc] init];
+ }
+ [delegate.connection setDelegateQueue:self.queue];
+
+ @synchronized (activeTransfers) {
+ activeTransfers[delegate.objectId] = delegate;
+ }
+ // Downloads can take time
+ // sending this to a new thread calling the download_async method
+ dispatch_async(
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
+ ^(void) { [delegate.connection start];}
+ );
+}
+
+- (NSMutableDictionary*)createFileTransferError:(int)code AndSource:(NSString*)source AndTarget:(NSString*)target
+{
+ NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:3];
+
+ [result setObject:[NSNumber numberWithInt:code] forKey:@"code"];
+ if (source != nil) {
+ [result setObject:source forKey:@"source"];
+ }
+ if (target != nil) {
+ [result setObject:target forKey:@"target"];
+ }
+ NSLog(@"FileTransferError %@", result);
+
+ return result;
+}
+
+- (NSMutableDictionary*)createFileTransferError:(int)code
+ AndSource:(NSString*)source
+ AndTarget:(NSString*)target
+ AndHttpStatus:(int)httpStatus
+ AndBody:(NSString*)body
+{
+ NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:5];
+
+ [result setObject:[NSNumber numberWithInt:code] forKey:@"code"];
+ if (source != nil) {
+ [result setObject:source forKey:@"source"];
+ }
+ if (target != nil) {
+ [result setObject:target forKey:@"target"];
+ }
+ [result setObject:[NSNumber numberWithInt:httpStatus] forKey:@"http_status"];
+ if (body != nil) {
+ [result setObject:body forKey:@"body"];
+ }
+ NSLog(@"FileTransferError %@", result);
+
+ return result;
+}
+
+- (void)onReset {
+ @synchronized (activeTransfers) {
+ while ([activeTransfers count] > 0) {
+ CDVFileTransferDelegate* delegate = [activeTransfers allValues][0];
+ [delegate cancelTransfer:delegate.connection];
+ }
+ }
+}
+
+@end
+
+@interface CDVFileTransferEntityLengthRequest : NSObject {
+ NSURLConnection* _connection;
+ CDVFileTransferDelegate* __weak _originalDelegate;
+}
+
+- (CDVFileTransferEntityLengthRequest*)initWithOriginalRequest:(NSURLRequest*)originalRequest andDelegate:(CDVFileTransferDelegate*)originalDelegate;
+
+@end
+
+@implementation CDVFileTransferEntityLengthRequest
+
+- (CDVFileTransferEntityLengthRequest*)initWithOriginalRequest:(NSURLRequest*)originalRequest andDelegate:(CDVFileTransferDelegate*)originalDelegate
+{
+ if (self) {
+ DLog(@"Requesting entity length for GZIPped content...");
+
+ NSMutableURLRequest* req = [originalRequest mutableCopy];
+ [req setHTTPMethod:@"HEAD"];
+ [req setValue:@"identity" forHTTPHeaderField:@"Accept-Encoding"];
+
+ _originalDelegate = originalDelegate;
+ _connection = [NSURLConnection connectionWithRequest:req delegate:self];
+ }
+ return self;
+}
+
+- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
+{
+ DLog(@"HEAD request returned; content-length is %lld", [response expectedContentLength]);
+ [_originalDelegate updateBytesExpected:[response expectedContentLength]];
+}
+
+- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
+{}
+
+- (void)connectionDidFinishLoading:(NSURLConnection*)connection
+{}
+
+@end
+
+@implementation CDVFileTransferDelegate
+
+@synthesize callbackId, connection = _connection, source, target, responseData, responseHeaders, command, bytesTransfered, bytesExpected, direction, responseCode, objectId, targetFileHandle, filePlugin;
+
+- (void)connectionDidFinishLoading:(NSURLConnection*)connection
+{
+ NSString* uploadResponse = nil;
+ NSString* downloadResponse = nil;
+ NSMutableDictionary* uploadResult;
+ CDVPluginResult* result = nil;
+
+ NSLog(@"File Transfer Finished with response code %d", self.responseCode);
+
+ if (self.direction == CDV_TRANSFER_UPLOAD) {
+ uploadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
+
+ if ((self.responseCode >= 200) && (self.responseCode < 300)) {
+ // create dictionary to return FileUploadResult object
+ uploadResult = [NSMutableDictionary dictionaryWithCapacity:3];
+ if (uploadResponse != nil) {
+ [uploadResult setObject:uploadResponse forKey:@"response"];
+ [uploadResult setObject:self.responseHeaders forKey:@"headers"];
+ }
+ [uploadResult setObject:[NSNumber numberWithLongLong:self.bytesTransfered] forKey:@"bytesSent"];
+ [uploadResult setObject:[NSNumber numberWithInt:self.responseCode] forKey:@"responseCode"];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:uploadResult];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:uploadResponse]];
+ }
+ }
+ if (self.direction == CDV_TRANSFER_DOWNLOAD) {
+ if (self.targetFileHandle) {
+ [self.targetFileHandle closeFile];
+ self.targetFileHandle = nil;
+ DLog(@"File Transfer Download success");
+
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self.filePlugin makeEntryForURL:self.targetURL]];
+ } else {
+ downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]];
+ }
+ }
+
+ [self.command.commandDelegate sendPluginResult:result callbackId:callbackId];
+
+ // remove connection for activeTransfers
+ @synchronized (command.activeTransfers) {
+ [command.activeTransfers removeObjectForKey:objectId];
+ // remove background id task in case our upload was done in the background
+ [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskID];
+ self.backgroundTaskID = UIBackgroundTaskInvalid;
+ }
+}
+
+- (void)removeTargetFile
+{
+ NSFileManager* fileMgr = [NSFileManager defaultManager];
+
+ NSString *targetPath = [self targetFilePath];
+ if ([fileMgr fileExistsAtPath:targetPath])
+ {
+ [fileMgr removeItemAtPath:targetPath error:nil];
+ }
+}
+
+- (void)cancelTransfer:(NSURLConnection*)connection
+{
+ [connection cancel];
+ @synchronized (self.command.activeTransfers) {
+ CDVFileTransferDelegate* delegate = self.command.activeTransfers[self.objectId];
+ [self.command.activeTransfers removeObjectForKey:self.objectId];
+ [[UIApplication sharedApplication] endBackgroundTask:delegate.backgroundTaskID];
+ delegate.backgroundTaskID = UIBackgroundTaskInvalid;
+ }
+
+ [self removeTargetFile];
+}
+
+- (void)cancelTransferWithError:(NSURLConnection*)connection errorMessage:(NSString*)errorMessage
+{
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[self.command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:self.source AndTarget:self.target AndHttpStatus:self.responseCode AndBody:errorMessage]];
+
+ NSLog(@"File Transfer Error: %@", errorMessage);
+ [self cancelTransfer:connection];
+ [self.command.commandDelegate sendPluginResult:result callbackId:callbackId];
+}
+
+- (NSString *)targetFilePath
+{
+ NSString *path = nil;
+ CDVFilesystemURL *sourceURL = [CDVFilesystemURL fileSystemURLWithString:self.target];
+ if (sourceURL && sourceURL.fileSystemName != nil) {
+ // This requires talking to the current CDVFile plugin
+ NSObject *fs = [self.filePlugin filesystemForURL:sourceURL];
+ path = [fs filesystemPathForURL:sourceURL];
+ } else {
+ // Extract the path part out of a file: URL.
+ path = [self.target hasPrefix:@"/"] ? [self.target copy] : [(NSURL *)[NSURL URLWithString:self.target] path];
+ }
+ return path;
+}
+
+- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
+{
+ NSError* __autoreleasing error = nil;
+
+ self.mimeType = [response MIMEType];
+ self.targetFileHandle = nil;
+
+ // required for iOS 4.3, for some reason; response is
+ // a plain NSURLResponse, not the HTTP subclass
+ if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
+ NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
+
+ self.responseCode = (int)[httpResponse statusCode];
+ self.bytesExpected = [response expectedContentLength];
+ self.responseHeaders = [httpResponse allHeaderFields];
+ if ((self.direction == CDV_TRANSFER_DOWNLOAD) && (self.responseCode == 200) && (self.bytesExpected == NSURLResponseUnknownLength)) {
+ // Kick off HEAD request to server to get real length
+ // bytesExpected will be updated when that response is returned
+ self.entityLengthRequest = [[CDVFileTransferEntityLengthRequest alloc] initWithOriginalRequest:connection.currentRequest andDelegate:self];
+ }
+ } else if ([response.URL isFileURL]) {
+ NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:[response.URL path] error:nil];
+ self.responseCode = 200;
+ self.bytesExpected = [attr[NSFileSize] longLongValue];
+ } else {
+ self.responseCode = 200;
+ self.bytesExpected = NSURLResponseUnknownLength;
+ }
+ if ((self.direction == CDV_TRANSFER_DOWNLOAD) && (self.responseCode >= 200) && (self.responseCode < 300)) {
+ // Download response is okay; begin streaming output to file
+ NSString *filePath = [self targetFilePath];
+ if (filePath == nil) {
+ // We couldn't find the asset. Send the appropriate error.
+ [self cancelTransferWithError:connection errorMessage:[NSString stringWithFormat:@"Could not create target file"]];
+ return;
+ }
+
+ NSString* parentPath = [filePath stringByDeletingLastPathComponent];
+
+ // create parent directories if needed
+ if ([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:&error] == NO) {
+ if (error) {
+ [self cancelTransferWithError:connection errorMessage:[NSString stringWithFormat:@"Could not create path to save downloaded file: %@", [error localizedDescription]]];
+ } else {
+ [self cancelTransferWithError:connection errorMessage:@"Could not create path to save downloaded file"];
+ }
+ return;
+ }
+ // create target file
+ if ([[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil] == NO) {
+ [self cancelTransferWithError:connection errorMessage:@"Could not create target file"];
+ return;
+ }
+ // open target file for writing
+ self.targetFileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
+ if (self.targetFileHandle == nil) {
+ [self cancelTransferWithError:connection errorMessage:@"Could not open target file for writing"];
+ }
+ DLog(@"Streaming to file %@", filePath);
+ }
+}
+
+- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
+{
+ NSString* body = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:body]];
+
+ NSLog(@"File Transfer Error: %@", [error localizedDescription]);
+
+ [self cancelTransfer:connection];
+ [self.command.commandDelegate sendPluginResult:result callbackId:callbackId];
+}
+
+- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
+{
+ self.bytesTransfered += data.length;
+ if (self.targetFileHandle) {
+ [self.targetFileHandle writeData:data];
+ } else {
+ [self.responseData appendData:data];
+ }
+ [self updateProgress];
+}
+
+- (void)updateBytesExpected:(long long)newBytesExpected
+{
+ DLog(@"Updating bytesExpected to %lld", newBytesExpected);
+ self.bytesExpected = newBytesExpected;
+ [self updateProgress];
+}
+
+- (void)updateProgress
+{
+ if (self.direction == CDV_TRANSFER_DOWNLOAD) {
+ BOOL lengthComputable = (self.bytesExpected != NSURLResponseUnknownLength);
+ // If the response is GZipped, and we have an outstanding HEAD request to get
+ // the length, then hold off on sending progress events.
+ if (!lengthComputable && (self.entityLengthRequest != nil)) {
+ return;
+ }
+ NSMutableDictionary* downloadProgress = [NSMutableDictionary dictionaryWithCapacity:3];
+ [downloadProgress setObject:[NSNumber numberWithBool:lengthComputable] forKey:@"lengthComputable"];
+ [downloadProgress setObject:[NSNumber numberWithLongLong:self.bytesTransfered] forKey:@"loaded"];
+ [downloadProgress setObject:[NSNumber numberWithLongLong:self.bytesExpected] forKey:@"total"];
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:downloadProgress];
+ [result setKeepCallbackAsBool:true];
+ [self.command.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }
+}
+
+- (void)connection:(NSURLConnection*)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
+{
+ if (self.direction == CDV_TRANSFER_UPLOAD) {
+ NSMutableDictionary* uploadProgress = [NSMutableDictionary dictionaryWithCapacity:3];
+
+ [uploadProgress setObject:[NSNumber numberWithBool:true] forKey:@"lengthComputable"];
+ [uploadProgress setObject:[NSNumber numberWithLongLong:totalBytesWritten] forKey:@"loaded"];
+ [uploadProgress setObject:[NSNumber numberWithLongLong:totalBytesExpectedToWrite] forKey:@"total"];
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:uploadProgress];
+ [result setKeepCallbackAsBool:true];
+ [self.command.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }
+ self.bytesTransfered = totalBytesWritten;
+}
+
+// for self signed certificates
+- (void)connection:(NSURLConnection*)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
+{
+ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
+ if (self.trustAllHosts) {
+ NSURLCredential* credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
+ [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
+ }
+ [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
+ } else {
+ [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
+ }
+}
+
+- (id)init
+{
+ if ((self = [super init])) {
+ self.responseData = [NSMutableData data];
+ self.targetFileHandle = nil;
+ }
+ return self;
+}
+
+@end
diff --git a/plugins/org.apache.cordova.file-transfer/src/ubuntu/file-transfer.cpp b/plugins/org.apache.cordova.file-transfer/src/ubuntu/file-transfer.cpp
new file mode 100644
index 0000000..9c553eb
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/ubuntu/file-transfer.cpp
@@ -0,0 +1,265 @@
+/*
+ *
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+#include "file-transfer.h"
+#include
+#include
+
+static void SetHeaders(QNetworkRequest &request, const QVariantMap &headers) {
+ for (const QString &key: headers.keys()) {
+ QVariant val = *headers.find(key);
+ QString value = val.toString();
+ if (val.userType() == QMetaType::QVariantList || val.userType() == QMetaType::QStringList) {
+ QList list = val.toList();
+ for (QVariant v: list) {
+ if (value.size())
+ value += ", ";
+ value += v.toString();
+ }
+ }
+ request.setRawHeader(key.toUtf8(), value.toUtf8());
+ }
+}
+
+void FileTransfer::download(int scId, int ecId, const QString& url, const QString &target, bool /*trustAllHost*/, int id, const QVariantMap &headers) {
+ QSharedPointer request(new FileTransferRequest(_manager, scId, ecId, id, this));
+
+ assert(_id2request.find(id) == _id2request.end());
+
+ _id2request.insert(id, request);
+
+ request->connect(request.data(), &FileTransferRequest::done, [&]() {
+ auto it = _id2request.find(id);
+ while (it != _id2request.end() && it.key() == id) {
+ if (it.value().data() == request.data()) {
+ _id2request.erase(it);
+ break;
+ }
+ it++;
+ }
+ });
+ request->download(url, target, headers);
+}
+
+void FileTransfer::upload(int scId, int ecId, const QString &fileURI, const QString& url, const QString& fileKey, const QString& fileName, const QString& mimeType,
+ const QVariantMap & params, bool /*trustAllHosts*/, bool /*chunkedMode*/, const QVariantMap &headers, int id, const QString &/*httpMethod*/) {
+ QSharedPointer request(new FileTransferRequest(_manager, scId, ecId, id, this));
+
+ assert(_id2request.find(id) == _id2request.end());
+
+ _id2request.insert(id, request);
+
+ request->connect(request.data(), &FileTransferRequest::done, [&]() {
+ auto it = _id2request.find(id);
+ while (it != _id2request.end() && it.key() == id) {
+ if (it.value().data() == request.data()) {
+ _id2request.erase(it);
+ break;
+ }
+ it++;
+ }
+ });
+ request->upload(url, fileURI, fileKey, fileName, mimeType, params, headers);
+}
+
+void FileTransfer::abort(int scId, int ecId, int id) {
+ Q_UNUSED(scId)
+ Q_UNUSED(ecId)
+
+ auto it = _id2request.find(id);
+ while (it != _id2request.end() && it.key() == id) {
+ (*it)->abort();
+ it++;
+ }
+}
+
+void FileTransferRequest::download(const QString& uri, const QString &targetURI, const QVariantMap &headers) {
+ QUrl url(uri);
+ QNetworkRequest request;
+
+ QSharedPointer filePlugin(_plugin->cordova()->getPlugin());
+
+ if (!filePlugin.data())
+ return;
+
+ if (!url.isValid()) {
+ QVariantMap map;
+ map.insert("code", INVALID_URL_ERR);
+ map.insert("source", uri);
+ map.insert("target", targetURI);
+ _plugin->cb(_ecId, map);
+ emit done();
+ return;
+ }
+
+ request.setUrl(url);
+ if (url.password().size() || url.userName().size()) {
+ QString headerData = "Basic " + (url.userName() + ":" + url.password()).toLocal8Bit().toBase64();
+ request.setRawHeader("Authorization", headerData.toLocal8Bit());
+ }
+ SetHeaders(request, headers);
+ _reply = QSharedPointer(_manager.get(request));
+
+ _reply->connect(_reply.data(), &QNetworkReply::finished, [this, targetURI, uri, filePlugin]() {
+ if (!_scId || _reply->error() != QNetworkReply::NoError)
+ return;
+
+ QPair f1(dynamic_cast(filePlugin.data())->resolveURI(targetURI));
+
+ QFile res(f1.second.absoluteFilePath());
+ if (!f1.first || !res.open(QIODevice::WriteOnly)) {
+ QVariantMap map;
+ map.insert("code", INVALID_URL_ERR);
+ map.insert("source", uri);
+ map.insert("target", targetURI);
+ _plugin->cb(_ecId, map);
+ emit done();
+ return;
+ }
+ res.write(_reply->readAll());
+
+ _plugin->cb(_scId, dynamic_cast(filePlugin.data())->file2map(f1.second));
+
+ emit done();
+ });
+ _reply->connect(_reply.data(), SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError)));
+ _reply->connect(_reply.data(), SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(progress(qint64, qint64)));
+}
+
+void FileTransferRequest::upload(const QString& _url, const QString& fileURI, QString fileKey, QString fileName, QString mimeType, const QVariantMap ¶ms, const QVariantMap &headers) {
+ QUrl url(_url);
+ QNetworkRequest request;
+
+ QSharedPointer filePlugin(_plugin->cordova()->getPlugin());
+
+ if (!filePlugin.data())
+ return;
+
+ if (!url.isValid()) {
+ QVariantMap map;
+ map.insert("code", INVALID_URL_ERR);
+ map.insert("source", fileURI);
+ map.insert("target", _url);
+ _plugin->cb(_ecId, map);
+ emit done();
+ return;
+ }
+
+ QPair f1(dynamic_cast(filePlugin.data())->resolveURI(fileURI));
+ QFile file(f1.second.absoluteFilePath());
+ if (!f1.first || !file.open(QIODevice::ReadOnly)) {
+ QVariantMap map;
+ map.insert("code", FILE_NOT_FOUND_ERR);
+ map.insert("source", fileURI);
+ map.insert("target", _url);
+ _plugin->cb(_ecId, map);
+ emit done();
+ return;
+ }
+ QString content{file.readAll()};
+
+ request.setUrl(url);
+ if (url.password().size() || url.userName().size()) {
+ QString headerData = "Basic " + (url.userName() + ":" + url.password()).toLocal8Bit().toBase64();
+ request.setRawHeader("Authorization", headerData.toLocal8Bit());
+ }
+ SetHeaders(request, headers);
+
+ QString boundary = QString("CORDOVA-QT-%1A").arg(qrand());
+ while (content.contains(boundary)) {
+ boundary += QString("B%1A").arg(qrand());
+ }
+
+ request.setHeader(QNetworkRequest::ContentTypeHeader, QString("multipart/form-data; boundary=") + boundary);
+
+ fileKey.replace("\"", "");
+ fileName.replace("\"", "");
+ mimeType.replace("\"", "");
+ QString part = "--" + boundary + "\r\n";
+
+ part += "Content-Disposition: form-data; name=\"" + fileKey +"\"; filename=\"" + fileName + "\"\r\n";
+ part += "Content-Type: " + mimeType + "\r\n\r\n";
+ part += content + "\r\n";
+
+ for (QString key: params.keys()) {
+ part += "--" + boundary + "\r\n";
+ part += "Content-Disposition: form-data; name=\"" + key + "\";\r\n\r\n";
+ part += params.find(key)->toString();
+ part += "\r\n";
+ }
+
+ part += QString("--") + boundary + "--" + "\r\n";
+
+ _reply = QSharedPointer(_manager.post(request, QByteArray(part.toUtf8())));
+
+ _reply->connect(_reply.data(), &QNetworkReply::finished, [this, content]() {
+ if (_reply->error() != QNetworkReply::NoError)
+ return;
+ int status = 200;
+ QVariant statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+ if (statusCode.isValid()) {
+ status = statusCode.toInt();
+ }
+
+ QVariantMap map;
+ map.insert("responseCode", status);
+ map.insert("response", QString(_reply->readAll()));
+ map.insert("bytesSent", content.size());
+ _plugin->cb(_scId, map);
+ emit done();
+ });
+ _reply->connect(_reply.data(), SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError)));
+ _reply->connect(_reply.data(), SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(progress(qint64, qint64)));
+}
+
+void FileTransferRequest::abort() {
+ QVariantMap map;
+ map.insert("code", ABORT_ERR);
+ _plugin->cb(_ecId, map);
+ _scId = 0;
+ emit done();
+}
+
+void FileTransferRequest::error(QNetworkReply::NetworkError code) {
+ Q_UNUSED(code);
+
+ int status = 404;
+ QVariant statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+ if (statusCode.isValid()) {
+ status = statusCode.toInt();
+ }
+
+ QVariantMap map;
+ map.insert("http_status", status);
+ map.insert("body", QString(_reply->readAll()));
+ map.insert("code", CONNECTION_ERR);
+ _plugin->cb(_ecId, map);
+ emit done();
+}
+
+void FileTransferRequest::progress(qint64 bytesReceived, qint64 bytesTotal) {
+ QVariantMap map;
+ map.insert("lengthComputable", true);
+ map.insert("total", bytesTotal);
+ map.insert("loaded", bytesReceived);
+
+ if (bytesReceived && bytesTotal && _scId)
+ _plugin->callbackWithoutRemove(_scId, CordovaInternal::format(map));
+}
diff --git a/plugins/org.apache.cordova.file-transfer/src/ubuntu/file-transfer.h b/plugins/org.apache.cordova.file-transfer/src/ubuntu/file-transfer.h
new file mode 100644
index 0000000..75822cb
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/ubuntu/file-transfer.h
@@ -0,0 +1,103 @@
+/*
+ *
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+#ifndef FILE_TRANSFER_H_SDASDASDAS
+#define FILE_TRANSFER_H_SDASDASDAS
+
+#include
+#include
+
+#include
+
+class FileTransfer;
+
+class FileTransferRequest: public QObject {
+ Q_OBJECT
+
+ QNetworkAccessManager &_manager;
+ int _scId, _ecId;
+ int _id;
+ QSharedPointer _reply;
+
+ enum FileTransferError {
+ FILE_NOT_FOUND_ERR = 1,
+ INVALID_URL_ERR = 2,
+ CONNECTION_ERR = 3,
+ ABORT_ERR = 4
+ };
+
+public:
+ FileTransferRequest(QNetworkAccessManager &manager, int scId, int ecId, int id, FileTransfer *plugin):
+ _manager(manager),
+ _scId(scId),
+ _ecId(ecId),
+ _id(id),
+ _plugin(plugin) {
+ }
+
+ void download(const QString& url, const QString &targetURI, const QVariantMap &headers);
+ void upload(const QString& _url, const QString& fileURI, QString fileKey, QString fileName, QString mimeType, const QVariantMap ¶ms, const QVariantMap &headers);
+ void abort();
+
+signals:
+ void done();
+
+private slots:
+ void progress(qint64 bytesReceived, qint64 bytesTotal);
+ void error(QNetworkReply::NetworkError code);
+private:
+ FileTransfer *_plugin;
+ Q_DISABLE_COPY(FileTransferRequest);
+};
+
+class FileTransfer : public CPlugin {
+ Q_OBJECT
+public:
+ explicit FileTransfer(Cordova *cordova): CPlugin(cordova) {
+ }
+
+ Cordova* cordova() {
+ return m_cordova;
+ }
+
+ virtual const QString fullName() override {
+ return FileTransfer::fullID();
+ }
+
+ virtual const QString shortName() override {
+ return "FileTransfer";
+ }
+
+ static const QString fullID() {
+ return "FileTransfer";
+ }
+
+public slots:
+ void abort(int scId, int ecId, int id);
+ void download(int scId, int ecId, const QString& url, const QString &target, bool /*trustAllHost*/, int id, const QVariantMap &/*headers*/);
+ void upload(int scId, int ecId, const QString &filePath, const QString& url, const QString& fileKey, const QString& fileName, const QString& mimeType,
+ const QVariantMap & params, bool /*trustAllHosts*/, bool /*chunkedMode*/, const QVariantMap &headers, int id, const QString &httpMethod);
+
+private:
+ QNetworkAccessManager _manager;
+ QMultiMap > _id2request;
+ int lastRequestId;
+};
+
+#endif
diff --git a/plugins/org.apache.cordova.file-transfer/src/windows/FileTransferProxy.js b/plugins/org.apache.cordova.file-transfer/src/windows/FileTransferProxy.js
new file mode 100644
index 0000000..3d299ef
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/windows/FileTransferProxy.js
@@ -0,0 +1,405 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+/*jshint -W030 */
+/*global Windows, WinJS*/
+/*global module, require*/
+
+var FTErr = require('./FileTransferError'),
+ ProgressEvent = require('org.apache.cordova.file.ProgressEvent'),
+ FileUploadResult = require('org.apache.cordova.file.FileUploadResult'),
+ FileProxy = require('org.apache.cordova.file.FileProxy'),
+ FileEntry = require('org.apache.cordova.file.FileEntry');
+
+var appData = Windows.Storage.ApplicationData.current;
+
+
+// Some private helper functions, hidden by the module
+function cordovaPathToNative(path) {
+
+ var cleanPath = String(path);
+ // turn / into \\
+ cleanPath = cleanPath.replace(/\//g, '\\');
+ // turn \\ into \
+ cleanPath = cleanPath.replace(/\\\\/g, '\\');
+ // strip end \\ characters
+ cleanPath = cleanPath.replace(/\\+$/g, '');
+ return cleanPath;
+}
+
+function nativePathToCordova(path) {
+ return String(path).replace(/\\/g, '/');
+}
+
+var fileTransferOps = [];
+
+function FileTransferOperation(state, promise) {
+ this.state = state;
+ this.promise = promise;
+}
+
+FileTransferOperation.PENDING = 0;
+FileTransferOperation.DONE = 1;
+FileTransferOperation.CANCELLED = 2;
+
+
+module.exports = {
+
+/*
+exec(win, fail, 'FileTransfer', 'upload',
+[filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]);
+*/
+ upload:function(successCallback, errorCallback, options) {
+ var filePath = options[0];
+ var server = options[1];
+ var fileKey = options[2] || 'source';
+ var fileName = options[3];
+ var mimeType = options[4];
+ var params = options[5];
+ // var trustAllHosts = options[6]; // todo
+ // var chunkedMode = options[7]; // todo
+ var headers = options[8] || {};
+ var uploadId = options[9];
+
+ if (!filePath || (typeof filePath !== 'string')) {
+ errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR,null,server));
+ return;
+ }
+
+ if (filePath.substr(0, 8) === "file:///") {
+ filePath = appData.localFolder.path + filePath.substr(8).split("/").join("\\");
+ } else if (filePath.indexOf('ms-appdata:///') === 0) {
+ // Handle 'ms-appdata' scheme
+ filePath = filePath.replace('ms-appdata:///local', appData.localFolder.path)
+ .replace('ms-appdata:///temp', appData.temporaryFolder.path);
+ }
+ // normalize path separators
+ filePath = cordovaPathToNative(filePath);
+
+ // Create internal download operation object
+ fileTransferOps[uploadId] = new FileTransferOperation(FileTransferOperation.PENDING, null);
+
+ Windows.Storage.StorageFile.getFileFromPathAsync(filePath)
+ .then(function (storageFile) {
+
+ if(!fileName) {
+ fileName = storageFile.name;
+ }
+ if(!mimeType) {
+ // use the actual content type of the file, probably this should be the default way.
+ // other platforms probably can't look this up.
+ mimeType = storageFile.contentType;
+ }
+
+ // check if download isn't already cancelled
+ var uploadOp = fileTransferOps[uploadId];
+ if (uploadOp && uploadOp.state === FileTransferOperation.CANCELLED) {
+ // Here we should call errorCB with ABORT_ERR error
+ errorCallback(new FTErr(FTErr.ABORT_ERR, nativePathToCordova(filePath), server));
+ return;
+ }
+
+ // setting request headers for uploader
+ var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader();
+ for (var header in headers) {
+ if (headers.hasOwnProperty(header)) {
+ uploader.setRequestHeader(header, headers[header]);
+ }
+ }
+
+ // adding params supplied to request payload
+ var transferParts = [];
+ for (var key in params) {
+ if (params.hasOwnProperty(key)) {
+ var contentPart = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart();
+ contentPart.setHeader("Content-Disposition", "form-data; name=\"" + key + "\"");
+ contentPart.setText(params[key]);
+ transferParts.push(contentPart);
+ }
+ }
+
+ // Adding file to upload to request payload
+ var fileToUploadPart = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart(fileKey, fileName);
+ fileToUploadPart.setFile(storageFile);
+ transferParts.push(fileToUploadPart);
+
+ // create download object. This will throw an exception if URL is malformed
+ var uri = new Windows.Foundation.Uri(server);
+ try {
+ uploader.createUploadAsync(uri, transferParts).then(
+ function (upload) {
+ // update internal TransferOperation object with newly created promise
+ var uploadOperation = upload.startAsync();
+ fileTransferOps[uploadId].promise = uploadOperation;
+
+ uploadOperation.then(
+ function (result) {
+ // Update TransferOperation object with new state, delete promise property
+ // since it is not actual anymore
+ var currentUploadOp = fileTransferOps[uploadId];
+ if (currentUploadOp) {
+ currentUploadOp.state = FileTransferOperation.DONE;
+ currentUploadOp.promise = null;
+ }
+
+ var response = result.getResponseInformation();
+ // creating a data reader, attached to response stream to get server's response
+ var reader = new Windows.Storage.Streams.DataReader(result.getResultStreamAt(0));
+ reader.loadAsync(result.progress.bytesReceived).then(function(size) {
+ var responseText = reader.readString(size);
+ var ftResult = new FileUploadResult(size, response.statusCode, responseText);
+ successCallback(ftResult);
+ reader.close();
+ });
+ },
+ function (error) {
+ var source = nativePathToCordova(filePath);
+
+ // Handle download error here.
+ // Wrap this routines into promise due to some async methods
+ var getTransferError = new WinJS.Promise(function(resolve) {
+ if (error.message === 'Canceled') {
+ // If download was cancelled, message property will be specified
+ resolve(new FTErr(FTErr.ABORT_ERR, source, server, null, null, error));
+ } else {
+ // in the other way, try to get response property
+ var response = upload.getResponseInformation();
+ if (!response) {
+ resolve(new FTErr(FTErr.CONNECTION_ERR, source, server));
+ } else {
+ var reader = new Windows.Storage.Streams.DataReader(upload.getResultStreamAt(0));
+ reader.loadAsync(upload.progress.bytesReceived).then(function (size) {
+ var responseText = reader.readString(size);
+ resolve(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, server, response.statusCode, responseText, error));
+ reader.close();
+ });
+ }
+ }
+ });
+
+ // Update TransferOperation object with new state, delete promise property
+ // since it is not actual anymore
+ var currentUploadOp = fileTransferOps[uploadId];
+ if (currentUploadOp) {
+ currentUploadOp.state = FileTransferOperation.CANCELLED;
+ currentUploadOp.promise = null;
+ }
+
+ // Cleanup, remove incompleted file
+ getTransferError.then(function(transferError) {
+ storageFile.deleteAsync().then(function() {
+ errorCallback(transferError);
+ });
+ });
+ },
+ function (evt) {
+ var progressEvent = new ProgressEvent('progress', {
+ loaded: evt.progress.bytesSent,
+ total: evt.progress.totalBytesToSend,
+ target: evt.resultFile
+ });
+ progressEvent.lengthComputable = true;
+ successCallback(progressEvent, { keepCallback: true });
+ }
+ );
+ },
+ function (err) {
+ var errorObj = new FTErr(FTErr.INVALID_URL_ERR);
+ errorObj.exception = err;
+ errorCallback(errorObj);
+ }
+ );
+ } catch (e) {
+ errorCallback(new FTErr(FTErr.INVALID_URL_ERR));
+ }
+ }, function(err) {
+ errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR, server, server, null, null, err));
+ });
+ },
+
+ // [source, target, trustAllHosts, id, headers]
+ download:function(successCallback, errorCallback, options) {
+ var source = options[0];
+ var target = options[1];
+ var downloadId = options[3];
+ var headers = options[4] || {};
+
+ if (!target) {
+ errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR));
+ return;
+ }
+ if (target.substr(0, 8) === "file:///") {
+ target = appData.localFolder.path + target.substr(8).split("/").join("\\");
+ } else if (target.indexOf('ms-appdata:///') === 0) {
+ // Handle 'ms-appdata' scheme
+ target = target.replace('ms-appdata:///local', appData.localFolder.path)
+ .replace('ms-appdata:///temp', appData.temporaryFolder.path);
+ }
+ target = cordovaPathToNative(target);
+
+ var path = target.substr(0, target.lastIndexOf("\\"));
+ var fileName = target.substr(target.lastIndexOf("\\") + 1);
+ if (path === null || fileName === null) {
+ errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR));
+ return;
+ }
+
+ var download = null;
+
+ // Create internal download operation object
+ fileTransferOps[downloadId] = new FileTransferOperation(FileTransferOperation.PENDING, null);
+
+ var downloadCallback = function(storageFolder) {
+ storageFolder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.generateUniqueName).then(function(storageFile) {
+
+ // check if download isn't already cancelled
+ var downloadOp = fileTransferOps[downloadId];
+ if (downloadOp && downloadOp.state === FileTransferOperation.CANCELLED) {
+ // Here we should call errorCB with ABORT_ERR error
+ errorCallback(new FTErr(FTErr.ABORT_ERR, source, target));
+ return;
+ }
+
+ // if download isn't cancelled, contunue with creating and preparing download operation
+ var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
+ for (var header in headers) {
+ if (headers.hasOwnProperty(header)) {
+ downloader.setRequestHeader(header, headers[header]);
+ }
+ }
+
+ // create download object. This will throw an exception if URL is malformed
+ try {
+ var uri = Windows.Foundation.Uri(source);
+ download = downloader.createDownload(uri, storageFile);
+ } catch (e) {
+ // so we handle this and call errorCallback
+ errorCallback(new FTErr(FTErr.INVALID_URL_ERR));
+ return;
+ }
+
+ var downloadOperation = download.startAsync();
+ // update internal TransferOperation object with newly created promise
+ fileTransferOps[downloadId].promise = downloadOperation;
+
+ downloadOperation.then(function () {
+
+ // Update TransferOperation object with new state, delete promise property
+ // since it is not actual anymore
+ var currentDownloadOp = fileTransferOps[downloadId];
+ if (currentDownloadOp) {
+ currentDownloadOp.state = FileTransferOperation.DONE;
+ currentDownloadOp.promise = null;
+ }
+
+ var nativeURI = storageFile.path.replace(appData.localFolder.path, 'ms-appdata:///local')
+ .replace(appData.temporaryFolder.path, 'ms-appdata:///temp')
+ .replace('\\', '/');
+
+ // Passing null as error callback here because downloaded file should exist in any case
+ // otherwise the error callback will be hit during file creation in another place
+ FileProxy.resolveLocalFileSystemURI(successCallback, null, [nativeURI]);
+ }, function(error) {
+
+ var getTransferError = new WinJS.Promise(function (resolve) {
+ // Handle download error here. If download was cancelled,
+ // message property will be specified
+ if (error.message === 'Canceled') {
+ resolve(new FTErr(FTErr.ABORT_ERR, source, target, null, null, error));
+ } else {
+ // in the other way, try to get response property
+ var response = download.getResponseInformation();
+ if (!response) {
+ resolve(new FTErr(FTErr.CONNECTION_ERR, source, target));
+ } else {
+ var reader = new Windows.Storage.Streams.DataReader(download.getResultStreamAt(0));
+ reader.loadAsync(download.progress.bytesReceived).then(function (bytesLoaded) {
+ var payload = reader.readString(bytesLoaded);
+ resolve(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, target, response.statusCode, payload, error));
+ });
+ }
+ }
+ });
+ getTransferError.then(function (fileTransferError) {
+
+ // Update TransferOperation object with new state, delete promise property
+ // since it is not actual anymore
+ var currentDownloadOp = fileTransferOps[downloadId];
+ if (currentDownloadOp) {
+ currentDownloadOp.state = FileTransferOperation.CANCELLED;
+ currentDownloadOp.promise = null;
+ }
+
+ // Cleanup, remove incompleted file
+ storageFile.deleteAsync().then(function() {
+ errorCallback(fileTransferError);
+ });
+ });
+
+ }, function(evt) {
+
+ var progressEvent = new ProgressEvent('progress', {
+ loaded: evt.progress.bytesReceived,
+ total: evt.progress.totalBytesToReceive,
+ target: evt.resultFile
+ });
+ progressEvent.lengthComputable = true;
+
+ successCallback(progressEvent, { keepCallback: true });
+ });
+ }, function(error) {
+ errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, target, null, null, error));
+ });
+ };
+
+ var fileNotFoundErrorCallback = function(error) {
+ errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, target, null, null, error));
+ };
+
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then(downloadCallback, function (error) {
+ // Handle non-existent directory
+ if (error.number === -2147024894) {
+ var parent = path.substr(0, path.lastIndexOf('\\')),
+ folderNameToCreate = path.substr(path.lastIndexOf('\\') + 1);
+
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(parent).then(function(parentFolder) {
+ parentFolder.createFolderAsync(folderNameToCreate).then(downloadCallback, fileNotFoundErrorCallback);
+ }, fileNotFoundErrorCallback);
+ } else {
+ fileNotFoundErrorCallback();
+ }
+ });
+ },
+
+ abort: function (successCallback, error, options) {
+ var fileTransferOpId = options[0];
+
+ // Try to find transferOperation with id specified, and cancel its' promise
+ var currentOp = fileTransferOps[fileTransferOpId];
+ if (currentOp) {
+ currentOp.state = FileTransferOperation.CANCELLED;
+ currentOp.promise && currentOp.promise.cancel();
+ }
+ }
+
+};
+
+require("cordova/exec/proxy").add("FileTransfer",module.exports);
diff --git a/plugins/org.apache.cordova.file-transfer/src/wp/FileTransfer.cs b/plugins/org.apache.cordova.file-transfer/src/wp/FileTransfer.cs
new file mode 100644
index 0000000..11db284
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/src/wp/FileTransfer.cs
@@ -0,0 +1,866 @@
+/*
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Net;
+using System.Runtime.Serialization;
+using System.Windows;
+using System.Security;
+using System.Diagnostics;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+ public class FileTransfer : BaseCommand
+ {
+ public class DownloadRequestState
+ {
+ // This class stores the State of the request.
+ public HttpWebRequest request;
+ public TransferOptions options;
+ public bool isCancelled;
+
+ public DownloadRequestState()
+ {
+ request = null;
+ options = null;
+ isCancelled = false;
+ }
+ }
+
+ public class TransferOptions
+ {
+ /// File path to upload OR File path to download to
+ public string FilePath { get; set; }
+
+ public string Url { get; set; }
+ /// Flag to recognize if we should trust every host (only in debug environments)
+ public bool TrustAllHosts { get; set; }
+ public string Id { get; set; }
+ public string Headers { get; set; }
+ public string CallbackId { get; set; }
+ public bool ChunkedMode { get; set; }
+ /// Server address
+ public string Server { get; set; }
+ /// File key
+ public string FileKey { get; set; }
+ /// File name on the server
+ public string FileName { get; set; }
+ /// File Mime type
+ public string MimeType { get; set; }
+ /// Additional options
+ public string Params { get; set; }
+ public string Method { get; set; }
+
+ public TransferOptions()
+ {
+ FileKey = "file";
+ FileName = "image.jpg";
+ MimeType = "image/jpeg";
+ }
+ }
+
+ ///
+ /// Boundary symbol
+ ///
+ private string Boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
+
+ // Error codes
+ public const int FileNotFoundError = 1;
+ public const int InvalidUrlError = 2;
+ public const int ConnectionError = 3;
+ public const int AbortError = 4; // not really an error, but whatevs
+
+ private static Dictionary InProcDownloads = new Dictionary();
+
+ ///
+ /// Uploading response info
+ ///
+ [DataContract]
+ public class FileUploadResult
+ {
+ ///
+ /// Amount of sent bytes
+ ///
+ [DataMember(Name = "bytesSent")]
+ public long BytesSent { get; set; }
+
+ ///
+ /// Server response code
+ ///
+ [DataMember(Name = "responseCode")]
+ public long ResponseCode { get; set; }
+
+ ///
+ /// Server response
+ ///
+ [DataMember(Name = "response", EmitDefaultValue = false)]
+ public string Response { get; set; }
+
+ ///
+ /// Creates FileUploadResult object with response values
+ ///
+ /// Amount of sent bytes
+ /// Server response code
+ /// Server response
+ public FileUploadResult(long bytesSent, long responseCode, string response)
+ {
+ this.BytesSent = bytesSent;
+ this.ResponseCode = responseCode;
+ this.Response = response;
+ }
+ }
+ ///
+ /// Represents transfer error codes for callback
+ ///
+ [DataContract]
+ public class FileTransferError
+ {
+ ///
+ /// Error code
+ ///
+ [DataMember(Name = "code", IsRequired = true)]
+ public int Code { get; set; }
+
+ ///
+ /// The source URI
+ ///
+ [DataMember(Name = "source", IsRequired = true)]
+ public string Source { get; set; }
+
+ ///
+ /// The target URI
+ ///
+ ///
+ [DataMember(Name = "target", IsRequired = true)]
+ public string Target { get; set; }
+
+ [DataMember(Name = "body", IsRequired = true)]
+ public string Body { get; set; }
+
+ ///
+ /// The http status code response from the remote URI
+ ///
+ [DataMember(Name = "http_status", IsRequired = true)]
+ public int HttpStatus { get; set; }
+
+ ///
+ /// Creates FileTransferError object
+ ///
+ /// Error code
+ public FileTransferError(int errorCode)
+ {
+ this.Code = errorCode;
+ this.Source = null;
+ this.Target = null;
+ this.HttpStatus = 0;
+ this.Body = "";
+ }
+ public FileTransferError(int errorCode, string source, string target, int status, string body = "")
+ {
+ this.Code = errorCode;
+ this.Source = source;
+ this.Target = target;
+ this.HttpStatus = status;
+ this.Body = body;
+ }
+ }
+
+ ///
+ /// Represents a singular progress event to be passed back to javascript
+ ///
+ [DataContract]
+ public class FileTransferProgress
+ {
+ ///
+ /// Is the length of the response known?
+ ///
+ [DataMember(Name = "lengthComputable", IsRequired = true)]
+ public bool LengthComputable { get; set; }
+ ///
+ /// amount of bytes loaded
+ ///
+ [DataMember(Name = "loaded", IsRequired = true)]
+ public long BytesLoaded { get; set; }
+ ///
+ /// Total bytes
+ ///
+ [DataMember(Name = "total", IsRequired = false)]
+ public long BytesTotal { get; set; }
+
+ public FileTransferProgress(long bTotal = 0, long bLoaded = 0)
+ {
+ LengthComputable = bTotal > 0;
+ BytesLoaded = bLoaded;
+ BytesTotal = bTotal;
+ }
+ }
+
+ ///
+ /// Upload options
+ ///
+ //private TransferOptions uploadOptions;
+
+ ///
+ /// Bytes sent
+ ///
+ private long bytesSent;
+
+ ///
+ /// sends a file to a server
+ ///
+ /// Upload options
+ /// exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]);
+ public void upload(string options)
+ {
+ options = options.Replace("{}", ""); // empty objects screw up the Deserializer
+ string callbackId = "";
+
+ TransferOptions uploadOptions = null;
+ HttpWebRequest webRequest = null;
+
+ try
+ {
+ try
+ {
+ string[] args = JSON.JsonHelper.Deserialize(options);
+ uploadOptions = new TransferOptions();
+ uploadOptions.FilePath = args[0];
+ uploadOptions.Server = args[1];
+ uploadOptions.FileKey = args[2];
+ uploadOptions.FileName = args[3];
+ uploadOptions.MimeType = args[4];
+ uploadOptions.Params = args[5];
+
+ bool trustAll = false;
+ bool.TryParse(args[6],out trustAll);
+ uploadOptions.TrustAllHosts = trustAll;
+
+ bool doChunked = false;
+ bool.TryParse(args[7], out doChunked);
+ uploadOptions.ChunkedMode = doChunked;
+
+ //8 : Headers
+ //9 : id
+ //10: method
+
+ uploadOptions.Headers = args[8];
+ uploadOptions.Id = args[9];
+ uploadOptions.Method = args[10];
+
+ uploadOptions.CallbackId = callbackId = args[11];
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ return;
+ }
+
+ Uri serverUri;
+ try
+ {
+ serverUri = new Uri(uploadOptions.Server);
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(InvalidUrlError, uploadOptions.Server, null, 0)));
+ return;
+ }
+ webRequest = (HttpWebRequest)WebRequest.Create(serverUri);
+ webRequest.ContentType = "multipart/form-data; boundary=" + Boundary;
+ webRequest.Method = uploadOptions.Method;
+
+ if (!string.IsNullOrEmpty(uploadOptions.Headers))
+ {
+ Dictionary headers = parseHeaders(uploadOptions.Headers);
+ foreach (string key in headers.Keys)
+ {
+ webRequest.Headers[key] = headers[key];
+ }
+ }
+
+ DownloadRequestState reqState = new DownloadRequestState();
+ reqState.options = uploadOptions;
+ reqState.request = webRequest;
+
+ InProcDownloads[uploadOptions.Id] = reqState;
+
+ webRequest.BeginGetRequestStream(uploadCallback, reqState);
+ }
+ catch (Exception /*ex*/)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError)),callbackId);
+ }
+ }
+
+ // example : "{\"Authorization\":\"Basic Y29yZG92YV91c2VyOmNvcmRvdmFfcGFzc3dvcmQ=\"}"
+ protected Dictionary parseHeaders(string jsonHeaders)
+ {
+ try
+ {
+ Dictionary result = new Dictionary();
+
+ string temp = jsonHeaders.StartsWith("{") ? jsonHeaders.Substring(1) : jsonHeaders;
+ temp = temp.EndsWith("}") ? temp.Substring(0, temp.Length - 1) : temp;
+
+ string[] strHeaders = temp.Split(',');
+ for (int n = 0; n < strHeaders.Length; n++)
+ {
+ // we need to use indexOf in order to WP7 compatible
+ int splitIndex = strHeaders[n].IndexOf(':');
+ if (splitIndex > 0)
+ {
+ string[] split = new string[2];
+ split[0] = strHeaders[n].Substring(0, splitIndex);
+ split[1] = strHeaders[n].Substring(splitIndex + 1);
+
+ split[0] = JSON.JsonHelper.Deserialize(split[0]);
+ split[1] = JSON.JsonHelper.Deserialize(split[1]);
+ result[split[0]] = split[1];
+ }
+ }
+ return result;
+ }
+ catch (Exception)
+ {
+ Debug.WriteLine("Failed to parseHeaders from string :: " + jsonHeaders);
+ }
+ return null;
+ }
+
+ public void download(string options)
+ {
+ TransferOptions downloadOptions = null;
+ HttpWebRequest webRequest = null;
+ string callbackId;
+
+ try
+ {
+ // source, target, trustAllHosts, this._id, headers
+ string[] optionStrings = JSON.JsonHelper.Deserialize(options);
+
+ downloadOptions = new TransferOptions();
+ downloadOptions.Url = optionStrings[0];
+ downloadOptions.FilePath = optionStrings[1];
+
+ bool trustAll = false;
+ bool.TryParse(optionStrings[2],out trustAll);
+ downloadOptions.TrustAllHosts = trustAll;
+
+ downloadOptions.Id = optionStrings[3];
+ downloadOptions.Headers = optionStrings[4];
+ downloadOptions.CallbackId = callbackId = optionStrings[5];
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ return;
+ }
+
+ try
+ {
+ // is the URL a local app file?
+ if (downloadOptions.Url.StartsWith("x-wmapp0") || downloadOptions.Url.StartsWith("file:"))
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ string cleanUrl = downloadOptions.Url.Replace("x-wmapp0:", "").Replace("file:", "").Replace("//","");
+
+ // pre-emptively create any directories in the FilePath that do not exist
+ string directoryName = getDirectoryName(downloadOptions.FilePath);
+ if (!string.IsNullOrEmpty(directoryName) && !isoFile.DirectoryExists(directoryName))
+ {
+ isoFile.CreateDirectory(directoryName);
+ }
+
+ // just copy from one area of iso-store to another ...
+ if (isoFile.FileExists(downloadOptions.Url))
+ {
+ isoFile.CopyFile(downloadOptions.Url, downloadOptions.FilePath);
+ }
+ else
+ {
+ // need to unpack resource from the dll
+ Uri uri = new Uri(cleanUrl, UriKind.Relative);
+ var resource = Application.GetResourceStream(uri);
+
+ if (resource != null)
+ {
+ // create the file destination
+ if (!isoFile.FileExists(downloadOptions.FilePath))
+ {
+ var destFile = isoFile.CreateFile(downloadOptions.FilePath);
+ destFile.Close();
+ }
+
+ using (FileStream fileStream = new IsolatedStorageFileStream(downloadOptions.FilePath, FileMode.Open, FileAccess.Write, isoFile))
+ {
+ long totalBytes = resource.Stream.Length;
+ int bytesRead = 0;
+ using (BinaryReader reader = new BinaryReader(resource.Stream))
+ {
+ using (BinaryWriter writer = new BinaryWriter(fileStream))
+ {
+ int BUFFER_SIZE = 1024;
+ byte[] buffer;
+
+ while (true)
+ {
+ buffer = reader.ReadBytes(BUFFER_SIZE);
+ // fire a progress event ?
+ bytesRead += buffer.Length;
+ if (buffer.Length > 0)
+ {
+ writer.Write(buffer);
+ DispatchFileTransferProgress(bytesRead, totalBytes, callbackId);
+ }
+ else
+ {
+ writer.Close();
+ reader.Close();
+ fileStream.Close();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ File.FileEntry entry = File.FileEntry.GetEntry(downloadOptions.FilePath);
+ if (entry != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, File.NOT_FOUND_ERR), callbackId);
+ }
+
+ return;
+ }
+ else
+ {
+ // otherwise it is web-bound, we will actually download it
+ //Debug.WriteLine("Creating WebRequest for url : " + downloadOptions.Url);
+ webRequest = (HttpWebRequest)WebRequest.Create(downloadOptions.Url);
+ }
+ }
+ catch (Exception /*ex*/)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,
+ new FileTransferError(InvalidUrlError, downloadOptions.Url, null, 0)));
+ return;
+ }
+
+ if (downloadOptions != null && webRequest != null)
+ {
+ DownloadRequestState state = new DownloadRequestState();
+ state.options = downloadOptions;
+ state.request = webRequest;
+ InProcDownloads[downloadOptions.Id] = state;
+
+ if (!string.IsNullOrEmpty(downloadOptions.Headers))
+ {
+ Dictionary headers = parseHeaders(downloadOptions.Headers);
+ foreach (string key in headers.Keys)
+ {
+ webRequest.Headers[key] = headers[key];
+ }
+ }
+
+ try
+ {
+ webRequest.BeginGetResponse(new AsyncCallback(downloadCallback), state);
+ }
+ catch (WebException)
+ {
+ // eat it
+ }
+ // dispatch an event for progress ( 0 )
+ lock (state)
+ {
+ if (!state.isCancelled)
+ {
+ var plugRes = new PluginResult(PluginResult.Status.OK, new FileTransferProgress());
+ plugRes.KeepCallback = true;
+ plugRes.CallbackId = callbackId;
+ DispatchCommandResult(plugRes, callbackId);
+ }
+ }
+ }
+ }
+
+ public void abort(string options)
+ {
+ Debug.WriteLine("Abort :: " + options);
+ string[] optionStrings = JSON.JsonHelper.Deserialize(options);
+ string id = optionStrings[0];
+ string callbackId = optionStrings[1];
+
+ if (InProcDownloads.ContainsKey(id))
+ {
+ DownloadRequestState state = InProcDownloads[id];
+ if (!state.isCancelled)
+ { // prevent multiple callbacks for the same abort
+ state.isCancelled = true;
+ if (!state.request.HaveResponse)
+ {
+ state.request.Abort();
+ InProcDownloads.Remove(id);
+ //callbackId = state.options.CallbackId;
+ //state = null;
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,
+ new FileTransferError(FileTransfer.AbortError)),
+ state.options.CallbackId);
+ }
+ }
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION), callbackId); // TODO: is it an IO exception?
+ }
+ }
+
+ private void DispatchFileTransferProgress(long bytesLoaded, long bytesTotal, string callbackId, bool keepCallback = true)
+ {
+ Debug.WriteLine("DispatchFileTransferProgress : " + callbackId);
+ // send a progress change event
+ FileTransferProgress progEvent = new FileTransferProgress(bytesTotal);
+ progEvent.BytesLoaded = bytesLoaded;
+ PluginResult plugRes = new PluginResult(PluginResult.Status.OK, progEvent);
+ plugRes.KeepCallback = keepCallback;
+ plugRes.CallbackId = callbackId;
+ DispatchCommandResult(plugRes, callbackId);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ private void downloadCallback(IAsyncResult asynchronousResult)
+ {
+ DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState;
+ HttpWebRequest request = reqState.request;
+
+ string callbackId = reqState.options.CallbackId;
+ try
+ {
+ HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
+
+ // send a progress change event
+ DispatchFileTransferProgress(0, response.ContentLength, callbackId);
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ // create any directories in the path that do not exist
+ string directoryName = getDirectoryName(reqState.options.FilePath);
+ if (!string.IsNullOrEmpty(directoryName) && !isoFile.DirectoryExists(directoryName))
+ {
+ isoFile.CreateDirectory(directoryName);
+ }
+
+ // create the file if not exists
+ if (!isoFile.FileExists(reqState.options.FilePath))
+ {
+ var file = isoFile.CreateFile(reqState.options.FilePath);
+ file.Close();
+ }
+
+ using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, FileAccess.Write, isoFile))
+ {
+ long totalBytes = response.ContentLength;
+ int bytesRead = 0;
+ using (BinaryReader reader = new BinaryReader(response.GetResponseStream()))
+ {
+ using (BinaryWriter writer = new BinaryWriter(fileStream))
+ {
+ int BUFFER_SIZE = 1024;
+ byte[] buffer;
+
+ while (true)
+ {
+ buffer = reader.ReadBytes(BUFFER_SIZE);
+ // fire a progress event ?
+ bytesRead += buffer.Length;
+ if (buffer.Length > 0 && !reqState.isCancelled)
+ {
+ writer.Write(buffer);
+ DispatchFileTransferProgress(bytesRead, totalBytes, callbackId);
+ }
+ else
+ {
+ writer.Close();
+ reader.Close();
+ fileStream.Close();
+ break;
+ }
+ System.Threading.Thread.Sleep(1);
+ }
+ }
+ }
+ }
+ if (reqState.isCancelled)
+ {
+ isoFile.DeleteFile(reqState.options.FilePath);
+ }
+ }
+
+ if (reqState.isCancelled)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(AbortError)),
+ callbackId);
+ }
+ else
+ {
+ File.FileEntry entry = new File.FileEntry(reqState.options.FilePath);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId);
+ }
+ }
+ catch (IsolatedStorageException)
+ {
+ // Trying to write the file somewhere within the IsoStorage.
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError)),
+ callbackId);
+ }
+ catch (SecurityException)
+ {
+ // Trying to write the file somewhere not allowed.
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError)),
+ callbackId);
+ }
+ catch (WebException webex)
+ {
+ // TODO: probably need better work here to properly respond with all http status codes back to JS
+ // Right now am jumping through hoops just to detect 404.
+ HttpWebResponse response = (HttpWebResponse)webex.Response;
+ if ((webex.Status == WebExceptionStatus.ProtocolError && response.StatusCode == HttpStatusCode.NotFound)
+ || webex.Status == WebExceptionStatus.UnknownError)
+ {
+ // Weird MSFT detection of 404... seriously... just give us the f(*$@ status code as a number ffs!!!
+ // "Numbers for HTTP status codes? Nah.... let's create our own set of enums/structs to abstract that stuff away."
+ // FACEPALM
+ // Or just cast it to an int, whiner ... -jm
+ int statusCode = (int)response.StatusCode;
+ string body = "";
+
+ using (Stream streamResponse = response.GetResponseStream())
+ {
+ using (StreamReader streamReader = new StreamReader(streamResponse))
+ {
+ body = streamReader.ReadToEnd();
+ }
+ }
+ FileTransferError ftError = new FileTransferError(ConnectionError, null, null, statusCode, body);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ftError),
+ callbackId);
+ }
+ else
+ {
+ lock (reqState)
+ {
+ if (!reqState.isCancelled)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,
+ new FileTransferError(ConnectionError)),
+ callbackId);
+ }
+ else
+ {
+ Debug.WriteLine("It happened");
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,
+ new FileTransferError(FileNotFoundError)),
+ callbackId);
+ }
+
+ //System.Threading.Thread.Sleep(1000);
+ if (InProcDownloads.ContainsKey(reqState.options.Id))
+ {
+ InProcDownloads.Remove(reqState.options.Id);
+ }
+ }
+
+ ///
+ /// Read file from Isolated Storage and sends it to server
+ ///
+ ///
+ private void uploadCallback(IAsyncResult asynchronousResult)
+ {
+ DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState;
+ HttpWebRequest webRequest = reqState.request;
+ string callbackId = reqState.options.CallbackId;
+
+ try
+ {
+ using (Stream requestStream = (webRequest.EndGetRequestStream(asynchronousResult)))
+ {
+ string lineStart = "--";
+ string lineEnd = Environment.NewLine;
+ byte[] boundaryBytes = System.Text.Encoding.UTF8.GetBytes(lineStart + Boundary + lineEnd);
+ string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"" + lineEnd + lineEnd + "{1}" + lineEnd;
+
+ if (!string.IsNullOrEmpty(reqState.options.Params))
+ {
+ Dictionary paramMap = parseHeaders(reqState.options.Params);
+ foreach (string key in paramMap.Keys)
+ {
+ requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
+ string formItem = string.Format(formdataTemplate, key, paramMap[key]);
+ byte[] formItemBytes = System.Text.Encoding.UTF8.GetBytes(formItem);
+ requestStream.Write(formItemBytes, 0, formItemBytes.Length);
+ }
+ requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
+ }
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoFile.FileExists(reqState.options.FilePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError, reqState.options.Server, reqState.options.FilePath, 0)));
+ return;
+ }
+
+ byte[] endRequest = System.Text.Encoding.UTF8.GetBytes(lineEnd + lineStart + Boundary + lineStart + lineEnd);
+ long totalBytesToSend = 0;
+
+ using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, isoFile))
+ {
+ string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" + lineEnd + "Content-Type: {2}" + lineEnd + lineEnd;
+ string header = string.Format(headerTemplate, reqState.options.FileKey, reqState.options.FileName, reqState.options.MimeType);
+ byte[] headerBytes = System.Text.Encoding.UTF8.GetBytes(header);
+
+ byte[] buffer = new byte[4096];
+ int bytesRead = 0;
+ //sent bytes needs to be reseted before new upload
+ bytesSent = 0;
+ totalBytesToSend = fileStream.Length;
+
+ requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
+
+ requestStream.Write(headerBytes, 0, headerBytes.Length);
+
+ while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
+ {
+ if (!reqState.isCancelled)
+ {
+ requestStream.Write(buffer, 0, bytesRead);
+ bytesSent += bytesRead;
+ DispatchFileTransferProgress(bytesSent, totalBytesToSend, callbackId);
+ System.Threading.Thread.Sleep(1);
+ }
+ else
+ {
+ throw new Exception("UploadCancelledException");
+ }
+ }
+ }
+
+ requestStream.Write(endRequest, 0, endRequest.Length);
+ }
+ }
+ // webRequest
+
+ webRequest.BeginGetResponse(ReadCallback, reqState);
+ }
+ catch (Exception /*ex*/)
+ {
+ if (!reqState.isCancelled)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError)), callbackId);
+ }
+ }
+ }
+
+ ///
+ /// Reads response into FileUploadResult
+ ///
+ ///
+ private void ReadCallback(IAsyncResult asynchronousResult)
+ {
+ DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState;
+ try
+ {
+ HttpWebRequest webRequest = reqState.request;
+ string callbackId = reqState.options.CallbackId;
+
+ if (InProcDownloads.ContainsKey(reqState.options.Id))
+ {
+ InProcDownloads.Remove(reqState.options.Id);
+ }
+
+ using (HttpWebResponse response = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult))
+ {
+ using (Stream streamResponse = response.GetResponseStream())
+ {
+ using (StreamReader streamReader = new StreamReader(streamResponse))
+ {
+ string responseString = streamReader.ReadToEnd();
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileUploadResult(bytesSent, (long)response.StatusCode, responseString)));
+ });
+ }
+ }
+ }
+ }
+ catch (WebException webex)
+ {
+ // TODO: probably need better work here to properly respond with all http status codes back to JS
+ // Right now am jumping through hoops just to detect 404.
+ if ((webex.Status == WebExceptionStatus.ProtocolError && ((HttpWebResponse)webex.Response).StatusCode == HttpStatusCode.NotFound)
+ || webex.Status == WebExceptionStatus.UnknownError)
+ {
+ int statusCode = (int)((HttpWebResponse)webex.Response).StatusCode;
+ FileTransferError ftError = new FileTransferError(ConnectionError, null, null, statusCode);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ftError), reqState.options.CallbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,
+ new FileTransferError(ConnectionError)),
+ reqState.options.CallbackId);
+ }
+ }
+ catch (Exception /*ex*/)
+ {
+ FileTransferError transferError = new FileTransferError(ConnectionError, reqState.options.Server, reqState.options.FilePath, 403);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, transferError), reqState.options.CallbackId);
+ }
+ }
+
+ // Gets the full path without the filename
+ private string getDirectoryName(String filePath)
+ {
+ string directoryName;
+ try
+ {
+ directoryName = filePath.Substring(0, filePath.LastIndexOf('/'));
+ }
+ catch
+ {
+ directoryName = "";
+ }
+ return directoryName;
+ }
+ }
+}
diff --git a/plugins/org.apache.cordova.file-transfer/tests/plugin.xml b/plugins/org.apache.cordova.file-transfer/tests/plugin.xml
new file mode 100644
index 0000000..5138b49
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/tests/plugin.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ Cordova File Transfer Plugin Tests
+ Apache 2.0
+
+
+
+
diff --git a/plugins/org.apache.cordova.file-transfer/tests/tests.js b/plugins/org.apache.cordova.file-transfer/tests/tests.js
new file mode 100644
index 0000000..f64a4b0
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/tests/tests.js
@@ -0,0 +1,952 @@
+/*
+*
+* 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 exports, cordova */
+/* global describe, it, expect, beforeEach, afterEach, jasmine, pending, spyOn */
+
+/* global FileTransfer, FileTransferError, FileUploadOptions, LocalFileSystem */
+
+exports.defineAutoTests = function () {
+
+ // constants
+ var GRACE_TIME_DELTA = 300; // in milliseconds
+ var UNKNOWN_HOST = "http://foobar.apache.org";
+ var HEADERS_ECHO = "http://whatheaders.com"; // NOTE: this site is very useful!
+
+ // config for upload test server
+ // NOTE:
+ // more info at https://github.com/apache/cordova-labs/tree/cordova-filetransfer
+ var SERVER = "http://cordova-filetransfer.jitsu.com";
+ var SERVER_WITH_CREDENTIALS = "http://cordova_user:cordova_password@cordova-filetransfer.jitsu.com";
+
+ // flags
+ var isWindows = function() {
+ return (cordova.platformId === "windows") || (navigator.appVersion.indexOf("MSAppHost/1.0") !== -1);
+ };
+
+ describe('FileTransferError', function () {
+
+ it('should exist', function () {
+ expect(FileTransferError).toBeDefined();
+ });
+
+ it('should be constructable', function () {
+ var transferError = new FileTransferError();
+ expect(transferError).toBeDefined();
+ });
+
+ it('filetransfer.spec.3 should expose proper constants', function () {
+
+ expect(FileTransferError.FILE_NOT_FOUND_ERR).toBeDefined();
+ expect(FileTransferError.INVALID_URL_ERR).toBeDefined();
+ expect(FileTransferError.CONNECTION_ERR).toBeDefined();
+ expect(FileTransferError.ABORT_ERR).toBeDefined();
+ expect(FileTransferError.NOT_MODIFIED_ERR).toBeDefined();
+
+ expect(FileTransferError.FILE_NOT_FOUND_ERR).toBe(1);
+ expect(FileTransferError.INVALID_URL_ERR).toBe(2);
+ expect(FileTransferError.CONNECTION_ERR).toBe(3);
+ expect(FileTransferError.ABORT_ERR).toBe(4);
+ expect(FileTransferError.NOT_MODIFIED_ERR).toBe(5);
+ });
+ });
+
+ describe('FileUploadOptions', function () {
+
+ it('should exist', function () {
+ expect(FileUploadOptions).toBeDefined();
+ });
+
+ it('should be constructable', function () {
+ var transferOptions = new FileUploadOptions();
+ expect(transferOptions).toBeDefined();
+ });
+ });
+
+ describe('FileTransfer', function () {
+
+ var persistentRoot, tempRoot;
+
+ // named callbacks
+ var unexpectedCallbacks = {
+ httpFail: function () {},
+ httpWin: function () {},
+ fileSystemFail: function () {},
+ fileSystemWin: function () {},
+ fileOperationFail: function () {},
+ fileOperationWin: function () {},
+ };
+
+ var expectedCallbacks = {
+ unsupportedOperation: function (response) {
+ console.log('spec called unsupported functionality; response:', response);
+ },
+ };
+
+ // helpers
+ var deleteFile = function (fileSystem, name, done) {
+ fileSystem.getFile(name, null,
+ function (fileEntry) {
+ fileEntry.remove(
+ function () {
+ console.log('deleted \'' + name + '\'');
+ done();
+ },
+ function () {
+ throw new Error('failed to delete: \'' + name + '\'');
+ }
+ );
+ },
+ function () {
+ console.log('no \'' + name + '\' to delete; skipping deletion');
+ done();
+ }
+ );
+ };
+
+ var writeFile = function (fileSystem, name, content, success) {
+ fileSystem.getFile(name, { create: true },
+ function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+
+ writer.onwrite = function () {
+ console.log('created test file \'' + name + '\'');
+ success(fileEntry);
+ };
+
+ writer.onabort = function (evt) {
+ throw new Error('aborted creating test file \'' + name + '\': ' + evt);
+ };
+
+ writer.error = function (evt) {
+ throw new Error('aborted creating test file \'' + name + '\': ' + evt);
+ };
+
+ writer.write(content + "\n");
+
+ }, unexpectedCallbacks.fileOperationFail);
+ },
+ function () {
+ throw new Error('could not create test file \'' + name + '\'');
+ }
+ );
+ };
+
+ var defaultOnProgressHandler = function (event) {
+ if (event.lengthComputable) {
+ expect(event.loaded).toBeGreaterThan(1);
+ expect(event.total).toBeGreaterThan(0);
+ expect(event.total).not.toBeLessThan(event.loaded);
+ expect(event.lengthComputable).toBe(true, 'lengthComputable');
+ } else {
+ expect(event.total).toBe(0);
+ }
+ };
+
+ var getMalformedUrl = function () {
+ if (cordova.platformId === 'android' || cordova.platformId === 'amazon-fireos') {
+ // bad protocol causes a MalformedUrlException on Android
+ return "httpssss://example.com";
+ } else {
+ // iOS doesn't care about protocol, space in hostname causes error
+ return "httpssss://exa mple.com";
+ }
+ };
+
+ // NOTE:
+ // there are several beforeEach calls, one per async call; since calling done()
+ // signifies a completed async call, each async call needs its own done(), and
+ // therefore its own beforeEach
+ beforeEach(function (done) {
+ window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
+ function (fileSystem) {
+ persistentRoot = fileSystem.root;
+ done();
+ },
+ function () {
+ throw new Error('Failed to initialize persistent file system.');
+ }
+ );
+ });
+
+ beforeEach(function (done) {
+ window.requestFileSystem(LocalFileSystem.TEMPORARY, 0,
+ function (fileSystem) {
+ tempRoot = fileSystem.root;
+ done();
+ },
+ function () {
+ throw new Error('Failed to initialize temporary file system.');
+ }
+ );
+ });
+
+ // spy on all named callbacks
+ beforeEach(function() {
+
+ // ignore the actual implementations of the unexpected callbacks
+ for (var callback in unexpectedCallbacks) {
+ if (unexpectedCallbacks.hasOwnProperty(callback)) {
+ spyOn(unexpectedCallbacks, callback);
+ }
+ }
+
+ // but run the implementations of the expected callbacks
+ for (callback in expectedCallbacks) {
+ if (expectedCallbacks.hasOwnProperty(callback)) {
+ spyOn(expectedCallbacks, callback).and.callThrough();
+ }
+ }
+ });
+
+ // at the end, check that none of the unexpected callbacks got called,
+ // and act on the expected callbacks
+ afterEach(function() {
+ for (var callback in unexpectedCallbacks) {
+ if (unexpectedCallbacks.hasOwnProperty(callback)) {
+ expect(unexpectedCallbacks[callback]).not.toHaveBeenCalled();
+ }
+ }
+
+ if (expectedCallbacks.unsupportedOperation.calls.any()) {
+ pending();
+ }
+ });
+
+ it('should initialise correctly', function() {
+ expect(persistentRoot).toBeDefined();
+ expect(tempRoot).toBeDefined();
+ });
+
+ it('should exist', function () {
+ expect(FileTransfer).toBeDefined();
+ });
+
+ it('filetransfer.spec.1 should be constructable', function () {
+ var transfer = new FileTransfer();
+ expect(transfer).toBeDefined();
+ });
+
+ it('filetransfer.spec.2 should expose proper functions', function () {
+
+ var transfer = new FileTransfer();
+
+ expect(transfer.upload).toBeDefined();
+ expect(transfer.download).toBeDefined();
+
+ expect(transfer.upload).toEqual(jasmine.any(Function));
+ expect(transfer.download).toEqual(jasmine.any(Function));
+ });
+
+ describe('methods', function() {
+
+ var transfer;
+
+ var root;
+ var fileName;
+ var localFilePath;
+
+ beforeEach(function() {
+
+ transfer = new FileTransfer();
+
+ // assign a default onprogress handler
+ transfer.onprogress = defaultOnProgressHandler;
+
+ // spy on the onprogress handler, but still call through to it
+ spyOn(transfer, 'onprogress').and.callThrough();
+
+ root = persistentRoot;
+ fileName = 'testFile.txt';
+ localFilePath = root.toURL() + fileName;
+ });
+
+ // NOTE:
+ // if download tests are failing, check the
+ // URL white list for the following URLs:
+ // - 'httpssss://example.com'
+ // - 'apache.org', with subdomains="true"
+ // - 'cordova-filetransfer.jitsu.com'
+ describe('download', function() {
+
+ // helpers
+ var verifyDownload = function (fileEntry) {
+ expect(fileEntry.name).toBe(fileName);
+ };
+
+ // delete the downloaded file
+ afterEach(function (done) {
+ deleteFile(root, fileName, done);
+ });
+
+ it('filetransfer.spec.4 should download a file', function (done) {
+
+ var fileURL = SERVER + '/robots.txt';
+
+ var fileWin = function (blob) {
+
+ if (transfer.onprogress.calls.any()) {
+ var lastProgressEvent = transfer.onprogress.calls.mostRecent().args[0];
+ expect(lastProgressEvent.loaded).not.toBeGreaterThan(blob.size);
+ } else {
+ console.log('no progress events were emitted');
+ }
+
+ done();
+ };
+
+ var downloadWin = function (entry) {
+
+ verifyDownload(entry);
+
+ // verify the FileEntry representing this file
+ entry.file(fileWin, unexpectedCallbacks.fileSystemFail);
+ };
+
+ transfer.download(fileURL, localFilePath, downloadWin, unexpectedCallbacks.httpFail);
+ });
+
+ it('filetransfer.spec.5 should download a file using http basic auth', function (done) {
+
+ var fileURL = SERVER_WITH_CREDENTIALS + '/download_basic_auth';
+
+ var downloadWin = function (entry) {
+ verifyDownload(entry);
+ done();
+ };
+
+ transfer.download(fileURL, localFilePath, downloadWin, unexpectedCallbacks.httpFail);
+ });
+
+ it('filetransfer.spec.6 should get 401 status on http basic auth failure', function (done) {
+
+ // NOTE:
+ // using server without credentials
+ var fileURL = SERVER + '/download_basic_auth';
+
+ var downloadFail = function (error) {
+ expect(error.http_status).toBe(401);
+ expect(error.http_status).not.toBe(404, "Ensure " + fileURL + " is in the white list");
+ done();
+ };
+
+ transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, downloadFail);
+ });
+
+ it("filetransfer.spec.7 should download a file using file:// (when hosted from file://)", function (done) {
+
+ // for Windows platform it's ms-appdata:/// by default, not file://
+ if (isWindows()) {
+ pending();
+ return;
+ }
+
+ var fileURL = window.location.protocol + '//' + window.location.pathname.replace(/ /g, '%20');
+
+ if (!/^file/.exec(fileURL) && cordova.platformId !== 'blackberry10') {
+ if (cordova.platformId !== 'windowsphone')
+ expect(fileURL).toMatch(/^file:/);
+ else
+ expect(fileURL).toMatch(/^x-wmapp0:/);
+ done();
+ return;
+ }
+
+ var downloadWin = function (entry) {
+ verifyDownload(entry);
+ done();
+ };
+
+ transfer.download(fileURL, localFilePath, downloadWin, unexpectedCallbacks.httpFail);
+ });
+
+ it("filetransfer.spec.8 should download a file using https://", function (done) {
+
+ var fileURL = "https://www.apache.org/licenses/";
+
+ var fileWin = function (file) {
+
+ var reader = new FileReader();
+
+ reader.onerror = unexpectedCallbacks.fileOperationFail;
+ reader.onload = function () {
+ expect(reader.result).toMatch(/The Apache Software Foundation/);
+ done();
+ };
+
+ reader.readAsText(file);
+ };
+
+ var downloadWin = function (entry) {
+ verifyDownload(entry);
+ entry.file(fileWin, unexpectedCallbacks.fileSystemFail);
+ };
+
+ transfer.download(fileURL, localFilePath, downloadWin, unexpectedCallbacks.httpFail);
+ });
+
+ it("filetransfer.spec.11 should call the error callback on abort()", function (done) {
+
+ var fileURL = 'http://cordova.apache.org/downloads/BlueZedEx.mp3';
+
+ transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, done);
+ transfer.abort();
+ });
+
+ it("filetransfer.spec.9 should not leave partial file due to abort", function (done) {
+
+ var fileURL = 'http://cordova.apache.org/downloads/logos_2.zip';
+
+ var downloadFail = function (error) {
+
+ expect(error.code).toBe(FileTransferError.ABORT_ERR);
+ expect(transfer.onprogress).toHaveBeenCalled();
+
+ // check that there is no file
+ root.getFile(localFilePath, null, unexpectedCallbacks.fileSystemWin, done);
+ };
+
+ // abort at the first onprogress event
+ transfer.onprogress = function (event) {
+ if (event.loaded > 0) {
+ transfer.abort();
+ }
+ };
+
+ spyOn(transfer, 'onprogress').and.callThrough();
+
+ transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, downloadFail);
+ });
+
+ it("filetransfer.spec.10 should be stopped by abort() right away", function (done) {
+
+ var fileURL = 'http://cordova.apache.org/downloads/BlueZedEx.mp3';
+
+ expect(transfer.abort).not.toThrow(); // should be a no-op.
+
+ var startTime = +new Date();
+
+ var downloadFail = function (error) {
+
+ expect(error.code).toBe(FileTransferError.ABORT_ERR);
+ expect(new Date() - startTime).toBeLessThan(GRACE_TIME_DELTA);
+
+ // delay calling done() to wait for the bogus abort()
+ setTimeout(done, GRACE_TIME_DELTA * 2);
+ };
+
+ transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, downloadFail);
+ transfer.abort();
+
+ // call abort() again, after a time greater than the grace period
+ setTimeout(function () {
+ expect(transfer.abort).not.toThrow();
+ }, GRACE_TIME_DELTA);
+ });
+
+ it("filetransfer.spec.12 should get http status on failure", function (done) {
+
+ var fileURL = SERVER + "/404";
+
+ var downloadFail = function (error) {
+
+ expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list");
+ expect(error.http_status).toBe(404);
+
+ done();
+ };
+
+ transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, downloadFail);
+ });
+
+ it("filetransfer.spec.13 should get http body on failure", function (done) {
+
+ var fileURL = SERVER + "/404";
+
+ var downloadFail = function (error) {
+
+ expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list");
+ expect(error.http_status).toBe(404);
+
+ expect(error.body).toBeDefined();
+ expect(error.body).toMatch('You requested a 404');
+
+ done();
+ };
+
+ transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, downloadFail);
+ });
+
+ it("filetransfer.spec.14 should handle malformed urls", function (done) {
+
+ var fileURL = getMalformedUrl();
+
+ var downloadFail = function (error) {
+
+ // Note: Android needs the bad protocol to be added to the access list
+ // won't match because ^https?:// is prepended to the regex
+ // The bad protocol must begin with http to avoid automatic prefix
+ expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list");
+ expect(error.code).toBe(FileTransferError.INVALID_URL_ERR);
+
+ done();
+ };
+
+ transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, downloadFail);
+ });
+
+ it("filetransfer.spec.15 should handle unknown host", function (done) {
+
+ var fileURL = UNKNOWN_HOST;
+
+ var downloadFail = function (error) {
+ expect(error.code).toBe(FileTransferError.CONNECTION_ERR);
+ done();
+ };
+
+ // turn off the onprogress handler
+ transfer.onprogress = function () {};
+
+ transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, downloadFail);
+ });
+
+ it("filetransfer.spec.16 should handle bad file path", function (done) {
+ var fileURL = SERVER;
+ transfer.download(fileURL, "c:\\54321", unexpectedCallbacks.httpWin, done);
+ });
+
+ it("filetransfer.spec.17 progress should work with gzip encoding", function (done) {
+
+ // lengthComputable false on bb10 when downloading gzip
+ if (cordova.platformId === 'blackberry10') {
+ pending();
+ return;
+ }
+
+ var fileURL = "http://www.apache.org/";
+
+ var downloadWin = function (entry) {
+ verifyDownload(entry);
+ done();
+ };
+
+ transfer.download(fileURL, localFilePath, downloadWin, unexpectedCallbacks.httpFail);
+ });
+
+ it("filetransfer.spec.30 downloaded file entries should have a toNativeURL method", function (done) {
+
+ var fileURL = SERVER + "/robots.txt";
+
+ var downloadWin = function (entry) {
+
+ expect(entry.toNativeURL).toBeDefined();
+ expect(entry.toNativeURL).toEqual(jasmine.any(Function));
+
+ var nativeURL = entry.toNativeURL();
+
+ expect(nativeURL).toBeTruthy();
+ expect(nativeURL).toEqual(jasmine.any(String));
+
+ if (isWindows()) {
+ expect(nativeURL.substring(0, 14)).toBe('ms-appdata:///');
+ } else {
+ expect(nativeURL.substring(0, 7)).toBe('file://');
+ }
+
+ done();
+ };
+
+ transfer.download(fileURL, localFilePath, downloadWin, unexpectedCallbacks.httpFail);
+ });
+
+ it("filetransfer.spec.28 (compatibility) should be able to download a file using local paths", function (done) {
+
+ var fileURL = SERVER + "/robots.txt";
+
+ var unsupported = function (response) {
+ expectedCallbacks.unsupportedOperation(response);
+ done();
+ };
+
+ var downloadWin = function (entry) {
+ verifyDownload(entry);
+ done();
+ };
+
+ var internalFilePath;
+ if (root.toInternalURL) {
+ internalFilePath = root.toInternalURL() + fileName;
+ } else {
+ internalFilePath = localFilePath;
+ }
+
+ // This is an undocumented interface to File which exists only for testing
+ // backwards compatibilty. By obtaining the raw filesystem path of the download
+ // location, we can pass that to transfer.download() to make sure that previously-stored
+ // paths are still valid.
+ cordova.exec(function (localPath) {
+ transfer.download(fileURL, localPath, downloadWin, unexpectedCallbacks.httpFail);
+ }, unsupported, 'File', '_getLocalFilesystemPath', [internalFilePath]);
+ });
+ });
+
+ describe('upload', function() {
+
+ var uploadParams;
+ var uploadOptions;
+
+ var fileName;
+ var fileContents;
+ var localFilePath;
+
+ // helpers
+ var verifyUpload = function (uploadResult) {
+
+ expect(uploadResult.bytesSent).toBeGreaterThan(0);
+ expect(uploadResult.responseCode).toBe(200);
+
+ var obj = null;
+ try {
+ obj = JSON.parse(uploadResult.response);
+ expect(obj.fields).toBeDefined();
+ expect(obj.fields.value1).toBe("test");
+ expect(obj.fields.value2).toBe("param");
+ } catch (e) {
+ expect(obj).not.toBeNull('returned data from server should be valid json');
+ }
+
+ expect(transfer.onprogress).toHaveBeenCalled();
+ };
+
+ beforeEach(function(done) {
+
+ fileName = 'fileToUpload.txt';
+ fileContents = 'upload test file';
+
+ uploadParams = {};
+ uploadParams.value1 = "test";
+ uploadParams.value2 = "param";
+
+ uploadOptions = new FileUploadOptions();
+ uploadOptions.fileKey = "file";
+ uploadOptions.fileName = fileName;
+ uploadOptions.mimeType = "text/plain";
+ uploadOptions.params = uploadParams;
+
+ var fileWin = function (entry) {
+ localFilePath = entry.toURL();
+ done();
+ };
+
+ // create a file to upload
+ writeFile(root, fileName, fileContents, fileWin);
+ });
+
+ // delete the uploaded file
+ afterEach(function (done) {
+ deleteFile(root, fileName, done);
+ });
+
+ it("filetransfer.spec.18 should be able to upload a file", function (done) {
+
+ var fileURL = SERVER + '/upload';
+
+ var uploadWin = function (uploadResult) {
+
+ verifyUpload(uploadResult);
+
+ if (cordova.platformId === 'ios') {
+ expect(uploadResult.headers).toBeDefined('Expected headers to be defined.');
+ expect(uploadResult.headers['Content-Type']).toBeDefined('Expected content-type header to be defined.');
+ }
+
+ done();
+ };
+
+ // NOTE: removing uploadOptions cause Android to timeout
+ transfer.upload(localFilePath, fileURL, uploadWin, unexpectedCallbacks.httpFail, uploadOptions);
+ });
+
+ it("filetransfer.spec.19 should be able to upload a file with http basic auth", function (done) {
+
+ var fileURL = SERVER_WITH_CREDENTIALS + "/upload_basic_auth";
+
+ var uploadWin = function (uploadResult) {
+ verifyUpload(uploadResult);
+ done();
+ };
+
+ // NOTE: removing uploadOptions cause Android to timeout
+ transfer.upload(localFilePath, fileURL, uploadWin, unexpectedCallbacks.httpFail, uploadOptions);
+ });
+
+ it("filetransfer.spec.21 should be stopped by abort() right away", function (done) {
+
+ var fileURL = SERVER + '/upload';
+ var startTime;
+
+ var uploadFail = function (e) {
+ expect(e.code).toBe(FileTransferError.ABORT_ERR);
+ expect(new Date() - startTime).toBeLessThan(GRACE_TIME_DELTA);
+
+ // delay calling done() to wait for the bogus abort()
+ setTimeout(done, GRACE_TIME_DELTA * 2);
+ };
+
+ var fileWin = function () {
+
+ startTime = +new Date();
+
+ expect(transfer.abort).not.toThrow();
+
+ // NOTE: removing uploadOptions cause Android to timeout
+ transfer.upload(localFilePath, fileURL, unexpectedCallbacks.httpWin, uploadFail, uploadOptions);
+ transfer.abort();
+
+ setTimeout(function () {
+ expect(transfer.abort).not.toThrow();
+ }, GRACE_TIME_DELTA);
+ };
+
+ writeFile(root, fileName, new Array(10000).join('aborttest!'), fileWin);
+ });
+
+ it("filetransfer.spec.22 should get http status and body on failure", function (done) {
+
+ var fileURL = SERVER + '/403';
+
+ var uploadFail = function (error) {
+ expect(error.http_status).toBe(403);
+ expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list");
+ done();
+ };
+
+ transfer.upload(localFilePath, fileURL, unexpectedCallbacks.httpWin, uploadFail, uploadOptions);
+ });
+
+ it("filetransfer.spec.24 should handle malformed urls", function (done) {
+
+ var fileURL = getMalformedUrl();
+
+ var uploadFail = function (error) {
+ expect(error.code).toBe(FileTransferError.INVALID_URL_ERR);
+ expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list");
+ done();
+ };
+
+ transfer.upload(localFilePath, fileURL, unexpectedCallbacks.httpWin, uploadFail, {});
+ });
+
+ it("filetransfer.spec.25 should handle unknown host", function (done) {
+
+ var fileURL = UNKNOWN_HOST;
+
+ var uploadFail = function (error) {
+ expect(error.code).toBe(FileTransferError.CONNECTION_ERR);
+ expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list");
+ done();
+ };
+
+ transfer.upload(localFilePath, fileURL, unexpectedCallbacks.httpWin, uploadFail, {});
+ });
+
+ it("filetransfer.spec.25 should handle missing file", function (done) {
+
+ var fileURL = SERVER + "/upload";
+
+ var uploadFail = function (error) {
+ expect(error.code).toBe(FileTransferError.FILE_NOT_FOUND_ERR);
+ expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list");
+ done();
+ };
+
+ transfer.upload('does_not_exist.txt', fileURL, unexpectedCallbacks.httpWin, uploadFail);
+ });
+
+ it("filetransfer.spec.26 should handle bad file path", function (done) {
+
+ var fileURL = SERVER + "/upload";
+
+ var uploadFail = function (error) {
+ expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list");
+ done();
+ };
+
+ transfer.upload("c:\\54321", fileURL, unexpectedCallbacks.httpWin, uploadFail);
+ });
+
+ it("filetransfer.spec.27 should be able to set custom headers", function (done) {
+
+ if (cordova.platformId === 'windowsphone') {
+ pending();
+ }
+
+ var fileURL = HEADERS_ECHO;
+
+ var uploadWin = function (uploadResult) {
+
+ expect(uploadResult.bytesSent).toBeGreaterThan(0);
+ expect(uploadResult.responseCode).toBe(200);
+ expect(uploadResult.response).toBeDefined();
+
+ var responseHtml = decodeURIComponent(uploadResult.response);
+
+ expect(responseHtml).toMatch(/CustomHeader1[\s\S]*CustomValue1/i);
+ expect(responseHtml).toMatch(/CustomHeader2[\s\S]*CustomValue2[\s\S]*CustomValue3/i, "Should allow array values");
+
+ done();
+ };
+
+ uploadOptions.headers = {
+ "CustomHeader1": "CustomValue1",
+ "CustomHeader2": ["CustomValue2", "CustomValue3"],
+ };
+
+ // NOTE: removing uploadOptions cause Android to timeout
+ transfer.upload(localFilePath, fileURL, uploadWin, unexpectedCallbacks.httpFail, uploadOptions);
+ });
+
+ it("filetransfer.spec.29 (compatibility) should be able to upload a file using local paths", function (done) {
+
+ var fileURL = SERVER + "/upload";
+
+ var unsupported = function (response) {
+ expectedCallbacks.unsupportedOperation(response);
+ done();
+ };
+
+ var uploadWin = function (uploadResult) {
+ verifyUpload(uploadResult);
+ done();
+ };
+
+ var internalFilePath;
+ if (root.toInternalURL) {
+ internalFilePath = root.toInternalURL() + fileName;
+ } else {
+ internalFilePath = localFilePath;
+ }
+
+ // This is an undocumented interface to File which exists only for testing
+ // backwards compatibilty. By obtaining the raw filesystem path of the download
+ // location, we can pass that to transfer.download() to make sure that previously-stored
+ // paths are still valid.
+ cordova.exec(function (localPath) {
+ transfer.upload(localPath, fileURL, uploadWin, unexpectedCallbacks.httpFail, uploadOptions);
+ }, unsupported, 'File', '_getLocalFilesystemPath', [internalFilePath]);
+ });
+ });
+ });
+ });
+};
+
+/******************************************************************************/
+/******************************************************************************/
+/******************************************************************************/
+
+exports.defineManualTests = function (contentEl, createActionButton) {
+ var imageURL = "http://apache.org/images/feather-small.gif";
+ var videoURL = "http://techslides.com/demos/sample-videos/small.mp4";
+
+ function clearResults() {
+ var results = document.getElementById("info");
+ results.innerHTML = '';
+ }
+
+ function downloadImg(source, urlFn, element, directory) {
+ var filename = source.substring(source.lastIndexOf("/") + 1);
+ filename = directory + filename || filename;
+ function download(fileSystem) {
+ var ft = new FileTransfer();
+ console.log("Starting download");
+ ft.download(source, fileSystem.root.toURL() + filename, function (entry) {
+ console.log("Download complete");
+ element.src = urlFn(entry);
+ console.log("Src URL is " + element.src);
+ console.log("Inserting element");
+ document.getElementById("info").appendChild(element);
+ }, function (e) { console.log("ERROR: ft.download " + e.code); });
+ }
+ console.log("Requesting filesystem");
+ clearResults();
+ window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, function (fileSystem) {
+ console.log("Checking for existing file");
+ if (directory !== undefined) {
+ console.log("Checking for existing directory.");
+ fileSystem.root.getDirectory(directory, {}, function (dirEntry) {
+ dirEntry.removeRecursively(function () {
+ download(fileSystem);
+ }, function () { console.log("ERROR: dirEntry.removeRecursively"); });
+ }, function () {
+ download(fileSystem);
+ });
+ } else {
+ fileSystem.root.getFile(filename, { create: false }, function (entry) {
+ console.log("Removing existing file");
+ entry.remove(function () {
+ download(fileSystem);
+ }, function () { console.log("ERROR: entry.remove"); });
+ }, function () {
+ download(fileSystem);
+ });
+ }
+ }, function () { console.log("ERROR: requestFileSystem"); });
+ }
+
+ /******************************************************************************/
+
+ var file_transfer_tests = '
Image File Transfer Tests
' +
+ '
The following tests should display an image of the Apache feather in the status box
' +
+ '' +
+ '' +
+ '' +
+ '
Video File Transfer Tests
' +
+ '
The following tests should display a video in the status box. The video should play when play is pressed
' +
+ '' +
+ '';
+
+ contentEl.innerHTML = '' +
+ file_transfer_tests;
+
+ createActionButton('Download and display img (cdvfile)', function () {
+ downloadImg(imageURL, function (entry) { return entry.toURL(); }, new Image());
+ }, 'cdv_image');
+
+ createActionButton('Download and display img (native)', function () {
+ downloadImg(imageURL, function (entry) { return entry.toNativeURL(); }, new Image());
+ }, 'native_image');
+
+ createActionButton('Download to a non-existent dir (should work)', function () {
+ downloadImg(imageURL, function (entry) { return entry.toURL(); }, new Image(), '/nonExistentDirTest/');
+ }, 'non-existent_dir');
+
+ createActionButton('Download and play video (cdvfile)', function () {
+ var videoElement = document.createElement('video');
+ videoElement.controls = "controls";
+ downloadImg(videoURL, function (entry) { return entry.toURL(); }, videoElement);
+ }, 'cdv_video');
+
+ createActionButton('Download and play video (native)', function () {
+ var videoElement = document.createElement('video');
+ videoElement.controls = "controls";
+ downloadImg(videoURL, function (entry) { return entry.toNativeURL(); }, videoElement);
+ }, 'native_video');
+};
diff --git a/plugins/org.apache.cordova.file-transfer/www/FileTransfer.js b/plugins/org.apache.cordova.file-transfer/www/FileTransfer.js
new file mode 100644
index 0000000..17fb782
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/www/FileTransfer.js
@@ -0,0 +1,212 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var argscheck = require('cordova/argscheck'),
+ exec = require('cordova/exec'),
+ FileTransferError = require('./FileTransferError'),
+ ProgressEvent = require('org.apache.cordova.file.ProgressEvent');
+
+function newProgressEvent(result) {
+ var pe = new ProgressEvent();
+ pe.lengthComputable = result.lengthComputable;
+ pe.loaded = result.loaded;
+ pe.total = result.total;
+ return pe;
+}
+
+function getUrlCredentials(urlString) {
+ var credentialsPattern = /^https?\:\/\/(?:(?:(([^:@\/]*)(?::([^@\/]*))?)?@)?([^:\/?#]*)(?::(\d*))?).*$/,
+ credentials = credentialsPattern.exec(urlString);
+
+ return credentials && credentials[1];
+}
+
+function getBasicAuthHeader(urlString) {
+ var header = null;
+
+
+ // This is changed due to MS Windows doesn't support credentials in http uris
+ // so we detect them by regexp and strip off from result url
+ // Proof: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/a327cf3c-f033-4a54-8b7f-03c56ba3203f/windows-foundation-uri-security-problem
+
+ if (window.btoa) {
+ var credentials = getUrlCredentials(urlString);
+ if (credentials) {
+ var authHeader = "Authorization";
+ var authHeaderValue = "Basic " + window.btoa(credentials);
+
+ header = {
+ name : authHeader,
+ value : authHeaderValue
+ };
+ }
+ }
+
+ return header;
+}
+
+var idCounter = 0;
+
+/**
+ * FileTransfer uploads a file to a remote server.
+ * @constructor
+ */
+var FileTransfer = function() {
+ this._id = ++idCounter;
+ this.onprogress = null; // optional callback
+};
+
+/**
+* Given an absolute file path, uploads a file on the device to a remote server
+* using a multipart HTTP request.
+* @param filePath {String} Full path of the file on the device
+* @param server {String} URL of the server to receive the file
+* @param successCallback (Function} Callback to be invoked when upload has completed
+* @param errorCallback {Function} Callback to be invoked upon error
+* @param options {FileUploadOptions} Optional parameters such as file name and mimetype
+* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false
+*/
+FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) {
+ argscheck.checkArgs('ssFFO*', 'FileTransfer.upload', arguments);
+ // check for options
+ var fileKey = null;
+ var fileName = null;
+ var mimeType = null;
+ var params = null;
+ var chunkedMode = true;
+ var headers = null;
+ var httpMethod = null;
+ var basicAuthHeader = getBasicAuthHeader(server);
+ if (basicAuthHeader) {
+ server = server.replace(getUrlCredentials(server) + '@', '');
+
+ options = options || {};
+ options.headers = options.headers || {};
+ options.headers[basicAuthHeader.name] = basicAuthHeader.value;
+ }
+
+ if (options) {
+ fileKey = options.fileKey;
+ fileName = options.fileName;
+ mimeType = options.mimeType;
+ headers = options.headers;
+ httpMethod = options.httpMethod || "POST";
+ if (httpMethod.toUpperCase() == "PUT"){
+ httpMethod = "PUT";
+ } else {
+ httpMethod = "POST";
+ }
+ if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") {
+ chunkedMode = options.chunkedMode;
+ }
+ if (options.params) {
+ params = options.params;
+ }
+ else {
+ params = {};
+ }
+ }
+
+ var fail = errorCallback && function(e) {
+ var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body, e.exception);
+ errorCallback(error);
+ };
+
+ var self = this;
+ var win = function(result) {
+ if (typeof result.lengthComputable != "undefined") {
+ if (self.onprogress) {
+ self.onprogress(newProgressEvent(result));
+ }
+ } else {
+ successCallback && successCallback(result);
+ }
+ };
+ exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]);
+};
+
+/**
+ * Downloads a file form a given URL and saves it to the specified directory.
+ * @param source {String} URL of the server to receive the file
+ * @param target {String} Full path of the file on the device
+ * @param successCallback (Function} Callback to be invoked when upload has completed
+ * @param errorCallback {Function} Callback to be invoked upon error
+ * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false
+ * @param options {FileDownloadOptions} Optional parameters such as headers
+ */
+FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options) {
+ argscheck.checkArgs('ssFF*', 'FileTransfer.download', arguments);
+ var self = this;
+
+ var basicAuthHeader = getBasicAuthHeader(source);
+ if (basicAuthHeader) {
+ source = source.replace(getUrlCredentials(source) + '@', '');
+
+ options = options || {};
+ options.headers = options.headers || {};
+ options.headers[basicAuthHeader.name] = basicAuthHeader.value;
+ }
+
+ var headers = null;
+ if (options) {
+ headers = options.headers || null;
+ }
+
+ var win = function(result) {
+ if (typeof result.lengthComputable != "undefined") {
+ if (self.onprogress) {
+ return self.onprogress(newProgressEvent(result));
+ }
+ } else if (successCallback) {
+ var entry = null;
+ if (result.isDirectory) {
+ entry = new (require('org.apache.cordova.file.DirectoryEntry'))();
+ }
+ else if (result.isFile) {
+ entry = new (require('org.apache.cordova.file.FileEntry'))();
+ }
+ entry.isDirectory = result.isDirectory;
+ entry.isFile = result.isFile;
+ entry.name = result.name;
+ entry.fullPath = result.fullPath;
+ entry.filesystem = new FileSystem(result.filesystemName || (result.filesystem == window.PERSISTENT ? 'persistent' : 'temporary'));
+ entry.nativeURL = result.nativeURL;
+ successCallback(entry);
+ }
+ };
+
+ var fail = errorCallback && function(e) {
+ var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body, e.exception);
+ errorCallback(error);
+ };
+
+ exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id, headers]);
+};
+
+/**
+ * Aborts the ongoing file transfer on this object. The original error
+ * callback for the file transfer will be called if necessary.
+ */
+FileTransfer.prototype.abort = function() {
+ exec(null, null, 'FileTransfer', 'abort', [this._id]);
+};
+
+module.exports = FileTransfer;
diff --git a/plugins/org.apache.cordova.file-transfer/www/FileTransferError.js b/plugins/org.apache.cordova.file-transfer/www/FileTransferError.js
new file mode 100644
index 0000000..07ca831
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/www/FileTransferError.js
@@ -0,0 +1,41 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+/**
+ * FileTransferError
+ * @constructor
+ */
+var FileTransferError = function(code, source, target, status, body, exception) {
+ this.code = code || null;
+ this.source = source || null;
+ this.target = target || null;
+ this.http_status = status || null;
+ this.body = body || null;
+ this.exception = exception || null;
+};
+
+FileTransferError.FILE_NOT_FOUND_ERR = 1;
+FileTransferError.INVALID_URL_ERR = 2;
+FileTransferError.CONNECTION_ERR = 3;
+FileTransferError.ABORT_ERR = 4;
+FileTransferError.NOT_MODIFIED_ERR = 5;
+
+module.exports = FileTransferError;
diff --git a/plugins/org.apache.cordova.file-transfer/www/blackberry10/FileTransfer.js b/plugins/org.apache.cordova.file-transfer/www/blackberry10/FileTransfer.js
new file mode 100644
index 0000000..f74914a
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/www/blackberry10/FileTransfer.js
@@ -0,0 +1,188 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var argscheck = require('cordova/argscheck'),
+ FileTransferError = require('./FileTransferError');
+ xhrImpl = require('./BB10XHRImplementation');
+
+
+function getBasicAuthHeader(urlString) {
+ var header = null;
+
+ if (window.btoa) {
+ // parse the url using the Location object
+ var url = document.createElement('a');
+ url.href = urlString;
+
+ var credentials = null;
+ var protocol = url.protocol + "//";
+ var origin = protocol + url.host;
+
+ // check whether there are the username:password credentials in the url
+ if (url.href.indexOf(origin) !== 0) { // credentials found
+ var atIndex = url.href.indexOf("@");
+ credentials = url.href.substring(protocol.length, atIndex);
+ }
+
+ if (credentials) {
+ var authHeader = "Authorization";
+ var authHeaderValue = "Basic " + window.btoa(credentials);
+
+ header = {
+ name : authHeader,
+ value : authHeaderValue
+ };
+ }
+ }
+
+ return header;
+}
+
+var idCounter = 0;
+
+/**
+ * FileTransfer uploads a file to a remote server.
+ * @constructor
+ */
+var FileTransfer = function() {
+ this._id = ++idCounter;
+ this.onprogress = null; // optional callback
+};
+
+/**
+* Given an absolute file path, uploads a file on the device to a remote server
+* using a multipart HTTP request.
+* @param filePath {String} Full path of the file on the device
+* @param server {String} URL of the server to receive the file
+* @param successCallback (Function} Callback to be invoked when upload has completed
+* @param errorCallback {Function} Callback to be invoked upon error
+* @param options {FileUploadOptions} Optional parameters such as file name and mimetype
+* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false
+*/
+FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) {
+ argscheck.checkArgs('ssFFO*', 'FileTransfer.upload', arguments);
+ // check for options
+ var fileKey = null;
+ var fileName = null;
+ var mimeType = null;
+ var params = null;
+ var chunkedMode = true;
+ var headers = null;
+ var httpMethod = null;
+ var basicAuthHeader = getBasicAuthHeader(server);
+ if (basicAuthHeader) {
+ options = options || {};
+ options.headers = options.headers || {};
+ options.headers[basicAuthHeader.name] = basicAuthHeader.value;
+ }
+
+ if (options) {
+ fileKey = options.fileKey;
+ fileName = options.fileName;
+ mimeType = options.mimeType;
+ headers = options.headers;
+ httpMethod = options.httpMethod || "POST";
+ if (httpMethod.toUpperCase() == "PUT"){
+ httpMethod = "PUT";
+ } else {
+ httpMethod = "POST";
+ }
+ if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") {
+ chunkedMode = options.chunkedMode;
+ }
+ if (options.params) {
+ params = options.params;
+ }
+ else {
+ params = {};
+ }
+ }
+
+ var fail = errorCallback && function(e) {
+ var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body);
+ errorCallback(error);
+ };
+
+ var self = this;
+ var win = function(result) {
+ if (typeof result.lengthComputable != "undefined") {
+ if (self.onprogress) {
+ self.onprogress(result);
+ }
+ } else {
+ successCallback && successCallback(result);
+ }
+ };
+ xhrImpl.upload(win, fail, [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]);
+};
+
+/**
+ * Downloads a file form a given URL and saves it to the specified directory.
+ * @param source {String} URL of the server to receive the file
+ * @param target {String} Full path of the file on the device
+ * @param successCallback (Function} Callback to be invoked when upload has completed
+ * @param errorCallback {Function} Callback to be invoked upon error
+ * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false
+ * @param options {FileDownloadOptions} Optional parameters such as headers
+ */
+FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options) {
+ argscheck.checkArgs('ssFF*', 'FileTransfer.download', arguments);
+ var self = this;
+
+ var basicAuthHeader = getBasicAuthHeader(source);
+ if (basicAuthHeader) {
+ options = options || {};
+ options.headers = options.headers || {};
+ options.headers[basicAuthHeader.name] = basicAuthHeader.value;
+ }
+
+ var headers = null;
+ if (options) {
+ headers = options.headers || null;
+ }
+
+ var win = function(result) {
+ if (typeof result.lengthComputable != "undefined") {
+ if (self.onprogress) {
+ return self.onprogress(result);
+ }
+ } else if (successCallback) {
+ successCallback(result);
+ }
+ };
+
+ var fail = errorCallback && function(e) {
+ var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body);
+ errorCallback(error);
+ };
+
+ xhrImpl.download(win, fail, [source, target, trustAllHosts, this._id, headers]);
+};
+
+/**
+ * Aborts the ongoing file transfer on this object. The original error
+ * callback for the file transfer will be called if necessary.
+ */
+FileTransfer.prototype.abort = function() {
+ xhrImpl.abort(null, null, [this._id]);
+};
+
+module.exports = FileTransfer;
diff --git a/plugins/org.apache.cordova.file-transfer/www/blackberry10/FileTransferProxy.js b/plugins/org.apache.cordova.file-transfer/www/blackberry10/FileTransferProxy.js
new file mode 100644
index 0000000..438999a
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/www/blackberry10/FileTransferProxy.js
@@ -0,0 +1,36 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+/*
+ * FileTransferProxy
+ *
+ * Register all FileTransfer exec calls to be handled by proxy
+ */
+
+var xhrFileTransfer = require('org.apache.cordova.file-transfer.xhrFileTransfer');
+
+module.exports = {
+ abort: xhrFileTransfer.abort,
+ download: xhrFileTransfer.download,
+ upload: xhrFileTransfer.upload
+};
+
+require('cordova/exec/proxy').add('FileTransfer', module.exports);
diff --git a/plugins/org.apache.cordova.file-transfer/www/blackberry10/xhrFileTransfer.js b/plugins/org.apache.cordova.file-transfer/www/blackberry10/xhrFileTransfer.js
new file mode 100644
index 0000000..87c8ec0
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/www/blackberry10/xhrFileTransfer.js
@@ -0,0 +1,259 @@
+/*
+ *
+ * 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 Blob:false */
+var cordova = require('cordova'),
+ resolve = cordova.require('org.apache.cordova.file.resolveLocalFileSystemURIProxy'),
+ requestAnimationFrame = cordova.require('org.apache.cordova.file.bb10RequestAnimationFrame'),
+ xhr = {};
+
+function getParentPath(filePath) {
+ var pos = filePath.lastIndexOf('/');
+ return filePath.substring(0, pos + 1);
+}
+
+function getFileName(filePath) {
+ var pos = filePath.lastIndexOf('/');
+ return filePath.substring(pos + 1);
+}
+
+function checkURL(url) {
+ return url.indexOf(' ') === -1 ? true : false;
+}
+
+module.exports = {
+ abort: function (win, fail, args) {
+ var id = args[0];
+ if (xhr[id]) {
+ xhr[id].abort();
+ if (typeof(win) === 'function') {
+ win();
+ }
+ } else if (typeof(fail) === 'function') {
+ fail();
+ }
+ },
+
+ upload: function(win, fail, args) {
+ var filePath = args[0],
+ server = args[1],
+ fileKey = args[2],
+ fileName = args[3],
+ mimeType = args[4],
+ params = args[5],
+ /*trustAllHosts = args[6],*/
+ chunkedMode = args[7],
+ headers = args[8],
+ onSuccess = function (data) {
+ if (typeof(win) === 'function') {
+ win(data);
+ }
+ },
+ onFail = function (code) {
+ delete xhr[fileKey];
+ if (typeof(fail) === 'function') {
+ fail(code);
+ }
+ };
+
+ if (!checkURL(server)) {
+ onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, server, filePath));
+ }
+
+ xhr[fileKey] = new XMLHttpRequest();
+ xhr[fileKey].onabort = function () {
+ onFail(new FileTransferError(FileTransferError.ABORT_ERR, server, filePath, this.status, xhr[fileKey].response));
+ };
+
+ resolve(function(entry) {
+ requestAnimationFrame(function () {
+ entry.nativeEntry.file(function(file) {
+ function uploadFile(blobFile) {
+ var fd = new FormData();
+
+ fd.append(fileKey, blobFile, fileName);
+ for (var prop in params) {
+ if(params.hasOwnProperty(prop)) {
+ fd.append(prop, params[prop]);
+ }
+ }
+
+ xhr[fileKey].open("POST", server);
+ xhr[fileKey].onload = function(evt) {
+ if (xhr[fileKey].status === 200) {
+ var result = new FileUploadResult();
+ result.bytesSent = file.size;
+ result.responseCode = xhr[fileKey].status;
+ result.response = xhr[fileKey].response;
+ delete xhr[fileKey];
+ onSuccess(result);
+ } else if (xhr[fileKey].status === 404) {
+ onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response));
+ } else {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response));
+ }
+ };
+ xhr[fileKey].ontimeout = function(evt) {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response));
+ };
+ xhr[fileKey].onerror = function () {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, this.status, xhr[fileKey].response));
+ };
+ xhr[fileKey].upload.onprogress = function (evt) {
+ if (evt.loaded > 0) {
+ onSuccess(evt);
+ }
+ };
+
+ for (var header in headers) {
+ if (headers.hasOwnProperty(header)) {
+ xhr[fileKey].setRequestHeader(header, headers[header]);
+ }
+ }
+
+ requestAnimationFrame(function () {
+ xhr[fileKey].send(fd);
+ });
+ }
+
+ var bytesPerChunk;
+ if (chunkedMode === true) {
+ bytesPerChunk = 1024 * 1024; // 1MB chunk sizes.
+ } else {
+ bytesPerChunk = file.size;
+ }
+ var start = 0;
+ var end = bytesPerChunk;
+ while (start < file.size) {
+ var chunk = file.slice(start, end, mimeType);
+ uploadFile(chunk);
+ start = end;
+ end = start + bytesPerChunk;
+ }
+ }, function(error) {
+ onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, filePath));
+ });
+ });
+ }, function(error) {
+ onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, filePath));
+ }, [filePath]);
+ },
+
+ download: function (win, fail, args) {
+ var source = args[0],
+ target = args[1],
+ id = args[3],
+ headers = args[4],
+ fileWriter,
+ onSuccess = function (entry) {
+ if (typeof(win) === 'function') {
+ win(entry);
+ }
+ },
+ onFail = function (error) {
+ var reader;
+ delete xhr[id];
+ if (typeof(fail) === 'function') {
+ if (error && error.body && typeof(error.body) === 'object') {
+ reader = new FileReader()._realReader;
+ reader.onloadend = function () {
+ error.body = this.result;
+ fail(error);
+ };
+ reader.onerror = function () {
+ fail(error);
+ };
+ reader.readAsText(error.body);
+ } else {
+ fail(error);
+ }
+ }
+ };
+
+ if (!checkURL(source)) {
+ onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, source, target));
+ }
+
+ xhr[id] = new XMLHttpRequest();
+
+ function writeFile(entry) {
+ entry.createWriter(function (writer) {
+ fileWriter = writer;
+ fileWriter.onwriteend = function (evt) {
+ if (!evt.target.error) {
+ entry.filesystemName = entry.filesystem.name;
+ delete xhr[id];
+ onSuccess(entry);
+ } else {
+ onFail(evt.target.error);
+ }
+ };
+ fileWriter.onerror = function (evt) {
+ onFail(evt.target.error);
+ };
+ fileWriter.write(new Blob([xhr[id].response]));
+ }, function (error) {
+ onFail(error);
+ });
+ }
+
+ xhr[id].onerror = function (e) {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, source, target, xhr[id].status, xhr[id].response));
+ };
+
+ xhr[id].onabort = function (e) {
+ onFail(new FileTransferError(FileTransferError.ABORT_ERR, source, target, xhr[id].status, xhr[id].response));
+ }
+
+ xhr[id].onload = function () {
+ if (xhr[id].readyState === xhr[id].DONE) {
+ if (xhr[id].status === 200 && xhr[id].response) {
+ resolveLocalFileSystemURI(getParentPath(target), function (dir) {
+ dir.getFile(getFileName(target), {create: true}, writeFile, function (error) {
+ onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, xhr[id].status, xhr[id].response));
+ });
+ }, function (error) {
+ onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, xhr[id].status, xhr[id].response));
+ });
+ } else if (xhr[id].status === 404) {
+ onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, source, target, xhr[id].status, xhr[id].response));
+ } else {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, source, target, xhr[id].status, xhr[id].response));
+ }
+ }
+ };
+ xhr[id].onprogress = function (evt) {
+ onSuccess(evt);
+ };
+ xhr[id].open("GET", source, true);
+ for (var header in headers) {
+ if (headers.hasOwnProperty(header)) {
+ xhr[id].setRequestHeader(header, headers[header]);
+ }
+ }
+ xhr[id].responseType = "blob";
+ requestAnimationFrame(function () {
+ if (xhr[id]) {
+ xhr[id].send();
+ }
+ });
+ }
+};
diff --git a/plugins/org.apache.cordova.file-transfer/www/firefoxos/FileTransferProxy.js b/plugins/org.apache.cordova.file-transfer/www/firefoxos/FileTransferProxy.js
new file mode 100644
index 0000000..164da1a
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/www/firefoxos/FileTransferProxy.js
@@ -0,0 +1,223 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var cordova = require('cordova'),
+ FileTransferError = require('./FileTransferError'),
+ xhr = {};
+
+function getParentPath(filePath) {
+ var pos = filePath.lastIndexOf('/');
+ return filePath.substring(0, pos + 1);
+}
+
+function getFileName(filePath) {
+ var pos = filePath.lastIndexOf('/');
+ return filePath.substring(pos + 1);
+}
+
+module.exports = {
+ abort: function (successCallback, errorCallback, args) {
+ var id = args[0];
+ if (xhr[id]) {
+ xhr[id].abort();
+ if (typeof(successCallback) === 'function') {
+ successCallback();
+ }
+ } else if (typeof(errorCallback) === 'function') {
+ errorCallback();
+ }
+ },
+
+ upload: function(successCallback, errorCallback, args) {
+ var filePath = args[0],
+ server = args[1],
+ fileKey = args[2],
+ fileName = args[3],
+ mimeType = args[4],
+ params = args[5],
+ /*trustAllHosts = args[6],*/
+ /*chunkedMode = args[7],*/
+ headers = args[8];
+
+ xhr[fileKey] = new XMLHttpRequest({mozSystem: true});
+ xhr[fileKey].onabort = function() {
+ onFail(new FileTransferError(FileTransferError.ABORT_ERR, server, filePath, this.status, xhr[fileKey].response));
+ };
+
+ window.resolveLocalFileSystemURL(filePath, function(entry) {
+ entry.file(function(file) {
+ var reader = new FileReader();
+
+ reader.onloadend = function() {
+ var blob = new Blob([this.result], {type: mimeType});
+ var fd = new FormData();
+
+ fd.append(fileKey, blob, fileName);
+
+ for (var prop in params) {
+ if (params.hasOwnProperty(prop)) {
+ fd.append(prop, params[prop]);
+ }
+ }
+
+ xhr[fileKey].open("POST", server);
+
+ xhr[fileKey].onload = function(evt) {
+ if (xhr[fileKey].status === 200) {
+ var result = new FileUploadResult();
+ result.bytesSent = blob.size;
+ result.responseCode = xhr[fileKey].status;
+ result.response = xhr[fileKey].response;
+ delete xhr[fileKey];
+ onSuccess(result);
+ } else if (xhr[fileKey].status === 404) {
+ onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response));
+ } else {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response));
+ }
+ };
+
+ xhr[fileKey].ontimeout = function() {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response));
+ };
+
+ xhr[fileKey].onerror = function() {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, this.status, xhr[fileKey].response));
+ };
+
+ for (var header in headers) {
+ if (headers.hasOwnProperty(header)) {
+ xhr[fileKey].setRequestHeader(header, headers[header]);
+ }
+ }
+
+ xhr[fileKey].send(fd);
+ };
+
+ reader.readAsArrayBuffer(file);
+
+ }, function() {
+ onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, filePath));
+ });
+ }, function() {
+ onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, filePath));
+ });
+
+ function onSuccess(data) {
+ if (typeof(successCallback) === 'function') {
+ successCallback(data);
+ }
+ }
+
+ function onFail(code) {
+ delete xhr[fileKey];
+ if (typeof(errorCallback) === 'function') {
+ errorCallback(code);
+ }
+ }
+ },
+
+ download: function (successCallback, errorCallback, args) {
+ var source = args[0],
+ target = args[1],
+ id = args[3],
+ headers = args[4];
+
+ xhr[id] = new XMLHttpRequest({mozSystem: true});
+
+ xhr[id].onload = function () {
+ if (xhr[id].readyState === xhr[id].DONE) {
+ if (xhr[id].status === 200 && xhr[id].response) {
+ window.resolveLocalFileSystemURL(getParentPath(target), function (dir) {
+ dir.getFile(getFileName(target), {create: true}, writeFile, function (error) {
+ onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, xhr[id].status, xhr[id].response));
+ });
+ }, function () {
+ onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, xhr[id].status, xhr[id].response));
+ });
+ } else if (xhr[id].status === 404) {
+ onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, source, target, xhr[id].status, xhr[id].response));
+ } else {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, source, target, xhr[id].status, xhr[id].response));
+ }
+ }
+ };
+
+ function writeFile(entry) {
+ entry.createWriter(function (fileWriter) {
+ fileWriter.onwriteend = function (evt) {
+ if (!evt.target.error) {
+ entry.filesystemName = entry.filesystem.name;
+ delete xhr[id];
+ onSuccess(entry);
+ } else {
+ onFail(evt.target.error);
+ }
+ };
+ fileWriter.onerror = function (evt) {
+ onFail(evt.target.error);
+ };
+ fileWriter.write(new Blob([xhr[id].response]));
+ }, function (error) {
+ onFail(error);
+ });
+ }
+
+ xhr[id].onerror = function (e) {
+ onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, source, target, xhr[id].status, xhr[id].response));
+ };
+
+ xhr[id].onabort = function (e) {
+ onFail(new FileTransferError(FileTransferError.ABORT_ERR, source, target, xhr[id].status, xhr[id].response));
+ };
+
+ xhr[id].open("GET", source, true);
+
+ for (var header in headers) {
+ if (headers.hasOwnProperty(header)) {
+ xhr[id].setRequestHeader(header, headers[header]);
+ }
+ }
+
+ xhr[id].responseType = "blob";
+
+ setTimeout(function () {
+ if (xhr[id]) {
+ xhr[id].send();
+ }
+ }, 0);
+
+ function onSuccess(entry) {
+ if (typeof(successCallback) === 'function') {
+ successCallback(entry);
+ }
+ }
+
+ function onFail(error) {
+ delete xhr[id];
+ if (typeof(errorCallback) === 'function') {
+ errorCallback(error);
+ }
+ }
+ }
+};
+
+require('cordova/exec/proxy').add('FileTransfer', module.exports);
diff --git a/plugins/org.apache.cordova.file-transfer/www/wp7/base64.js b/plugins/org.apache.cordova.file-transfer/www/wp7/base64.js
new file mode 100644
index 0000000..6c211e7
--- /dev/null
+++ b/plugins/org.apache.cordova.file-transfer/www/wp7/base64.js
@@ -0,0 +1,71 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
+ INVALID_CHARACTER_ERR = (function () {
+ // fabricate a suitable error object
+ try { document.createElement('$'); }
+ catch (error) { return error; }
+ }());
+
+ // encoder
+ // [https://gist.github.com/999166] by [https://github.com/nignag]
+ window.btoa || (
+ window.btoa = function (input) {
+ for (
+ // initialize result and counter
+ var block, charCode, idx = 0, map = chars, output = '';
+ // if the next input index does not exist:
+ // change the mapping table to "="
+ // check if d has no fractional digits
+ input.charAt(idx | 0) || (map = '=', idx % 1) ;
+ // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
+ output += map.charAt(63 & block >> 8 - idx % 1 * 8)
+ ) {
+ charCode = input.charCodeAt(idx += 3 / 4);
+ if (charCode > 0xFF) throw INVALID_CHARACTER_ERR;
+ block = block << 8 | charCode;
+ }
+ return output;
+ });
+
+ // decoder
+ // [https://gist.github.com/1020396] by [https://github.com/atk]
+ window.atob || (
+ window.atob = function (input) {
+ input = input.replace(/=+$/, '')
+ if (input.length % 4 == 1) throw INVALID_CHARACTER_ERR;
+ for (
+ // initialize result and counters
+ var bc = 0, bs, buffer, idx = 0, output = '';
+ // get next character
+ buffer = input.charAt(idx++) ;
+ // character found in table? initialize bit storage and add its ascii value;
+ ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
+ // and if not first of each 4 characters,
+ // convert the first 8 bits to one ascii character
+ bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
+ ) {
+ // try to find character in table (0-63, not found => -1)
+ buffer = chars.indexOf(buffer);
+ }
+ return output;
+ });
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/.fetch.json b/plugins/org.apache.cordova.file/.fetch.json
new file mode 100644
index 0000000..f103810
--- /dev/null
+++ b/plugins/org.apache.cordova.file/.fetch.json
@@ -0,0 +1 @@
+{"source":{"type":"registry","id":"org.apache.cordova.file"}}
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/CONTRIBUTING.md b/plugins/org.apache.cordova.file/CONTRIBUTING.md
new file mode 100644
index 0000000..f7dbcab
--- /dev/null
+++ b/plugins/org.apache.cordova.file/CONTRIBUTING.md
@@ -0,0 +1,37 @@
+
+
+# Contributing to Apache Cordova
+
+Anyone can contribute to Cordova. And we need your contributions.
+
+There are multiple ways to contribute: report bugs, improve the docs, and
+contribute code.
+
+For instructions on this, start with the
+[contribution overview](http://cordova.apache.org/#contribute).
+
+The details are explained there, but the important items are:
+ - Sign and submit an Apache ICLA (Contributor License Agreement).
+ - Have a Jira issue open that corresponds to your contribution.
+ - Run the tests so your patch doesn't break existing functionality.
+
+We look forward to your contributions!
diff --git a/plugins/org.apache.cordova.file/LICENSE b/plugins/org.apache.cordova.file/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/plugins/org.apache.cordova.file/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/NOTICE b/plugins/org.apache.cordova.file/NOTICE
new file mode 100644
index 0000000..8ec56a5
--- /dev/null
+++ b/plugins/org.apache.cordova.file/NOTICE
@@ -0,0 +1,5 @@
+Apache Cordova
+Copyright 2012 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/plugins/org.apache.cordova.file/README.md b/plugins/org.apache.cordova.file/README.md
new file mode 100644
index 0000000..6bc6cc2
--- /dev/null
+++ b/plugins/org.apache.cordova.file/README.md
@@ -0,0 +1,22 @@
+
+
+# org.apache.cordova.file
+
+Plugin documentation: [doc/index.md](doc/index.md)
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/RELEASENOTES.md b/plugins/org.apache.cordova.file/RELEASENOTES.md
new file mode 100644
index 0000000..6ab79f4
--- /dev/null
+++ b/plugins/org.apache.cordova.file/RELEASENOTES.md
@@ -0,0 +1,308 @@
+
+# Release Notes
+
+### 0.2.1 (Sept 5, 2013)
+* [CB-4656] Don't add newlines in data urls within readAsDataUrl.
+* [CB-4514] Making DirectoryCopy Recursive
+* [iOS] Simplify the code in resolveLocalFileSystemURI
+
+### 0.2.3 (Sept 25, 2013)
+* CB-4889 bumping&resetting version
+* [CB-4903] File Plugin not loading Windows8
+* [CB-4903] File Plugin not loading Windows8
+* CB-4889 renaming references
+* CB-4889 renaming org.apache.cordova.core.file to org.apache.cordova.file
+* Rename CHANGELOG.md -> RELEASENOTES.md
+* [CB-4771] Expose TEMPORARY and PERSISTENT constants on window.
+* Fix compiler/lint warnings
+* [CB-4764] Move DirectoryManager.java into file plugin
+* [CB-4763] Copy FileHelper.java into the plugin.
+* [CB-2901] [BlackBerry10] Automatically unsandbox filesystem if path is not in app sandbox
+* [CB-4752] Incremented plugin version on dev branch.
+
+### 0.2.4 (Oct 9, 2013)
+* CB-5020 - File plugin should execute on a separate thread
+* [CB-4915] Incremented plugin version on dev branch.
+* CB-4504: Updating FileUtils.java to compensate for Java porting failures in the Android SDK. This fails because Java knows nothing about android_asset not being an actual filesystem
+
+ ### 0.2.5 (Oct 28, 2013)
+* CB-5129: Add a consistent filesystem attribute to FileEntry and DirectoryEntry objects
+* CB-5128: added repo + issue tag to plugin.xml for file plugin
+* CB-5015 [BlackBerry10] Add missing dependency for File.slice
+* [CB-5010] Incremented plugin version on dev branch.
+
+### 1.0.0 (Feb 05, 2014)
+* CB-5974: Use safe 'Compatibilty' mode by default
+* CB-5915: CB-5916: Reorganize preference code to make defaults possible
+* CB-5974: Android: Don't allow File operations to continue when not configured
+* CB-5960: ios: android: Properly handle parent references in getFile/getDirectory
+* [ubuntu] adopt to recent changes
+* Add default FS root to new FS objects
+* CB-5899: Make DirectoryReader.readEntries return properly formatted Entry objects
+* Add constuctor params to FileUploadResult related to CB-2421
+* Fill out filesystem attribute of entities returned from resolveLocalFileSystemURL
+* CB-5916: Create documents directories if they don't exist
+* CB-5915: Create documents directories if they don't exist
+* CB-5916: Android: Fix unfortunate NPE in config check
+* CB-5916: Android: Add "/files/" to persistent files path
+* CB-5915: ios: Update config preference (and docs) to match issue
+* CB-5916: Android: Add config preference for Android persistent storage location
+* iOS: Add config preference for iOS persistent storage location
+* iOS: Android: Allow third-party plugin registration
+* Android: Expose filePlugin getter so that other plugins can register filesystems
+* Fix typos in deprecation message
+* Add backwards-compatibility shim for file-transfer
+* Android: Allow third-party plugin registration
+* CB-5810 [BlackBerry10] resolve local:/// paths (application assets)
+* CB-5774: create DirectoryEntry instead of FileEntry
+* Initial fix for CB-5747
+* Change default FS URL scheme to "cdvfile"
+* Android: Properly format content urls
+* Android, iOS: Replace "filesystem" protocol string with constant
+* Android: Allow absolute paths on Entry.getFile / Entry.getDirectory
+* Android: Make clear that getFile takes a path, not just a filename
+* CB-5008: Rename resolveLocalFileSystemURI to resolveLocalFileSystemURL; deprecate original
+* Remove old file reference from plugin.xml
+* Android: Refactor File API
+* CB-4899 [BlackBerry10] Fix resolve directories
+* CB-5602 Windows8. Fix File Api mobile spec tests
+* Android: Better support for content urls and cross-filesystem copy/move ops
+* CB-5699 [BlackBerry10] Update resolveLocalFileSystemURI implementation
+* CB-5658 Update license comment formatting of doc/index.md
+* CB-5658 Add doc.index.md for File plugin.
+* CB-5658 Delete stale snapshot of plugin docs
+* CB-5403: Backwards-compatibility with file:// urls where possible
+* CB-5407: Fixes for ContentFilesystem
+* Android: Add method for testing backwards-compatibility of filetransfer plugin
+* iOS: Add method for testing backwards-compatiblity of filetransfer plugin
+* Android: Updates to allow FileTransfer to continue to work
+* Android: Clean up unclosed file objects
+* CB-5407: Cleanup
+* CB-5407: Add new Android source files to plugin.xml
+* CB-5407: Move read, write and truncate methods into modules
+* CB-5407: Move copy/move methods into FS modules
+* CB-5407: Move getParent into FS modules
+* CB-5407: Move getmetadata methods into FS modules
+* CB-5407: Move readdir methods into FS modules
+* CB-5407: Move remove methods into FS modules
+* CB-5407: Move getFile into FS modules
+* CB-5407: Start refactoring android code: Modular filesystems, rfs, rlfsurl
+* CB-5407: Update android JS to use FS urls
+* CB-5405: Use URL formatting for Entry.toURL
+* CB-5532 Fix
+* Log file path for File exceptions.
+* Partial fix for iOS File compatibility with previous fileTransfer plugin
+* CB-5532 WP8. Add binary data support to FileWriter
+* CB-5531 WP8. File Api readAsText incorrectly handles position args
+* Added ubuntu platform support
+* Added amazon-fireos platform support
+* CB-5118 [BlackBerry10] Add check for undefined error handler
+* CB-5406: Extend public API for dependent plugins
+* CB-5403: Bump File plugin major version
+* CB-5406: Split iOS file plugin into modules
+* CB-5406: Factor out filesystem providers in iOS
+* CB-5408: Add handler for filesystem:// urls
+* CB-5406: Update iOS native code to use filesystem URLs internally
+* CB-5405: Update JS code to use URLs exclusively
+* CB-4816 Fix file creation outside sandbox for BB10
+
+### 1.0.1 (Feb 28, 2014)
+* CB-6116 Fix error where resolveLocalFileSystemURL would fail
+* CB-6106 Add support for nativeURL attribute on Entry objects
+* CB-6110 iOS: Fix typo in filesystemPathForURL: method
+* Android: Use most specific FS match when resolving file: URIs
+* iOS: Update fileSystemURLforLocalPath: to return the most match url.
+* Allow third-party plugin registration, and the total count of fs type is not limited to just 4.
+* CB-6097 Added missing files for amazon-fireos platform. Added onLoad flag to true.
+* CB-6087 Android, iOS: Load file plugin on startup
+* CB-6013 BlackBerry10: wrap webkit prefixed called in requestAnimationFrame
+* Update plugin writers' documentation
+* CB-6080 Fix file copy when src and dst are on different local file systems
+* CB-6057 Add methods for plugins to convert between URLs and paths
+* CB-6050 Public method for returning a FileEntry from a device file path
+* CB-2432 CB-3185, CB-5975: Fix Android handling of content:// URLs
+* CB-6022 Add upgrade notes to doc
+* CB-5233 Make asset-library urls work properly on iOS
+* CB-6012 Preserve query strings on cdvfile:// URLs where necessary
+* CB-6010 Test properly for presence of URLforFilesystemPath method
+* CB-5959 Entry.getMetadata should return size attribute
+
+### 1.1.0 (Apr 17, 2014)
+* CB-4965: Remove tests from file plugin
+* Android: Allow file:/ URLs
+* CB-6422: [windows8] use cordova/exec/proxy
+* CB-6249: [android] Opportunistically resolve content urls to file
+* CB-6394: [ios, android] Add extra filesystem roots
+* CB-6394: [ios, android] Fix file resolution for the device root case
+* CB-6394: [ios] Return ENCODING_ERR when fs name is not valid
+* CB-6393: Change behaviour of toURL and toNativeURL
+* ios: Style: plugin initialization
+* ios: Fix handling of file URLs with encoded spaces
+* Always use Android's recommended temp file location for temporary file system
+* CB-6352: Allow FileSystem objects to be serialized to JSON
+* CB-5959: size is explicitly 0 if not set, file.spec.46&47 are testing the type of size
+* CB-6242: [BlackBerry10] Add deprecated version of resolveLocalFileSystemURI
+* CB-6242: [BlackBerry10] add file:/// prefix for toURI / toURL
+* CB-6242: [BlackBerry10] Polyfill window.requestAnimationFrame for OS < 10.2
+* CB-6242: [BlackBerry10] Override window.resolveLocalFileSystemURL
+* CB-6212: [iOS] fix warnings compiled under arm64 64-bit
+* ios: Don't cache responses from CDVFile's URLProtocol
+* CB-6199: [iOS] Fix toNativeURL() not escaping characters properly
+* CB-6148: Fix cross-filesystem copy and move
+* fixed setMetadata() to use the formatted fullPath
+* corrected typo which leads to a "comma expression"
+* CB-4952: ios: Resolve symlinks in file:// URLs
+* Add docs about the extraFileSystems preference
+* CB-6460: Update license headers
+
+### 1.2.0 (Jun 05, 2014)
+* CB-6127 Spanish and French Translations added. Github close #31
+* updated this reference to window
+* Add missing semicolon (copy & paste error)
+* Fix compiler warning about symbol in interface not matching implementation
+* Fix sorting order in supported platforms
+* ubuntu: increase quota value
+* ubuntu: Change FS URL scheme to 'cdvfile'
+* ubuntu: Return size with Entry.getMetadata() method
+* CB-6803 Add license
+* Initial implementation for Firefox OS
+* Small wording tweaks
+* Fixed toURL() toInternalURL() information in the doku
+* ios: Don't fail a write of zero-length payload.
+* CB-285 Docs for cordova.file.\*Directory properties
+* CB-285 Add cordova.file.\*Directory properties for iOS & Android
+* CB-3440 [BlackBerry10] Proxy based implementation
+* Fix typo in docs "app-bundle" -> "bundle"
+* CB-6583 ios: Fix failing to create entry when space in parent path
+* CB-6571 android: Make DirectoryEntry.toURL() have a trailing /
+* CB-6491 add CONTRIBUTING.md
+* CB-6525 android, ios: Allow file: URLs in all APIs. Fixes FileTransfer.download not being called.
+* fix the Windows 8 implementation of the getFile method
+* Update File.js for typo: lastModifiedData --> lastModifiedDate (closes #38)
+* Add error codes.
+* CB-5980 Updated version and RELEASENOTES.md for release 1.0.0
+* Add NOTICE file
+* CB-6114 Updated version and RELEASENOTES.md for release 1.0.1
+* CB-5980 Updated version and RELEASENOTES.md for release 1.0.0
+
+### 1.2.1
+* CB-6922 Fix inconsistent handling of lastModifiedDate and modificationTime
+* CB-285: Document filesystem root properties
+
+### 1.3.0 (Aug 06, 2014)
+* **FFOS** Remove unsupported paths from requestAllPaths
+* **FFOS** Support for resolve URI, request all paths and local app directory.
+* CB-4263 set ready state to done before onload
+* CB-7167 [BlackBerry10] copyTo - return wrapped entry rather than native
+* CB-7167 [BlackBerry10] Add directory support to getFileMetadata
+* CB-7167 [BlackBerry10] Fix tests detection of blob support (window.Blob is BlobConstructor object)
+* CB-7161 [BlackBerry10] Add file system directory paths
+* CB-7093 Create separate plugin.xml for new-style tests
+* CB-7057 Docs update: elaborate on what directories are for
+* CB-7093: Undo the effects of an old bad S&R command
+* CB-7093: Remove a bunch of unneeded log messages
+* CB-7093: Add JS module to plugin.xml file for auto-tests
+* CB-7093 Ported automated file tests
+* **WINDOWS** remove extra function closure, not needed
+* **WINDOWS** remove check for undefined fail(), it is defined by the proxy and always exists
+* **WINDOWS** re-apply readAsBinaryString and readAsArrayBuffer
+* **WINDOWS** Moved similar calls to be the same calls, aliased long namespaced functions
+* CB-6127 Updated translations for docs.
+* CB-6571 Fix getParentForLocalURL to work correctly with directories with trailing '/' (This closes #58)
+* UTTypeCopyPreferredTagWithClass returns nil mimetype for css when there is no network
+* updated spec links in docs ( en only )
+* CB-6571 add trailing space it is missing in DirectoryEnty constructor.
+* CB-6980 Fixing filesystem:null property in Entry
+* Add win8 support for readAsBinaryString and readAsArrayBuffer
+* [FFOS] Update FileProxy.js
+* CB-6940: Fixing up commit from dzeims
+* CB-6940: Android: cleanup try/catch exception handling
+* CB-6940: context.getExternal* methods return null if sdcard isn't in mounted state, causing exceptions that prevent startup from reaching readystate
+* Fix mis-handling of filesystem reference in Entry.moveTo ('this' used in closure).
+* CB-6902: Use File.lastModified rather than .lastModifiedDate
+* CB-6922: Remove unused getMetadata native code
+* CB-6922: Use getFileMetadata consistently to get metadata
+* changed fullPath to self.rootDocsPath
+* CB-6890: Fix pluginManager access for 4.0.x branch
+
+### 1.3.1 (Sep 17, 2014)
+* CB-7471 cordova-plugin-file documentation translation
+* CB-7272 Replace confusing "r/o" abbreviation with just "r"
+* CB-7423 encode path before attempting to resolve
+* CB-7375 Fix the filesystem name in resolveLocalFileSystemUri
+* CB-7445 [BlackBerry10] resolveLocalFileSystemURI - change DEFAULT_SIZE to MAX_SIZE
+* CB-7458 [BlackBerry10] resolveLocalFileSystemURL - add filesystem property
+* CB-7445 [BlackBerry10] Add default file system size to prevent quota exceeded error on initial install
+* CB-7431 Avoid calling done() twice in file.spec.109 test
+* CB-7413 Adds support of 'ms-appdata://' URIs
+* CB-7422 [File Tests] Use proper fileSystem to create fullPath
+* CB-7375 [Entry] get proper filesystem in Entry
+* Amazon related changes.
+* CB-7375 Remove leading slash statement from condition
+* Refactored much of the logic in FileMetadata constructor. Directory.size will return 0
+* CB-7419 [WP8] Added support to get metada from dir
+* CB-7418 [DirectoryEntry] Added fullPath variable as part of condition
+* CB-7417 [File tests] added proper matcher to compare fullPath property
+* CB-7375 Partial revert to resolve WP8 failures
+* Overwrite existing file on getFile when create is true
+* CB-7375 CB-6148: Ensure that return values from copy and move operations reference the correct filesystem
+* CB-6724 changed style detail on documentation
+* Added new js files to amazon-fireos platform.
+* Adds Windows platform
+* Fixes multiple mobilespec tests errors
+* Removed test/tests.js module from main plugin.xml
+* CB-7094 renamed folder to tests + added nested plugin.xml
+* added documentation for manual tests
+* CB-6923 Adding support to handle relative paths
+* Style improvements on Manual tests
+* CB-7094 Ported File manual tests
+
+### 1.3.2 (Dec 02, 2014)
+* Gets rid of thread block error in File plugin
+* CB-7917 Made tests file.spec.114 - 116 pass for **Windows** platform
+* CB-7977 Mention `deviceready` in plugin docs
+* CB-7602: Fix `isCopyOnItself` logic
+* CB-7700 cordova-plugin-file documentation translation: cordova-plugin-file
+* Use one proxy for both **Windows** and **Windows8** platforms
+* CB-6994 Fixes result, returned by proxy's write method
+* [fxos] update `__format__` to match `pathsPrefix`
+* CB-6994 Improves merged code to be able to write a File
+* Optimize `FileProxy` for **Windows** platforms
+* Synchronize changes with **Windows** platform
+* Fix function write for big files on **Windows 8**
+* Write file in background
+* CB-7487 **Android** Broadcast file write This allows MTP USB shares to show the file immediately without reboot/manual refresh using 3rd party app.
+* CB-7700 cordova-plugin-file documentation translation: cordova-plugin-file
+* CB-7571 Bump version of nested plugin to match parent plugin
+
+### 1.3.3 (Feb 04, 2015)
+* CB-7927 Encoding data to bytes instead of chars when writing a file.
+* ios: Fix compile warning about implicit int conversion
+* CB-8351 ios: Use base64EncodedStringWithOptions instead of CordovaLib's class extension
+* CB-8351 ios: Use argumentForIndex rather than NSArray extension
+* CB-8351 ios: Use a local copy of valueForKeyIsNumber rather than CordovaLib's version
+* windows: Handle url's containing absolute windows path starting with drive letter and colon (encoded as %3A) through root FS
+* windows: Rework to use normal url form
+* android: refactor: Make Filesystem base class store its own name, rootUri, and rootEntry
+* android: Simplify code a bit by making makeEntryForPath not throw JSONException
+* CB-6431 android: Fix plugin breaking content: URLs
+* CB-7375 Never create new FileSystem instances (except on windows since they don't implement requestAllFileSystems())
diff --git a/plugins/org.apache.cordova.file/doc/de/index.md b/plugins/org.apache.cordova.file/doc/de/index.md
new file mode 100644
index 0000000..be82fbf
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/de/index.md
@@ -0,0 +1,275 @@
+
+
+# org.apache.cordova.file
+
+Dieses Plugin implementiert eine File-API, die Lese-/Schreibzugriff Zugriff auf Dateien, die auf dem Gerät befinden.
+
+Dieses Plugin basiert auf mehrere Angaben, einschließlich: die HTML5-File-API
+
+Die (heute nicht mehr existierenden) Verzeichnisse und System neuesten Erweiterungen: , obwohl die meisten von den Plugin-Code wurde geschrieben, als eine frühere Spec aktuell waren:
+
+Es implementiert auch die FileWriter Spec:
+
+Verwendung finden Sie in HTML5 Rocks ausgezeichnete [Dateisystem Artikel.][1]
+
+ [1]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+Finden Sie einen Überblick über andere Speicheroptionen Cordovas [Speicher-Führer][2].
+
+ [2]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## Installation
+
+ cordova plugin add org.apache.cordova.file
+
+
+## Unterstützte Plattformen
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Firefox OS
+* iOS
+* Windows Phone 7 und 8 *
+* Windows 8 *
+
+* *Diese Plattformen unterstützen nicht `FileReader.readAsArrayBuffer` noch `FileWriter.write(blob)` .*
+
+## Wo Dateien gespeichert
+
+Stand: V1 werden URLs auf wichtige Datei-System-Verzeichnisse zur Verfügung gestellt. Jede URL in der Form *file:///path/to/spot/*ist, und konvertiert werden können eine `DirectoryEntry` mit`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-Die schreibgeschützten Verzeichnis, in dem die Anwendung installiert ist. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.applicationStorageDirectory`-Root-Verzeichnis der Anwendungs-Sandbox; auf iOS ist schreibgeschützt (aber bestimmte Unterverzeichnisse [wie `/Documents` ] sind Lese-und Schreibzugriff). Alle enthaltene Daten ist für die app privat. ( *iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.dataDirectory`-Beständige und private Datenspeicherung innerhalb der Anwendungs-Sandbox, die mit internen Speicher (auf Android, externen Speicher verwenden, verwenden Sie `.externalDataDirectory` ). Auf iOS, ist dieses Verzeichnis nicht mit iCloud synchronisiert (verwenden Sie `.syncedDataDirectory` ). (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.cacheDirectory`-Verzeichnis der zwischengespeicherten Daten-Dateien oder Dateien, die Ihre app einfach neu erstellen können. Das Betriebssystem kann diese Dateien löschen, wenn das Gerät auf Speicher knapp wird, dennoch sollten die apps vom Betriebssystem zum Löschen von Dateien hier nicht verlassen. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`-Anwendungsraum auf externen Speicher. (*Android*)
+
+* `cordova.file.externalDataDirectory`-Wo, app-spezifische Datendateien auf externen Speicher setzen. (*Android*)
+
+* `cordova.file.externalCacheDirectory`-Anwendungscache auf externen Speicher. (*Android*)
+
+* `cordova.file.externalRootDirectory`-Externer Speicher (SD-Karte) Stamm. (*Android*, *BlackBerry 10*)
+
+* `cordova.file.tempDirectory`-Temp-Verzeichnis, dem das OS auf deaktivieren können wird. Verlassen Sie sich nicht auf das Betriebssystem, um dieses Verzeichnis zu löschen; Ihre Anwendung sollte immer Dateien gegebenenfalls entfernen. (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-Hält app-spezifische Dateien, die (z. B. auf iCloud) synchronisiert werden sollten. (*iOS*)
+
+* `cordova.file.documentsDirectory`-Dateien für die app, aber privat sind sinnvoll, andere Anwendungen (z.B. Office-Dateien). (*iOS*)
+
+* `cordova.file.sharedDirectory`-Dateien für alle Anwendungen (*BlackBerry 10* weltweit verfügbar)
+
+## Dateisystemlayouts
+
+Obwohl technisch ein Implementierungsdetail, kann es sehr hilfreich zu wissen wie die `cordova.file.*` Eigenschaften anzeigen auf physischen Pfade auf einem echten Gerät.
+
+### iOS-Datei-System-Layout
+
+| Gerätepfad | `Cordova.file.*` | `iosExtraFileSystems` | R/w? | persistent? | OS löscht | Sync | Private |
+|:-------------------------------------------- |:--------------------------- |:--------------------- |:----:|:-----------:|:----------:|:----:|:-------:|
+| `/ Var/mobile/Applications/< UUID > /` | applicationStorageDirectory | - | r | N/A | N/A | N/A | Ja |
+| `appname.app/` | applicationDirectory | Bundle | r | N/A | N/A | N/A | Ja |
+| `www/` | - | - | r | N/A | N/A | N/A | Ja |
+| `Documents/` | documentsDirectory | Dokumente | R/w | Ja | Nein | Ja | Ja |
+| `NoCloud/` | - | Dokumente-nosync | R/w | Ja | Nein | Nein | Ja |
+| `Library` | - | Bibliothek | R/w | Ja | Nein | Ja? | Ja |
+| `NoCloud/` | dataDirectory | Bibliothek-nosync | R/w | Ja | Nein | Nein | Ja |
+| `Cloud/` | syncedDataDirectory | - | R/w | Ja | Nein | Ja | Ja |
+| `Caches/` | cacheDirectory | Cache | R/w | Ja * | Ja * * *| | Nein | Ja |
+| `tmp/` | tempDirectory | - | R/w | Nicht * * | Ja * * *| | Nein | Ja |
+
+* Dateien werden hinweg app Neustarts und Upgrades beibehalten, aber dieses Verzeichnis kann gelöscht werden, wenn das OS begehrt. Ihre Anwendung sollte in der Lage, Inhalte zu erschaffen, die möglicherweise gelöscht werden.
+
+* *-Dateien kann über app-Neustarts beizubehalten, aber verlasse dich nicht auf dieses Verhalten. Dateien sind nicht unbedingt Aktuelles beibehalten. Ihre Anwendung sollte Dateien aus diesem Verzeichnis entfernen, wenn es gilt, diese Dateien werden entfernt, da das OS nicht wann (oder auch wenn) garantiert.
+
+* * *| The OS kann den Inhalt dieses Verzeichnisses löschen, wenn es sich anfühlt, ist es erforderlich, aber verlassen Sie sich nicht dazu. Sie sollten dieses Verzeichnis entsprechend Ihrer Anwendung deaktivieren.
+
+### Android File System-Layout
+
+| Gerätepfad | `Cordova.file.*` | `AndroidExtraFileSystems` | R/w? | persistent? | OS löscht | Private |
+|:--------------------------------- |:----------------------------------- |:------------------------- |:----:|:-----------:|:---------:|:-------:|
+| `file:///android_asset/` | applicationDirectory | | r | N/A | N/A | Ja |
+| `/ Data/Data/< app-Id > /` | applicationStorageDirectory | - | R/w | N/A | N/A | Ja |
+| `cache` | cacheDirectory | Cache | R/w | Ja | Ja * | Ja |
+| `files` | dataDirectory | Dateien | R/w | Ja | Nein | Ja |
+| `Documents` | | Dokumente | R/w | Ja | Nein | Ja |
+| `< Sdcard > /` | externalRootDirectory | sdcard | R/w | Ja | Nein | Nein |
+| `Android/data//` | externalApplicationStorageDirectory | - | R/w | Ja | Nein | Nein |
+| `cache` | externalCacheDirectry | Cache-extern | R/w | Ja | Nicht * * | Nein |
+| `files` | externalDataDirectory | Dateien-extern | R/w | Ja | Nein | Nein |
+
+* Das Betriebssystem kann regelmäßig dieses Verzeichnis zu löschen, aber verlasse dich nicht auf dieses Verhalten. Deaktivieren Sie den Inhalt dieses Verzeichnisses für Ihre Anwendung geeigneten. Ein Benutzer den Cache manuell löschen sollte, werden die Inhalte dieses Verzeichnisses entfernt.
+
+* * The OS nicht klar dieses Verzeichnis automatisch; Sie sind verantwortlich für die Inhalte selbst verwalten. Der Benutzer den Cache manuell löschen sollte, werden der Inhalt des Verzeichnisses entfernt.
+
+**Hinweis**: Wenn externe Speichergeräte nicht bereitgestellt werden kann, die `cordova.file.external*` Eigenschaften sind`null`.
+
+### BlackBerry 10-File-System-Layout
+
+| Gerätepfad | `Cordova.file.*` | R/w? | persistent? | OS löscht | Private |
+|:--------------------------------------------------- |:--------------------------- |:----:|:-----------:|:---------:|:-------:|
+| `file:///Accounts/1000/APPDATA/ < app Id > /` | applicationStorageDirectory | r | N/A | N/A | Ja |
+| `app/native` | applicationDirectory | r | N/A | N/A | Ja |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | R/w | Nein | Ja | Ja |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | R/w | Ja | Nein | Ja |
+| `file:///Accounts/1000/Removable/sdcard` | externalRemovableDirectory | R/w | Ja | Nein | Nein |
+| `file:///Accounts/1000/Shared` | sharedDirectory | R/w | Ja | Nein | Nein |
+
+*Hinweis*: Wenn die Anwendung bereitgestellt wird, um Perimeter zu arbeiten, alle Pfade sind relativ /accounts/1000-enterprise.
+
+## Android Eigenarten
+
+### Android permanenten Speicherort
+
+Es gibt mehrere gültige Speicherorte, persistente Dateien auf einem Android-Gerät zu speichern. Finden Sie auf [dieser Seite][3] eine ausführliche Diskussion über die verschiedenen Möglichkeiten.
+
+ [3]: http://developer.android.com/guide/topics/data/data-storage.html
+
+Frühere Versionen des Plugins wählen würde, den Speicherort der temporären und permanenten Dateien beim Start, basierend auf, ob das Gerät behauptete, dass die SD-Karte (oder gleichwertige Speicherpartition) bereitgestellt wurde. Wenn die SD-Karte eingelegt wurde, oder wenn eine große interne Speicherpartition verfügbar war (wie auf Nexus-Geräten) und dann in die Wurzel dieses Raumes, die persistenten Dateien gespeichert werden. Dies bedeutete, dass alle Cordova apps aller verfügbaren Dateien auf der Karte sehen konnte.
+
+Wenn die SD-Karte nicht verfügbar war, dann Vorgängerversionen Daten unter speichern würde `/data/data/` , die isoliert Anwendungen voneinander, aber möglicherweise noch Ursache Daten zwischen Benutzern freigegeben werden.
+
+Es ist nun möglich, auszuwählen, ob zum Speichern von Dateien in den internen Datei-Speicherort oder unter Verwendung der bisherigen Logik mit einer Vorliebe in Ihrer Anwendung `config.xml` Datei. Hierzu fügen Sie eines dieser beiden Linien zu `config.xml` :
+
+
+
+
+
+
+Ohne diese Zeile das Datei-Plugin verwendet `Compatibility` als Standard. Wenn ein Präferenz-Tag vorhanden ist, und nicht einen der folgenden Werte, wird die Anwendung nicht gestartet.
+
+Wenn Ihre Anwendung für Benutzer zuvor versandt wird, mithilfe eines älteren (Pre-1.0) Version dieses Plugins und gespeicherte Dateien im permanenten Dateisystem hat, dann legen Sie die Voreinstellung "" auf `Compatibility` . Wechseln die Location auf "Internal" würde bedeuten, dass Benutzer, die aktualisieren Sie ihre Anwendung, möglicherweise nicht auf ihre zuvor gespeicherte Dateien, abhängig von ihrem Gerät zugreifen.
+
+Wenn die Anwendung neu, oder nie zuvor, Dateien im Dateisystem persistent gespeichert hat, dann die `Internal` Einstellung wird im Allgemeinen empfohlen.
+
+## iOS Macken
+
+* `cordova.file.applicationStorageDirectory`ist schreibgeschützt; zum Speichern von Dateien im Stammverzeichnis der Versuch schlägt fehl. Verwenden Sie eine der anderen `cordova.file.*` für iOS definierten Eigenschaften (nur `applicationDirectory` und `applicationStorageDirectory` sind schreibgeschützt).
+* `FileReader.readAsText(blob, encoding)`
+ * Die `encoding` Parameter wird nicht unterstützt und UTF-8-Kodierung ist immer wirksam.
+
+### iOS permanenten Speicherort
+
+Es gibt zwei gültige Speicherorte persistente Dateien auf ein iOS-Gerät speichern: das Dokumenten-Verzeichnis und das Verzeichnis Library. Frühere Versionen des Plugins gespeichert immer nur persistente Dateien im Verzeichnis Dokumente. Dies hatte den Nebeneffekt einer Anwendung Dateien in iTunes, die oft unbeabsichtigte, speziell für Anwendungen, die viele kleine Dateien behandeln war, sichtbar zu machen, anstatt komplette Dokumente für den Export, die den beabsichtigten Zweck des Verzeichnisses ist zu produzieren.
+
+Es ist nun möglich, auszuwählen, ob zum Speichern von Dateien in Dokumente oder Verzeichnis Library mit einer Vorliebe in Ihrer Anwendung `config.xml` Datei. Hierzu fügen Sie eines dieser beiden Linien zu `config.xml` :
+
+
+
+
+
+
+Ohne diese Zeile das Datei-Plugin verwendet `Compatibility` als Standard. Wenn ein Präferenz-Tag vorhanden ist, und nicht einen der folgenden Werte, wird die Anwendung nicht gestartet.
+
+Wenn Ihre Anwendung für Benutzer zuvor versandt wird, mithilfe eines älteren (Pre-1.0) Version dieses Plugins und gespeicherte Dateien im permanenten Dateisystem hat, dann legen Sie die Voreinstellung "" auf `Compatibility` . Wechsel der Lage zu `Library` würde bedeuten, dass Benutzer, aktualisieren Sie ihre Anwendung, nicht in der Lage, ihre zuvor gespeicherte Dateien zugreifen.
+
+Wenn die Anwendung neu, oder nie zuvor, Dateien im Dateisystem persistent gespeichert hat, dann die `Library` Einstellung wird im Allgemeinen empfohlen.
+
+## Firefox OS Macken
+
+Der Datei-System-API wird von Firefox-OS nicht nativ unterstützt und wird als ein Shim auf IndexedDB implementiert.
+
+* Schlägt nicht fehl, wenn Sie nicht leere Verzeichnisse entfernen
+* Metadaten wird für Verzeichnisse nicht unterstützt.
+* Methoden `copyTo` und `moveTo` unterstützen keine Verzeichnisse
+
+Die folgenden Datenpfade werden unterstützt: * `applicationDirectory` -verwendet `xhr` um lokale Dateien zu erhalten, die mit der app verpackt sind. * `dataDirectory` - Für persistente app-spezifische Daten-Dateien. * `cacheDirectory` -Cache-Dateien, die app startet überleben sollte (Apps sollten nicht vom Betriebssystem zum Löschen von Dateien hier verlassen).
+
+## Upgrade Notes
+
+In v1.0.0 des Plugins die `FileEntry` und `DirectoryEntry` Strukturen haben sich geändert, um mehr im Einklang mit der veröffentlichten Spezifikation zu sein.
+
+Vorgängerversionen (Pre-1.0.0) des Plugins gespeichert den Gerät-Absolute-Dateispeicherort in der `fullPath` -Eigenschaft der `Entry` Objekte. Diese Pfade würde in der Regel aussehen
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Diese Pfade wurden auch zurückgegeben, indem die `toURL()` -Methode des der `Entry` Objekte.
+
+Mit v1.0.0 das `fullPath` -Attribut ist der Pfad zu der Datei, *relativ zum Stammverzeichnis des Dateisystems HTML*. Also die oben genannten Pfade würden jetzt beide durch dargestellt werden ein `FileEntry` -Objekt mit einer `fullPath` von
+
+ /path/to/file
+
+
+Wenn Ihre Anwendung mit absoluter Gerätepfade arbeitet und Sie zuvor diese Pfade durch abgerufenen die `fullPath` -Eigenschaft des `Entry` Objekte, dann Sie Ihren Code mithilfe von update sollte `entry.toURL()` statt.
+
+Für rückwärts Kompatibilität, die `resolveLocalFileSystemURL()` -Methode akzeptiert einen Absolute-Gerätepfad und kehren ein `Entry` Objekt entspricht, solange diese Datei innerhalb existiert der `TEMPORARY` oder `PERSISTENT` Dateisysteme.
+
+Dies wurde vor allem ein Problem mit dem File-Transfer-Plugin, die zuvor-Absolute-Gerätepfade verwendet (und kann damit noch einverstanden). Es wurde aktualisiert, um ordnungsgemäß mit Dateisystem-URLs, so anstelle `entry.fullPath` mit `entry.toURL()` sollte lösen Sie alle Probleme, die immer des Plugin zum Arbeiten mit Dateien auf dem Gerät.
+
+In v1.1.0 der Rückgabewert der `toURL()` wurde geändert (siehe \[CB-6394\] (https://issues.apache.org/jira/browse/CB-6394)), um eine absolute "file://" URL zurückgeben. wo immer möglich. Sicherstellung einer ' Cdvfile:'-URL Sie können `toInternalURL()` jetzt. Diese Methode gibt jetzt Dateisystem URLs der Form zurück.
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+die benutzt werden können, um die Datei eindeutig zu identifizieren.
+
+## Liste der Fehlercodes und Bedeutungen
+
+Wenn ein Fehler ausgelöst wird, wird eines der folgenden Codes verwendet werden.
+
+| Code | Konstante |
+| ----:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## Konfigurieren das Plugin (Optional)
+
+Die Menge der verfügbaren Dateisysteme kann pro Plattform konfiguriert sein. Erkennen von iOS und Android ein -Tag im `config.xml` die Namen der Dateisysteme installiert werden. Standardmäßig sind alle Datei-System-Roots aktiviert.
+
+
+
+
+
+### Android
+
+* `files`: Die Anwendung interner Speicher Dateiverzeichnis
+* `files-external`: Das Verzeichnis der Anwendung externe Datei Speicher
+* `sdcard`: Das externe Globaldatei-Speicherverzeichnis (Dies ist die Wurzel der SD-Karte, sofern installiert). Sie müssen die `android.permission.WRITE_EXTERNAL_STORAGE` Erlaubnis, diese zu verwenden.
+* `cache`: Internen Cache-Verzeichnis der Anwendung
+* `cache-external`: Die Anwendung externer Cache-Verzeichnis
+* `root`: Das gesamte Gerät-Dateisystem
+
+Android unterstützt auch eine spezielle Dateisystem mit dem Namen "Dokumente", die ein Unterverzeichnis "/ Dokumente /" die "Dateien" Dateisystem darstellt.
+
+### iOS
+
+* `library`: Bibliothek das Anwendungsverzeichnis
+* `documents`: Dokumente das Anwendungsverzeichnis
+* `cache`: Cache-Verzeichnis der Anwendung
+* `bundle`: Die Anwendung Bündel; den Speicherort der die app selbst auf dem Datenträger (schreibgeschützt)
+* `root`: Das gesamte Gerät-Dateisystem
+
+Standardmäßig können die Bibliothek und Dokumenten-Verzeichnisse mit iCloud synchronisiert werden. Können Sie auch beantragen, zwei zusätzliche Dateisysteme, `library-nosync` und `documents-nosync` , die repräsentieren eine spezielle nicht synchronisierten Verzeichnis innerhalb der `/Library` oder `/Documents` Dateisystem.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/de/plugins.md b/plugins/org.apache.cordova.file/doc/de/plugins.md
new file mode 100644
index 0000000..1f4297f
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/de/plugins.md
@@ -0,0 +1,124 @@
+
+
+# Hinweise für Plugin-Entwickler
+
+Diese Notizen sollen in erster Linie für Android und iOS-Entwickler, die Plugins welche Schnittstelle mit dem Dateisystem, mit dem Datei-Plugin schreiben möchten.
+
+## Arbeiten mit Cordova-Datei-System-URLs
+
+Seit der Version 1.0.0, wurde dieses Plugin URLs mit verwendet eine `cdvfile` Regelung für die gesamte Kommunikation über die Brücke, sondern als raw-Device Dateisystempfade zu JavaScript auszusetzen.
+
+Auf der Seite JavaScript bedeutet dies, dass FileEntries und DirectoryEntry-Objekt ein FullPath-Attribut haben, die relativ zum Stammverzeichnis des Dateisystems HTML ist. Wenn Ihr Plugins-JavaScript-API ein FileEntries oder DirectoryEntry-Objekt akzeptiert, rufen Sie `.toURL()` auf das Objekt vor der Übergabe an systemeigenen Code über die Brücke.
+
+### Konvertieren von Cdvfile: / / URLs auf Fileystem Pfade
+
+Plugins, die auf das Dateisystem schreiben müssen, möchten möglicherweise eine empfangene Datei-System-URL auf eine tatsächliche Stelle des Dateisystems zu konvertieren. Es gibt mehrere Wege, dies zu tun, je nach einheitlichen Plattform.
+
+Es ist wichtig zu erinnern, dass nicht alle `cdvfile://` URLs sind zuweisbaren real Dateien auf das Gerät. Einige URLs verweisen auf Vermögenswerte auf Gerät die werden nicht durch Dateien dargestellt, oder sogar auf Remoteressourcen verweisen kann. Aufgrund dieser Möglichkeiten sollten Plugins immer testen, ob sie ein sinnvolles Ergebnis zu erhalten, wieder beim URLs in Pfade umwandeln.
+
+#### Android
+
+Auf Android, die einfachste Methode zum Konvertieren eines `cdvfile://` darin, dass die URL zu einem Dateisystempfad verwenden `org.apache.cordova.CordovaResourceApi` . `CordovaResourceApi`verfügt über mehrere Methoden der verarbeiten kann `cdvfile://` URLs:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+Es ist auch möglich, das Plugin Datei direkt zu verwenden:
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+Konvertieren von einen Pfad zu einer `cdvfile://` URL:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+Wenn Ihr Plugin eine Datei erstellt, und Sie dafür ein FileEntries-Objekt zurückgeben möchten, verwenden Sie das Datei-Plugin:
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+Cordova auf iOS verwendet nicht das gleiche `CordovaResourceApi` Konzept als Android. Auf iOS sollten Sie das Datei-Plugin verwenden, zum Konvertieren von URLs und Dateisystem-Pfaden.
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+Wenn Ihr Plugin eine Datei erstellt, und Sie dafür ein FileEntries-Objekt zurückgeben möchten, verwenden Sie das Datei-Plugin:
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### JavaScript
+
+In JavaScript, bekommen eine `cdvfile://` URL aus einem FileEntries oder DirectoryEntry-Objekt, rufen Sie einfach `.toURL()` drauf:
+
+ var cdvfileURL = entry.toURL();
+
+
+Im Plugin Antwort Handler um aus einer zurückgegebenen FileEntries-Struktur in ein tatsächliches Entry-Objekt zu konvertieren sollte Handlercode importieren die Datei-Erweiterung und ein neues Objekt erstellen:
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/es/index.md b/plugins/org.apache.cordova.file/doc/es/index.md
new file mode 100644
index 0000000..b2fec9f
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/es/index.md
@@ -0,0 +1,275 @@
+
+
+# org.apache.cordova.file
+
+Este plugin implementa una API de archivo que permite acceso de lectura/escritura a los archivos que residen en el dispositivo.
+
+Este plugin se basa en varias especificaciones, incluyendo: el HTML5 archivo API
+
+Los directorios (ahora extinto) y sistema de extensiones más recientes: aunque la mayor parte del código del plugin fue escrito cuando una especificación anterior era actual:
+
+También implementa la especificación de FileWriter:
+
+Para el uso, por favor, consulte 'HTML5 Rocks excelente [FileSystem artículo.][1]
+
+ [1]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+Para tener una visión general de otras opciones de almacenamiento, consulte [Guía de almacenamiento Cordova][2].
+
+ [2]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## Instalación
+
+ cordova plugin add org.apache.cordova.file
+
+
+## Plataformas soportadas
+
+* Amazon fire OS
+* Android
+* BlackBerry 10
+* Firefox OS
+* iOS
+* Windows Phone 7 y 8 *
+* Windows 8 *
+
+* *No son compatibles con estas plataformas `FileReader.readAsArrayBuffer` ni `FileWriter.write(blob)` .*
+
+## Donde almacenar los archivos
+
+A partir de v1.2.0, URLs a directorios de sistema de archivos importantes son proporcionadas. Cada dirección URL está en la forma *file:///path/to/spot/*y se puede convertir en un `DirectoryEntry` usando`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-Directorio Read-only donde está instalada la aplicación. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.applicationStorageDirectory`-Directorio del entorno limitado de la aplicación; en iOS esta ubicación es de sólo lectura (pero subdirectorios específicos [como `/Documents` ] son de lectura y escritura). Todos los datos contenidos dentro es privado para la aplicación. ( *iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.dataDirectory`-Almacenamiento de datos persistente y privadas dentro de entorno limitado de la aplicación utilizando la memoria interna (en Android, si necesitas usar memoria externa, use `.externalDataDirectory` ). En iOS, este directorio no está sincronizado con iCloud (utilice `.syncedDataDirectory` ). (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.cacheDirectory`-Directorio para los archivos de datos almacenados en caché o los archivos que su aplicación puede volver a crear fácilmente. El sistema operativo puede borrar estos archivos cuando el dispositivo se agota en almacenamiento de información, sin embargo, aplicaciones no deben confiar en el sistema operativo para eliminar los archivos de aquí. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`-Espacio aplicación de almacenamiento externo. (*Android*)
+
+* `cordova.file.externalDataDirectory`¿Dónde poner los archivos de datos específicos de la aplicación de almacenamiento externo. (*Android*)
+
+* `cordova.file.externalCacheDirectory`-Caché aplicación de almacenamiento externo. (*Android*)
+
+* `cordova.file.externalRootDirectory`-Raíz de almacenamiento externo (tarjeta SD). (*Android*, *BlackBerry 10*)
+
+* `cordova.file.tempDirectory`-Directorio temporal que puede borrar el sistema operativo en sí. No confíe en el sistema operativo para borrar este directorio; su aplicación siempre debe eliminar archivos según corresponda. (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-Contiene los archivos de la aplicación específica que deben ser sincronizados (e.g. a iCloud). (*iOS*)
+
+* `cordova.file.documentsDirectory`-Archivos privados a la aplicación, pero que son significativos para otra aplicación (por ejemplo archivos de Office). (*iOS*)
+
+* `cordova.file.sharedDirectory`-Archivos disponibles globalmente para todas las aplicaciones (*BlackBerry 10*)
+
+## Diseños de sistema de archivo
+
+Aunque técnicamente un detalle de la implementación, puede ser muy útil saber cómo la `cordova.file.*` mapa de propiedades en trazados físicos en un dispositivo real.
+
+### iOS diseño de sistema de archivo
+
+| Ruta de dispositivo | `Cordova.file.*` | `iosExtraFileSystems` | ¿r/w? | ¿persistente? | OS despeja | sincronización | privado |
+|:-------------------------------------------- |:--------------------------- |:--------------------- |:-----:|:-------------:|:----------:|:--------------:|:-------:|
+| `/ var/mobile/Applications/< UUID > /` | applicationStorageDirectory | - | r | N / A | N / A | N / A | Sí |
+| `appname.app/` | applicationDirectory | Bundle | r | N / A | N / A | N / A | Sí |
+| `www/` | - | - | r | N / A | N / A | N / A | Sí |
+| `Documents/` | documentsDirectory | documentos | r/w | Sí | No | Sí | Sí |
+| `NoCloud/` | - | documentos-nosync | r/w | Sí | No | No | Sí |
+| `Library` | - | Biblioteca | r/w | Sí | No | ¿Sí? | Sí |
+| `NoCloud/` | dataDirectory | Biblioteca-nosync | r/w | Sí | No | No | Sí |
+| `Cloud/` | syncedDataDirectory | - | r/w | Sí | No | Sí | Sí |
+| `Caches/` | cacheDirectory | caché | r/w | Sí * | Si * * *| | No | Sí |
+| `tmp/` | tempDirectory | - | r/w | No * * | Si * * *| | No | Sí |
+
+* Archivos persisten a través de la aplicación se reinicia y actualizaciones, pero este directorio puede ser despejó cuando el OS desea. Su aplicación debe ser capaz de recrear cualquier contenido que puede ser eliminado.
+
+* * Archivos pueden persistir a través de la aplicación se reinicia, pero no confiar en este comportamiento. Los archivos no se garantizan que persisten a través de actualizaciones. Su aplicación debe eliminar los archivos de este directorio cuando es aplicable, como el sistema operativo no garantiza cuando (o incluso si) estos archivos se quitan.
+
+* * *| OS la puede borrar el contenido de este directorio cuando se siente que es necesario, pero no dependen de éste. Debe borrar este directorio según sea apropiado para su aplicación.
+
+### Disposición del sistema Android File
+
+| Ruta de dispositivo | `Cordova.file.*` | `AndroidExtraFileSystems` | ¿r/w? | ¿persistente? | OS despeja | privado |
+|:----------------------------------------- |:----------------------------------- |:------------------------- |:-----:|:-------------:|:----------:|:-------:|
+| `File:///android_asset/` | applicationDirectory | | r | N / A | N / A | Sí |
+| `/Data/data/< id de aplicación > /` | applicationStorageDirectory | - | r/w | N / A | N / A | Sí |
+| `cache` | cacheDirectory | caché | r/w | Sí | Sí * | Sí |
+| `files` | dataDirectory | archivos | r/w | Sí | No | Sí |
+| `Documents` | | documentos | r/w | Sí | No | Sí |
+| `< sdcard > /` | externalRootDirectory | sdcard | r/w | Sí | No | No |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | Sí | No | No |
+| `cache` | externalCacheDirectry | caché-externo | r/w | Sí | No * * | No |
+| `files` | externalDataDirectory | archivos externos | r/w | Sí | No | No |
+
+* El sistema operativo puede eliminar periódicamente este directorio, pero no dependen de este comportamiento. Borrar el contenido de este directorio según sea apropiado para su aplicación. El contenido de este directorio debe un usuario purga la caché manualmente, se eliminan.
+
+* * El sistema operativo no borra este directorio automáticamente; Usted es responsable de administrar el contenido mismo. Deberá el usuario purga la caché manualmente, se extraen los contenidos del directorio.
+
+**Nota**: Si no se puede montar de almacenamiento externo, el `cordova.file.external*` Propiedades`null`.
+
+### Disposición del sistema blackBerry 10 archivo
+
+| Ruta de dispositivo | `Cordova.file.*` | ¿r/w? | ¿persistente? | OS despeja | privado |
+|:------------------------------------------------------------- |:--------------------------- |:-----:|:-------------:|:----------:|:-------:|
+| `File:///accounts/1000/AppData/ < id de aplicación > /` | applicationStorageDirectory | r | N / A | N / A | Sí |
+| `app/native` | applicationDirectory | r | N / A | N / A | Sí |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | No | Sí | Sí |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | Sí | No | Sí |
+| `File:///accounts/1000/Removable/sdcard` | externalRemovableDirectory | r/w | Sí | No | No |
+| `File:///accounts/1000/shared` | sharedDirectory | r/w | Sí | No | No |
+
+*Nota*: cuando se implementa la aplicación al trabajo de perímetro, todos los caminos son relativos a /accounts/1000-enterprise.
+
+## Rarezas Android
+
+### Ubicación de almacenamiento persistente Android
+
+Hay múltiples ubicaciones válidas para almacenar archivos persistentes en un dispositivo Android. Vea [esta página][3] para una extensa discusión de las distintas posibilidades.
+
+ [3]: http://developer.android.com/guide/topics/data/data-storage.html
+
+Las versiones anteriores del plugin elegiría la ubicación de los archivos temporales y persistentes en el arranque, basado en si el dispositivo afirmó que fue montado en la tarjeta SD (o partición de almacenamiento equivalente). Si fue montada en la tarjeta SD, o una partición de gran almacenamiento interno estaba disponible (como en dispositivos de Nexus,) y luego los archivos persistentes se almacenaría en la raíz de ese espacio. Esto significaba que todas las apps Cordova podían ver todos los archivos disponibles en la tarjeta.
+
+Si la tarjeta SD no estaba disponible, entonces versiones anteriores podría almacenar datos debajo de `/data/data/` , que aísla las apps del otro, pero puede todavía causa datos para ser compartido entre los usuarios.
+
+Ahora es posible elegir si desea almacenar archivos en la ubicación de almacenamiento del archivo interno, o usando la lógica anterior, con una preferencia en de la aplicación `config.xml` archivo. Para ello, añada una de estas dos líneas a `config.xml` :
+
+
+
+
+
+
+Sin esta línea, se utilizará el archivo plugin `Compatibility` como valor predeterminado. Si una etiqueta de preferencia está presente y no es uno de estos valores, no se iniciará la aplicación.
+
+Si su solicitud se ha enviado previamente a los usuarios, mediante una mayor (1.0 pre) versión de este plugin y archivos almacenados en el sistema de ficheros persistente, entonces debería establecer la preferencia en `Compatibility` . Cambiar la ubicación para "Internal" significa que los usuarios existentes que actualización su aplicación pueden ser incapaces de acceder a sus archivos previamente almacenadas, dependiendo de su dispositivo.
+
+Si su solicitud es nuevo, o nunca antes ha almacenado archivos en el sistema de ficheros persistente, entonces el `Internal` generalmente se recomienda el ajuste.
+
+## iOS rarezas
+
+* `cordova.file.applicationStorageDirectory`es de sólo lectura; intentar almacenar archivos en el directorio raíz fallará. Utilice uno de los `cordova.file.*` las propiedades definidas para iOS (sólo `applicationDirectory` y `applicationStorageDirectory` son de sólo lectura).
+* `FileReader.readAsText(blob, encoding)`
+ * El `encoding` no se admite el parámetro, y codificación UTF-8 es siempre en efecto.
+
+### iOS ubicación de almacenamiento persistente
+
+Hay dos ubicaciones válidas para almacenar archivos persistentes en un dispositivo iOS: el directorio de documentos y el directorio de biblioteca. Las versiones anteriores del plugin sólo almacenan archivos persistentes en el directorio de documentos. Esto tenía el efecto secundario de todos los archivos de la aplicación haciendo visible en iTunes, que era a menudo involuntarios, especialmente para aplicaciones que manejan gran cantidad de archivos pequeños, en lugar de producir documentos completos para la exportación, que es la finalidad del directorio.
+
+Ahora es posible elegir si desea almacenar archivos en los documentos o directorio de bibliotecas, con preferencia en de la aplicación `config.xml` archivo. Para ello, añada una de estas dos líneas a `config.xml` :
+
+
+
+
+
+
+Sin esta línea, se utilizará el archivo plugin `Compatibility` como valor predeterminado. Si una etiqueta de preferencia está presente y no es uno de estos valores, no se iniciará la aplicación.
+
+Si su solicitud se ha enviado previamente a los usuarios, mediante una mayor (1.0 pre) versión de este plugin y archivos almacenados en el sistema de ficheros persistente, entonces debería establecer la preferencia en `Compatibility` . Cambiar la ubicación de `Library` significa que los usuarios existentes que actualización su aplicación sería incapaces de acceder a sus archivos previamente almacenadas.
+
+Si su solicitud es nuevo, o nunca antes ha almacenado archivos en el sistema de ficheros persistente, entonces el `Library` generalmente se recomienda el ajuste.
+
+## Firefox OS rarezas
+
+La API de sistema de archivo de forma nativa no es compatible con Firefox OS y se implementa como una cuña en la parte superior indexedDB.
+
+* No falla cuando eliminar directorios no vacía
+* No admite metadatos para directorios
+* Los métodos `copyTo` y `moveTo` no son compatibles con directorios
+
+Se admiten las siguientes rutas de datos: * `applicationDirectory` -usa `xhr` para obtener los archivos locales que están envasados con la aplicación. * `dataDirectory` - Para archivos de datos específicos de aplicación persistente. * `cacheDirectory` -En caché archivos que deben sobrevivir se reinicia la aplicación (aplicaciones no deben confiar en el sistema operativo para eliminar archivos aquí).
+
+## Actualización de notas
+
+En v1.0.0 de este plugin, la `FileEntry` y `DirectoryEntry` han cambiado las estructuras, para estar más acorde con las especificaciones publicadas.
+
+Versiones anteriores (pre-1.0.0) del plugin almacenan el dispositivo-absoluto-archivo-ubicación en la `fullPath` propiedad de `Entry` objetos. Estos caminos típicamente parecería
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Estas rutas también fueron devueltos por el `toURL()` método de la `Entry` objetos.
+
+Con v1.0.0, la `fullPath` es la ruta del archivo, *relativo a la raíz del sistema de archivos HTML*. Así, los caminos anteriores que ahora ambos ser representado por un `FileEntry` objeto con un `fullPath` de
+
+ /path/to/file
+
+
+Si su aplicación funciona con dispositivo-absoluto-caminos, y previamente obtenido esos caminos a través de la `fullPath` propiedad de `Entry` objetos, entonces debe actualizar su código para utilizar `entry.toURL()` en su lugar.
+
+Para atrás compatibilidad, el `resolveLocalFileSystemURL()` método aceptará un dispositivo-absoluto-trayectoria y volverá un `Entry` objeto correspondiente, mientras exista ese archivo dentro o el `TEMPORARY` o `PERSISTENT` filesystems.
+
+Esto ha sido particularmente un problema con el plugin de transferencia de archivos, que anteriormente utilizado dispositivo-absoluto-caminos (y todavía puede aceptarlas). Se ha actualizado para que funcione correctamente con las URLs de FileSystem, reemplazando así `entry.fullPath` con `entry.toURL()` debe resolver cualquier problema conseguir ese plugin para trabajar con archivos en el dispositivo.
+
+En v1.1.0 el valor devuelto de `toURL()` fue cambiado (véase \[CB-6394\] (https://issues.apache.org/jira/browse/CB-6394)) para devolver una dirección URL absoluta 'file://'. siempre que sea posible. Para asegurar una ' cdvfile:'-URL puede usar `toInternalURL()` ahora. Este método devolverá ahora filesystem URLs de la forma
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+que puede utilizarse para identificar el archivo únicamente.
+
+## Lista de códigos de Error y significados
+
+Cuando se produce un error, uno de los siguientes códigos se utilizará.
+
+| Código | Constante |
+| ------:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## Configurando el Plugin (opcional)
+
+El conjunto de los sistemas de ficheros disponibles puede ser configurado por plataforma. Tanto iOS y Android reconocen un etiqueta en `config.xml` que nombra a los sistemas de archivos para ser instalado. De forma predeterminada, se activan todas las raíces del sistema de archivos.
+
+
+
+
+
+### Android
+
+* `files`: Directorio de almacenamiento de archivos internos de la aplicación
+* `files-external`: Directorio de almacenamiento del la aplicación archivo externo
+* `sdcard`: El directorio de almacenamiento de archivo externo global (esta es la raíz de la tarjeta SD, si uno está instalado). Debes tener el `android.permission.WRITE_EXTERNAL_STORAGE` permiso para usar esto.
+* `cache`: Directorio de la aplicación la memoria caché interna
+* `cache-external`: Directorio de caché externo de la aplicación
+* `root`: El sistema de archivos de todo el dispositivo
+
+Android también es compatible con un sistema de archivos especial llamado "documentos", que representa un subdirectorio "/ documentos /" dentro del sistema de archivos "archivos".
+
+### iOS
+
+* `library`: Directorio de la aplicación la biblioteca
+* `documents`: Directorio de documentos de la solicitud
+* `cache`: Directorio de caché de la aplicación la
+* `bundle`: Paquete la aplicación; la ubicación de la aplicación en sí mismo en el disco (sólo lectura)
+* `root`: El sistema de archivos de todo el dispositivo
+
+De forma predeterminada, los directorios de documentos y la biblioteca pueden ser sincronizados con iCloud. También puede solicitar dos sistemas de archivos adicionales, `library-nosync` y `documents-nosync` , que representa un directorio especial no sincronizados dentro de la `/Library` o `/Documents` sistema de ficheros.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/es/plugins.md b/plugins/org.apache.cordova.file/doc/es/plugins.md
new file mode 100644
index 0000000..fd126fc
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/es/plugins.md
@@ -0,0 +1,124 @@
+
+
+# Notas para los desarrolladores de plugin
+
+Estas notas están pensadas principalmente para los desarrolladores de Android y el iOS que quieran escribir plugins que interfaz con el sistema de archivos usando el plugin de archivo.
+
+## Trabajando con Cordova archivo sistema URLs
+
+Desde la versión 1.0.0, este plugin utiliza las direcciones URL con un `cdvfile` plan para todas las comunicaciones sobre el puente, en lugar de exponer rutas de sistema de archivo de dispositivo raw a JavaScript.
+
+En el lado de JavaScript, esto significa que los objetos FileEntry y DirectoryEntry tienen un atributo fullPath que es relativo a la raíz del sistema de archivos HTML. Si JavaScript API del plugin acepta un objeto FileEntry o DirectoryEntry, debe llamar a `.toURL()` en ese objeto antes de pasarla a través del puente a código nativo.
+
+### Conversión de cdvfile: / / URL al fileystem caminos
+
+Plugins que necesita escribir en el sistema de archivos puede querer convertir un archivo recibido sistema URL a una ubicación de archivos reales. Hay varias formas de hacer esto, dependiendo de la plataforma nativa.
+
+Es importante recordar que no todos `cdvfile://` URLs son asignables a los archivos reales en el dispositivo. Algunas URLs pueden referirse a activos en dispositivos que no están representados por los archivos, o incluso pueden referirse a recursos remotos. Debido a estas posibilidades, plugins siempre debe probar si consiguen un resultado significativo cuando tratando de convertir URLs en trazados.
+
+#### Android
+
+En Android, el método más sencillo para convertir un `cdvfile://` URL a una ruta de sistema de archivos es utilizar `org.apache.cordova.CordovaResourceApi` . `CordovaResourceApi`tiene varios métodos que pueden manejar `cdvfile://` URLs:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+También es posible utilizar directamente el archivo plugin:
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+Para convertir de un camino hacia un `cdvfile://` URL:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+Si tu plugin crea un archivo y desea devolver un objeto FileEntry para ello, utilice el archivo plugin:
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+Cordova en iOS no utiliza el mismo `CordovaResourceApi` concepto como Android. En iOS, utilice el archivo plugin para convertir las direcciones URL y rutas de archivos.
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+Si tu plugin crea un archivo y desea devolver un objeto FileEntry para ello, utilice el archivo plugin:
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### JavaScript
+
+En JavaScript, para conseguir un `cdvfile://` URL de un objeto FileEntry o DirectoryEntry, simplemente llame a `.toURL()` en él:
+
+ var cdvfileURL = entry.toURL();
+
+
+En plugin manipuladores de respuesta, para convertir de una estructura FileEntry devuelta a un objeto real de la entrada, el código del controlador debe importar el archivo plugin y crear un nuevo objeto:
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/fr/index.md b/plugins/org.apache.cordova.file/doc/fr/index.md
new file mode 100644
index 0000000..5a82b94
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/fr/index.md
@@ -0,0 +1,275 @@
+
+
+# org.apache.cordova.file
+
+Ce plugin implémente une API de fichier permettant l'accès en lecture/écriture aux fichiers qui résident sur le périphérique.
+
+Ce plugin est basé sur plusieurs spécifications, y compris : l'API de fichier HTML5
+
+Les répertoires (aujourd'hui disparue) et le système des extensions plus récentes : bien que la plupart du code du plugin a été écrit quand une technique antérieure était en vigueur :
+
+Il met également en œuvre la spécification FileWriter :
+
+Pour son utilisation, veuillez vous reporter au HTML5 Rocks' excellent [article de système de fichiers.][1]
+
+ [1]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+Pour un aperçu des autres options de stockage, consultez [guide d'entreposage de Cordova][2].
+
+ [2]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## Installation
+
+ cordova plugin add org.apache.cordova.file
+
+
+## Plates-formes prises en charge
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Firefox OS
+* iOS
+* Windows Phone 7 et 8 *
+* Windows 8 *
+
+* *Ces plates-formes ne supportent pas `FileReader.readAsArrayBuffer` ni `FileWriter.write(blob)` .*
+
+## Emplacement de stockage des fichiers
+
+À partir de v1.2.0, URL vers des répertoires de système de fichiers importants est fournis. Chaque URL est dans la forme *file:///path/to/spot/*et peut être converti en un `DirectoryEntry` à l'aide`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-Lecture seule répertoire où l'application est installée. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.applicationStorageDirectory`-Répertoire racine du bac à sable de l'application ; cet endroit est en lecture seule sur iOS (mais les sous-répertoires spécifiques [comme `/Documents` ] sont en lecture / écriture). Toutes les données qu'il contient est privé de l'application. ( *iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.dataDirectory`-Stockage des données persistants et privés au sein de bac à sable de l'application à l'aide de la mémoire interne (sur Android, si vous avez besoin d'utiliser une mémoire externe, utilisez `.externalDataDirectory` ). Sur iOS, ce répertoire n'est pas synchronisé avec iCloud (utiliser `.syncedDataDirectory` ). (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.cacheDirectory`-Répertoire pour les fichiers de données en mémoire cache ou les fichiers que votre application peut recréer facilement. L'OS peut supprimer ces fichiers lorsque l'appareil faiblit sur stockage, néanmoins, les applications ne doivent pas compter sur l'OS pour supprimer les fichiers ici. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`-Espace l'application sur le stockage externe. (*Android*)
+
+* `cordova.file.externalDataDirectory`-Où placer les fichiers de données d'application spécifiques sur le stockage externe. (*Android*)
+
+* `cordova.file.externalCacheDirectory`-Cache de l'application sur le stockage externe. (*Android*)
+
+* `cordova.file.externalRootDirectory`-Racine de stockage externe (carte SD). (*Android*, *BlackBerry 10*)
+
+* `cordova.file.tempDirectory`-Répertoire temp que l'OS peut effacer à volonté. Ne comptez pas sur l'OS pour effacer ce répertoire ; votre application doit toujours supprimer les fichiers selon le cas. (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-Contient des fichiers d'app spécifique qui doivent se synchroniser (par exemple à iCloud). (*iOS*)
+
+* `cordova.file.documentsDirectory`-Fichiers privés à l'app, mais qui sont significatives pour l'autre application (par exemple les fichiers Office). (*iOS*)
+
+* `cordova.file.sharedDirectory`-Fichiers disponibles globalement à toutes les applications (*BlackBerry 10*)
+
+## Structures de système de fichiers
+
+Bien que techniquement un détail d'implémentation, il peut être très utile de savoir comment les `cordova.file.*` carte de propriétés à des chemins d'accès physiques sur un périphérique réel.
+
+### iOS agencement de système de fichier
+
+| Chemin de l'unité | `Cordova.file.*` | `iosExtraFileSystems` | r/w ? | persistants ? | OS efface | Sync | privé |
+|:-------------------------------------------- |:--------------------------- |:--------------------- |:-----:|:-------------:|:-----------:|:-----:|:-----:|
+| `/ var/mobile/Applications/< UUID > /` | applicationStorageDirectory | - | r | N/A | N/A | N/A | Oui |
+| `appname.app/` | applicationDirectory | Bundle | r | N/A | N/A | N/A | Oui |
+| `www/` | - | - | r | N/A | N/A | N/A | Oui |
+| `Documents/` | documentsDirectory | documents | r/w | Oui | Non | Oui | Oui |
+| `NoCloud/` | - | documents-nosync | r/w | Oui | Non | Non | Oui |
+| `Library` | - | Bibliothèque | r/w | Oui | Non | Oui ? | Oui |
+| `NoCloud/` | dataDirectory | Bibliothèque-nosync | r/w | Oui | Non | Non | Oui |
+| `Cloud/` | syncedDataDirectory | - | r/w | Oui | Non | Oui | Oui |
+| `Caches/` | cacheDirectory | cache | r/w | Oui * | Oui * * *| | Non | Oui |
+| `tmp/` | tempDirectory | - | r/w | Ne * * | Oui * * *| | Non | Oui |
+
+* Fichiers persistent à travers les redémarrages de l'application et mises à niveau, mais ce répertoire peut être effacé à chaque fois que les désirs de l'OS. Votre application doit être en mesure de recréer tout contenu qui pourrait être supprimé.
+
+* * Fichiers peuvent persister redémarrages de l'application, mais ne vous fiez pas ce comportement. Les fichiers ne sont pas garantis à persister dans l'ensemble de mises à jour. Votre application doit supprimer les fichiers de ce répertoire lorsqu'elle s'applique, comme le système d'exploitation ne garantit pas quand (ou même si) ces fichiers sont supprimés.
+
+* * *| L'OS peut effacer le contenu de ce répertoire chaque fois qu'il se sent il est nécessaire, mais ne comptez pas là-dessus. Vous devez supprimer ce répertoire comme approprié pour votre application.
+
+### Agencement de système de fichiers Android
+
+| Chemin de l'unité | `Cordova.file.*` | `AndroidExtraFileSystems` | r/w ? | persistants ? | OS efface | privé |
+|:----------------------------------- |:----------------------------------- |:------------------------- |:-----:|:-------------:|:---------:|:-----:|
+| `file:///android_asset/` | applicationDirectory | | r | N/A | N/A | Oui |
+| `/ données/data/app < id > /` | applicationStorageDirectory | - | r/w | N/A | N/A | Oui |
+| `cache` | cacheDirectory | cache | r/w | Oui | Oui * | Oui |
+| `files` | dataDirectory | fichiers | r/w | Oui | Non | Oui |
+| `Documents` | | documents | r/w | Oui | Non | Oui |
+| `< sdcard > /` | externalRootDirectory | sdcard | r/w | Oui | Non | Non |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | Oui | Non | Non |
+| `cache` | externalCacheDirectry | cache-externe | r/w | Oui | Ne * * | Non |
+| `files` | externalDataDirectory | fichiers externes | r/w | Oui | Non | Non |
+
+* Le système d'exploitation peut effacer périodiquement ce répertoire, mais ne vous fiez pas ce comportement. Effacer le contenu de ce répertoire comme approprié pour votre application. Un utilisateur doit purger le cache manuellement, le contenu de ce répertoire est supprimé.
+
+* * The OS vous n'effacez pas ce répertoire automatiquement ; vous êtes chargé de gérer le contenu vous-même. L'utilisateur devrait purger le cache manuellement, le contenu du répertoire est supprimé.
+
+**Remarque**: si le stockage externe ne peut pas être monté, les `cordova.file.external*` sont des propriétés`null`.
+
+### Configuration du système blackBerry 10 fichier
+
+| Chemin de l'unité | `Cordova.file.*` | r/w ? | persistants ? | OS efface | privé |
+|:--------------------------------------------------- |:--------------------------- |:-----:|:-------------:|:---------:|:-----:|
+| `file:///Accounts/1000/AppData/ < id app > /` | applicationStorageDirectory | r | N/A | N/A | Oui |
+| `app/native` | applicationDirectory | r | N/A | N/A | Oui |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | Non | Oui | Oui |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | Oui | Non | Oui |
+| `file:///Accounts/1000/Removable/sdcard` | externalRemovableDirectory | r/w | Oui | Non | Non |
+| `file:///Accounts/1000/Shared` | sharedDirectory | r/w | Oui | Non | Non |
+
+*Remarque*: lorsque l'application est déployée dans le périmètre de travail, tous les chemins sont par rapport à /accounts/1000-enterprise.
+
+## Quirks Android
+
+### Emplacement de stockage persistant Android
+
+Il y a plusieurs emplacements valides pour stocker des fichiers persistants sur un appareil Android. Voir [cette page][3] pour une analyse approfondie des diverses possibilités.
+
+ [3]: http://developer.android.com/guide/topics/data/data-storage.html
+
+Les versions précédentes du plugin choisirait l'emplacement des fichiers temporaires et persistantes au démarrage, basé sur la question de savoir si le dispositif réclamé que la carte SD (ou une partition de stockage équivalent) a été montée. Si la carte SD a été montée, ou si une partition de stockage interne importante était disponible (comme sur les appareils Nexus,) puis les fichiers persistants seraient stockés dans la racine de cet espace. Cela signifie que toutes les apps de Cordova pouvaient voir tous les fichiers disponibles sur la carte.
+
+Si la carte SD n'était pas disponible, les versions précédentes pourraient stocker des données sous `/data/data/` , qui isole des apps de l'autre, mais peut encore cause données à partager entre les utilisateurs.
+
+Il est maintenant possible de choisir de stocker les fichiers dans l'emplacement de stockage de fichier interne, ou en utilisant la logique précédente, avec une préférence au sein de votre application `config.xml` fichier. Pour ce faire, ajoutez l'un de ces deux lignes de `config.xml` :
+
+
+
+
+
+
+Sans cette ligne, utilisera le fichier plugin `Compatibility` par défaut. Si une balise de préférence est présente et n'est pas une des valeurs suivantes, l'application ne démarrera pas.
+
+Si votre application a déjà été expédiée aux utilisateurs, en utilisant une ancienne (avant 1.0) version de ce plugin et dispose des fichiers stockés dans le système de fichiers persistant, alors vous devez définir la préférence au `Compatibility` . Commutation de l'emplacement « Internal » signifierait que les utilisateurs existants qui mettre à niveau leur application peuvent être impossible d'accéder à leurs fichiers déjà enregistrés, selon leur appareil.
+
+Si votre application est nouvelle ou a jamais précédemment stocké les fichiers dans le système de fichiers persistant, puis la `Internal` réglage est généralement recommandé.
+
+## iOS Quirks
+
+* `cordova.file.applicationStorageDirectory`est en lecture seule ; tentative de stocker des fichiers dans le répertoire racine échoue. Utilisez l'une de l'autre `cordova.file.*` les propriétés définies pour iOS (seulement `applicationDirectory` et `applicationStorageDirectory` sont en lecture seule).
+* `FileReader.readAsText(blob, encoding)`
+ * Le `encoding` paramètre n'est pas pris en charge, et le codage UTF-8 est toujours en vigueur.
+
+### emplacement de stockage persistant d'iOS
+
+Il y a deux emplacements valides pour stocker des fichiers persistants sur un appareil iOS : le répertoire de Documents et le répertoire de la bibliothèque. Les versions précédentes du plugin stockaient ne jamais fichiers persistants dans le répertoire de Documents. Cela a eu l'effet secondaire de rendre tous les fichiers de l'application visible dans iTunes, qui était souvent inattendus, en particulier pour les applications qui traitent beaucoup de petits fichiers, plutôt que de produire des documents complets destinés à l'exportation, qui est l'objectif visé par le répertoire.
+
+Il est maintenant possible de choisir de stocker les fichiers dans le répertoire de bibliothèque, avec une préférence au sein de votre application ou de documents `config.xml` fichier. Pour ce faire, ajoutez l'un de ces deux lignes de `config.xml` :
+
+
+
+
+
+
+Sans cette ligne, utilisera le fichier plugin `Compatibility` par défaut. Si une balise de préférence est présente et n'est pas une des valeurs suivantes, l'application ne démarrera pas.
+
+Si votre application a déjà été expédiée aux utilisateurs, en utilisant une ancienne (avant 1.0) version de ce plugin et dispose des fichiers stockés dans le système de fichiers persistant, alors vous devez définir la préférence au `Compatibility` . Changer l'emplacement de `Library` voudrait dire que les utilisateurs existants qui mettre à niveau leur application serait incapables d'accéder à leurs fichiers déjà enregistrés.
+
+Si votre application est nouvelle ou a jamais précédemment stocké les fichiers dans le système de fichiers persistant, puis la `Library` réglage est généralement recommandé.
+
+## Firefox OS Quirks
+
+L'API de système de fichier n'est pas nativement pris en charge par Firefox OS et est implémentée comme une cale d'épaisseur sur le dessus d'indexedDB.
+
+* Ne manque pas lors de la suppression des répertoires non vide
+* Ne supporte pas les métadonnées pour les répertoires
+* Méthodes `copyTo` et `moveTo` ne prennent pas en charge les répertoires
+
+Les chemins de données suivants sont pris en charge: * `applicationDirectory` -utilise `xhr` pour obtenir des fichiers les qui sont emballés avec le $ $ etAPP. * `dataDirectory` - Pour les fichiers de données persistantes de app spécifique. * `cacheDirectory` -Mise en cache de fichiers qui doivent survivre les redémarrages de l'application (les applications ne doivent pas compter sur le système d'exploitation pour supprimer les fichiers ici).
+
+## Notes de mise à niveau
+
+Dans v1.0.0 de ce plugin, la `FileEntry` et `DirectoryEntry` structures ont changé, pour être plus conforme à la spécification publiée.
+
+Les versions précédentes de (pré-1.0.0) du plugin stockaient l'appareil-absolu-fichier-emplacement dans la `fullPath` propriété de `Entry` objets. Ces chemins seraient présente généralement comme
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Ces chemins ont été également renvoyés par la `toURL()` méthode de la `Entry` des objets.
+
+Avec v1.0.0, le `fullPath` attribut contient le chemin vers le fichier, *par rapport à la racine du système de fichiers HTML*. Ainsi, les chemins d'accès ci-dessus seraient maintenant tous les deux être représenté par un `FileEntry` d'objet avec un `fullPath` de
+
+ /path/to/file
+
+
+Si votre application fonctionne avec le dispositif-absolu-chemins, et que vous avez récupéré précédemment ces chemins à travers la `fullPath` propriété de `Entry` objets, puis vous devez mettre à jour votre code d'utiliser `entry.toURL()` à la place.
+
+Pour vers l'arrière compatibilité, le `resolveLocalFileSystemURL()` méthode acceptera un chemin absolu de l'unité et retournera un `Entry` objet correspond, tant que ce fichier existe au sein de soit le `TEMPORARY` ou `PERSISTENT` systèmes de fichiers.
+
+Cela a été particulièrement un problème avec le plugin de transfert de fichiers, qui autrefois périphérique-absolu-chemins (et peut encore accepter). Il a été mis à jour pour fonctionner correctement avec des URL de système de fichiers, remplaçant ainsi `entry.fullPath` avec `entry.toURL()` devrait résoudre tout problème obtenir ce plugin pour travailler avec des fichiers sur le périphérique.
+
+Dans v1.1.0 la valeur de retour de `toURL()` a été changé (voir \[CB-6394\] (https://issues.apache.org/jira/browse/CB-6394)) pour renvoyer une URL absolue « file:// ». dans la mesure du possible. Pour assurer un ' cdvfile:'-URL, vous pouvez utiliser `toInternalURL()` maintenant. Cette méthode retourne maintenant filesystem URL du formulaire
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+qui peut servir à identifier de manière unique le fichier.
+
+## Liste des Codes d'erreur et leur signification
+
+Lorsqu'une erreur est levée, l'un des codes suivants sera utilisé.
+
+| Code | Constant |
+| ----:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## Configuration du Plugin (facultatif)
+
+L'ensemble des systèmes de fichiers disponibles peut être configurée par plate-forme. Les iOS et Android reconnaissent une tag dans `config.xml` qui désigne les systèmes de fichiers à installer. Par défaut, toutes les racines du système de fichiers sont activées.
+
+
+
+
+
+### Android
+
+* `files`: Répertoire de stockage de l'application le fichier interne
+* `files-external`: Répertoire de stockage de l'application le fichier externe
+* `sdcard`: Le répertoire de stockage global fichier externe (c'est la racine de la carte SD, s'il est installé). Vous devez avoir la `android.permission.WRITE_EXTERNAL_STORAGE` permission de l'utiliser.
+* `cache`: Répertoire de cache interne de l'application
+* `cache-external`: Répertoire de cache externe de l'application
+* `root`: Le système de fichiers de tout dispositif
+
+Android prend également en charge un système de fichiers spécial nommé « documents », qui représente un sous-répertoire « / Documents / » dans le système de fichiers « files ».
+
+### iOS
+
+* `library`: Répertoire de l'application la
+* `documents`: Répertoire de Documents de l'application la
+* `cache`: Répertoire du Cache de l'application la
+* `bundle`: Bundle de l'application ; l'emplacement de l'application elle-même sur disque (lecture seule)
+* `root`: Le système de fichiers de tout dispositif
+
+Par défaut, vous peuvent synchroniser les répertoires de la bibliothèque et les documents à iCloud. Vous pouvez également demander des deux autres systèmes de fichiers, `library-nosync` et `documents-nosync` , qui représentent un répertoire spécial non synchronisées dans le `/Library` ou `/Documents` système de fichiers.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/fr/plugins.md b/plugins/org.apache.cordova.file/doc/fr/plugins.md
new file mode 100644
index 0000000..070384e
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/fr/plugins.md
@@ -0,0 +1,124 @@
+
+
+# Notes pour les développeurs de plugins
+
+Ces notes sont principalement destinés à des développeurs Android et iOS qui veulent écrire des plugins qui interface avec le système de fichiers en utilisant le fichier plugin.
+
+## Travaillant avec des URL de système pour le fichier Cordova
+
+Depuis la version 1.0.0, ce plugin utilise des URL avec un `cdvfile` guichet pour toutes les communications sur le pont, plutôt que d'exposer les chemins de système de fichiers de périphérique brut à JavaScript.
+
+Du côté du JavaScript, cela signifie que les objets DirectoryEntry et de FileEntry ont un attribut fullPath qui est relatif à la racine du système de fichiers HTML. Si l'API JavaScript de votre plugin accepte un objet FileEntry ou DirectoryEntry, vous devez appeler `.toURL()` sur cet objet avant de le passer sur le pont en code natif.
+
+### Conversion des cdvfile: / / URL pour les chemins de fileystem
+
+Plugins qui ont besoin d'écrire dans le système de fichiers pouvez convertir un fichier reçu système URL vers un emplacement de système de fichiers réels. Il y a plusieurs façons de le faire, selon la plate-forme native.
+
+Il est important de rappeler que pas tous les `cdvfile://` URL sont cartographiables à des fichiers sur le périphérique. Certaines URL peut faire référence aux actifs sur les périphériques qui ne sont pas représentés par des fichiers, ou peuvent même faire référence aux ressources distantes. En raison de ces possibilités, plugins devraient toujours tester si ils obtiennent un résultat significatif, retour en essayant de convertir les URL aux chemins d'accès.
+
+#### Android
+
+Sur Android, la méthode la plus simple pour convertir un `cdvfile://` URL vers un chemin d'accès de système de fichiers est d'utiliser `org.apache.cordova.CordovaResourceApi` . `CordovaResourceApi`comporte plusieurs méthodes qui peuvent gérer `cdvfile://` URL :
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+Il est également possible d'utiliser le fichier plugin directement :
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+Pour convertir un chemin d'accès à un `cdvfile://` URL :
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+Si votre plugin crée un fichier et que vous souhaitez renvoyer un objet FileEntry pour cela, utilisez le fichier plugin :
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+Cordova sur iOS n'utilise pas le même `CordovaResourceApi` concept d'Android. Sur iOS, vous devez utiliser le fichier plugin pour convertir entre les URL et les chemins d'accès de système de fichiers.
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+Si votre plugin crée un fichier et que vous souhaitez renvoyer un objet FileEntry pour cela, utilisez le fichier plugin :
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### JavaScript
+
+En JavaScript, pour obtenir un `cdvfile://` URL d'un objet FileEntry ou DirectoryEntry, il suffit d'appeler `.toURL()` à ce sujet :
+
+ var cdvfileURL = entry.toURL();
+
+
+Dans gestionnaires de plugin de réponse, pour convertir une structure FileEntry retournée vers un objet réel de l'entrée, votre code de gestionnaire doit importer le fichier plugin et créer un nouvel objet :
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/index.md b/plugins/org.apache.cordova.file/doc/index.md
new file mode 100644
index 0000000..a464e4f
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/index.md
@@ -0,0 +1,371 @@
+
+
+# org.apache.cordova.file
+
+
+This plugin implements a File API allowing read/write access to files residing on the device.
+
+This plugin is based on several specs, including :
+The HTML5 File API
+[http://www.w3.org/TR/FileAPI/](http://www.w3.org/TR/FileAPI/)
+
+The (now-defunct) Directories and System extensions
+Latest:
+[http://www.w3.org/TR/2012/WD-file-system-api-20120417/](http://www.w3.org/TR/2012/WD-file-system-api-20120417/)
+Although most of the plugin code was written when an earlier spec was current:
+[http://www.w3.org/TR/2011/WD-file-system-api-20110419/](http://www.w3.org/TR/2011/WD-file-system-api-20110419/)
+
+It also implements the FileWriter spec :
+[http://dev.w3.org/2009/dap/file-system/file-writer.html](http://dev.w3.org/2009/dap/file-system/file-writer.html)
+
+For usage, please refer to HTML5 Rocks' excellent [FileSystem article.](http://www.html5rocks.com/en/tutorials/file/filesystem/)
+
+For an overview of other storage options, refer to Cordova's
+[storage guide](http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html).
+
+This plugin defines global `cordova.file` object.
+
+Although in the global scope, it is not available until after the `deviceready` event.
+
+ document.addEventListener("deviceready", onDeviceReady, false);
+ function onDeviceReady() {
+ console.log(cordova.file);
+ }
+
+## Installation
+
+ cordova plugin add org.apache.cordova.file
+
+## Supported Platforms
+
+- Amazon Fire OS
+- Android
+- BlackBerry 10
+- Firefox OS
+- iOS
+- Windows Phone 7 and 8*
+- Windows 8*
+
+\* _These platforms do not support `FileReader.readAsArrayBuffer` nor `FileWriter.write(blob)`._
+
+## Where to Store Files
+
+As of v1.2.0, URLs to important file-system directories are provided.
+Each URL is in the form _file:///path/to/spot/_, and can be converted to a
+`DirectoryEntry` using `window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory` - Read-only directory where the application
+ is installed. (_iOS_, _Android_, _BlackBerry 10_)
+
+* `cordova.file.applicationStorageDirectory` - Root directory of the application's
+ sandbox; on iOS this location is read-only (but specific subdirectories [like
+ `/Documents`] are read-write). All data contained within is private to the app. (
+ _iOS_, _Android_, _BlackBerry 10_)
+
+* `cordova.file.dataDirectory` - Persistent and private data storage within the
+ application's sandbox using internal memory (on Android, if you need to use
+ external memory, use `.externalDataDirectory`). On iOS, this directory is not
+ synced with iCloud (use `.syncedDataDirectory`). (_iOS_, _Android_, _BlackBerry 10_)
+
+* `cordova.file.cacheDirectory` - Directory for cached data files or any files
+ that your app can re-create easily. The OS may delete these files when the device
+ runs low on storage, nevertheless, apps should not rely on the OS to delete files
+ in here. (_iOS_, _Android_, _BlackBerry 10_)
+
+* `cordova.file.externalApplicationStorageDirectory` - Application space on
+ external storage. (_Android_)
+
+* `cordova.file.externalDataDirectory` - Where to put app-specific data files on
+ external storage. (_Android_)
+
+* `cordova.file.externalCacheDirectory` - Application cache on external storage.
+ (_Android_)
+
+* `cordova.file.externalRootDirectory` - External storage (SD card) root. (_Android_, _BlackBerry 10_)
+
+* `cordova.file.tempDirectory` - Temp directory that the OS can clear at will. Do not
+ rely on the OS to clear this directory; your app should always remove files as
+ applicable. (_iOS_)
+
+* `cordova.file.syncedDataDirectory` - Holds app-specific files that should be synced
+ (e.g. to iCloud). (_iOS_)
+
+* `cordova.file.documentsDirectory` - Files private to the app, but that are meaningful
+ to other application (e.g. Office files). (_iOS_)
+
+* `cordova.file.sharedDirectory` - Files globally available to all applications (_BlackBerry 10_)
+
+## File System Layouts
+
+Although technically an implementation detail, it can be very useful to know how
+the `cordova.file.*` properties map to physical paths on a real device.
+
+### iOS File System Layout
+
+| Device Path | `cordova.file.*` | `iosExtraFileSystems` | r/w? | persistent? | OS clears | sync | private |
+|:-----------------------------------------------|:----------------------------|:----------------------|:----:|:-----------:|:---------:|:----:|:-------:|
+| `/var/mobile/Applications//` | applicationStorageDirectory | - | r | N/A | N/A | N/A | Yes |
+| `appname.app/` | applicationDirectory | bundle | r | N/A | N/A | N/A | Yes |
+| `www/` | - | - | r | N/A | N/A | N/A | Yes |
+| `Documents/` | documentsDirectory | documents | r/w | Yes | No | Yes | Yes |
+| `NoCloud/` | - | documents-nosync | r/w | Yes | No | No | Yes |
+| `Library` | - | library | r/w | Yes | No | Yes? | Yes |
+| `NoCloud/` | dataDirectory | library-nosync | r/w | Yes | No | No | Yes |
+| `Cloud/` | syncedDataDirectory | - | r/w | Yes | No | Yes | Yes |
+| `Caches/` | cacheDirectory | cache | r/w | Yes* | Yes\*\*\*| No | Yes |
+| `tmp/` | tempDirectory | - | r/w | No\*\* | Yes\*\*\*| No | Yes |
+
+
+ \* Files persist across app restarts and upgrades, but this directory can
+ be cleared whenever the OS desires. Your app should be able to recreate any
+ content that might be deleted.
+
+\*\* Files may persist across app restarts, but do not rely on this behavior. Files
+ are not guaranteed to persist across updates. Your app should remove files from
+ this directory when it is applicable, as the OS does not guarantee when (or even
+ if) these files are removed.
+
+\*\*\* The OS may clear the contents of this directory whenever it feels it is
+ necessary, but do not rely on this. You should clear this directory as
+ appropriate for your application.
+
+### Android File System Layout
+
+| Device Path | `cordova.file.*` | `AndroidExtraFileSystems` | r/w? | persistent? | OS clears | private |
+|:------------------------------------------------|:----------------------------|:--------------------------|:----:|:-----------:|:---------:|:-------:|
+| `file:///android_asset/` | applicationDirectory | | r | N/A | N/A | Yes |
+| `/data/data//` | applicationStorageDirectory | - | r/w | N/A | N/A | Yes |
+| `cache` | cacheDirectory | cache | r/w | Yes | Yes\* | Yes |
+| `files` | dataDirectory | files | r/w | Yes | No | Yes |
+| `Documents` | | documents | r/w | Yes | No | Yes |
+| `/` | externalRootDirectory | sdcard | r/w | Yes | No | No |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | Yes | No | No |
+| `cache` | externalCacheDirectry | cache-external | r/w | Yes | No\*\*| No |
+| `files` | externalDataDirectory | files-external | r/w | Yes | No | No |
+
+\* The OS may periodically clear this directory, but do not rely on this behavior. Clear
+ the contents of this directory as appropriate for your application. Should a user
+ purge the cache manually, the contents of this directory are removed.
+
+\*\* The OS does not clear this directory automatically; you are responsible for managing
+ the contents yourself. Should the user purge the cache manually, the contents of the
+ directory are removed.
+
+**Note**: If external storage can't be mounted, the `cordova.file.external*`
+properties are `null`.
+
+### BlackBerry 10 File System Layout
+
+| Device Path | `cordova.file.*` | r/w? | persistent? | OS clears | private |
+|:-------------------------------------------------------------|:----------------------------|:----:|:-----------:|:---------:|:-------:|
+| `file:///accounts/1000/appdata//` | applicationStorageDirectory | r | N/A | N/A | Yes |
+| `app/native` | applicationDirectory | r | N/A | N/A | Yes |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | No | Yes | Yes |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | Yes | No | Yes |
+| `file:///accounts/1000/removable/sdcard` | externalRemovableDirectory | r/w | Yes | No | No |
+| `file:///accounts/1000/shared` | sharedDirectory | r/w | Yes | No | No |
+
+*Note*: When application is deployed to work perimeter, all paths are relative to /accounts/1000-enterprise.
+
+## Android Quirks
+
+### Android Persistent storage location
+
+There are multiple valid locations to store persistent files on an Android
+device. See [this page](http://developer.android.com/guide/topics/data/data-storage.html)
+for an extensive discussion of the various possibilities.
+
+Previous versions of the plugin would choose the location of the temporary and
+persistent files on startup, based on whether the device claimed that the SD
+Card (or equivalent storage partition) was mounted. If the SD Card was mounted,
+or if a large internal storage partition was available (such as on Nexus
+devices,) then the persistent files would be stored in the root of that space.
+This meant that all Cordova apps could see all of the files available on the
+card.
+
+If the SD card was not available, then previous versions would store data under
+`/data/data/`, which isolates apps from each other, but may still
+cause data to be shared between users.
+
+It is now possible to choose whether to store files in the internal file
+storage location, or using the previous logic, with a preference in your
+application's `config.xml` file. To do this, add one of these two lines to
+`config.xml`:
+
+
+
+
+
+Without this line, the File plugin will use `Compatibility` as the default. If
+a preference tag is present, and is not one of these values, the application
+will not start.
+
+If your application has previously been shipped to users, using an older (pre-
+1.0) version of this plugin, and has stored files in the persistent filesystem,
+then you should set the preference to `Compatibility`. Switching the location to
+"Internal" would mean that existing users who upgrade their application may be
+unable to access their previously-stored files, depending on their device.
+
+If your application is new, or has never previously stored files in the
+persistent filesystem, then the `Internal` setting is generally recommended.
+
+
+## iOS Quirks
+
+- `cordova.file.applicationStorageDirectory` is read-only; attempting to store
+ files within the root directory will fail. Use one of the other `cordova.file.*`
+ properties defined for iOS (only `applicationDirectory` and `applicationStorageDirectory` are
+ read-only).
+- `FileReader.readAsText(blob, encoding)`
+ - The `encoding` parameter is not supported, and UTF-8 encoding is always in effect.
+
+### iOS Persistent storage location
+
+There are two valid locations to store persistent files on an iOS device: the
+Documents directory and the Library directory. Previous versions of the plugin
+only ever stored persistent files in the Documents directory. This had the
+side-effect of making all of an application's files visible in iTunes, which
+was often unintended, especially for applications which handle lots of small
+files, rather than producing complete documents for export, which is the
+intended purpose of the directory.
+
+It is now possible to choose whether to store files in the documents or library
+directory, with a preference in your application's `config.xml` file. To do this,
+add one of these two lines to `config.xml`:
+
+
+
+
+
+Without this line, the File plugin will use `Compatibility` as the default. If
+a preference tag is present, and is not one of these values, the application
+will not start.
+
+If your application has previously been shipped to users, using an older (pre-
+1.0) version of this plugin, and has stored files in the persistent filesystem,
+then you should set the preference to `Compatibility`. Switching the location to
+`Library` would mean that existing users who upgrade their application would be
+unable to access their previously-stored files.
+
+If your application is new, or has never previously stored files in the
+persistent filesystem, then the `Library` setting is generally recommended.
+
+## Firefox OS Quirks
+
+The File System API is not natively supported by Firefox OS and is implemented
+as a shim on top of indexedDB.
+
+* Does not fail when removing non-empty directories
+* Does not support metadata for directories
+* Methods `copyTo` and `moveTo` do not support directories
+
+The following data paths are supported:
+* `applicationDirectory` - Uses `xhr` to get local files that are packaged with the app.
+* `dataDirectory` - For persistent app-specific data files.
+* `cacheDirectory` - Cached files that should survive app restarts (Apps should not rely
+on the OS to delete files in here).
+
+## Upgrading Notes
+
+In v1.0.0 of this plugin, the `FileEntry` and `DirectoryEntry` structures have changed,
+to be more in line with the published specification.
+
+Previous (pre-1.0.0) versions of the plugin stored the device-absolute-file-location
+in the `fullPath` property of `Entry` objects. These paths would typically look like
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+These paths were also returned by the `toURL()` method of the `Entry` objects.
+
+With v1.0.0, the `fullPath` attribute is the path to the file, _relative to the root of
+the HTML filesystem_. So, the above paths would now both be represented by a `FileEntry`
+object with a `fullPath` of
+
+ /path/to/file
+
+If your application works with device-absolute-paths, and you previously retrieved those
+paths through the `fullPath` property of `Entry` objects, then you should update your code
+to use `entry.toURL()` instead.
+
+For backwards compatibility, the `resolveLocalFileSystemURL()` method will accept a
+device-absolute-path, and will return an `Entry` object corresponding to it, as long as that
+file exists within either the `TEMPORARY` or `PERSISTENT` filesystems.
+
+This has particularly been an issue with the File-Transfer plugin, which previously used
+device-absolute-paths (and can still accept them). It has been updated to work correctly
+with FileSystem URLs, so replacing `entry.fullPath` with `entry.toURL()` should resolve any
+issues getting that plugin to work with files on the device.
+
+In v1.1.0 the return value of `toURL()` was changed (see [CB-6394] (https://issues.apache.org/jira/browse/CB-6394))
+to return an absolute 'file://' URL. wherever possible. To ensure a 'cdvfile:'-URL you can use `toInternalURL()` now.
+This method will now return filesystem URLs of the form
+
+ cdvfile://localhost/persistent/path/to/file
+
+which can be used to identify the file uniquely.
+
+## List of Error Codes and Meanings
+When an error is thrown, one of the following codes will be used.
+
+| Code | Constant |
+|-----:|:------------------------------|
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## Configuring the Plugin (Optional)
+
+The set of available filesystems can be configured per-platform. Both iOS and
+Android recognize a tag in `config.xml` which names the
+filesystems to be installed. By default, all file-system roots are enabled.
+
+
+
+
+### Android
+
+* `files`: The application's internal file storage directory
+* `files-external`: The application's external file storage directory
+* `sdcard`: The global external file storage directory (this is the root of the SD card, if one is installed). You must have the `android.permission.WRITE_EXTERNAL_STORAGE` permission to use this.
+* `cache`: The application's internal cache directory
+* `cache-external`: The application's external cache directory
+* `root`: The entire device filesystem
+
+Android also supports a special filesystem named "documents", which represents a "/Documents/" subdirectory within the "files" filesystem.
+
+### iOS
+
+* `library`: The application's Library directory
+* `documents`: The application's Documents directory
+* `cache`: The application's Cache directory
+* `bundle`: The application's bundle; the location of the app itself on disk (read-only)
+* `root`: The entire device filesystem
+
+By default, the library and documents directories can be synced to iCloud. You can also request two additional filesystems, `library-nosync` and `documents-nosync`, which represent a special non-synced directory within the `/Library` or `/Documents` filesystem.
diff --git a/plugins/org.apache.cordova.file/doc/it/index.md b/plugins/org.apache.cordova.file/doc/it/index.md
new file mode 100644
index 0000000..c2bedf5
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/it/index.md
@@ -0,0 +1,275 @@
+
+
+# org.apache.cordova.file
+
+Questo plugin implementa un API File permettendo l'accesso di lettura/scrittura ai file che risiedono sul dispositivo.
+
+Questo plugin si basa su diverse specifiche, tra cui: The HTML5 File API
+
+Le directory (ormai defunta) e il sistema delle estensioni più recenti: anche se la maggior parte del codice plugin è stato scritto quando una spec precedenti era corrente:
+
+Implementa inoltre FileWriter spec:
+
+Per l'utilizzo, fare riferimento a HTML5 Rocks' eccellente [articolo FileSystem.][1]
+
+ [1]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+Per una panoramica delle altre opzioni di archiviazione, consultare [Guida di archiviazione di Cordova][2].
+
+ [2]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## Installazione
+
+ cordova plugin add org.apache.cordova.file
+
+
+## Piattaforme supportate
+
+* Amazon fuoco OS
+* Android
+* BlackBerry 10
+* Firefox OS
+* iOS
+* Windows Phone 7 e 8 *
+* Windows 8 *
+
+* *Non supportano queste piattaforme `FileReader.readAsArrayBuffer` né `FileWriter.write(blob)` .*
+
+## Dove memorizzare i file
+
+A partire dalla v 1.2.0, vengono forniti gli URL per le directory importanti file di sistema. Ogni URL è nella forma *file:///path/to/spot/*e può essere convertito in un `DirectoryEntry` utilizzando`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-Sola lettura directory dove è installato l'applicazione. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.applicationStorageDirectory`-Directory radice di sandbox dell'applicazione; su iOS questa posizione è in sola lettura (ma sottodirectory specifiche [come `/Documents` ] sono di sola lettura). Tutti i dati contenuti all'interno è privato all'app. ( *iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.dataDirectory`-Archiviazione dati persistente e privati nella sandbox dell'applicazione utilizzando la memoria interna (su Android, se è necessario utilizzare la memoria esterna, utilizzare `.externalDataDirectory` ). IOS, questa directory non è sincronizzata con iCloud (utilizzare `.syncedDataDirectory` ). (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.cacheDirectory`-Directory per i file memorizzati nella cache di dati o qualsiasi file che app possibile ricreare facilmente. L'OS può eliminare questi file quando il dispositivo viene eseguito basso sull'archiviazione, tuttavia, apps non deve basarsi sul sistema operativo per cancellare i file qui. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`-Spazio applicazione su storage esterno. (*Android*)
+
+* `cordova.file.externalDataDirectory`-Dove mettere i file di dati specifico app su storage esterno. (*Android*)
+
+* `cordova.file.externalCacheDirectory`-Cache applicazione su storage esterno. (*Android*)
+
+* `cordova.file.externalRootDirectory`-Radice di archiviazione esterna (scheda SD). (*Android*, *BlackBerry, 10*)
+
+* `cordova.file.tempDirectory`-Temp directory che l'OS è possibile cancellare a volontà. Non fare affidamento sul sistema operativo per cancellare questa directory; l'app deve sempre rimuovere file come applicabile. (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-Contiene i file app specifiche che devono essere sincronizzati (per esempio a iCloud). (*iOS*)
+
+* `cordova.file.documentsDirectory`-I file privati per le app, ma che sono significativi per altre applicazioni (ad esempio i file di Office). (*iOS*)
+
+* `cordova.file.sharedDirectory`-File disponibili globalmente a tutte le applicazioni (*BlackBerry 10*)
+
+## Layout dei file di sistema
+
+Anche se tecnicamente un dettaglio di implementazione, può essere molto utile per conoscere come la `cordova.file.*` Proprietà mappa per percorsi fisici su un dispositivo reale.
+
+### iOS File sistema Layout
+
+| Percorso dispositivo | `Cordova.file.*` | `iosExtraFileSystems` | r/w? | persistente? | OS cancella | sincronizzazione | privato |
+|:-------------------------------------------- |:--------------------------- |:--------------------- |:----:|:------------:|:-----------:|:----------------:|:-------:|
+| `/ var/mobile/Applications/< UUID > /` | applicationStorageDirectory | - | r | N/A | N/A | N/A | Sì |
+| `appname.app/` | applicationDirectory | bundle | r | N/A | N/A | N/A | Sì |
+| `www/` | - | - | r | N/A | N/A | N/A | Sì |
+| `Documents/` | documentsDirectory | documenti | r/w | Sì | No | Sì | Sì |
+| `NoCloud/` | - | nosync-documenti | r/w | Sì | No | No | Sì |
+| `Library` | - | libreria | r/w | Sì | No | Sì? | Sì |
+| `NoCloud/` | dataDirectory | nosync-libreria | r/w | Sì | No | No | Sì |
+| `Cloud/` | syncedDataDirectory | - | r/w | Sì | No | Sì | Sì |
+| `Caches/` | cacheDirectory | cache | r/w | Sì * | Sì * * *| | No | Sì |
+| `tmp/` | tempDirectory | - | r/w | No * * | Sì * * *| | No | Sì |
+
+* File persistono attraverso riavvii app e aggiornamenti, ma questa directory può essere azzerata ogni volta che desideri l'OS. L'app dovrebbe essere in grado di ricreare qualsiasi contenuto che potrebbe essere eliminato.
+
+* * File può persistere attraverso app riavvii, ma non fare affidamento su questo comportamento. I file non sono garantiti a persistere attraverso gli aggiornamenti. L'app deve rimuovere i file dalla directory quando è applicabile, come il sistema operativo non garantisce quando (o anche se) questi file vengono rimossi.
+
+* * *| Il sistema operativo può cancellare il contenuto di questa directory ogni volta che si sente è necessario, ma non fare affidamento su questo. Si dovrebbe cancellare questa directory come adatto per l'applicazione.
+
+### Layout sistema Android File
+
+| Percorso dispositivo | `Cordova.file.*` | `AndroidExtraFileSystems` | r/w? | persistente? | OS cancella | privato |
+|:--------------------------------- |:----------------------------------- |:------------------------- |:----:|:------------:|:-----------:|:-------:|
+| `File:///android_asset/` | applicationDirectory | | r | N/A | N/A | Sì |
+| `< app-id > /dati/dati / /` | applicationStorageDirectory | - | r/w | N/A | N/A | Sì |
+| `cache` | cacheDirectory | cache | r/w | Sì | Sì * | Sì |
+| `files` | dataDirectory | file | r/w | Sì | No | Sì |
+| `Documents` | | documenti | r/w | Sì | No | Sì |
+| `< sdcard > /` | externalRootDirectory | sdcard | r/w | Sì | No | No |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | Sì | No | No |
+| `cache` | externalCacheDirectry | cache-esterno | r/w | Sì | No * * | No |
+| `files` | externalDataDirectory | file-esterno | r/w | Sì | No | No |
+
+* Il sistema operativo può cancellare periodicamente questa directory, ma non fare affidamento su questo comportamento. Cancellare il contenuto di questa directory come adatto per l'applicazione. Il contenuto di questa directory dovrebbe un utente eliminare manualmente la cache, vengono rimossi.
+
+* * Il sistema operativo non cancella questa directory automaticamente; Tu sei responsabile per la gestione dei contenuti da soli. Il contenuto della directory dovrebbe l'utente eliminare manualmente la cache, vengono rimossi.
+
+**Nota**: se la memorizzazione esterna non può essere montato, il `cordova.file.external*` sono di proprietà`null`.
+
+### BlackBerry 10 File sistema Layout
+
+| Percorso dispositivo | `Cordova.file.*` | r/w? | persistente? | OS cancella | privato |
+|:--------------------------------------------------- |:--------------------------- |:----:|:------------:|:-----------:|:-------:|
+| `File:///accounts/1000/AppData/ < id app > /` | applicationStorageDirectory | r | N/A | N/A | Sì |
+| `app/native` | applicationDirectory | r | N/A | N/A | Sì |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | No | Sì | Sì |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | Sì | No | Sì |
+| `File:///accounts/1000/Removable/sdcard` | externalRemovableDirectory | r/w | Sì | No | No |
+| `File:///accounts/1000/Shared` | sharedDirectory | r/w | Sì | No | No |
+
+*Nota*: quando l'applicazione viene distribuita a lavorare perimetrale, tutti i percorsi sono relativi a /accounts/1000-enterprise.
+
+## Stranezze Android
+
+### Posizione di archiviazione persistente Android
+
+Ci sono più percorsi validi per memorizzare i file persistenti su un dispositivo Android. Vedi [questa pagina][3] per un'ampia discussione delle varie possibilità.
+
+ [3]: http://developer.android.com/guide/topics/data/data-storage.html
+
+Versioni precedenti del plugin avrebbe scelto il percorso dei file temporanei e permanenti su avvio, in base se il dispositivo ha sostenuto che la scheda SD (o partizione storage equivalente) è stato montato. Se è stata montata sulla scheda SD o una partizione di storage interno grande era disponibile (come sui dispositivi Nexus,) allora saranno memorizzati i file persistenti nella radice di quello spazio. Questo significava che tutte le apps di Cordova poteva vedere tutti i file disponibili sulla carta.
+
+Se la scheda SD non era disponibile, poi versioni precedenti vuoi memorizzare dati sotto `/data/data/` , che isola i apps da altro, ma può ancora causa dati da condividere tra gli utenti.
+
+Ora è possibile scegliere se memorizzare i file nel percorso di archiviazione di file interno o utilizzando la logica precedente, con una preferenza nell'applicazione `config.xml` file. A tale scopo, aggiungere una di queste due linee di `config.xml` :
+
+
+
+
+
+
+Senza questa linea, si utilizzerà il File del plugin `Compatibility` come predefinito. Se è presente un tag di preferenza e non è uno di questi valori, l'applicazione non si avvia.
+
+Se l'applicazione è stato spedito in precedenza agli utenti, utilizzando un vecchio (pre-1.0) versione di questo plugin e ha i file memorizzati nel filesystem persistente, quindi è necessario impostare la preferenza di `Compatibility` . La posizione su "Interno" di commutazione significherebbe che gli utenti esistenti che aggiornare la loro applicazione potrebbero essere Impossibile accedere ai loro file precedentemente memorizzati, a seconda del loro dispositivo.
+
+Se l'applicazione è nuova, o ha mai precedentemente memorizzati i file nel filesystem persistente, poi il `Internal` impostazione è generalmente raccomandato.
+
+## iOS stranezze
+
+* `cordova.file.applicationStorageDirectory`è di sola lettura; tentativo di memorizzare i file all'interno della directory radice avrà esito negativo. Utilizzare uno degli altri `cordova.file.*` proprietà definite per iOS (solo `applicationDirectory` e `applicationStorageDirectory` sono di sola lettura).
+* `FileReader.readAsText(blob, encoding)`
+ * Il `encoding` parametro non è supportato, e codifica UTF-8 è sempre attivo.
+
+### posizione di archiviazione persistente di iOS
+
+Ci sono due percorsi validi per memorizzare i file persistenti su un dispositivo iOS: la directory documenti e la biblioteca. Precedenti versioni del plugin archiviati solo mai persistenti file nella directory documenti. Questo ha avuto l'effetto collaterale di tutti i file di un'applicazione che rende visibili in iTunes, che era spesso involontaria, soprattutto per le applicazioni che gestiscono un sacco di piccoli file, piuttosto che produrre documenti completi per l'esportazione, che è la destinazione della directory.
+
+Ora è possibile scegliere se memorizzare i file nella directory di libreria, con una preferenza nella vostra applicazione o documenti `config.xml` file. A tale scopo, aggiungere una di queste due linee di `config.xml` :
+
+
+
+
+
+
+Senza questa linea, si utilizzerà il File del plugin `Compatibility` come predefinito. Se è presente un tag di preferenza e non è uno di questi valori, l'applicazione non si avvia.
+
+Se l'applicazione è stato spedito in precedenza agli utenti, utilizzando un vecchio (pre-1.0) versione di questo plugin e ha i file memorizzati nel filesystem persistente, quindi è necessario impostare la preferenza di `Compatibility` . Il percorso per il passaggio `Library` significherebbe che gli utenti esistenti che aggiornare la loro applicazione è in grado di accedere ai loro file precedentemente memorizzati.
+
+Se l'applicazione è nuova, o ha mai precedentemente memorizzati i file nel filesystem persistente, poi il `Library` impostazione è generalmente raccomandato.
+
+## Firefox OS stranezze
+
+L'API di sistema del File non è supportato nativamente dal sistema operativo Firefox e viene implementato come uno spessore in cima indexedDB.
+
+* Non manca quando si rimuove le directory non vuota
+* Non supporta i metadati per le directory
+* Metodi `copyTo` e `moveTo` non supporta le directory
+
+Sono supportati i seguenti percorsi di dati: * `applicationDirectory` -utilizza `xhr` per ottenere i file locali che sono confezionati con l'app. * `dataDirectory` - Per i file di dati persistenti app specifiche. * `cacheDirectory` -Cache file che dovrebbero sopravvivere si riavvia app (applicazioni non devono basarsi sull'OS di eliminare i file qui).
+
+## Note di aggiornamento
+
+In v 1.0.0 di questo plugin, il `FileEntry` e `DirectoryEntry` strutture sono cambiati, per essere più in linea con le specifiche pubblicate.
+
+Versioni precedenti (pre-1.0.0) del plugin archiviati il dispositivo-assoluto--percorso del file nella `fullPath` proprietà di `Entry` oggetti. In genere questi percorsi si sarebbe simile
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Questi percorsi sono stati restituiti anche dal `toURL()` metodo della `Entry` oggetti.
+
+Con v 1.0.0, la `fullPath` attributo è il percorso del file, *rispetto alla radice del filesystem HTML*. Così, i percorsi sopra sarebbe ora sia rappresentato da un `FileEntry` oggetto con un `fullPath` di
+
+ /path/to/file
+
+
+Se l'applicazione funziona con dispositivo-assoluto-percorsi, e estratto in precedenza tali percorsi attraverso la `fullPath` proprietà di `Entry` oggetti, allora si dovrebbe aggiornare il codice per utilizzare `entry.toURL()` invece.
+
+Per indietro compatibilità, il `resolveLocalFileSystemURL()` Metodo accetterà un dispositivo-assoluto-percorso e restituirà un `Entry` oggetto corrispondente ad essa, finché quel file esiste all'interno sia il `TEMPORARY` o `PERSISTENT` filesystem.
+
+Questo particolare è stato un problema con il plugin di trasferimento File, che in precedenza utilizzati percorsi-dispositivo-assoluto (e ancora può accoglierli). Esso è stato aggiornato per funzionare correttamente con gli URL di FileSystem, così sostituendo `entry.fullPath` con `entry.toURL()` dovrebbe risolvere eventuali problemi ottenendo quel plugin per lavorare con i file nel dispositivo.
+
+In v. 1.1.0 il valore restituito di `toURL()` è stato cambiato (vedere \[CB-6394\] (https://issues.apache.org/jira/browse/CB-6394)) per restituire un URL assoluto 'file://'. ove possibile. Per assicurare un ' cdvfile:'-URL, è possibile utilizzare `toInternalURL()` ora. Questo metodo restituirà ora filesystem URL del modulo
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+che può essere utilizzato per identificare univocamente il file.
+
+## Elenco dei codici di errore e significati
+
+Quando viene generato un errore, uno dei seguenti codici da utilizzare.
+
+| Codice | Costante |
+| ------:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## Configurare il Plugin (opzionale)
+
+Il set di filesystem disponibili può essere configurato per ogni piattaforma. Sia iOS che Android riconoscere un taggare in `config.xml` che denomina il filesystem per essere installato. Per impostazione predefinita, tutte le radici del file system sono abilitate.
+
+
+
+
+
+### Android
+
+* `files`: Directory di archiviazione di file interno dell'applicazione
+* `files-external`: Directory di archiviazione dell'applicazione il file esterno
+* `sdcard`: La directory di archiviazione di file esterni globale (questa è la radice della scheda SD, se uno è installato). Deve avere il `android.permission.WRITE_EXTERNAL_STORAGE` il permesso di usare questo.
+* `cache`: La directory dell'applicazione cache interna
+* `cache-external`: La directory dell'applicazione cache esterna
+* `root`: Dispositivo intero filesystem
+
+Android supporta anche un filesystem speciale denominato "documenti", che rappresenta una sottodirectory "/ documenti /" all'interno del filesystem "files".
+
+### iOS
+
+* `library`: La directory dell'applicazione libreria
+* `documents`: La directory dell'applicazione documenti
+* `cache`: La directory dell'applicazione Cache
+* `bundle`: Bundle dell'applicazione; la posizione dell'app sul disco (sola lettura)
+* `root`: Dispositivo intero filesystem
+
+Per impostazione predefinita, la directory di libreria e documenti può essere sincronizzata a iCloud. È anche possibile richiedere due filesystem aggiuntivi, `library-nosync` e `documents-nosync` , che rappresentano una speciale directory non sincronizzati entro il `/Library` o `/Documents` filesystem.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/it/plugins.md b/plugins/org.apache.cordova.file/doc/it/plugins.md
new file mode 100644
index 0000000..c02ca2f
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/it/plugins.md
@@ -0,0 +1,124 @@
+
+
+# Note per gli sviluppatori di plugin
+
+Queste note sono principalmente destinate agli sviluppatori di Android e iOS che vogliono scrivere plugin quale interfaccia con il sistema di file utilizzando il File del plugin.
+
+## Lavorare con file di Cordova sistema gli URL
+
+Dalla versione 1.0.0, questo plugin ha utilizzato gli URL con un `cdvfile` regime per tutte le comunicazioni oltre il ponte, piuttosto che esporre i percorsi del dispositivo raw file system a JavaScript.
+
+Sul lato JavaScript, questo significa che gli oggetti FileEntry e DirectoryEntry hanno un attributo fullPath che è relativo alla directory principale del sistema di file HTML. Se JavaScript API del vostro plugin accetta un oggetto FileEntry o DirectoryEntry, dovrebbe chiamare `.toURL()` su quell'oggetto prima di passarlo attraverso il ponte in codice nativo.
+
+### Conversione cdvfile: / / URL per percorsi fileystem
+
+Plugin che occorre scrivere al filesystem potrebbe voler convertire un URL del sistema file ricevuto in una filesystem effettiva posizione. Ci sono diversi modi di fare questo, a seconda della piattaforma nativa.
+
+È importante ricordare che non tutti i `cdvfile://` gli URL sono mappabili ai veri file sul dispositivo. Alcuni URL può riferirsi a beni sul dispositivo che non sono rappresentati da file, o possono anche riferirsi a risorse remote. A causa di queste possibilità, plugin dovrebbe sempre verificare se ottengono un risultato espressivo indietro quando si tenta di convertire gli URL in tracciati.
+
+#### Android
+
+Su Android, il metodo più semplice per convertire un `cdvfile://` URL a un percorso di file System è quello di utilizzare `org.apache.cordova.CordovaResourceApi` . `CordovaResourceApi`dispone di diversi metodi in grado di gestire `cdvfile://` URL:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+È anche possibile utilizzare direttamente il File del plugin:
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+Per convertire da un percorso a un `cdvfile://` URL:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+Se il tuo plugin crea un file e si desidera restituire un oggetto FileEntry per esso, utilizzare il File del plugin:
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+Cordova su iOS non utilizza lo stesso `CordovaResourceApi` concetto come Android. Su iOS, è necessario utilizzare il File del plugin per la conversione tra URL e percorsi del file System.
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+Se il tuo plugin crea un file e si desidera restituire un oggetto FileEntry per esso, utilizzare il File del plugin:
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### JavaScript
+
+In JavaScript, per ottenere un `cdvfile://` URL da un oggetto FileEntry o DirectoryEntry, semplicemente chiamare `.toURL()` su di esso:
+
+ var cdvfileURL = entry.toURL();
+
+
+Nei gestori di risposta plugin, per convertire da una struttura FileEntry restituita in un oggetto effettivo della voce, il codice del gestore dovrebbe importare il File del plugin e creare un nuovo oggetto:
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/ja/index.md b/plugins/org.apache.cordova.file/doc/ja/index.md
new file mode 100644
index 0000000..16e9621
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/ja/index.md
@@ -0,0 +1,275 @@
+
+
+# org.apache.cordova.file
+
+このプラグインは、デバイス上のファイルへの読み取り/書き込みアクセスを許可するファイル API を実装します。
+
+このプラグインを含む、いくつかの仕様に基づいています:、HTML5 File API の
+
+(今は亡き) ディレクトリとシステムは、最新の拡張機能: プラグインのコードのほとんどはときに、以前の仕様に書かれていたが現在は:
+
+FileWriter 仕様も実装しています:
+
+使用法を参照してください HTML5 岩 ' 優秀な[ファイルシステム記事][1]。
+
+ [1]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+他のストレージ オプションの概要については、コルドバの[ストレージ ・ ガイド][2]を参照してください。.
+
+ [2]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## インストール
+
+ cordova plugin add org.apache.cordova.file
+
+
+## サポートされているプラットフォーム
+
+* アマゾン火 OS
+* アンドロイド
+* ブラックベリー 10
+* Firefox の OS
+* iOS
+* Windows Phone 7 と 8 *
+* Windows 8 *
+
+**これらのプラットフォームがサポートしていない `FileReader.readAsArrayBuffer` も `FileWriter.write(blob)` .*
+
+## ファイルを保存する場所
+
+V1.2.0、現在重要なファイル システム ディレクトリへの Url を提供しています。 各 URL はフォーム*file:///path/to/spot/*に変換することができます、 `DirectoryEntry` を使用して`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-読み取り専用のディレクトリは、アプリケーションがインストールされています。(*iOS*、*アンドロイド*、*ブラックベリー 10*)
+
+* `cordova.file.applicationStorageDirectory`-アプリケーションのサンド ボックス; のルート ディレクトリiOS でこの場所が読み取り専用 (特定のサブディレクトリが [のような `/Documents` ] は、読み取り/書き込み)。 内に含まれるすべてのデータは、アプリケーションにプライベートです。 ( *iOS*、*アンドロイド*、*ブラックベリー 10*)
+
+* `cordova.file.dataDirectory`内部メモリを使用して、アプリケーションのサンド ボックス内で永続なプライベート データ ストレージ (外部メモリを使用する必要がある場合使用して Android 上で `.externalDataDirectory` )。 IOS は、このディレクトリは iCloud と同期されません (使用する `.syncedDataDirectory` )。 (*iOS*、*アンドロイド*、*ブラックベリー 10*)
+
+* `cordova.file.cacheDirectory`-キャッシュされたデータ ファイルやアプリに簡単に再作成できる任意のファイルのディレクトリ。 ストレージ デバイスが不足したときに、OS がこれらのファイルを削除可能性があります、それにもかかわらず、アプリはここにファイルを削除する OS に依存しないでください。 (*iOS*、*アンドロイド*、*ブラックベリー 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`外部ストレージのアプリケーション領域。(*アンドロイド*)
+
+* `cordova.file.externalDataDirectory`-外部ストレージ上のアプリ固有のデータ ファイルを配置する場所。(*アンドロイド*)
+
+* `cordova.file.externalCacheDirectory`外部ストレージにアプリケーション キャッシュ。(*アンドロイド*)
+
+* `cordova.file.externalRootDirectory`-外部ストレージ (SD カード) ルート。(*アンドロイド*、*ブラックベリー 10*)
+
+* `cordova.file.tempDirectory`-OS をクリアすることができます temp ディレクトリが。 このディレクトリ; オフに OS に依存しません。アプリが常に該当するファイルを削除します。 (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-(例えば iCloud) に同期する必要がありますアプリケーション固有のファイルを保持します。(*iOS*)
+
+* `cordova.file.documentsDirectory`-ファイル、アプリケーションにプライベートは他のアプリケーション (Office ファイルなど) を意味です。(*iOS*)
+
+* `cordova.file.sharedDirectory`すべてのアプリケーション (*ブラックベリー 10*にグローバルに使用できるファイル)
+
+## ファイル ・ システム ・ レイアウト
+
+非常に知っておくと便利することができますが技術的に実装の詳細、どのように、 `cordova.file.*` プロパティを実際のデバイス上の物理パスにマップ。
+
+### iOS ファイル システムのレイアウト
+
+| デバイス ・ パス | `cordova.file.*` | `iosExtraFileSystems` | r/w ですか? | 永続的なですか? | OS を消去します | 同期 | プライベート |
+|:------------------------------------ |:--------------------------- |:--------------------- |:--------:|:--------:|:----------:|:------:|:------:|
+| `/var/モバイル/アプリケーション/< UUID >/` | applicationStorageDirectory | - | r | N/A | N/A | N/A | はい |
+| `appname.app/` | ディレクトリ | バンドル | r | N/A | N/A | N/A | はい |
+| `www/` | - | - | r | N/A | N/A | N/A | はい |
+| `Documents/` | documentsDirectory | ドキュメント | r/w | はい | いいえ | はい | はい |
+| `NoCloud/` | - | ドキュメント nosync | r/w | はい | いいえ | いいえ | はい |
+| `Library` | - | ライブラリ | r/w | はい | いいえ | はいですか? | はい |
+| `NoCloud/` | dataDirectory | ライブラリ nosync | r/w | はい | いいえ | いいえ | はい |
+| `Cloud/` | syncedDataDirectory | - | r/w | はい | いいえ | はい | はい |
+| `Caches/` | cacheDirectory | キャッシュ | r/w | はい * | はい * * *| | いいえ | はい |
+| `tmp/` | tempDirectory | - | r/w | いいえ * * | はい * * *| | いいえ | はい |
+
+* アプリを再起動し、アップグレードとの間でファイルを保持が、OS を希望するたびにこのディレクトリを削除することができます。アプリを削除可能性があります任意のコンテンツを再作成することができる必要があります。
+
+* * ファイル アプリケーション再起動を渡って続くことがありますが、この動作に依存しないでください。 ファイルは、更新を維持するは保証されません。 アプリが該当する場合このディレクトリからファイルを削除する必要があります、これらのファイルが削除されるとき (または場合でも)、OS は保証しません。
+
+* * *| OS はそれ、必要だと感じているときにこのディレクトリの内容を消去可能性がありますが、これに依存しません。 この適切なディレクトリに、アプリケーションをオフにする必要があります。
+
+### 人造人間ファイル ・ システム ・ レイアウト
+
+| デバイス ・ パス | `cordova.file.*` | `AndroidExtraFileSystems` | r/w ですか? | 永続的なですか? | OS を消去します | プライベート |
+|:--------------------------------- |:----------------------------------- |:------------------------- |:--------:|:--------:|:---------:|:------:|
+| `file:///android_asset/` | ディレクトリ | | r | N/A | N/A | はい |
+| `/データ/データ/< app id >/` | applicationStorageDirectory | - | r/w | N/A | N/A | はい |
+| `cache` | cacheDirectory | キャッシュ | r/w | はい | はい * | はい |
+| `files` | dataDirectory | ファイル | r/w | はい | いいえ | はい |
+| `Documents` | | ドキュメント | r/w | はい | いいえ | はい |
+| `< sd カード >/` | externalRootDirectory | sd カード | r/w | はい | いいえ | いいえ |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | はい | いいえ | いいえ |
+| `cache` | externalCacheDirectry | 外部キャッシュ | r/w | はい | いいえ * * | いいえ |
+| `files` | externalDataDirectory | 外部ファイル | r/w | はい | いいえ | いいえ |
+
+* OS このディレクトリを定期的に消去可能性がありますが、この動作に依存しないでください。 アプリケーションの必要に応じてこのディレクトリの内容をオフにします。 ユーザーは手動でキャッシュを削除する必要があります、このディレクトリの内容が削除されます。
+
+* * OS はこのディレクトリは自動的にクリアされません自分でコンテンツを管理するために責任があります。 ユーザは手動でキャッシュを消去する必要があります、ディレクトリの内容が削除されます。
+
+**注**: 外部記憶装置をマウントできない場合、 `cordova.file.external*` プロパティは、`null`.
+
+### ブラックベリー 10 ファイル ・ システム ・ レイアウト
+
+| デバイス ・ パス | `cordova.file.*` | r/w ですか? | 永続的なですか? | OS を消去します | プライベート |
+|:------------------------------------------------- |:--------------------------- |:--------:|:--------:|:---------:|:------:|
+| `file:///accounts/1000/appdata/< app id >/` | applicationStorageDirectory | r | N/A | N/A | はい |
+| `app/native` | ディレクトリ | r | N/A | N/A | はい |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | いいえ | はい | はい |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | はい | いいえ | はい |
+| `file:///accounts/1000/removable/sdcard` | externalRemovableDirectory | r/w | はい | いいえ | いいえ |
+| `file:///accounts/1000/shared` | sharedDirectory | r/w | はい | いいえ | いいえ |
+
+*注*: すべてのパスは/accounts/1000-enterprise 基準に境界を動作するようにアプリケーションを展開するとき。
+
+## Android の癖
+
+### Android の永続的なストレージの場所
+
+Android のデバイスに永続的なファイルを格納する複数の有効な場所があります。 さまざまな可能性について広範な議論のための[このページ][3]を参照してください。
+
+ [3]: http://developer.android.com/guide/topics/data/data-storage.html
+
+以前のバージョンのプラグインは、デバイスの SD カード (または同等のストレージ パーティション) マウントされていたと主張したかどうかに基づいて、起動時に一時と永続的なファイルの場所を選ぶでしょう。 SD カードがマウントされている場合、または大規模な内部ストレージ パーティションが利用可能な場合 (ようネクサス デバイス上) し、永続的なファイルは、その領域のルートに格納されます。 これはすべての Cordova アプリ見ることができる利用可能なファイルのすべてのカードに意味しました。
+
+SD カードがない場合、以前のバージョンがデータを格納する `/data/data/` が分離、お互いからアプリがまだ原因をユーザー間で共有されるデータ。
+
+内部ファイルの保存場所やアプリケーションの優先順位以前のロジックを使用してファイルを保存するかどうかを選択することは今 `config.xml` ファイル。 これを行うに、追加する次の 2 行のいずれか `config.xml` :
+
+
+
+
+
+
+この行がなければファイルのプラグインが使用する `Compatibility` 、デフォルトとして。優先タグが存在し、これらの値の 1 つではない場合、アプリケーションは起動しません。
+
+アプリケーションは、ユーザーに以前出荷されている場合、古い (前 1.0) を使用してこのプラグインのバージョンは、永続的なファイルシステムに保存されているファイルしに優先順位を設定する必要があります `Compatibility` 。 自分のアプリケーションをアップグレードする既存のユーザーを彼らの装置によって、以前に保存されたファイルにアクセスすることができることがあることを意味する「内部」に場所をスイッチングします。
+
+場合は、アプリケーションが新しい、または永続的なファイルシステムにファイルが格納され決して以前し、 `Internal` の設定は一般にお勧めします。
+
+## iOS の癖
+
+* `cordova.file.applicationStorageDirectory`読み取り専用;ルート ディレクトリ内のファイルを保存しようは失敗します。 他の 1 つを使用して `cordova.file.*` iOS のため定義されているプロパティ (のみ `applicationDirectory` と `applicationStorageDirectory` は読み取り専用)。
+* `FileReader.readAsText(blob, encoding)`
+ * `encoding`パラメーターはサポートされていませんし、utf-8 エンコーディングが常に有効です。
+
+### iOS の永続的なストレージの場所
+
+IOS デバイスに永続的なファイルを格納する 2 つの有効な場所がある: ドキュメントとライブラリのディレクトリ。 プラグインの以前のバージョンは、唯一のこれまでドキュメント ディレクトリに永続的なファイルを格納されます。 これは、ディレクトリの目的は、輸出のための完全なドキュメントを作成するのではなくなかったがしばしば意図されていたり、特に多数の小さいファイルを処理するアプリケーションの場合、iTunes に表示されているすべてのアプリケーションのファイルを作るの副作用があった。
+
+ドキュメントまたはアプリケーションの優先順位のライブラリ ディレクトリにファイルを保存するかどうかを選択することは今 `config.xml` ファイル。 これを行うに、追加する次の 2 行のいずれか `config.xml` :
+
+
+
+
+
+
+この行がなければファイルのプラグインが使用する `Compatibility` 、デフォルトとして。優先タグが存在し、これらの値の 1 つではない場合、アプリケーションは起動しません。
+
+アプリケーションは、ユーザーに以前出荷されている場合、古い (前 1.0) を使用してこのプラグインのバージョンは、永続的なファイルシステムに保存されているファイルしに優先順位を設定する必要があります `Compatibility` 。 スイッチングする場所 `Library` は自分のアプリケーションをアップグレードする既存のユーザーを以前に保存されたファイルにアクセスすることができるだろうことを意味します。
+
+場合は、アプリケーションが新しい、または永続的なファイルシステムにファイルが格納され決して以前し、 `Library` の設定は一般にお勧めします。
+
+## Firefox OS 癖
+
+ファイル システム API Firefox OS でネイティブ サポートされていないと、indexedDB の上にシムとして実装されています。
+
+* 空でないディレクトリを削除するときに失敗しません
+* ディレクトリのメタデータをサポートしていません
+* 方法 `copyTo` と `moveTo` ディレクトリをサポートしていません
+
+次のデータ パスがサポートされています: * `applicationDirectory` -を使用して `xhr` アプリケーションと共にパッケージ化されるローカル ファイルを取得します。 * `dataDirectory` - 永続的なアプリケーション固有のデータ ファイル。 * `cacheDirectory` -キャッシュされたアプリの再起動後も維持する必要がありますファイル (アプリはここにファイルを削除する OS に依存しないでください)。
+
+## ノートをアップグレードします。
+
+このプラグインのデベロッパーで、 `FileEntry` と `DirectoryEntry` 構造変更、公開された仕様に沿ったより多くであります。
+
+プラグインの前 (pre 1.0.0) バージョン、デバイス-絶対-ファイルの場所に格納されている、 `fullPath` のプロパティ `Entry` オブジェクト。これらのパスはようになります通常
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+これらのパスはまたによって返された、 `toURL()` 法、 `Entry` オブジェクト。
+
+デベロッパーと、 `fullPath` 属性は、 *HTML のファイルシステムのルートに対する相対パス*のファイルへのパス。 したがって、上記のパスは両方によって表される今、 `FileEntry` オブジェクトが、 `fullPath` の
+
+ /path/to/file
+
+
+場合は、アプリケーションはデバイス絶対パスで動作し、以前からそれらのパスを取得、 `fullPath` のプロパティ `Entry` を使用してコードを更新する必要があり、オブジェクト `entry.toURL()` 代わりに。
+
+後方互換性、 `resolveLocalFileSystemURL()` メソッドはデバイス絶対パスを受け入れるし、戻ります、 `Entry` オブジェクトのいずれかの内でそのファイルが存在する限り、それに対応する、 `TEMPORARY` または `PERSISTENT` ファイルシステム。
+
+これは特に以前デバイス絶対パスを使用してファイル転送のプラグインで問題となっている (そしてまだそれらを受け入れることができます)。 ので交換、ファイルシステムの Url で正しく動作するように更新されている `entry.fullPath` と `entry.toURL()` デバイス上のファイルで動作するプラグインを得て問題を解決する必要があります。
+
+V1.1.0 戻り値での `toURL()` が変更された (\[CB-6394\] (https://issues.apache.org/jira/browse/CB-6394) を参照) を絶対的な 'file://' で始まる URL を返します。 可能な限り。 確保するために、' cdvfile:'-使用することができます URL `toInternalURL()` 今。 このメソッドは、フォームのファイルシステムの Url を返します今
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+これはファイルを一意に識別するために使用できます。
+
+## エラー コードと意味のリスト
+
+エラーがスローされると、次のコードのいずれかが使用されます。
+
+| コード | 定数 |
+| ---:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## (省略可能) プラグインを構成します。
+
+利用可能なファイルシステムのセットは構成されたプラットフォームをすることができます。IOS と Android の両方を認識します。 タグの `config.xml` をインストールするファイルシステムの名前します。既定では、すべてのファイル システムのルートが有効になります。
+
+
+
+
+
+### アンドロイド
+
+* `files`: アプリケーション内部のファイル ・ ストレージ ・ ディレクトリ
+* `files-external`: アプリケーションの外部のファイルのストレージ ディレクトリ
+* `sdcard`: グローバル外部ファイル ストレージ ディレクトリ (これは SD カードのルートがインストールされている場合)。 必要があります、 `android.permission.WRITE_EXTERNAL_STORAGE` これを使用するアクセス許可。
+* `cache`: アプリケーションの内部キャッシュ ディレクトリ
+* `cache-external`: アプリケーション ディレクトリ外部キャッシュ
+* `root`: デバイス全体のファイルシステム
+
+アンドロイドを「ファイル」ファイルシステム内の"ドキュメント/"サブディレクトリを表す"ドキュメント"という名前の特殊なファイルシステムもサポートしています。
+
+### iOS
+
+* `library`: アプリケーションのライブラリ ディレクトリ
+* `documents`: アプリケーションの Documents ディレクトリ
+* `cache`: アプリケーションのキャッシュ ディレクトリ
+* `bundle`: アプリケーションのバンドル。アプリ自体 (読み取りのみ) ディスク上の場所
+* `root`: デバイス全体のファイルシステム
+
+既定では、ライブラリとドキュメント ディレクトリを iCloud に同期できます。 2 つの追加のファイルシステムを要求することもできます `library-nosync` と `documents-nosync` 、内の特別な非同期ディレクトリを表す、 `/Library` または `/Documents` ファイルシステム。
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/ja/plugins.md b/plugins/org.apache.cordova.file/doc/ja/plugins.md
new file mode 100644
index 0000000..3dedb28
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/ja/plugins.md
@@ -0,0 +1,124 @@
+
+
+# プラグイン開発者のためのノート
+
+これらのノートは、主に人造人間と iOS 開発者向けファイルのプラグインを使用して、ファイル システムでプラグインのインターフェイスを記述するもの。
+
+## コルドバのファイル システムの Url の操作
+
+バージョン 1.0.0 以降このプラグインが使用した Url とは `cdvfile` raw デバイス ファイル システム パスを JavaScript に公開するのではなく、ブリッジ上のすべての通信方式します。
+
+JavaScript 側認証と DirectoryEntry オブジェクトの HTML ファイル システムのルートからの相対である fullPath 属性があることを意味します。 呼び出す必要がありますプラグインの JavaScript API は、認証または DirectoryEntry オブジェクトを受け入れる場合 `.toURL()` 、橋を渡ってネイティブ コードに渡す前に、そのオブジェクト。
+
+### Cdvfile を変換する://fileystem のパスに Url
+
+ファイルシステムへの書き込みする必要があるプラグインは実際のファイルシステムの場所に受信ファイル用の URL を変換したい可能性があります。ネイティブのプラットフォームによって、これを行うための複数の方法があります。
+
+覚えておくことが重要ですすべてではない `cdvfile://` の Url はデバイス上の実際のファイルをマッピング可能な。 いくつかの Url は、ファイルでは表されないまたはリモートのリソースを参照することができますもをデバイス上の資産を参照できます。 プラグインはこれらの可能性のために彼らの Url パスに変換するしようとしているときに戻って、有意義な結果を得るかどうか常にテスト必要があります。
+
+#### アンドロイド
+
+Android 上で変換する最も簡単な方法は `cdvfile://` を使用してファイルシステムのパスに URL が `org.apache.cordova.CordovaResourceApi` 。 `CordovaResourceApi`扱うことができるいくつかの方法があります `cdvfile://` Url:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+また、ファイル プラグインを直接使用することが可能です。
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+パスから変換する、 `cdvfile://` の URL:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+あなたのプラグインは、ファイルを作成し、認証オブジェクトを返す場合ファイルのプラグインを使用:
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+IOS にコルドバと同じ使用しない `CordovaResourceApi` アンドロイドとして概念。IOS、上ファイル プラグインを使用して Url をファイルシステムのパスに変換する必要があります。
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+あなたのプラグインは、ファイルを作成し、認証オブジェクトを返す場合ファイルのプラグインを使用:
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### Java スクリプトの設定
+
+Java スクリプトの設定を取得するには `cdvfile://` 認証または DirectoryEntry オブジェクトから URL を単に呼び出す `.toURL()` に:
+
+ var cdvfileURL = entry.toURL();
+
+
+プラグインハンドラーの応答で返された FileEntry 構造体の実際のエントリ オブジェクトを変換するハンドラーのコードする必要がありますファイル プラグインをインポートし、新しいオブジェクトを作成します。
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/ko/index.md b/plugins/org.apache.cordova.file/doc/ko/index.md
new file mode 100644
index 0000000..f34c00e
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/ko/index.md
@@ -0,0 +1,275 @@
+
+
+# org.apache.cordova.file
+
+이 플러그인은 장치에 있는 파일에 대 한 읽기/쓰기 액세스를 허용 하는 파일 API를 구현 합니다.
+
+이 플러그인을 포함 한 몇 가지 사양에 따라: HTML5 파일 API는
+
+(지금은 없어진) 디렉터리와 시스템 확장 최신: 플러그인 코드의 대부분은 때 이전 사양 작성 되었습니다 있지만 현재는:
+
+그것은 또한 FileWriter 사양 구현:
+
+사용을 참조 하십시오 HTML5 바위 ' 우수한 [파일 시스템 문서.][1]
+
+ [1]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+다른 저장소 옵션에 대 한 개요, 코르도바의 [저장소 가이드][2] 를 참조합니다.
+
+ [2]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## 설치
+
+ cordova plugin add org.apache.cordova.file
+
+
+## 지원 되는 플랫폼
+
+* 아마존 화재 운영 체제
+* 안 드 로이드
+* 블랙베리 10
+* Firefox 운영 체제
+* iOS
+* Windows Phone 7과 8 *
+* 윈도우 8 *
+
+* *가이 플랫폼을 지원 하지 않는 `FileReader.readAsArrayBuffer` 이나 `FileWriter.write(blob)` .*
+
+## 파일을 저장할 위치를
+
+V1.2.0, 현재 중요 한 파일 시스템 디렉터리에 Url도 제공 됩니다. 각 URL 형태 *file:///path/to/spot/*이며 변환할 수는 `DirectoryEntry` 를 사용 하 여`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-읽기 전용 디렉터리는 응용 프로그램을 설치 합니다. (*iOS*, *안 드 로이드*, *블랙베리 10*)
+
+* `cordova.file.applicationStorageDirectory`응용 프로그램의 샌드박스;의 루트 디렉터리 iOS에이 위치에는 읽기 전용 (특정 하위 디렉토리만 [같은 `/Documents` ]은 읽기 / 쓰기). 포함 된 모든 데이터는 응용 프로그램에 전용. ( *iOS*, *안 드 로이드*, *블랙베리 10*)
+
+* `cordova.file.dataDirectory`-내부 메모리를 사용 하 여 응용 프로그램의 샌드박스 내에서 영구 및 개인 데이터 스토리지 (안 드 로이드, 외부 메모리를 사용 해야 하는 경우 사용 하 여 `.externalDataDirectory` ). IOS에이 디렉터리 iCloud와 동기화 되지 되 (를 사용 하 여 `.syncedDataDirectory` ). (*iOS*, *안 드 로이드*, *블랙베리 10*)
+
+* `cordova.file.cacheDirectory`-디렉터리 캐시 데이터 파일 또는 모든 파일을 당신의 app를 다시 쉽게 만들 수 있습니다. 운영 체제 장치 저장소 부족 하면 이러한 파일을 삭제할 수 있습니다, 그리고 그럼에도 불구 하 고, 애플 리 케이 션 여기에 파일을 삭제 하려면 운영 체제에 의존 하지 말아야 합니다. (*iOS*, *안 드 로이드*, *블랙베리 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`-응용 프로그램 외부 저장 공간입니다. (*안 드 로이드*)
+
+* `cordova.file.externalDataDirectory`-외부 저장소에 응용 프로그램 특정 데이터 파일을 넣어 어디. (*안 드 로이드*)
+
+* `cordova.file.externalCacheDirectory`외부 저장소에 응용 프로그램 캐시입니다. (*안 드 로이드*)
+
+* `cordova.file.externalRootDirectory`-외부 저장 (SD 카드) 루트입니다. (*안 드 로이드*, *블랙베리 10*)
+
+* `cordova.file.tempDirectory`-운영 체제에서 지울 수 있습니다 임시 디렉터리 것입니다. 이 디렉터리;를 운영 체제에 의존 하지 마십시오 귀하의 응용 프로그램 항상 해당 하는 경우 파일을 제거 해야 합니다. (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-(ICloud)를 예를 들어 동기화 해야 하는 응용 프로그램 관련 파일을 보유 하 고 있습니다. (*iOS*)
+
+* `cordova.file.documentsDirectory`-파일 애플 리 케이 션, 하지만 그 개인은 다른 응용 프로그램 (예: Office 파일)에 의미입니다. (*iOS*)
+
+* `cordova.file.sharedDirectory`-모든 응용 프로그램 (*블랙베리 10* 에 전세계적으로 사용 가능한 파일)
+
+## 파일 시스템 레이아웃
+
+비록 기술적으로 구현 세부 사항, 그것은 매우 유용할 수 있습니다 알고 어떻게 `cordova.file.*` 실제 장치에 실제 경로를 속성 지도.
+
+### iOS 파일 시스템 레이아웃
+
+| 장치 경로 | `cordova.file.*` | `iosExtraFileSystems` | r/w? | 영구? | OS 지웁니다 | 동기화 | 개인 |
+|:------------------------------------ |:--------------------------- |:--------------------- |:----:|:------:|:---------:|:---:|:--:|
+| `/ var/모바일/응용 프로그램/< UUID > /` | applicationStorageDirectory | - | r | N/A | N/A | N/A | 예 |
+| `appname.app/` | applicationDirectory | 번들 | r | N/A | N/A | N/A | 예 |
+| `www/` | - | - | r | N/A | N/A | N/A | 예 |
+| `Documents/` | documentsDirectory | 문서 | r/w | 예 | 없음 | 예 | 예 |
+| `NoCloud/` | - | 문서 nosync | r/w | 예 | 없음 | 없음 | 예 |
+| `Library` | - | 라이브러리 | r/w | 예 | 없음 | 그래? | 예 |
+| `NoCloud/` | dataDirectory | 라이브러리 nosync | r/w | 예 | 없음 | 없음 | 예 |
+| `Cloud/` | syncedDataDirectory | - | r/w | 예 | 없음 | 예 | 예 |
+| `Caches/` | cacheDirectory | 캐시 | r/w | 예 * | 예 * * *| | 없음 | 예 |
+| `tmp/` | tempDirectory | - | r/w | 아니 * * | 예 * * *| | 없음 | 예 |
+
+* 파일 응용 프로그램 다시 시작 및 업그레이드, 유지 하지만 OS 욕망 언제 든 지이 디렉터리를 지울 수 있습니다. 앱 삭제 될 수 있습니다 모든 콘텐츠를 다시 만들 수 있어야 합니다.
+
+* * 파일 응용 프로그램 다시 시작에서 지속 될 수 있습니다 하지만이 동작에 의존 하지 마십시오. 파일 여러 업데이트를 보장 하지 않습니다. 때 해당 앱이이 디렉터리에서 파일을 제거 해야, 이러한 파일을 제거할 때 (또는 경우에도) 운영 체제 보증 하지 않습니다으로.
+
+* * *| OS 그것이 필요를 느낀다 언제 든 지이 디렉터리의 내용을 취소 될 수 있습니다 하지만 이것에 의존 하지 마십시오. 이 디렉터리를 응용 프로그램에 대 한 적절 한 선택을 취소 해야 합니다.
+
+### 안 드 로이드 파일 시스템 레이아웃
+
+| 장치 경로 | `cordova.file.*` | `AndroidExtraFileSystems` | r/w? | 영구? | OS 지웁니다 | 개인 |
+|:--------------------------------- |:----------------------------------- |:------------------------- |:----:|:---:|:-------:|:--:|
+| `file:///android_asset/` | applicationDirectory | | r | N/A | N/A | 예 |
+| `/data/데이터/< app id > /` | applicationStorageDirectory | - | r/w | N/A | N/A | 예 |
+| `cache` | cacheDirectory | 캐시 | r/w | 예 | 예 * | 예 |
+| `files` | dataDirectory | 파일 | r/w | 예 | 없음 | 예 |
+| `Documents` | | 문서 | r/w | 예 | 없음 | 예 |
+| `< sdcard > /` | externalRootDirectory | sdcard | r/w | 예 | 없음 | 없음 |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | 예 | 없음 | 없음 |
+| `cache` | externalCacheDirectry | 외부 캐시 | r/w | 예 | 아니 * * | 없음 |
+| `files` | externalDataDirectory | 파일 외부 | r/w | 예 | 없음 | 없음 |
+
+* OS 수 있습니다 정기적으로이 디렉터리에 있지만이 동작에 의존 하지 마십시오. 이 응용 프로그램이 디렉터리의 내용을 취소 합니다. 사용자 수동으로 캐시 제거 해야,이 디렉터리의 내용은 제거 됩니다.
+
+* * OS 지워지지 않습니다이 디렉터리 자동으로; 콘텐츠를 관리 하기 위한 책임이 있습니다. 사용자 수동으로 캐시 제거 합니다, 디렉터리의 내용은 제거 됩니다.
+
+**참고**: 외부 저장소를 탑재할 수 없는 경우는 `cordova.file.external*` 속성은`null`.
+
+### 블랙베리 10 파일 시스템 레이아웃
+
+| 장치 경로 | `cordova.file.*` | r/w? | 영구? | OS 지웁니다 | 개인 |
+|:--------------------------------------------------- |:--------------------------- |:----:|:---:|:-------:|:--:|
+| `file:///accounts/1000/appdata/ < app id > /` | applicationStorageDirectory | r | N/A | N/A | 예 |
+| `app/native` | applicationDirectory | r | N/A | N/A | 예 |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | 없음 | 예 | 예 |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | 예 | 없음 | 예 |
+| `file:///accounts/1000/removable/sdcard` | externalRemovableDirectory | r/w | 예 | 없음 | 없음 |
+| `file:///accounts/1000/shared` | sharedDirectory | r/w | 예 | 없음 | 없음 |
+
+*참고*: 모든 경로 /accounts/1000-enterprise를 기준으로 응용 프로그램 경계를 작동 하도록 배포 될 때.
+
+## 안 드 로이드 단점
+
+### 안 드 로이드 영구 저장 위치
+
+안 드 로이드 장치에 영구 파일을 저장할 여러 유효한 위치가 있다. 다양 한 가능성의 광범위 한 토론에 대 한 [이 페이지][3] 를 참조 하십시오.
+
+ [3]: http://developer.android.com/guide/topics/data/data-storage.html
+
+플러그인의 이전 버전을 시작할 때, 장치는 SD 카드 (또는 해당 스토리지 파티션) 탑재 했다 주장 하는 여부에 따라 임시 및 영구 파일의 위치를 선택 합니다. SD 카드 마운트, 또는 큰 내부 스토리지 파티션에 사용할 수 있었습니다 (같은 넥서스 장치에) 그 후에 영구 파일 공간의 루트에 저장 됩니다. 이 모든 코르 도우 바 애플 리 케이 션 카드에 모두 사용할 수 있는 파일을 볼 수 있는 의미 합니다.
+
+SD 카드는 사용할 수 있는 경우 이전 버전에서 데이터 저장 `/data/data/` 는 서로 다른 애플 리 케이 션을 분리 하지만 여전히 원인 데이터를 사용자 간에 공유할 수 있습니다.
+
+내부 파일 저장 위치 또는 응용 프로그램에서 기본 설정으로 이전 논리를 사용 하 여 파일을 저장할 것인지를 선택할 수 `config.xml` 파일. 이렇게 하려면 다음 두 줄을 중 하나를 추가 `config.xml` :
+
+
+
+
+
+
+파일 플러그인 사용이 줄이 없으면 `Compatibility` 는 기본적으로. 기본 태그,이 이러한 값 중 하나가 아닌 경우에 응용 프로그램이 시작 되지 않습니다.
+
+이전 (사전 1.0)을 사용 하는 경우 응용 프로그램 사용자에 게 발송 되었다 이전,이 플러그인의 버전 영구 파일 시스템에 저장 된 파일은 다음 환경 설정을 설정 해야 및 `Compatibility` . "내부"의 위치 전환 그들의 응용 프로그램을 업그레이드 기존 사용자의 그들의 장치에 따라 그들의 이전에 저장 된 파일에 액세스할 수 수 있다는 뜻입니다.
+
+응용 프로그램은 새로운, 또는 이전 영구 파일 시스템에 파일을 저장 하는 경우는 `Internal` 일반적으로 권장 됩니다.
+
+## iOS 단점
+
+* `cordova.file.applicationStorageDirectory`읽기 전용; 루트 디렉터리 내에서 파일을 저장 하려고에 실패 합니다. 다른 중 하나를 사용 하 여 `cordova.file.*` iOS에 대해 정의 된 속성 (만 `applicationDirectory` 와 `applicationStorageDirectory` 는 읽기 전용).
+* `FileReader.readAsText(blob, encoding)`
+ * `encoding`매개 변수는 지원 되지 않습니다, 및 효과에 항상 u t F-8 인코딩을 합니다.
+
+### iOS 영구 저장소 위치
+
+IOS 디바이스에 영구 파일을 저장할 두 개의 유효한 위치가 있다: 문서 디렉터리 및 라이브러리 디렉터리. 플러그인의 이전 버전은 오직 문서 디렉토리에 영구 파일을 저장. 이 부작용 보다는 아니었다 수시로 특히 많은 작은 파일을 처리 하는 응용 프로그램에 대 한 의도, iTunes에 표시 모든 응용 프로그램 파일을 만드는 디렉터리의 용도 내보내기에 대 한 완전 한 문서를 생산 했다.
+
+문서 또는 응용 프로그램에서 기본 설정으로 라이브러리 디렉토리에 파일을 저장할 것인지를 선택할 수 `config.xml` 파일. 이렇게 하려면 다음 두 줄을 중 하나를 추가 `config.xml` :
+
+
+
+
+
+
+파일 플러그인 사용이 줄이 없으면 `Compatibility` 는 기본적으로. 기본 태그,이 이러한 값 중 하나가 아닌 경우에 응용 프로그램이 시작 되지 않습니다.
+
+이전 (사전 1.0)을 사용 하는 경우 응용 프로그램 사용자에 게 발송 되었다 이전,이 플러그인의 버전 영구 파일 시스템에 저장 된 파일은 다음 환경 설정을 설정 해야 및 `Compatibility` . 위치를 스위칭 `Library` 기존 사용자에 게 응용 프로그램을 업그레이 드의 그들의 이전에 저장 된 파일에 액세스할 수 것을 의미할 것입니다.
+
+응용 프로그램은 새로운, 또는 이전 영구 파일 시스템에 파일을 저장 하는 경우는 `Library` 일반적으로 권장 됩니다.
+
+## 파이어 폭스 OS 단점
+
+파일 시스템 API Firefox 운영 체제에서 기본적으로 지원 하지 및 indexedDB 위에 심으로 구현 됩니다.
+
+* 비어 있지 않은 디렉터리를 제거할 때 실패 하지 않습니다.
+* 디렉터리에 대 한 메타 데이터를 지원 하지 않습니다.
+* 메서드 `copyTo` 및 `moveTo` 디렉터리를 지원 하지 않습니다
+
+다음 데이터 경로 지원 됩니다: * `applicationDirectory` -를 사용 하 여 `xhr` 응용 프로그램으로 패키지 된 로컬 파일을. * `dataDirectory` -영구 응용 프로그램 특정 데이터 파일에 대 한. * `cacheDirectory` -캐시 응용 프로그램 다시 시작 해야 하는 파일 (애플 리 케이 션은 여기에 파일을 삭제 하려면 운영 체제에 의존 하지 말아야).
+
+## 업그레이드 노트
+
+이 플러그인의 v1.0.0에는 `FileEntry` 와 `DirectoryEntry` 구조 변경, 게시 된 사양에 맞춰 더 많은 것.
+
+플러그인의 이전 (pre-1.0.0) 버전 저장 장치-절대--있는 파일 위치는 `fullPath` 속성의 `Entry` 개체. 이러한 경로 일반적으로 같습니다.
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+이러한 경로 또한 반환한 했다는 `toURL()` 의 메서드는 `Entry` 개체.
+
+V1.0.0와 `fullPath` 특성은 *HTML 파일 시스템의 루트에 상대적인*파일의 경로를. 그래서, 위의 경로 지금 둘 다에 의해 대표 될 것 이라고는 `FileEntry` 개체는 `fullPath` 의
+
+ /path/to/file
+
+
+장치 절대 경로, 응용 프로그램 작동 하 고 이전을 통해 그 경로 검색 하는 경우는 `fullPath` 속성의 `Entry` 개체를 사용 하 여 코드를 업데이트 해야 하는 다음 `entry.toURL()` 대신.
+
+대 한 뒤 호환성는 `resolveLocalFileSystemURL()` 장치-절대-경로, 수락할 메서드와 반환 합니다는 `Entry` 파일 중 하나에 내 존재 하는 경우, 해당 개체는 `TEMPORARY` 또는 `PERSISTENT` 파일 시스템.
+
+이 특히 이전 장치 절대 경로 사용 하는 파일 전송 플러그인에 문제가 있다 (그리고 아직도 그들을 받아들일 수.) 파일 시스템 Url, 그래서 대체 올바르게 작동 하도록 업데이 트 되었습니다 `entry.fullPath` 함께 `entry.toURL()` 장치에 파일을 사용 하는 플러그인을 지 고 문제를 해결 해야 합니다.
+
+V1.1.0 반환 값에서에서의 `toURL()` 변경 되었습니다 (\[CB-6394\] (https://issues.apache.org/jira/browse/CB-6394) 참조)는 'file://' 절대 URL을 반환 합니다. 가능 하다 면. 보장 하는 ' cdvfile:'-URL을 사용할 수 있습니다 `toInternalURL()` 지금. 이 메서드 이제 양식의 파일 Url을 반환 합니다.
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+어떤 파일을 고유 하 게 식별 하려면 사용할 수 있습니다.
+
+## 오류 코드 및 의미의 목록
+
+오류가 throw 됩니다 때 다음 코드 중 하나가 사용 됩니다.
+
+| 코드 | 상수 |
+| --:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## (선택 사항) 플러그인 구성
+
+사용 가능한 파일 시스템의 집합 플랫폼 당 구성된 될 수 있습니다. IOS와 안 드 로이드를 인식 한 태그에 대 한 `config.xml` 는 설치 될 파일 시스템의 이름. 기본적으로 모든 파일 시스템 루트 사용할 수 있습니다.
+
+
+
+
+
+### 안 드 로이드
+
+* `files`: 응용 프로그램의 내부 파일 저장 디렉토리
+* `files-external`: 응용 프로그램의 외부 파일 저장 디렉토리
+* `sdcard`: 글로벌 외부 파일 저장 디렉토리 (이것은 SD 카드의 루트 설치 된 경우). 가지고 있어야 합니다는 `android.permission.WRITE_EXTERNAL_STORAGE` 이 사용 하는 허가.
+* `cache`: 응용 프로그램의 내부 캐시 디렉터리
+* `cache-external`: 응용 프로그램의 외부 캐시 디렉터리
+* `root`: 전체 장치 파일 시스템
+
+안 드 로이드는 또한 "파일" 파일 시스템 내에서 "/ 문서 /" 하위 디렉토리를 나타내는 "문서" 라는 특별 한 파일을 지원 합니다.
+
+### iOS
+
+* `library`: 응용 프로그램의 라이브러리 디렉터리
+* `documents`: 응용 프로그램의 문서 디렉토리
+* `cache`: 응용 프로그램의 캐시 디렉터리
+* `bundle`: 응용 프로그램의 번들; (읽기 전용) 디스크에 응용 프로그램 자체의 위치
+* `root`: 전체 장치 파일 시스템
+
+기본적으로 라이브러리 및 문서 디렉토리 iCloud에 동기화 할 수 있습니다. 또한 2 개의 추가적인 파일 시스템을 요청할 수 있습니다 `library-nosync` 과 `documents-nosync` , 어떤 특별 한 비 동기화 디렉터리 내에서 대표는 `/Library` 또는 `/Documents` 파일 시스템.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/ko/plugins.md b/plugins/org.apache.cordova.file/doc/ko/plugins.md
new file mode 100644
index 0000000..d5ec121
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/ko/plugins.md
@@ -0,0 +1,124 @@
+
+
+# 플러그인 개발자를 위한 노트
+
+이 노트는 주로 파일 플러그인을 사용 하 여 파일 시스템 플러그인 인터페이스를 작성 하 고 싶은 안 드 로이드와 iOS 개발자를 위한 것입니다.
+
+## 코르 도우 바 파일 시스템 Url 작업
+
+버전 1.0.0, 이후이 플러그인과 Url 사용은 `cdvfile` 교량, 모든 통신 체계 보다는 자바 원시 장치 파일 시스템 경로 노출.
+
+자바 스크립트 측면에서 즉, FileEntry 및 DirectoryEntry 개체 fullPath 속성을 HTML 파일 시스템의 루트에 상대적입니다. FileEntry 또는 DirectoryEntry 개체를 수락 하는 플러그인의 자바 API를 호출 해야 `.toURL()` 다리에 걸쳐 네이티브 코드에 전달 하기 전에 해당 개체에.
+
+### Cdvfile 변환: / / fileystem 경로 Url
+
+플러그인 파일 시스템을 작성 하는 실제 파일 시스템 위치에 받은 파일 시스템 URL을 변환 할 수 있습니다. 이렇게, 네이티브 플랫폼에 따라 여러 방법이 있다.
+
+기억 하는 것이 중요 하다 모든 `cdvfile://` Url은 장치에 실제 파일을 매핑. 일부 Url 파일에 의해 표현 되지 않는 또는 심지어 원격 리소스를 참조할 수 있는 장치에 자산을 참조할 수 있습니다. 이러한 가능성 때문에 플러그인 경로를 Url을 변환 하려고 할 때 다시 의미 있는 결과 얻을 지 여부를 항상 테스트 해야 합니다.
+
+#### 안 드 로이드
+
+안 드 로이드, 변환 하는 간단한 방법에는 `cdvfile://` URL을 파일 시스템 경로 사용 하는 `org.apache.cordova.CordovaResourceApi` . `CordovaResourceApi`처리할 수 있는 여러 가지 방법에는 `cdvfile://` Url:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+그것은 또한 파일 플러그인을 직접 사용할 수 있습니다:
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+경로를 변환 하는 `cdvfile://` URL:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+플러그인 파일을 만들고 그것에 대 한 FileEntry 개체를 반환 하려면, 파일 플러그인을 사용.
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+IOS에서 코르도바 같은 사용 하지 않는 `CordovaResourceApi` 안 드 로이드 개념. Ios, Url 및 파일 시스템 경로 사이의 변환할 파일 플러그인을 사용 해야 합니다.
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+플러그인 파일을 만들고 그것에 대 한 FileEntry 개체를 반환 하려면, 파일 플러그인을 사용.
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### 자바 스크립트
+
+자바 스크립트에서는 `cdvfile://` FileEntry 또는 DirectoryEntry 개체에서 URL를 호출 하면 `.toURL()` 그것에:
+
+ var cdvfileURL = entry.toURL();
+
+
+플러그인 응답 처리기에서 반환 된 FileEntry 구조에서 실제 항목 개체 변환 하려면 처리기 코드 해야 파일 플러그인 가져오고 새 개체를 만들:
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/pl/index.md b/plugins/org.apache.cordova.file/doc/pl/index.md
new file mode 100644
index 0000000..e00b33c
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/pl/index.md
@@ -0,0 +1,275 @@
+
+
+# org.apache.cordova.file
+
+Ten plugin implementuje API pliku, dzięki czemu dostęp do odczytu i zapisu do plików znajdujących się na urządzeniu.
+
+Ten plugin jest oparty na kilka specyfikacje, w tym: HTML5 File API
+
+Katalogi (nieistniejącego już) i System Najnowsze rozszerzenia: , chociaż większość z ten plugin kod został napisany podczas wcześniejszych specyfikacji były aktualne:
+
+To również implementuje specyfikację FileWriter:
+
+Wykorzystania, prosimy odnieść się do skały HTML5 doskonałe [plików art.][1]
+
+ [1]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+Omówienie innych opcji przechowywania odnoszą się do Cordova z [magazynu przewodnik][2].
+
+ [2]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## Instalacja
+
+ cordova plugin add org.apache.cordova.file
+
+
+## Obsługiwane platformy
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Firefox OS
+* iOS
+* Windows Phone 7 i 8 *
+* Windows 8 *
+
+* *Nie obsługują te platformy `FileReader.readAsArrayBuffer` , ani `FileWriter.write(blob)` .*
+
+## Gdzie przechowywać pliki
+
+Od v1.2.0 znajdują się adresy URL do katalogów ważne systemu plików. Każdy adres URL jest w formie *file:///path/to/spot/*i mogą być konwertowane na `DirectoryEntry` za pomocą`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-Tylko do odczytu katalogu gdzie jest zainstalowana aplikacja. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.applicationStorageDirectory`-Katalogu obszaru izolowanego aplikacji; na iOS to miejsce jest tylko do odczytu (ale podkatalogów określonego [jak `/Documents` ] są odczytu i zapisu). Wszystkie dane zawarte w jest prywatną do aplikacji. ( *iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.dataDirectory`-Trwałe i prywatne dane magazynowanie w izolowanym aplikacji przy użyciu pamięci wewnętrznej (na Android, jeśli trzeba użyć zewnętrznej pamięci, należy użyć `.externalDataDirectory` ). Na iOS, Katalog ten nie jest zsynchronizowane z iCloud (za pomocą `.syncedDataDirectory` ). (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.cacheDirectory`-Katalog dla plików buforowanych danych lub pliki, które aplikacji ponownie można łatwo tworzyć. System operacyjny może usunąć te pliki, gdy urządzenie działa niski na przechowywanie, niemniej jednak aplikacje nie powinny polegać na OS, aby usunąć pliki tutaj. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`-Stosowania przestrzeni na zewnętrznej pamięci masowej. (*Android*)
+
+* `cordova.file.externalDataDirectory`-Gdzie umieścić pliki danych specyficznych dla aplikacji na zewnętrznej pamięci masowej. (*Android*)
+
+* `cordova.file.externalCacheDirectory`-Pamięci podręcznej aplikacji na zewnętrznej pamięci masowej. (*Android*)
+
+* `cordova.file.externalRootDirectory`-Korzeń zewnętrznej pamięci masowej (karty SD). (*Android*, *BlackBerry 10*)
+
+* `cordova.file.tempDirectory`-Temp katalogu systemu operacyjnego można wyczyścić w będzie. Nie należy polegać na OS wobec usunąć ten katalog; aplikacji należy zawsze usunąć pliki jako obowiązujące. (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-Posiada pliki specyficzne dla aplikacji, które powinny być zsynchronizowane (np. do iCloud). (*iOS*)
+
+* `cordova.file.documentsDirectory`-Pliki prywatne do aplikacji, ale że mają znaczenie dla innych aplikacji (np. plików pakietu Office). (*iOS*)
+
+* `cordova.file.sharedDirectory`-Pliki dostępne na całym świecie do wszystkich aplikacji (*BlackBerry 10*)
+
+## Plik System układy
+
+Chociaż technicznie implementacyjnym, może być bardzo przydatne wiedzieć, jak `cordova.file.*` Właściwości mapy fizycznej ścieżki na prawdziwe urządzenie.
+
+### iOS układ systemu plików
+
+| Ścieżka urządzenia | `Cordova.File.*` | `iosExtraFileSystems` | r/w? | trwałe? | Czyści OS | Synchronizacja | prywatne |
+|:-------------------------------------------- |:--------------------------- |:--------------------- |:----:|:-------:|:-----------:|:--------------:|:--------:|
+| `/ var/mobile/Applications/< UUID > /` | applicationStorageDirectory | - | r | N/D! | N/D! | N/D! | Tak |
+| `appname.app/` | applicationDirectory | pakiet | r | N/D! | N/D! | N/D! | Tak |
+| `www/` | - | - | r | N/D! | N/D! | N/D! | Tak |
+| `Documents/` | documentsDirectory | dokumenty | r/w | Tak | Nr | Tak | Tak |
+| `NoCloud/` | - | dokumenty nosync | r/w | Tak | Nr | Nr | Tak |
+| `Library` | - | Biblioteka | r/w | Tak | Nr | Tak? | Tak |
+| `NoCloud/` | dataDirectory | Biblioteka nosync | r/w | Tak | Nr | Nr | Tak |
+| `Cloud/` | syncedDataDirectory | - | r/w | Tak | Nr | Tak | Tak |
+| `Caches/` | cacheDirectory | pamięci podręcznej | r/w | Tak * | Tak * * *| | Nr | Tak |
+| `tmp/` | tempDirectory | - | r/w | Nie * * | Tak * * *| | Nr | Tak |
+
+* Pliki utrzymywały aplikacja zostanie ponownie uruchomiony i uaktualnienia, ale w tym katalogu mogą być rozliczone, gdy OS pragnienia. Aplikacji powinny być w stanie odtworzyć zawartość, która może być usunięta.
+
+* * Plików może utrzymywać się po ponownym uruchomieniu aplikacji, ale nie opierają się na tym zachowaniu. Pliki nie są gwarantowane w aktualizacji. Aplikacji należy usunąć pliki z tego katalogu, gdy ma to zastosowanie, ponieważ system operacyjny nie gwarantuje Kiedy (lub nawet jeśli) te pliki zostaną usunięte.
+
+* * *| System operacyjny może wyczyścić zawartość w tym katalogu, gdy czuje, że jest to konieczne, ale nie powoływać się na to. Należy wyczyścić ten katalog jako odpowiednie dla aplikacji.
+
+### Układ systemu Android plików
+
+| Ścieżka urządzenia | `Cordova.File.*` | `AndroidExtraFileSystems` | r/w? | trwałe? | Czyści OS | prywatne |
+|:--------------------------------------- |:----------------------------------- |:------------------------------- |:----:|:-------:|:---------:|:--------:|
+| `file:///android_asset/` | applicationDirectory | | r | N/D! | N/D! | Tak |
+| `/Data/danych/< Aplikacja id > /` | applicationStorageDirectory | - | r/w | N/D! | N/D! | Tak |
+| `cache` | cacheDirectory | pamięci podręcznej | r/w | Tak | Tak * | Tak |
+| `files` | dataDirectory | pliki | r/w | Tak | Nr | Tak |
+| `Documents` | | dokumenty | r/w | Tak | Nr | Tak |
+| `< sdcard > /` | externalRootDirectory | sdcard | r/w | Tak | Nr | Nr |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | Tak | Nr | Nr |
+| `cache` | externalCacheDirectry | zewnętrznych pamięci podręcznej | r/w | Tak | Nie * * | Nr |
+| `files` | externalDataDirectory | zewnętrznych plików | r/w | Tak | Nr | Nr |
+
+* System operacyjny może okresowo usunąć ten katalog, ale nie opierają się na tym zachowaniu. Wyczyść zawartość tego katalogu jako odpowiednie dla danej aplikacji. Należy użytkownik przeczyścić pamięć podręczną ręcznie, zawartość w tym katalogu są usuwane.
+
+* * System operacyjny nie usunąć ten katalog automatycznie; Jesteś odpowiedzialny za zarządzanie zawartość siebie. Należy użytkownik przeczyścić pamięć podręczną ręcznie, zawartość katalogu są usuwane.
+
+**Uwaga**: Jeśli nie mogą być montowane pamięci masowej, `cordova.file.external*` Właściwości są`null`.
+
+### Układ systemu plików blackBerry 10
+
+| Ścieżka urządzenia | `Cordova.File.*` | r/w? | trwałe? | Czyści OS | prywatne |
+|:--------------------------------------------------------- |:--------------------------- |:----:|:-------:|:---------:|:--------:|
+| `file:///accounts/1000/AppData/ < id aplikacji > /` | applicationStorageDirectory | r | N/D! | N/D! | Tak |
+| `app/native` | applicationDirectory | r | N/D! | N/D! | Tak |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | Nr | Tak | Tak |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | Tak | Nr | Tak |
+| `file:///accounts/1000/Removable/sdcard` | externalRemovableDirectory | r/w | Tak | Nr | Nr |
+| `file:///accounts/1000/Shared` | sharedDirectory | r/w | Tak | Nr | Nr |
+
+*Uwaga*: gdy aplikacja jest rozmieszczana do pracy obwodu, wszystkie ścieżki są względne do /accounts/1000-enterprise.
+
+## Dziwactwa Androida
+
+### Lokalizacja przechowywania trwałych Android
+
+Istnieje wiele prawidłowe lokalizacje do przechowywania trwałych plików na telefonie z systemem Android. Zobacz [tę stronę][3] do szerokiej dyskusji o różnych możliwościach.
+
+ [3]: http://developer.android.com/guide/topics/data/data-storage.html
+
+Poprzednie wersje pluginu wybrać lokalizację plików tymczasowych i trwałe podczas uruchamiania, czy urządzenie twierdził, że karta SD (lub równoważne magazynowanie podzia³) był montowany w oparciu. Czy karta SD została zamontowana, czy duży wewnętrzny magazynowanie podzia³ był dostępny (takie jak na Nexus urządzenia,) a następnie trwałe pliki będą przechowywane w katalogu głównego tego miejsca. Oznaczało to, że wszystkie aplikacje Cordova może Zobacz wszystkie pliki dostępne na karcie.
+
+Jeśli karta SD nie był dostępny, a następnie poprzednie wersje będzie przechowywać dane w `/data/data/` , która izoluje aplikacje od siebie, ale nadal może spowodować danych, które mają być współużytkowane przez użytkowników.
+
+Teraz jest możliwe, aby zdecydować, czy do przechowywania plików w lokalizacji magazynu plików, lub przy użyciu poprzedniej logiki, z preferencją w aplikacji `config.xml` pliku. Aby to zrobić, należy dodać jeden z tych dwóch linii do `config.xml` :
+
+
+
+
+
+
+Bez tej linii, użyje pliku plugin `Compatibility` jako domyślny. Jeśli znacznik preferencji jest obecny i to nie jedną z tych wartości, aplikacja nie zostanie uruchomiona.
+
+Jeśli aplikacja wcześniej zostało wysłane do użytkowników, przy użyciu starszych (pre-1.0) wersję tego pluginu oraz pliki przechowywane w trwałych plików, a następnie należy ustawić preferencje `Compatibility` . Przełączania lokalizacji do "Wewnętrznego" oznacza, że istniejących użytkowników, którzy ich aplikacja może być niesłabnący wobec dostęp ich wcześniej zapisane pliki, w zależności od ich urządzenie.
+
+Jeśli aplikacja jest nowy, lub nigdy wcześniej przechowywane pliki w trwałych plików, a następnie `Internal` ustawienie jest zwykle zalecane.
+
+## Dziwactwa iOS
+
+* `cordova.file.applicationStorageDirectory`jest tylko do odczytu; próby przechowywania plików w katalogu głównym zakończy się niepowodzeniem. Użyj jednego z innych `cordova.file.*` właściwości zdefiniowane dla iOS (tylko `applicationDirectory` i `applicationStorageDirectory` są tylko do odczytu).
+* `FileReader.readAsText(blob, encoding)`
+ * `encoding`Parametr nie jest obsługiwana, i kodowanie UTF-8 jest zawsze w efekcie.
+
+### iOS lokalizacja przechowywania trwałych
+
+Istnieją dwa ważne miejsca trwałe pliki na urządzenia iOS: katalogu dokumentów i katalogu biblioteki. Poprzednie wersje pluginu tylko kiedykolwiek przechowywane trwałe pliki w katalogu dokumentów. To miał ten efekt uboczny od rozpoznawalności wszystkie pliki aplikacji w iTunes, który był często niezamierzone, zwłaszcza dla aplikacji, które obsługują wiele małych plików, zamiast produkuje kompletne dokumenty do wywozu, który jest przeznaczenie katalogu.
+
+Teraz jest możliwe, aby zdecydować, czy do przechowywania danych w dokumentach lub katalogu biblioteki, z preferencją w aplikacji `config.xml` pliku. Aby to zrobić, należy dodać jeden z tych dwóch linii do `config.xml` :
+
+
+
+
+
+
+Bez tej linii, użyje pliku plugin `Compatibility` jako domyślny. Jeśli znacznik preferencji jest obecny i to nie jedną z tych wartości, aplikacja nie zostanie uruchomiona.
+
+Jeśli aplikacja wcześniej zostało wysłane do użytkowników, przy użyciu starszych (pre-1.0) wersję tego pluginu oraz pliki przechowywane w trwałych plików, a następnie należy ustawić preferencje `Compatibility` . Przełączania lokalizacji `Library` oznaczałoby, że istniejących użytkowników, którzy ich aplikacja będzie niesłabnący wobec dostęp ich wcześniej zapisane pliki.
+
+Jeśli aplikacja jest nowy, lub nigdy wcześniej przechowywane pliki w trwałych plików, a następnie `Library` ustawienie jest zwykle zalecane.
+
+## Firefox OS dziwactwa
+
+API systemu plików nie jest obsługiwany macierzyście przez Firefox OS i jest zaimplementowany jako podkładki na indexedDB.
+
+* Nie usuwając niepuste katalogi
+* Nie obsługuje metadane dla katalogów
+* Metody `copyTo` i `moveTo` nie obsługuje katalogi
+
+Obsługiwane są następujące ścieżki danych: * `applicationDirectory` -używa `xhr` Aby uzyskać lokalne pliki, które są pakowane z aplikacji. * `dataDirectory` - Na trwałe dane specyficzne dla aplikacji pliki. * `cacheDirectory` -Buforowane pliki, które powinny przetrwać ponowne uruchomienie aplikacji (aplikacje nie powinny polegać na OS, aby usunąć pliki tutaj).
+
+## Uaktualniania notatek
+
+W v1.0.0 ten plugin `FileEntry` i `DirectoryEntry` struktur zmieniły się, aby być bardziej zgodnie z opublikowaną specyfikacją.
+
+Poprzednie wersje (pre-1.0.0) plugin przechowywane urządzenia bezwzględna plik lokalizacja w `fullPath` Właściwość `Entry` obiektów. Te ścieżki zazwyczaj będzie wyglądać
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Te ścieżki również zostały zwrócone przez `toURL()` Metoda `Entry` obiektów.
+
+Z v1.0.0 `fullPath` atrybut jest ścieżką do pliku, *względem katalogu głównego systemu plików HTML*. Tak, powyżej ścieżki będzie teraz zarówno reprezentowana przez `FileEntry` obiekt z `fullPath` z
+
+ /path/to/file
+
+
+Jeśli aplikacja działa z ścieżki bezwzględnej urządzeń, i możesz wcześniej źródło tych ścieżek za pomocą `fullPath` Właściwość `Entry` obiektów, a następnie należy zaktualizować kod, aby użyć `entry.toURL()` zamiast.
+
+Do tyłu zgodności, `resolveLocalFileSystemURL()` Metoda akceptuje pomysł ścieżka bezwzględna i zwróci `Entry` obiektu odpowiadającego mu, tak długo, jak ten plik istnieje w albo `TEMPORARY` lub `PERSISTENT` plików.
+
+To szczególnie został problem z pluginem transferu plików, które poprzednio używane ścieżki bezwzględnej urządzeń (i wciąż można je przyjąć). To został zaktualizowany, aby działać poprawnie z adresów URL plików, więc wymiana `entry.fullPath` z `entry.toURL()` powinno rozwiązać wszelkie problemy dostawanie ten plugin do pracy z plików w pamięci urządzenia.
+
+W v1.1.0 wartość zwracana z `toURL()` został zmieniony (patrz \[CB-6394\] (https://issues.apache.org/jira/browse/CB-6394)) zwraca adres URL absolutnej "file://". wszędzie tam, gdzie jest to możliwe. Aby zapewnić ' cdvfile:'-URL można użyć `toInternalURL()` teraz. Ta metoda zwróci teraz adresy URL plików formularza
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+który służy do jednoznacznej identyfikacji pliku.
+
+## Wykaz kodów błędów i ich znaczenie
+
+Gdy błąd jest generowany, jeden z następujących kodów będzie służyć.
+
+| Kod | Stała |
+| ---:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## Konfigurowanie wtyczka (opcjonalny)
+
+Zestaw dostępnych plików może być skonfigurowany na platformie. Zarówno iOS i Android uchwyt w `config.xml` które nazwy plików do instalacji. Domyślnie włączone są wszystkie korzenie systemu plików.
+
+
+
+
+
+### Android
+
+* `files`: Katalog plików aplikacja
+* `files-external`: Katalog aplikacji zewnętrznych plików
+* `sdcard`: Globalny plik zewnętrzny magazyn katalogu (to jest głównym karty SD, jeśli jedna jest zainstalowana). Musisz mieć `android.permission.WRITE_EXTERNAL_STORAGE` uprawnienie do korzystania z tego.
+* `cache`: Katalogu stosowanie wewnętrznej pamięci podręcznej
+* `cache-external`: Katalog aplikacji zewnętrznych pamięci podręcznej
+* `root`: Całe urządzenie systemu plików
+
+Android obsługuje również specjalnych plików o nazwie "dokumenty", który reprezentuje podkatalog "/ dokumenty /" w ramach systemu plików "pliki".
+
+### iOS
+
+* `library`: Katalog biblioteki aplikacji
+* `documents`: Katalog dokumentów wniosek
+* `cache`: W katalogu pamięci podręcznej aplikacja
+* `bundle`: Pakiet aplikacji; Lokalizacja aplikacji na dysku (tylko do odczytu)
+* `root`: Całe urządzenie systemu plików
+
+Domyślnie katalogi biblioteki i dokumenty mogą być synchronizowane iCloud. Można również zażądać dwóch dodatkowych plików, `library-nosync` i `documents-nosync` , które stanowią specjalny katalog nie zsynchronizowane w `/Library` lub `/Documents` systemu plików.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/pl/plugins.md b/plugins/org.apache.cordova.file/doc/pl/plugins.md
new file mode 100644
index 0000000..a1f9169
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/pl/plugins.md
@@ -0,0 +1,124 @@
+
+
+# Uwagi dla programistów wtyczki
+
+Te notatki są przeznaczone przede wszystkim dla Androida i iOS programistów, którzy chcieli pisac pluginy które interfejs z systemu plików za pomocą wtyczki pliku.
+
+## Praca z Cordova pliku system adresów URL
+
+Od wersji 1.0.0, ten plugin jest używane adresy URL z `cdvfile` system do komunikacji przez most, a nie narażać urządzenia raw ścieżki systemu plików JavaScript.
+
+Na stronie JavaScript oznacza to, że FileEntry i DirectoryEntry obiekty mają fullPath atrybut, który jest głównym systemie plików HTML. Jeśli twój plugin JavaScript API akceptuje obiektu DirectoryEntry lub FileEntry, należy zadzwonić `.toURL()` na obiekt przed przekazaniem przez most do kodu macierzystego.
+
+### Konwersja cdvfile: / / URL do ścieżki fileystem
+
+Wtyczek, które trzeba pisać do systemu plików może chcesz przekonwertować odebranych plików systemu adres URL lokalizacji rzeczywistych plików. Istnieje wiele sposobów działania, od macierzystego platformy.
+
+Ważne jest, aby pamiętać, że nie wszystkie `cdvfile://` adresy URL są można zmapować na prawdziwe akta urządzeniu. Niektóre adresy URL może odnosić się do aktywów na urządzeniu, które nie są reprezentowane przez pliki, lub nawet może odnosić się do zasobów zdalnych. Z powodu tych możliwości wtyczki należy zawsze sprawdzić, czy się znaczących wyników wstecz, podczas próby konwersji adresów URL do ścieżki.
+
+#### Android
+
+Na Android, najprostsza metoda konwersji `cdvfile://` URL do ścieżki systemu plików jest użycie `org.apache.cordova.CordovaResourceApi` . `CordovaResourceApi`jest kilka metod, które mogą obsługiwać `cdvfile://` adresów URL:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+Jest również możliwe, aby korzystać z wtyczki pliku bezpośrednio:
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+Do przeliczenia ścieżki do `cdvfile://` adres URL:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+Jeśli twój plugin tworzy plik, i chcesz zwraca obiekt FileEntry dla niego, użyj pliku plugin:
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+Cordova na iOS nie korzystać z tego samego `CordovaResourceApi` koncepcji jak Android. Na iOS należy użyć pliku plugin do konwersji między adresach URL i ścieżkach plików.
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+Jeśli twój plugin tworzy plik, i chcesz zwraca obiekt FileEntry dla niego, użyj pliku plugin:
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### JavaScript
+
+W JavaScript, aby uzyskać `cdvfile://` adres URL z obiektu FileEntry lub DirectoryEntry, wystarczy zadzwonić `.toURL()` na to:
+
+ var cdvfileURL = entry.toURL();
+
+
+W plugin obsługi odpowiedzi do przeliczenia strukturę FileEntry wrócił do rzeczywistego obiektu wejścia, kod obsługi należy importować pliku plugin i utworzyć nowy obiekt:
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/plugins.md b/plugins/org.apache.cordova.file/doc/plugins.md
new file mode 100644
index 0000000..a3329d6
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/plugins.md
@@ -0,0 +1,120 @@
+
+
+Notes for plugin developers
+===========================
+
+These notes are primarily intended for Android and iOS developers who want to write plugins which interface with the file system using the File plugin.
+
+Working with Cordova file system URLs
+-------------------------------------
+
+Since version 1.0.0, this plugin has used URLs with a `cdvfile` scheme for all communication over the bridge, rather than exposing raw device file system paths to JavaScript.
+
+On the JavaScript side, this means that FileEntry and DirectoryEntry objects have a fullPath attribute which is relative to the root of the HTML file system. If your plugin's JavaScript API accepts a FileEntry or DirectoryEntry object, you should call `.toURL()` on that object before passing it across the bridge to native code.
+
+### Converting cdvfile:// URLs to fileystem paths
+
+Plugins which need to write to the filesystem may want to convert a received file system URL to an actual filesystem location. There are multiple ways of doing this, depending on the native platform.
+
+It is important to remember that not all `cdvfile://` URLs are mappable to real files on the device. Some URLs can refer to assets on device which are not represented by files, or can even refer to remote resources. Because of these possibilities, plugins should always test whether they get a meaningful result back when trying to convert URLs to paths.
+
+#### Android
+
+On Android, the simplest method to convert a `cdvfile://` URL to a filesystem path is to use `org.apache.cordova.CordovaResourceApi`. `CordovaResourceApi` has several methods which can handle `cdvfile://` URLs:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+It is also possible to use the File plugin directly:
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+To convert from a path to a `cdvfile://` URL:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+If your plugin creates a file, and you want to return a FileEntry object for it, use the File plugin:
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+#### iOS
+
+Cordova on iOS does not use the same `CordovaResourceApi` concept as Android. On iOS, you should use the File plugin to convert between URLs and filesystem paths.
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+If your plugin creates a file, and you want to return a FileEntry object for it, use the File plugin:
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+#### JavaScript
+
+In JavaScript, to get a `cdvfile://` URL from a FileEntry or DirectoryEntry object, simply call `.toURL()` on it:
+
+ var cdvfileURL = entry.toURL();
+
+In plugin response handlers, to convert from a returned FileEntry structure to an actual Entry object, your handler code should import the File plugin and create a new object:
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
+
diff --git a/plugins/org.apache.cordova.file/doc/ru/index.md b/plugins/org.apache.cordova.file/doc/ru/index.md
new file mode 100644
index 0000000..2a23e69
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/ru/index.md
@@ -0,0 +1,275 @@
+
+
+# org.apache.cordova.file
+
+Этот плагин реализует API файла, позволяя доступ на чтение и запись в файлы, находящиеся на устройстве.
+
+Этот плагин основан на нескольких спецификации, в том числе: HTML5 API файла
+
+(Ныне несуществующей) каталоги и систему расширений Последнее: , хотя большая часть кода, плагин был написан, когда ранее спец был текущим:
+
+Он также реализует уничтожал spec:
+
+Для использования, пожалуйста, обратитесь к HTML5 скалы отличные [файловой системы статьи.][1]
+
+ [1]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+Обзор других вариантов хранения найти Cordova [хранения руководства][2].
+
+ [2]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## Установка
+
+ cordova plugin add org.apache.cordova.file
+
+
+## Поддерживаемые платформы
+
+* Amazon Fire OS
+* Android
+* BlackBerry 10
+* Firefox OS
+* iOS
+* Windows Phone 7 и 8 *
+* Windows 8 *
+
+* *Эти платформы не поддерживают `FileReader.readAsArrayBuffer` , ни `FileWriter.write(blob)` .*
+
+## Где хранить файлы
+
+По состоянию на v1.2.0 приведены URL-адреса для важных файлов в системные каталоги. Каждый URL-адрес в виде *file:///path/to/spot/*и может быть преобразован в `DirectoryEntry` с помощью`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-Каталог только для чтения, где установлено приложение. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.applicationStorageDirectory`-Корневой каталог приложения в песочнице; на iOS это место только для чтения (но определенных подкаталогов [как `/Documents` ], чтения записи). Все данные, содержащиеся в частных в приложение. ( *iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.dataDirectory`-Хранения стойкие и частных данных в приложения в песочнице с использованием внутренней памяти (на Android, если необходимо использовать внешнюю память, использовать `.externalDataDirectory` ). На iOS, этот каталог не синхронизируется с iCloud (использование `.syncedDataDirectory` ). (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.cacheDirectory`-Каталог для кэшированных данных файлов или все файлы, которые ваше приложение может повторно создать легко. ОС может удалить эти файлы, когда устройства хватает на хранение, тем не менее, приложения не должны опираться на OS, чтобы удалить файлы здесь. (*iOS*, *Android*, *BlackBerry 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`-Пространство приложения на внешнем хранилище. (*Android*)
+
+* `cordova.file.externalDataDirectory`-Куда положить файлы данных конкретного приложения на внешнем хранилище. (*Android*)
+
+* `cordova.file.externalCacheDirectory`-Применение кэш на внешние накопители. (*Android*)
+
+* `cordova.file.externalRootDirectory`-Корень внешние накопители (SD карта). (*Android*, *BlackBerry 10*)
+
+* `cordova.file.tempDirectory`-Временный каталог, что ОС можно снять на будет. Не следует полагаться на OS, чтобы очистить этот каталог; Ваше приложение всегда следует удалять файлы в соответствующих случаях. (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-Содержит файлы приложения, которые должны быть синхронизированы (например, к iCloud). (*iOS*)
+
+* `cordova.file.documentsDirectory`-Файлы для приложения, но это являются значимыми для других приложений (например, файлы Office). (*iOS*)
+
+* `cordova.file.sharedDirectory`-Файлы глобально доступной для всех приложений (*BlackBerry 10*)
+
+## Макеты файловой системы
+
+Хотя технически деталь реализации, она может быть очень полезно знать как `cordova.file.*` карта свойства физические пути на реальном устройстве.
+
+### iOS расположения файловой системы
+
+| Путь к устройству | `Cordova.File.*` | `iosExtraFileSystems` | r/w? | стойкие? | Очищает ОС | Синхронизация | частные |
+|:---------------------------------------------- |:--------------------------- |:--------------------- |:----:|:--------:|:----------:|:-------------:|:-------:|
+| `/ var/мобильного/применения/< UUID > /` | applicationStorageDirectory | - | r | Н/Д | Н/Д | Н/Д | Да |
+| `appname.app/` | applicationDirectory | расслоение | r | Н/Д | Н/Д | Н/Д | Да |
+| `www/` | - | - | r | Н/Д | Н/Д | Н/Д | Да |
+| `Documents/` | documentsDirectory | документы | r/w | Да | Нет | Да | Да |
+| `NoCloud/` | - | документы nosync | r/w | Да | Нет | Нет | Да |
+| `Library` | - | Библиотека | r/w | Да | Нет | Да? | Да |
+| `NoCloud/` | dataDirectory | Библиотека nosync | r/w | Да | Нет | Нет | Да |
+| `Cloud/` | syncedDataDirectory | - | r/w | Да | Нет | Да | Да |
+| `Caches/` | cacheDirectory | кэш | r/w | Да * | Да * * *| | Нет | Да |
+| `tmp/` | tempDirectory | - | r/w | Не * * | Да * * *| | Нет | Да |
+
+* Файлы сохраняются приложения перезагрузки и обновления, но этот каталог может быть очищен, когда ОС желаний. Ваше приложение должно иметь возможность воссоздать любое содержание, которое может быть удалено.
+
+* * Файлы могут сохраняться перезагрузками app, но не полагайтесь на это поведение. Для продолжения обновления файлов не гарантируется. Приложение должно удалить файлы из этого каталога, когда это применимо, как операционная система не гарантирует когда (или даже если) эти файлы будут удалены.
+
+* * *| ОС может очистить содержимое этого каталога, когда он считает это необходимым, но не полагайтесь на это. Необходимо снять этот каталог в зависимости от вашего приложения.
+
+### Расположения Android файловой системы
+
+| Путь к устройству | `Cordova.File.*` | `AndroidExtraFileSystems` | r/w? | стойкие? | Очищает ОС | частные |
+|:--------------------------------- |:----------------------------------- |:------------------------- |:----:|:--------:|:----------:|:-------:|
+| `File:///android_asset/` | applicationDirectory | | r | Н/Д | Н/Д | Да |
+| `/ Data/data/< app-id > /` | applicationStorageDirectory | - | r/w | Н/Д | Н/Д | Да |
+| `cache` | cacheDirectory | кэш | r/w | Да | Да * | Да |
+| `files` | dataDirectory | файлы | r/w | Да | Нет | Да |
+| `Documents` | | документы | r/w | Да | Нет | Да |
+| `< sdcard > /` | externalRootDirectory | SDCard | r/w | Да | Нет | Нет |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | Да | Нет | Нет |
+| `cache` | externalCacheDirectry | кэш внешние | r/w | Да | Не * * | Нет |
+| `files` | externalDataDirectory | файлы внешние | r/w | Да | Нет | Нет |
+
+* ОС может периодически удалять этот каталог, но не полагайтесь на это поведение. Очистите содержимое этого каталога в зависимости от вашего приложения. Если пользователь вручную очистить кэш, содержимое этого каталога будут удалены.
+
+* * ОС не ясно этот каталог автоматически; Вы несете ответственность за управление содержимым самостоятельно. Если пользователь вручную очистить кэш, содержимое каталога будут удалены.
+
+**Примечание**: Если нельзя монтировать внешние накопители, `cordova.file.external*` Свойства`null`.
+
+### BlackBerry 10 расположения файловой системы
+
+| Путь к устройству | `Cordova.File.*` | r/w? | стойкие? | Очищает ОС | частные |
+|:--------------------------------------------------- |:--------------------------- |:----:|:--------:|:----------:|:-------:|
+| `File:///Accounts/1000/AppData/ < app id > /` | applicationStorageDirectory | r | Н/Д | Н/Д | Да |
+| `app/native` | applicationDirectory | r | Н/Д | Н/Д | Да |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | Нет | Да | Да |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | Да | Нет | Да |
+| `File:///Accounts/1000/Removable/SDCard` | externalRemovableDirectory | r/w | Да | Нет | Нет |
+| `File:///Accounts/1000/Shared` | sharedDirectory | r/w | Да | Нет | Нет |
+
+*Примечание*: при развертывании приложения для работы периметра, все пути относительны /accounts/1000-enterprise.
+
+## Особенности Android
+
+### Местоположение Android постоянного хранения
+
+Есть несколько допустимых мест для хранения постоянных файлов на устройстве Android. Смотрите [эту страницу][3] для широкого обсуждения различных возможностей.
+
+ [3]: http://developer.android.com/guide/topics/data/data-storage.html
+
+Предыдущие версии плагина будет выбирать расположение временных и постоянных файлов при запуске, основанный на ли устройство утверждал, что SD-карта (или эквивалентные хранения раздел) был смонтирован. Если была смонтирована SD-карты, или если большой внутренней памяти раздел был доступен (такие как на устройствах Nexus,), то постоянные файлы будут храниться в корне этого пространства. Это означало, что все apps Cordova могли видеть все файлы, имеющиеся на карте.
+
+Если SD-карты не был доступен, то предыдущих версий будет хранить данные в `/data/data/` , которая изолирует приложения друг от друга, но все еще может причина данные распределяются между пользователями.
+
+Это теперь можно выбрать, следует ли хранить файлы в месте хранения внутренних файлов, или с использованием предыдущей логики, с предпочтением в вашем приложении `config.xml` файл. Чтобы сделать это, добавить один из этих двух линий в `config.xml` :
+
+
+
+
+
+
+Без этой линии, будет использовать файл плагина `Compatibility` по умолчанию. Если тег предпочтений присутствует и не является одним из этих значений, приложение не запустится.
+
+Если ранее была отгружена приложения для пользователей, используя старые (до 1.0) версию этого плагина и имеет сохраненные файлы в файловой системе постоянных, то вы должны установить предпочтения `Compatibility` . Переключение местоположение для «Внутреннего» будет означать, что существующие пользователи, которые обновить их применение может быть не в состоянии получить доступ к их сохраненные ранее файлы, в зависимости от их устройства.
+
+Если ваше приложение является новым или ранее никогда не хранит файлы в стойких файловой системы, то `Internal` как правило рекомендуется настройка.
+
+## Особенности iOS
+
+* `cordova.file.applicationStorageDirectory`доступен только для чтения; попытка хранения файлов в корневом каталоге не удастся. Использовать один из других `cordova.file.*` свойства, определенные для iOS (только `applicationDirectory` и `applicationStorageDirectory` доступны только для чтения).
+* `FileReader.readAsText(blob, encoding)`
+ * `encoding`Параметр не поддерживается, и UTF-8 кодирование действует всегда.
+
+### iOS место постоянного хранения
+
+Существует два допустимых местоположений для хранения постоянных файлов на устройства iOS: документы каталогов и библиотека. Предыдущие версии плагина только когда-либо постоянные файлы хранятся в папке документы. Это был побочный эффект делает все файлы приложения в iTunes, который часто был непреднамеренным, особенно для приложений, которые обрабатывают большое количество мелких файлов, вместо того, чтобы производить полный комплект документов для экспорта, который является цель каталога.
+
+Это теперь можно выбрать, следует ли хранить файлы в документы или каталоге библиотеки, с предпочтением в вашем приложении `config.xml` файл. Чтобы сделать это, добавить один из этих двух линий в `config.xml` :
+
+
+
+
+
+
+Без этой линии, будет использовать файл плагина `Compatibility` по умолчанию. Если тег предпочтений присутствует и не является одним из этих значений, приложение не запустится.
+
+Если ранее была отгружена приложения для пользователей, используя старые (до 1.0) версию этого плагина и имеет сохраненные файлы в файловой системе постоянных, то вы должны установить предпочтения `Compatibility` . Переключение расположения `Library` будет означать, что существующих пользователей обновить их применения будет не в состоянии получить доступ к их сохраненные ранее файлы.
+
+Если ваше приложение является новым или ранее никогда не хранит файлы в стойких файловой системы, то `Library` как правило рекомендуется настройка.
+
+## Особенности Firefox OS
+
+API файловой системы изначально не поддерживается Firefox OS и реализуется как оболочка поверх indexedDB.
+
+* Не вынимая непустые каталоги
+* Не поддерживает метаданные для каталогов
+* Методы `copyTo` и `moveTo` не поддерживает каталоги
+
+Поддерживаются следующие пути данных: * `applicationDirectory` -использует `xhr` чтобы получить локальные файлы, которые упакованы с приложением. * `dataDirectory` - Для постоянных данных конкретного приложения файлов. * `cacheDirectory` -Кэшированных файлов, которые должны выжить перезагрузки приложения (Apps не следует полагаться на OS, чтобы удалить файлы из здесь).
+
+## Обновление примечания
+
+В v1.0.0 этого плагина `FileEntry` и `DirectoryEntry` структур изменились, более соответствует опубликованной спецификации.
+
+Предыдущий (pre-1.0.0) версии плагина хранятся устройства Абсолют файл расположение в `fullPath` свойства `Entry` объектов. Эти пути обычно будет выглядеть
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+Эти пути также были возвращены `toURL()` метода `Entry` объектов.
+
+С v1.0.0 `fullPath` атрибут является путь к файлу, *заданный относительно корня файловой системы HTML*. Таким образом, выше пути будет теперь оба быть представлено `FileEntry` объект с `fullPath` из
+
+ /path/to/file
+
+
+Если ваше приложение работает с устройства Абсолют путями, и ранее были получены эти пути через `fullPath` свойства `Entry` объектов, то вам следует обновить код для использования `entry.toURL()` вместо этого.
+
+Для обратной совместимости, `resolveLocalFileSystemURL()` метод будет принимать путь Абсолют устройства и будет возвращать `Entry` объект, соответствующий его, до тех пор, как этот файл существует в рамках либо `TEMPORARY` или `PERSISTENT` файловых систем.
+
+Это особенно была проблема с плагином передачи файлов, который ранее использовался устройства Абсолют пути (и все еще может принять их). Он был обновлен для корректной работы с файловой системы URL, так что замена `entry.fullPath` с `entry.toURL()` должен решить любые вопросы, получить этот плагин для работы с файлами на устройстве.
+
+В v1.1.0 возвращаемое значение из `toURL()` был изменен (см. \[CB-6394\] (https://issues.apache.org/jira/browse/CB-6394)) для возвращения URL-адрес абсолютным «file://». где это возможно. Для обеспечения ' cdvfile:'-вы можете использовать URL-адрес `toInternalURL()` сейчас. Этот метод будет возвращать теперь файловой системы URL формы
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+который может использоваться для уникальной идентификации файла.
+
+## Список кодов ошибок и значения
+
+Когда возникает ошибка, один из следующих кодов будет использоваться.
+
+| Код | Постоянная |
+| ---:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## Настройка плагина (опционально)
+
+Набор доступных файловых систем может быть настроен на платформе. IOS и Android признают тег в `config.xml` имена которых файловые системы для установки. По умолчанию включены все корни файловой системы.
+
+
+
+
+
+### Android
+
+* `files`: Каталог для хранения внутреннего файла приложения
+* `files-external`: Каталог хранения внешнего файла приложения
+* `sdcard`: Глобальный внешний файл каталог хранения (это корень SD-карты, если таковая установлена). Вы должны иметь `android.permission.WRITE_EXTERNAL_STORAGE` разрешение использовать это.
+* `cache`: Каталог внутреннего кэша приложения
+* `cache-external`: Каталог приложения внешней кэш-памяти
+* `root`: Все устройство файловой системы
+
+Android поддерживает также Специальный файловую систему под названием «документы», которая представляет подкаталог «/ документы /» в пределах файловой системы «файлы».
+
+### iOS
+
+* `library`: Каталог библиотеки приложения
+* `documents`: Каталог документов приложения
+* `cache`: Каталог кэша приложения
+* `bundle`: Пакет приложения; расположение самого приложения на диске (только для чтения)
+* `root`: Все устройство файловой системы
+
+По умолчанию каталоги библиотеки и документы можно синхронизировать с iCloud. Вы также можете заказать два дополнительных файловых систем, `library-nosync` и `documents-nosync` , которые представляют Специальный каталог не синхронизируются в `/Library` или `/Documents` файловой системы.
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/ru/plugins.md b/plugins/org.apache.cordova.file/doc/ru/plugins.md
new file mode 100644
index 0000000..b9a637a
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/ru/plugins.md
@@ -0,0 +1,124 @@
+
+
+# Примечания для разработчиков плагинов
+
+Эти примечания предназначены прежде всего для Android и iOS разработчиков, которые хотят писать плагины какой интерфейс с файловой системой, с помощью файла плагина.
+
+## Работа с Кордова файловой системы URL
+
+Начиная с версии 1.0.0, этот плагин использует URL-адресов с `cdvfile` схема для всех коммуникации через мост, а не подвергая пути файловой системы raw устройства для JavaScript.
+
+На стороне JavaScript это означает, что объекты DirectoryEntry и FileEntry fullPath атрибут, который является по отношению к корневой файловой системе HTML. Если ваш плагин JavaScript API принимает объект DirectoryEntry или FileEntry, необходимо вызвать `.toURL()` для этого объекта перед передачей их через мост в машинный код.
+
+### Преобразование cdvfile: / / URL-адреса в пути fileystem
+
+Плагины, которые нужно написать в файловой системе может потребоваться преобразовать URL-адреса системы полученный файл в место фактической файловой системы. Существует несколько способов сделать это, в зависимости от родной платформе.
+
+Важно помнить, что не все `cdvfile://` URL-адреса являются отображаемыми файлами на устройстве. Некоторые URL может относиться к активам на устройстве, которые не представлены файлы, или может даже обратиться к удаленным ресурсам. Из-за эти возможности плагины следует всегда проверять ли они получают результат обратно при попытке преобразовать URL-адреса в пути.
+
+#### Android
+
+На Android, самый простой способ для преобразования `cdvfile://` URL-адрес к пути файловой системы заключается в использовании `org.apache.cordova.CordovaResourceApi` . `CordovaResourceApi`Есть несколько методов, которые можно обработать `cdvfile://` URL-адреса:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+Это также можно использовать плагин файл непосредственно:
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+Для преобразования пути к `cdvfile://` URL-адрес:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+Если ваш плагин создает файл, и вы хотите вернуть объект FileEntry для него, используйте файл плагина:
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+Кордова на iOS не использовать те же `CordovaResourceApi` понятие, как Android. На iOS вы должны использовать файл плагин для преобразования URL-адреса и пути файловой системы.
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+Если ваш плагин создает файл, и вы хотите вернуть объект FileEntry для него, используйте файл плагина:
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### JavaScript
+
+В JavaScript, чтобы получить `cdvfile://` URL от объекта DirectoryEntry или FileEntry, просто позвоните `.toURL()` на него:
+
+ var cdvfileURL = entry.toURL();
+
+
+В плагин обработчики ответ для преобразования из возвращаемой структуры FileEntry объект фактического вступления, код обработчика следует импортировать файл плагина и создайте новый объект:
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/zh/index.md b/plugins/org.apache.cordova.file/doc/zh/index.md
new file mode 100644
index 0000000..4d2077a
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/zh/index.md
@@ -0,0 +1,282 @@
+
+
+# org.apache.cordova.file
+
+這個外掛程式實現檔 API 允許對檔駐留在該設備上的讀/寫訪問。
+
+這個外掛程式基於幾個規格,包括: HTML5 檔 API [HTTP://www.w3.org/TR/FileAPI/][1]
+
+ [1]: http://www.w3.org/TR/FileAPI/
+
+(現已解散) 目錄和系統擴展最新: [HTTP://www.w3.org/TR/2012/WD-file-system-api-20120417/][2]雖然大部分的外掛程式代碼寫時較早的規格是當前: [HTTP://www.w3.org/TR/2011/WD-file-system-api-20110419/][3]
+
+ [2]: http://www.w3.org/TR/2012/WD-file-system-api-20120417/
+ [3]: http://www.w3.org/TR/2011/WD-file-system-api-20110419/
+
+它還實現 FileWriter 規格: [HTTP://dev.w3.org/2009/dap/file-system/file-writer.html][4]
+
+ [4]: http://dev.w3.org/2009/dap/file-system/file-writer.html
+
+用法,請參閱對 HTML5 的岩石優秀[檔案系統文章。][5]
+
+ [5]: http://www.html5rocks.com/en/tutorials/file/filesystem/
+
+其他存儲選項的概述,請參閱科爾多瓦的[存儲指南][6].
+
+ [6]: http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html
+
+## 安裝
+
+ cordova plugin add org.apache.cordova.file
+
+
+## 支援的平臺
+
+* 亞馬遜火 OS
+* Android 系統
+* 黑莓 10
+* 火狐瀏覽器的作業系統
+* iOS
+* Windows Phone 7 和 8 *
+* Windows 8 *
+
+**這些平臺不支援 `FileReader.readAsArrayBuffer` ,也不 `FileWriter.write(blob)` .*
+
+## 存儲檔的位置
+
+自 v1.2.0,提供重要的檔案系統目錄的 Url。 每個 URL 是在表單*file:///path/to/spot/*,和可以轉換為 `DirectoryEntry` 使用`window.resolveLocalFileSystemURL()`.
+
+* `cordova.file.applicationDirectory`-唯讀目錄在哪裡安裝的應用程式。(*iOS*、*安卓*、*黑莓 10*)
+
+* `cordova.file.applicationStorageDirectory`-根目錄下的應用程式的沙箱 ;在 iOS 上此位置是唯讀 (但特定的子目錄 [像 `/Documents` ] 都是讀寫)。 中包含的所有資料都是私有的應用程式。 ( *iOS*、*安卓*、*黑莓 10*)
+
+* `cordova.file.dataDirectory`資料持久性和私有資料存儲在內部記憶體使用的應用程式的沙箱內 (在安卓系統,如果你需要使用外部儲存體,使用 `.externalDataDirectory` )。 在 iOS,此目錄不與 iCloud 同步 (使用 `.syncedDataDirectory` )。 (*iOS*、*安卓*、*黑莓 10*)
+
+* `cordova.file.cacheDirectory`-緩存的資料檔案或您的應用程式重新可以輕鬆地創建的任何檔的目錄。 作業系統可能會刪除這些檔,該設備在存儲上運行低時,然而,應用程式不應依賴的作業系統,請刪除檔在這裡。 (*iOS*、*安卓*、*黑莓 10*)
+
+* `cordova.file.externalApplicationStorageDirectory`-應用程式外部存儲上的空間。(*安卓*)
+
+* `cordova.file.externalDataDirectory`-放在外部存儲特定于應用程式的資料檔案的位置。(*安卓*)
+
+* `cordova.file.externalCacheDirectory`-在外部存儲應用程式緩存。(*安卓*)
+
+* `cordova.file.externalRootDirectory`-外部存儲 (SD 卡) 的根。(*安卓*、*黑莓 10*)
+
+* `cordova.file.tempDirectory`-OS 可以清除時的空目錄會。 不依賴于 OS,以清除此目錄 ;您的應用程式,應總是移除作為適用的檔。 (*iOS*)
+
+* `cordova.file.syncedDataDirectory`-保存應同步 (例如到 iCloud) 的特定于應用程式的檔。(*iOS*)
+
+* `cordova.file.documentsDirectory`-檔私有的應用程式,但這是對其他應用程式 (例如 Office 檔) 有意義。(*iOS*)
+
+* `cordova.file.sharedDirectory`-對所有應用程式 (*黑莓 10*全域可用的檔)
+
+## 檔案系統佈局
+
+雖然技術上的實現細節,它可以是非常有用,要知道如何 `cordova.file.*` 屬性對應到真正的設備上的實體路徑。
+
+### iOS 檔案系統佈局
+
+| 設備路徑 | `cordova.file.*` | `iosExtraFileSystems` | r/w 嗎? | 持續性嗎? | OS 清除 | 同步 | 私人 |
+|:------------------------------- |:--------------------------- |:--------------------- |:------:|:------:|:----------:|:---:|:--:|
+| `/ 無功/移動/應用程式/< UUID > /` | applicationStorageDirectory | - | r | 不適用 | 不適用 | 不適用 | 是啊 |
+| `appname.app/` | applicationDirectory | 束 | r | 不適用 | 不適用 | 不適用 | 是啊 |
+| `www/` | - | - | r | 不適用 | 不適用 | 不適用 | 是啊 |
+| `Documents/` | documentsDirectory | 檔 | r/w | 是啊 | 無 | 是啊 | 是啊 |
+| `NoCloud/` | - | 檔 nosync | r/w | 是啊 | 無 | 無 | 是啊 |
+| `Library` | - | 圖書館 | r/w | 是啊 | 無 | 是嗎? | 是啊 |
+| `NoCloud/` | dataDirectory | 圖書館 nosync | r/w | 是啊 | 無 | 無 | 是啊 |
+| `Cloud/` | syncedDataDirectory | - | r/w | 是啊 | 無 | 是啊 | 是啊 |
+| `Caches/` | cacheDirectory | 快取記憶體 | r/w | 是啊 * | 是的 * * *| | 無 | 是啊 |
+| `tmp/` | tempDirectory | - | r/w | 沒有 * * | 是的 * * *| | 無 | 是啊 |
+
+* 檔堅持跨應用程式重新開機和升級,但是每當 OS 的欲望,可以清除此目錄。您的應用程式應該能夠重新創建任何內容可能會被刪除。
+
+* * 檔可能會持續整個應用程式重新開機,但不是依賴于這種行為。 不保證檔在更新之間持續存在。 您的應用程式應該從這個目錄刪除檔,在適當的時候,作為作業系統並不能保證時 (或即使) 中刪除這些檔。
+
+* * *| OS 可能會清除此目錄的內容,每當它感覺它是必要的但不是依賴于此。 你應該清除此目錄為適合您的應用程式。
+
+### Android 的檔案系統佈局
+
+| 設備路徑 | `cordova.file.*` | `AndroidExtraFileSystems` | r/w 嗎? | 持續性嗎? | OS 清除 | 私人 |
+|:--------------------------------- |:----------------------------------- |:------------------------- |:------:|:-----:|:------:|:--:|
+| `file:///android_asset/` | applicationDirectory | | r | 不適用 | 不適用 | 是啊 |
+| `/ 資料資料/< 應用程式 id > /` | applicationStorageDirectory | - | r/w | 不適用 | 不適用 | 是啊 |
+| `cache` | cacheDirectory | 快取記憶體 | r/w | 是啊 | 是啊 * | 是啊 |
+| `files` | dataDirectory | 檔 | r/w | 是啊 | 無 | 是啊 |
+| `Documents` | | 檔 | r/w | 是啊 | 無 | 是啊 |
+| `< sd 卡 > /` | externalRootDirectory | sd 卡 | r/w | 是啊 | 無 | 無 |
+| `Android/data//` | externalApplicationStorageDirectory | - | r/w | 是啊 | 無 | 無 |
+| `cache` | externalCacheDirectry | 外部快取記憶體 | r/w | 是啊 | 沒有 * * | 無 |
+| `files` | externalDataDirectory | 外部檔 | r/w | 是啊 | 無 | 無 |
+
+* 的作業系統可能會定期清除此目錄中,但不是依賴于這種行為。 清除此為適合您的應用程式目錄中的內容。 使用者應手動清除緩存中,這個目錄中的內容將被刪除。
+
+* * OS 不會清除此目錄自動 ;你是負責管理你自己的內容。 使用者應手動清除緩存中,目錄中的內容將被刪除。
+
+**注意**: 如果不能裝載外部存儲, `cordova.file.external*` 屬性是`null`.
+
+### 黑莓 10 檔案系統佈局
+
+| 設備路徑 | `cordova.file.*` | r/w 嗎? | 持續性嗎? | OS 清除 | 私人 |
+|:---------------------------------------------------- |:--------------------------- |:------:|:-----:|:-----:|:--:|
+| `file:///accounts/1000/appdata/ < 應用程式 id > /` | applicationStorageDirectory | r | 不適用 | 不適用 | 是啊 |
+| `app/native` | applicationDirectory | r | 不適用 | 不適用 | 是啊 |
+| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | 無 | 是啊 | 是啊 |
+| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | 是啊 | 無 | 是啊 |
+| `file:///accounts/1000/removable/sdcard` | externalRemovableDirectory | r/w | 是啊 | 無 | 無 |
+| `file:///accounts/1000/shared` | sharedDirectory | r/w | 是啊 | 無 | 無 |
+
+*注意*: 當應用程式部署工作週邊時,所有路徑都是相對於 /accounts/1000-enterprise。
+
+## Android 的怪癖
+
+### Android 的持久性存儲位置
+
+有很多有效的位置來存儲持久性檔在 Android 設備上。 請參閱[此頁][7]為廣泛地討論的各種可能性。
+
+ [7]: http://developer.android.com/guide/topics/data/data-storage.html
+
+以前版本的外掛程式會選擇在啟動時,基於該設備是否聲稱 SD 卡 (或等效存儲分區) 展開,臨時和永久檔的位置。 如果被掛載 SD 卡,或者如果一個大的內部存儲分區是可用 (如 Nexus 設備上) 然後持久性檔將存儲在該空間的根目錄中。 這就意味著所有的科爾多瓦應用程式可以看到所有可用的檔在卡上。
+
+如果 SD 卡不是可用的那麼以前的版本中將存儲資料下的 `/data/data/` ,其中隔離應用程式從彼此,但仍可能導致使用者之間共用的資料。
+
+現在可以選擇是否將檔存儲在內部檔存儲位置,或使用以前的邏輯,在您的應用程式的偏好與 `config.xml` 檔。 要做到這一點,添加到這兩條線之一 `config.xml` :
+
+
+
+
+
+
+如果這條線,沒有檔外掛程式將使用 `Compatibility` 作為預設值。如果偏好的標記是存在的並不是這些值之一,應用程式將無法啟動。
+
+如果您的應用程式先前已經運送到使用者,使用較舊的 (預 1.0) 的這個外掛程式,版本和已存儲的檔中的持久性的檔案系統,然後您應該將首選項設置為 `Compatibility` 。 切換到"內部"的位置就意味著現有使用者升級他們的應用程式可能無法訪問他們以前存儲的檔,他們的設備。
+
+如果您的應用程式是新的或有以前從未存儲檔在持久性的檔案系統,然後 `Internal` 一般建議設置。
+
+## iOS 的怪癖
+
+* `cordova.file.applicationStorageDirectory`是唯讀的 ;試圖存儲內的根目錄中的檔將會失敗。 使用的另一個 `cordova.file.*` 為 iOS 定義的屬性 (只有 `applicationDirectory` 和 `applicationStorageDirectory` 都是唯讀)。
+* `FileReader.readAsText(blob, encoding)`
+ * `encoding`參數不受支援,而 utf-8 編碼總是效果。
+
+### iOS 的持久性存儲位置
+
+有兩個有效的位置來存儲持久性的 iOS 設備上的檔: 檔目錄和庫目錄。 以前版本的外掛程式永遠只能存儲持久性檔在檔目錄中。 這有副作用 — — 使所有的應用程式的檔可見在 iTunes 中,往往是意料之外,尤其是對於處理大量小檔的應用程式,而不是生產用於出口,是意欲的目的的目錄的完整文檔。
+
+現在可以選擇是否將檔存儲在檔或庫目錄,在您的應用程式的偏好與 `config.xml` 檔。 要做到這一點,添加到這兩條線之一 `config.xml` :
+
+
+
+
+
+
+如果這條線,沒有檔外掛程式將使用 `Compatibility` 作為預設值。如果偏好的標記是存在的並不是這些值之一,應用程式將無法啟動。
+
+如果您的應用程式先前已經運送到使用者,使用較舊的 (預 1.0) 的這個外掛程式,版本和已存儲的檔中的持久性的檔案系統,然後您應該將首選項設置為 `Compatibility` 。 切換到的位置 `Library` 意味著現有使用者升級他們的應用程式將無法訪問他們以前存儲的檔。
+
+如果您的應用程式是新的或有以前從未存儲檔在持久性的檔案系統,然後 `Library` 一般建議設置。
+
+## 火狐瀏覽器作業系統的怪癖
+
+檔案系統 API 本身不支援通過 Firefox OS,作為墊片在 indexedDB 上實現的。
+
+* 不會失敗時刪除非空的目錄
+* 不支援中繼資料的目錄
+* 方法 `copyTo` 和 `moveTo` 不支援目錄
+
+支援以下資料路徑: * `applicationDirectory` -使用 `xhr` 來獲得與該應用程式打包的本地檔。 * `dataDirectory` -為持久性的特定于應用程式的資料檔案。 * `cacheDirectory` -緩存的檔應該生存重新開機應用程式 (應用程式不應依賴的作業系統,請刪除檔在這裡)。
+
+## 升級筆記
+
+在這個外掛程式,v1.0.0 `FileEntry` 和 `DirectoryEntry` 結構已經改變,更符合已發佈的規範。
+
+以前 (pre-1.0.0) 版本的外掛程式存放裝置固定檔案位置在 `fullPath` 屬性的 `Entry` 物件。這些路徑通常會看起來像
+
+ /var/mobile/Applications//Documents/path/to/file (iOS)
+ /storage/emulated/0/path/to/file (Android)
+
+
+這些路徑還返回的 `toURL()` 方法的 `Entry` 物件。
+
+與 v1.0.0, `fullPath` 的屬性是檔,*相對於 HTML 檔案系統的根目錄*的路徑。 所以,上面的路徑會現在都由代表 `FileEntry` 物件與 `fullPath` 的
+
+ /path/to/file
+
+
+如果您的應用程式與設備-絕對路徑,和你以前檢索到這些路徑通過 `fullPath` 屬性的 `Entry` 物件,然後您應該更新代碼以使用 `entry.toURL()` 相反。
+
+為向後相容性, `resolveLocalFileSystemURL()` 方法將接受設備-絕對路徑,並將返回 `Entry` 對應于它,只要該檔存在內任一物件 `TEMPORARY` 或 `PERSISTENT` 的檔案系統。
+
+這特別是一直與檔案傳輸外掛程式,以前使用過的設備-絕對路徑的問題 (和仍然可以接受他們)。 它已更新正常工作與檔案系統的 Url,所以更換 `entry.fullPath` 與 `entry.toURL()` 應解決獲取該外掛程式來處理檔在設備上的任何問題。
+
+V1.1.0 的傳回值中的 `toURL()` 被更改 (見 [CB-6394] (HTTPs://issues.apache.org/jira/browse/CB-6394)) 為返回一個絕對 file:// URL。 只要有可能。 以確保 ' cdvfile:'-您可以使用的 URL `toInternalURL()` 現在。 現在,此方法將返回檔案系統的表單的 Url
+
+ cdvfile://localhost/persistent/path/to/file
+
+
+它可以用於唯一地標識該檔。
+
+## 錯誤代碼和含義的清單
+
+當拋出一個錯誤時,將使用以下代碼之一。
+
+| 代碼 | 恒 |
+| --:|:----------------------------- |
+| 1 | `NOT_FOUND_ERR` |
+| 2 | `SECURITY_ERR` |
+| 3 | `ABORT_ERR` |
+| 4 | `NOT_READABLE_ERR` |
+| 5 | `ENCODING_ERR` |
+| 6 | `NO_MODIFICATION_ALLOWED_ERR` |
+| 7 | `INVALID_STATE_ERR` |
+| 8 | `SYNTAX_ERR` |
+| 9 | `INVALID_MODIFICATION_ERR` |
+| 10 | `QUOTA_EXCEEDED_ERR` |
+| 11 | `TYPE_MISMATCH_ERR` |
+| 12 | `PATH_EXISTS_ERR` |
+
+## 配置的外掛程式 (可選)
+
+可用的檔案系統的一整套可以配置每個平臺。IOS 和安卓系統識別 在標記 `config.xml` 哪一個名字要安裝的檔案系統。預設情況下,啟用所有檔案系統的根。
+
+
+
+
+
+### 安卓系統
+
+* `files`: 應用程式的內部檔存儲目錄
+* `files-external`: 應用程式的外部檔存儲目錄
+* `sdcard`: (這是根的 SD 卡,如果其中一個安裝) 全球外部檔存儲目錄。 您必須具有 `android.permission.WRITE_EXTERNAL_STORAGE` 使用此許可權。
+* `cache`: 應用程式的內部緩存目錄
+* `cache-external`: 應用程式的外部快取記憶體目錄
+* `root`: 整個設備的檔案系統
+
+安卓系統還支援一個特別的檔案系統命名為"檔",它代表"檔"的檔案系統中的子目錄"/ 檔 /"。
+
+### iOS
+
+* `library`: 應用程式的庫目錄
+* `documents`: 應用程式的檔目錄
+* `cache`: 應用程式的緩存目錄
+* `bundle`: 應用程式的包 ;應用程式本身 (唯讀) 的磁片上的位置
+* `root`: 整個設備的檔案系統
+
+預設情況下,圖書館和檔目錄可以同步到 iCloud。 您也可以要求兩個額外的檔案系統, `library-nosync` 和 `documents-nosync` ,它代表一個特殊的非同步目錄內 `/Library` 或 `/Documents` 檔案系統。
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/doc/zh/plugins.md b/plugins/org.apache.cordova.file/doc/zh/plugins.md
new file mode 100644
index 0000000..462fb06
--- /dev/null
+++ b/plugins/org.apache.cordova.file/doc/zh/plugins.md
@@ -0,0 +1,124 @@
+
+
+# 外掛程式開發人員須知
+
+這些筆記主要針對 Android 和 iOS 開發者想要使用的檔案系統使用的檔外掛程式編寫外掛程式哪個介面。
+
+## 工作與科爾多瓦檔案系統 Url
+
+1.0.0 版以來,這個外掛程式用了 Url 與 `cdvfile` 在橋樑,為所有的通信計畫,而不是暴露 JavaScript 原始設備的檔案系統路徑。
+
+在 JavaScript 方面,這意味著 FileEntry 和 DirectoryEntry 的物件具有一個完整路徑屬性是相對於 HTML 檔案系統的根目錄。 如果你的外掛程式的 JavaScript API 接受 FileEntry 或 DirectoryEntry 的物件,則應調用 `.toURL()` 對該物件之前將它在橋上傳遞給本機代碼。
+
+### 轉換 cdvfile: / / fileystem 的路徑的 Url
+
+需要寫入到檔案系統的外掛程式可能會想要將收到的檔案系統的 URL 轉換為實際的檔案系統位置。有多種方法做這個,根據本機平臺。
+
+它是重要的是要記住,不是所有 `cdvfile://` 的 Url 均可映射到設備上的實際檔。 某些 Url 可以引用在設備上不是由檔,或甚至可以引用遠端資源的資產。 由於這些可能性,外掛程式應始終測試是否回試圖將 Url 轉換成路徑時,他們得到有意義的結果。
+
+#### Android 系統
+
+在 android 系統,最簡單的方法來轉換 `cdvfile://` 檔案系統路徑的 URL 是使用 `org.apache.cordova.CordovaResourceApi` 。 `CordovaResourceApi`有幾種方法,可處理 `cdvfile://` 的 Url:
+
+ // webView is a member of the Plugin class
+ CordovaResourceApi resourceApi = webView.getResourceApi();
+
+ // Obtain a file:/// URL representing this file on the device,
+ // or the same URL unchanged if it cannot be mapped to a file
+ Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
+
+
+它也是可以直接使用檔外掛程式:
+
+ import org.apache.cordova.file.FileUtils;
+ import org.apache.cordova.file.FileSystem;
+ import java.net.MalformedURLException;
+
+ // Get the File plugin from the plugin manager
+ FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+
+ // Given a URL, get a path for it
+ try {
+ String path = filePlugin.filesystemPathForURL(cdvfileURL);
+ } catch (MalformedURLException e) {
+ // The filesystem url wasn't recognized
+ }
+
+
+要轉換的路徑從 `cdvfile://` 的 URL:
+
+ import org.apache.cordova.file.LocalFilesystemURL;
+
+ // Get a LocalFilesystemURL object for a device path,
+ // or null if it cannot be represented as a cdvfile URL.
+ LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
+ // Get the string representation of the URL object
+ String cdvfileURL = url.toString();
+
+
+如果你的外掛程式創建一個檔,並且您想要為它返回一個 FileEntry 物件,使用該檔外掛程式:
+
+ // Return a JSON structure suitable for returning to JavaScript,
+ // or null if this file is not representable as a cdvfile URL.
+ JSONObject entry = filePlugin.getEntryForFile(file);
+
+
+#### iOS
+
+科爾多瓦在 iOS 上的不使用相同 `CordovaResourceApi` 作為 android 系統的概念。在 iOS,應使用檔外掛程式的 Url 和檔案系統路徑之間進行轉換。
+
+ // Get a CDVFilesystem URL object from a URL string
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
+ // Get a path for the URL object, or nil if it cannot be mapped to a file
+ NSString* path = [filePlugin filesystemPathForURL:url];
+
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get the string representation of the URL object
+ NSString* cdvfileURL = [url absoluteString];
+
+
+如果你的外掛程式創建一個檔,並且您想要為它返回一個 FileEntry 物件,使用該檔外掛程式:
+
+ // Get a CDVFilesystem URL object for a device path, or
+ // nil if it cannot be represented as a cdvfile URL.
+ CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
+ // Get a structure to return to JavaScript
+ NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
+
+
+#### JavaScript
+
+在 JavaScript 中,得到 `cdvfile://` URL 從 FileEntry 或 DirectoryEntry 的物件,只需調用 `.toURL()` 對它:
+
+ var cdvfileURL = entry.toURL();
+
+
+在外掛程式回應處理常式,將從返回的 FileEntry 結構轉換為實際的輸入物件,您的處理常式代碼應該導入的檔外掛程式並創建一個新的物件:
+
+ // create appropriate Entry object
+ var entry;
+ if (entryStruct.isDirectory) {
+ entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ } else {
+ entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
+ }
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/package.json b/plugins/org.apache.cordova.file/package.json
new file mode 100644
index 0000000..1e70106
--- /dev/null
+++ b/plugins/org.apache.cordova.file/package.json
@@ -0,0 +1,27 @@
+{
+ "version": "1.3.3",
+ "name": "org.apache.cordova.file",
+ "cordova_name": "File",
+ "description": "Cordova File Plugin",
+ "license": "Apache 2.0",
+ "repo": "https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git",
+ "issue": "https://issues.apache.org/jira/browse/CB/component/12320651",
+ "keywords": [
+ "cordova",
+ "file"
+ ],
+ "platforms": [
+ "android",
+ "amazon-fireos",
+ "ubuntu",
+ "ios",
+ "wp7",
+ "wp8",
+ "blackberry10",
+ "windows8",
+ "windows",
+ "firefoxos"
+ ],
+ "engines": [],
+ "englishdoc": "\n\n# org.apache.cordova.file\n\n\nThis plugin implements a File API allowing read/write access to files residing on the device.\n\nThis plugin is based on several specs, including : \nThe HTML5 File API\n[http://www.w3.org/TR/FileAPI/](http://www.w3.org/TR/FileAPI/)\n\nThe (now-defunct) Directories and System extensions\nLatest: \n[http://www.w3.org/TR/2012/WD-file-system-api-20120417/](http://www.w3.org/TR/2012/WD-file-system-api-20120417/)\nAlthough most of the plugin code was written when an earlier spec was current:\n[http://www.w3.org/TR/2011/WD-file-system-api-20110419/](http://www.w3.org/TR/2011/WD-file-system-api-20110419/)\n\nIt also implements the FileWriter spec :\n[http://dev.w3.org/2009/dap/file-system/file-writer.html](http://dev.w3.org/2009/dap/file-system/file-writer.html)\n\nFor usage, please refer to HTML5 Rocks' excellent [FileSystem article.](http://www.html5rocks.com/en/tutorials/file/filesystem/)\n\nFor an overview of other storage options, refer to Cordova's\n[storage guide](http://cordova.apache.org/docs/en/edge/cordova_storage_storage.md.html).\n\nThis plugin defines global `cordova.file` object.\n\nAlthough in the global scope, it is not available until after the `deviceready` event.\n\n document.addEventListener(\"deviceready\", onDeviceReady, false);\n function onDeviceReady() {\n console.log(cordova.file);\n }\n\n## Installation\n\n cordova plugin add org.apache.cordova.file\n\n## Supported Platforms\n\n- Amazon Fire OS\n- Android\n- BlackBerry 10\n- Firefox OS\n- iOS\n- Windows Phone 7 and 8*\n- Windows 8*\n\n\\* _These platforms do not support `FileReader.readAsArrayBuffer` nor `FileWriter.write(blob)`._\n\n## Where to Store Files\n\nAs of v1.2.0, URLs to important file-system directories are provided.\nEach URL is in the form _file:///path/to/spot/_, and can be converted to a\n`DirectoryEntry` using `window.resolveLocalFileSystemURL()`.\n\n* `cordova.file.applicationDirectory` - Read-only directory where the application \n is installed. (_iOS_, _Android_, _BlackBerry 10_)\n\n* `cordova.file.applicationStorageDirectory` - Root directory of the application's \n sandbox; on iOS this location is read-only (but specific subdirectories [like \n `/Documents`] are read-write). All data contained within is private to the app. (\n _iOS_, _Android_, _BlackBerry 10_)\n\n* `cordova.file.dataDirectory` - Persistent and private data storage within the \n application's sandbox using internal memory (on Android, if you need to use \n external memory, use `.externalDataDirectory`). On iOS, this directory is not \n synced with iCloud (use `.syncedDataDirectory`). (_iOS_, _Android_, _BlackBerry 10_)\n\n* `cordova.file.cacheDirectory` - Directory for cached data files or any files \n that your app can re-create easily. The OS may delete these files when the device \n runs low on storage, nevertheless, apps should not rely on the OS to delete files \n in here. (_iOS_, _Android_, _BlackBerry 10_)\n\n* `cordova.file.externalApplicationStorageDirectory` - Application space on \n external storage. (_Android_)\n\n* `cordova.file.externalDataDirectory` - Where to put app-specific data files on \n external storage. (_Android_)\n\n* `cordova.file.externalCacheDirectory` - Application cache on external storage. \n (_Android_)\n\n* `cordova.file.externalRootDirectory` - External storage (SD card) root. (_Android_, _BlackBerry 10_)\n\n* `cordova.file.tempDirectory` - Temp directory that the OS can clear at will. Do not \n rely on the OS to clear this directory; your app should always remove files as \n applicable. (_iOS_)\n\n* `cordova.file.syncedDataDirectory` - Holds app-specific files that should be synced \n (e.g. to iCloud). (_iOS_)\n\n* `cordova.file.documentsDirectory` - Files private to the app, but that are meaningful \n to other application (e.g. Office files). (_iOS_)\n\n* `cordova.file.sharedDirectory` - Files globally available to all applications (_BlackBerry 10_)\n\n## File System Layouts\n\nAlthough technically an implementation detail, it can be very useful to know how \nthe `cordova.file.*` properties map to physical paths on a real device.\n\n### iOS File System Layout\n\n| Device Path | `cordova.file.*` | `iosExtraFileSystems` | r/w? | persistent? | OS clears | sync | private |\n|:-----------------------------------------------|:----------------------------|:----------------------|:----:|:-----------:|:---------:|:----:|:-------:|\n| `/var/mobile/Applications//` | applicationStorageDirectory | - | r | N/A | N/A | N/A | Yes |\n| `appname.app/` | applicationDirectory | bundle | r | N/A | N/A | N/A | Yes |\n| `www/` | - | - | r | N/A | N/A | N/A | Yes |\n| `Documents/` | documentsDirectory | documents | r/w | Yes | No | Yes | Yes |\n| `NoCloud/` | - | documents-nosync | r/w | Yes | No | No | Yes |\n| `Library` | - | library | r/w | Yes | No | Yes? | Yes |\n| `NoCloud/` | dataDirectory | library-nosync | r/w | Yes | No | No | Yes |\n| `Cloud/` | syncedDataDirectory | - | r/w | Yes | No | Yes | Yes |\n| `Caches/` | cacheDirectory | cache | r/w | Yes* | Yes\\*\\*\\*| No | Yes |\n| `tmp/` | tempDirectory | - | r/w | No\\*\\* | Yes\\*\\*\\*| No | Yes |\n\n\n \\* Files persist across app restarts and upgrades, but this directory can \n be cleared whenever the OS desires. Your app should be able to recreate any \n content that might be deleted.\n\n\\*\\* Files may persist across app restarts, but do not rely on this behavior. Files \n are not guaranteed to persist across updates. Your app should remove files from \n this directory when it is applicable, as the OS does not guarantee when (or even \n if) these files are removed.\n\n\\*\\*\\* The OS may clear the contents of this directory whenever it feels it is \n necessary, but do not rely on this. You should clear this directory as \n appropriate for your application.\n\n### Android File System Layout\n\n| Device Path | `cordova.file.*` | `AndroidExtraFileSystems` | r/w? | persistent? | OS clears | private |\n|:------------------------------------------------|:----------------------------|:--------------------------|:----:|:-----------:|:---------:|:-------:|\n| `file:///android_asset/` | applicationDirectory | | r | N/A | N/A | Yes |\n| `/data/data//` | applicationStorageDirectory | - | r/w | N/A | N/A | Yes |\n| `cache` | cacheDirectory | cache | r/w | Yes | Yes\\* | Yes |\n| `files` | dataDirectory | files | r/w | Yes | No | Yes |\n| `Documents` | | documents | r/w | Yes | No | Yes |\n| `/` | externalRootDirectory | sdcard | r/w | Yes | No | No |\n| `Android/data//` | externalApplicationStorageDirectory | - | r/w | Yes | No | No |\n| `cache` | externalCacheDirectry | cache-external | r/w | Yes | No\\*\\*| No |\n| `files` | externalDataDirectory | files-external | r/w | Yes | No | No |\n\n\\* The OS may periodically clear this directory, but do not rely on this behavior. Clear \n the contents of this directory as appropriate for your application. Should a user \n purge the cache manually, the contents of this directory are removed.\n\n\\*\\* The OS does not clear this directory automatically; you are responsible for managing \n the contents yourself. Should the user purge the cache manually, the contents of the \n directory are removed.\n\n**Note**: If external storage can't be mounted, the `cordova.file.external*` \nproperties are `null`.\n\n### BlackBerry 10 File System Layout\n\n| Device Path | `cordova.file.*` | r/w? | persistent? | OS clears | private |\n|:-------------------------------------------------------------|:----------------------------|:----:|:-----------:|:---------:|:-------:|\n| `file:///accounts/1000/appdata//` | applicationStorageDirectory | r | N/A | N/A | Yes |\n| `app/native` | applicationDirectory | r | N/A | N/A | Yes |\n| `data/webviews/webfs/temporary/local__0` | cacheDirectory | r/w | No | Yes | Yes |\n| `data/webviews/webfs/persistent/local__0` | dataDirectory | r/w | Yes | No | Yes |\n| `file:///accounts/1000/removable/sdcard` | externalRemovableDirectory | r/w | Yes | No | No |\n| `file:///accounts/1000/shared` | sharedDirectory | r/w | Yes | No | No |\n\n*Note*: When application is deployed to work perimeter, all paths are relative to /accounts/1000-enterprise.\n\n## Android Quirks\n\n### Android Persistent storage location\n\nThere are multiple valid locations to store persistent files on an Android\ndevice. See [this page](http://developer.android.com/guide/topics/data/data-storage.html)\nfor an extensive discussion of the various possibilities.\n\nPrevious versions of the plugin would choose the location of the temporary and\npersistent files on startup, based on whether the device claimed that the SD\nCard (or equivalent storage partition) was mounted. If the SD Card was mounted,\nor if a large internal storage partition was available (such as on Nexus\ndevices,) then the persistent files would be stored in the root of that space.\nThis meant that all Cordova apps could see all of the files available on the\ncard.\n\nIf the SD card was not available, then previous versions would store data under\n`/data/data/`, which isolates apps from each other, but may still\ncause data to be shared between users.\n\nIt is now possible to choose whether to store files in the internal file\nstorage location, or using the previous logic, with a preference in your\napplication's `config.xml` file. To do this, add one of these two lines to\n`config.xml`:\n\n \n\n \n\nWithout this line, the File plugin will use `Compatibility` as the default. If\na preference tag is present, and is not one of these values, the application\nwill not start.\n\nIf your application has previously been shipped to users, using an older (pre-\n1.0) version of this plugin, and has stored files in the persistent filesystem,\nthen you should set the preference to `Compatibility`. Switching the location to\n\"Internal\" would mean that existing users who upgrade their application may be\nunable to access their previously-stored files, depending on their device.\n\nIf your application is new, or has never previously stored files in the\npersistent filesystem, then the `Internal` setting is generally recommended.\n\n\n## iOS Quirks\n\n- `cordova.file.applicationStorageDirectory` is read-only; attempting to store \n files within the root directory will fail. Use one of the other `cordova.file.*`\n properties defined for iOS (only `applicationDirectory` and `applicationStorageDirectory` are\n read-only).\n- `FileReader.readAsText(blob, encoding)`\n - The `encoding` parameter is not supported, and UTF-8 encoding is always in effect.\n\n### iOS Persistent storage location\n\nThere are two valid locations to store persistent files on an iOS device: the\nDocuments directory and the Library directory. Previous versions of the plugin\nonly ever stored persistent files in the Documents directory. This had the\nside-effect of making all of an application's files visible in iTunes, which\nwas often unintended, especially for applications which handle lots of small\nfiles, rather than producing complete documents for export, which is the\nintended purpose of the directory.\n\nIt is now possible to choose whether to store files in the documents or library\ndirectory, with a preference in your application's `config.xml` file. To do this,\nadd one of these two lines to `config.xml`:\n\n \n\n \n\nWithout this line, the File plugin will use `Compatibility` as the default. If\na preference tag is present, and is not one of these values, the application\nwill not start.\n\nIf your application has previously been shipped to users, using an older (pre-\n1.0) version of this plugin, and has stored files in the persistent filesystem,\nthen you should set the preference to `Compatibility`. Switching the location to\n`Library` would mean that existing users who upgrade their application would be\nunable to access their previously-stored files.\n\nIf your application is new, or has never previously stored files in the\npersistent filesystem, then the `Library` setting is generally recommended.\n\n## Firefox OS Quirks\n\nThe File System API is not natively supported by Firefox OS and is implemented\nas a shim on top of indexedDB. \n \n* Does not fail when removing non-empty directories\n* Does not support metadata for directories\n* Methods `copyTo` and `moveTo` do not support directories\n\nThe following data paths are supported:\n* `applicationDirectory` - Uses `xhr` to get local files that are packaged with the app.\n* `dataDirectory` - For persistent app-specific data files.\n* `cacheDirectory` - Cached files that should survive app restarts (Apps should not rely\non the OS to delete files in here).\n\n## Upgrading Notes\n\nIn v1.0.0 of this plugin, the `FileEntry` and `DirectoryEntry` structures have changed,\nto be more in line with the published specification.\n\nPrevious (pre-1.0.0) versions of the plugin stored the device-absolute-file-location\nin the `fullPath` property of `Entry` objects. These paths would typically look like\n\n /var/mobile/Applications//Documents/path/to/file (iOS)\n /storage/emulated/0/path/to/file (Android)\n\nThese paths were also returned by the `toURL()` method of the `Entry` objects.\n\nWith v1.0.0, the `fullPath` attribute is the path to the file, _relative to the root of\nthe HTML filesystem_. So, the above paths would now both be represented by a `FileEntry`\nobject with a `fullPath` of\n\n /path/to/file\n\nIf your application works with device-absolute-paths, and you previously retrieved those\npaths through the `fullPath` property of `Entry` objects, then you should update your code\nto use `entry.toURL()` instead.\n\nFor backwards compatibility, the `resolveLocalFileSystemURL()` method will accept a\ndevice-absolute-path, and will return an `Entry` object corresponding to it, as long as that\nfile exists within either the `TEMPORARY` or `PERSISTENT` filesystems.\n\nThis has particularly been an issue with the File-Transfer plugin, which previously used\ndevice-absolute-paths (and can still accept them). It has been updated to work correctly\nwith FileSystem URLs, so replacing `entry.fullPath` with `entry.toURL()` should resolve any\nissues getting that plugin to work with files on the device.\n\nIn v1.1.0 the return value of `toURL()` was changed (see [CB-6394] (https://issues.apache.org/jira/browse/CB-6394))\nto return an absolute 'file://' URL. wherever possible. To ensure a 'cdvfile:'-URL you can use `toInternalURL()` now.\nThis method will now return filesystem URLs of the form\n\n cdvfile://localhost/persistent/path/to/file\n\nwhich can be used to identify the file uniquely.\n\n## List of Error Codes and Meanings\nWhen an error is thrown, one of the following codes will be used. \n\n| Code | Constant |\n|-----:|:------------------------------|\n| 1 | `NOT_FOUND_ERR` |\n| 2 | `SECURITY_ERR` |\n| 3 | `ABORT_ERR` |\n| 4 | `NOT_READABLE_ERR` |\n| 5 | `ENCODING_ERR` |\n| 6 | `NO_MODIFICATION_ALLOWED_ERR` |\n| 7 | `INVALID_STATE_ERR` |\n| 8 | `SYNTAX_ERR` |\n| 9 | `INVALID_MODIFICATION_ERR` |\n| 10 | `QUOTA_EXCEEDED_ERR` |\n| 11 | `TYPE_MISMATCH_ERR` |\n| 12 | `PATH_EXISTS_ERR` |\n\n## Configuring the Plugin (Optional)\n\nThe set of available filesystems can be configured per-platform. Both iOS and\nAndroid recognize a tag in `config.xml` which names the\nfilesystems to be installed. By default, all file-system roots are enabled.\n\n \n \n\n### Android\n\n* `files`: The application's internal file storage directory\n* `files-external`: The application's external file storage directory\n* `sdcard`: The global external file storage directory (this is the root of the SD card, if one is installed). You must have the `android.permission.WRITE_EXTERNAL_STORAGE` permission to use this.\n* `cache`: The application's internal cache directory\n* `cache-external`: The application's external cache directory\n* `root`: The entire device filesystem\n\nAndroid also supports a special filesystem named \"documents\", which represents a \"/Documents/\" subdirectory within the \"files\" filesystem.\n\n### iOS\n\n* `library`: The application's Library directory\n* `documents`: The application's Documents directory\n* `cache`: The application's Cache directory\n* `bundle`: The application's bundle; the location of the app itself on disk (read-only)\n* `root`: The entire device filesystem\n\nBy default, the library and documents directories can be synced to iCloud. You can also request two additional filesystems, `library-nosync` and `documents-nosync`, which represent a special non-synced directory within the `/Library` or `/Documents` filesystem.\n"
+}
\ No newline at end of file
diff --git a/plugins/org.apache.cordova.file/plugin.xml b/plugins/org.apache.cordova.file/plugin.xml
new file mode 100644
index 0000000..c1a89d4
--- /dev/null
+++ b/plugins/org.apache.cordova.file/plugin.xml
@@ -0,0 +1,345 @@
+
+
+
+
+ File
+ Cordova File Plugin
+ Apache 2.0
+ cordova,file
+ https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git
+ https://issues.apache.org/jira/browse/CB/component/12320651
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/org.apache.cordova.file/src/android/ContentFilesystem.java b/plugins/org.apache.cordova.file/src/android/ContentFilesystem.java
new file mode 100644
index 0000000..79e1ecc
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/ContentFilesystem.java
@@ -0,0 +1,319 @@
+/*
+ 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.
+ */
+package org.apache.cordova.file;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaResourceApi;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.PluginManager;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+
+public class ContentFilesystem extends Filesystem {
+
+ private CordovaInterface cordova;
+ private CordovaResourceApi resourceApi;
+
+ public ContentFilesystem(CordovaInterface cordova, CordovaWebView webView) {
+ super(Uri.parse("content://"), "content");
+ this.cordova = cordova;
+
+ Class webViewClass = webView.getClass();
+ PluginManager pm = null;
+ try {
+ Method gpm = webViewClass.getMethod("getPluginManager");
+ pm = (PluginManager) gpm.invoke(webView);
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ if (pm == null) {
+ try {
+ Field pmf = webViewClass.getField("pluginManager");
+ pm = (PluginManager)pmf.get(webView);
+ } catch (NoSuchFieldException e) {
+ } catch (IllegalAccessException e) {
+ }
+ }
+ this.resourceApi = new CordovaResourceApi(webView.getContext(), pm);
+ }
+
+ @Override
+ public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
+ if ("/".equals(inputURL.fullPath)) {
+ return LocalFilesystem.makeEntryForURL(inputURL, true, inputURL.URL.toString());
+ }
+
+ // Get the cursor to validate that the file exists
+ Cursor cursor = openCursorForURL(inputURL);
+ String filePath = null;
+ try {
+ if (cursor == null || !cursor.moveToFirst()) {
+ throw new FileNotFoundException();
+ }
+ filePath = filesystemPathForCursor(cursor);
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ if (filePath == null) {
+ filePath = inputURL.URL.toString();
+ } else {
+ filePath = "file://" + filePath;
+ }
+ return makeEntryForPath(inputURL.fullPath, inputURL.filesystemName, false /*fp.isDirectory()*/, filePath);
+ }
+
+ @Override
+ public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
+ String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
+ if (options != null) {
+ if (options.optBoolean("create")) {
+ throw new IOException("Cannot create content url");
+ }
+ }
+ LocalFilesystemURL requestedURL = new LocalFilesystemURL(Uri.withAppendedPath(inputURL.URL, fileName));
+ File fp = new File(this.filesystemPathForURL(requestedURL));
+ if (!fp.exists()) {
+ throw new FileNotFoundException("path does not exist");
+ }
+ if (directory) {
+ if (fp.isFile()) {
+ throw new TypeMismatchException("path doesn't exist or is file");
+ }
+ } else {
+ if (fp.isDirectory()) {
+ throw new TypeMismatchException("path doesn't exist or is directory");
+ }
+ }
+ // Return the directory
+ return makeEntryForPath(requestedURL.fullPath, requestedURL.filesystemName, directory, Uri.fromFile(fp).toString());
+
+ }
+
+ @Override
+ public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL)
+ throws NoModificationAllowedException {
+
+ String filePath = filesystemPathForURL(inputURL);
+ File file = new File(filePath);
+ try {
+ this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ MediaStore.Images.Media.DATA + " = ?",
+ new String[] { filePath });
+ } catch (UnsupportedOperationException t) {
+ // Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
+ // The ContentResolver applies only when the file was registered in the
+ // first case, which is generally only the case with images.
+ }
+ return file.delete();
+ }
+
+ @Override
+ public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL)
+ throws NoModificationAllowedException {
+ throw new NoModificationAllowedException("Cannot remove content url");
+ }
+
+ @Override
+ public JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL)
+ throws FileNotFoundException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ Integer size = null;
+ Integer lastModified = null;
+ Cursor cursor = openCursorForURL(inputURL);
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ size = resourceSizeForCursor(cursor);
+ lastModified = lastModifiedDateForCursor(cursor);
+ } else {
+ throw new FileNotFoundException();
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ JSONObject metadata = new JSONObject();
+ try {
+ metadata.put("size", size);
+ metadata.put("type", resourceApi.getMimeType(inputURL.URL));
+ metadata.put("name", inputURL.filesystemName);
+ metadata.put("fullPath", inputURL.fullPath);
+ metadata.put("lastModifiedDate", lastModified);
+ } catch (JSONException e) {
+ return null;
+ }
+ return metadata;
+ }
+
+ @Override
+ public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
+ Filesystem srcFs, LocalFilesystemURL srcURL, boolean move)
+ throws IOException, InvalidModificationException, JSONException,
+ NoModificationAllowedException, FileExistsException {
+ if (LocalFilesystem.class.isInstance(srcFs)) {
+ /* Same FS, we can shortcut with CordovaResourceApi operations */
+ // Figure out where we should be copying to
+ final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL);
+
+ OutputStream os = resourceApi.openOutputStream(destURL.URL);
+ CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcURL.URL);
+ if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
+ throw new NoModificationAllowedException("Cannot move file at source URL");
+ }
+ try {
+ resourceApi.copyResource(ofrr, os);
+ } catch (IOException e) {
+ throw new IOException("Cannot read file at source URL");
+ }
+ if (move) {
+ srcFs.removeFileAtLocalURL(srcURL);
+ }
+ return makeEntryForURL(destinationURL, false, destinationURL.URL.toString());
+ } else {
+ // Need to copy the hard way
+ return super.copyFileToURL(destURL, newName, srcFs, srcURL, move);
+ }
+ }
+
+
+ @Override
+ public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
+ ReadFileCallback readFileCallback) throws IOException {
+ CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(inputURL.URL);
+ if (end < 0) {
+ end = ofrr.length;
+ }
+ long numBytesToRead = end - start;
+ try {
+ if (start > 0) {
+ ofrr.inputStream.skip(start);
+ }
+ LimitedInputStream inputStream = new LimitedInputStream(ofrr.inputStream, numBytesToRead);
+ readFileCallback.handleData(inputStream, ofrr.mimeType);
+ } finally {
+ ofrr.inputStream.close();
+ }
+ }
+
+ @Override
+ public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
+ int offset, boolean isBinary) throws NoModificationAllowedException {
+ throw new NoModificationAllowedException("Couldn't write to file given its content URI");
+ }
+ @Override
+ public long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
+ throws NoModificationAllowedException {
+ throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
+ }
+
+ protected Cursor openCursorForURL(LocalFilesystemURL url) {
+ ContentResolver contentResolver = this.cordova.getActivity().getContentResolver();
+ Cursor cursor = contentResolver.query(url.URL, null, null, null, null);
+ return cursor;
+ }
+
+ protected String filesystemPathForCursor(Cursor cursor) {
+ final String[] LOCAL_FILE_PROJECTION = { MediaStore.Images.Media.DATA };
+ int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
+ if (columnIndex != -1) {
+ return cursor.getString(columnIndex);
+ }
+ return null;
+ }
+
+ protected Integer resourceSizeForCursor(Cursor cursor) {
+ int columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+ if (columnIndex != -1) {
+ String sizeStr = cursor.getString(columnIndex);
+ if (sizeStr != null) {
+ return Integer.parseInt(sizeStr,10);
+ }
+ }
+ return null;
+ }
+
+ protected Integer lastModifiedDateForCursor(Cursor cursor) {
+ final String[] LOCAL_FILE_PROJECTION = { MediaStore.MediaColumns.DATE_MODIFIED };
+ int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
+ if (columnIndex != -1) {
+ String dateStr = cursor.getString(columnIndex);
+ if (dateStr != null) {
+ return Integer.parseInt(dateStr,10);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String filesystemPathForURL(LocalFilesystemURL url) {
+ Cursor cursor = openCursorForURL(url);
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ return filesystemPathForCursor(cursor);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+ @Override
+ public LocalFilesystemURL URLforFilesystemPath(String path) {
+ // Returns null as we don't support reverse mapping back to content:// URLs
+ return null;
+ }
+
+ @Override
+ public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
+ String path = filesystemPathForURL(inputURL);
+ File file = new File(path);
+ return file.exists();
+ }
+
+ @Override
+ OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL)
+ throws IOException {
+ OutputStream os = resourceApi.openOutputStream(inputURL.URL);
+ return os;
+ }
+}
diff --git a/plugins/org.apache.cordova.file/src/android/DirectoryManager.java b/plugins/org.apache.cordova.file/src/android/DirectoryManager.java
new file mode 100644
index 0000000..c2d1278
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/DirectoryManager.java
@@ -0,0 +1,133 @@
+/*
+ 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.
+*/
+package org.apache.cordova.file;
+
+import android.os.Environment;
+import android.os.StatFs;
+
+import java.io.File;
+
+/**
+ * This class provides file directory utilities.
+ * All file operations are performed on the SD card.
+ *
+ * It is used by the FileUtils class.
+ */
+public class DirectoryManager {
+
+ @SuppressWarnings("unused")
+ private static final String LOG_TAG = "DirectoryManager";
+
+ /**
+ * Determine if a file or directory exists.
+ * @param name The name of the file to check.
+ * @return T=exists, F=not found
+ */
+ public static boolean testFileExists(String name) {
+ boolean status;
+
+ // If SD card exists
+ if ((testSaveLocationExists()) && (!name.equals(""))) {
+ File path = Environment.getExternalStorageDirectory();
+ File newPath = constructFilePaths(path.toString(), name);
+ status = newPath.exists();
+ }
+ // If no SD card
+ else {
+ status = false;
+ }
+ return status;
+ }
+
+ /**
+ * Get the free disk space
+ *
+ * @return Size in KB or -1 if not available
+ */
+ public static long getFreeDiskSpace(boolean checkInternal) {
+ String status = Environment.getExternalStorageState();
+ long freeSpace = 0;
+
+ // If SD card exists
+ if (status.equals(Environment.MEDIA_MOUNTED)) {
+ freeSpace = freeSpaceCalculation(Environment.getExternalStorageDirectory().getPath());
+ }
+ else if (checkInternal) {
+ freeSpace = freeSpaceCalculation("/");
+ }
+ // If no SD card and we haven't been asked to check the internal directory then return -1
+ else {
+ return -1;
+ }
+
+ return freeSpace;
+ }
+
+ /**
+ * Given a path return the number of free KB
+ *
+ * @param path to the file system
+ * @return free space in KB
+ */
+ private static long freeSpaceCalculation(String path) {
+ StatFs stat = new StatFs(path);
+ long blockSize = stat.getBlockSize();
+ long availableBlocks = stat.getAvailableBlocks();
+ return availableBlocks * blockSize / 1024;
+ }
+
+ /**
+ * Determine if SD card exists.
+ *
+ * @return T=exists, F=not found
+ */
+ public static boolean testSaveLocationExists() {
+ String sDCardStatus = Environment.getExternalStorageState();
+ boolean status;
+
+ // If SD card is mounted
+ if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) {
+ status = true;
+ }
+
+ // If no SD card
+ else {
+ status = false;
+ }
+ return status;
+ }
+
+ /**
+ * Create a new file object from two file paths.
+ *
+ * @param file1 Base file path
+ * @param file2 Remaining file path
+ * @return File object
+ */
+ private static File constructFilePaths (String file1, String file2) {
+ File newPath;
+ if (file2.startsWith(file1)) {
+ newPath = new File(file2);
+ }
+ else {
+ newPath = new File(file1 + "/" + file2);
+ }
+ return newPath;
+ }
+}
diff --git a/plugins/org.apache.cordova.file/src/android/EncodingException.java b/plugins/org.apache.cordova.file/src/android/EncodingException.java
new file mode 100644
index 0000000..e9e1653
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/EncodingException.java
@@ -0,0 +1,29 @@
+/*
+ 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.
+*/
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class EncodingException extends Exception {
+
+ public EncodingException(String message) {
+ super(message);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/FileExistsException.java b/plugins/org.apache.cordova.file/src/android/FileExistsException.java
new file mode 100644
index 0000000..5c4d83d
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/FileExistsException.java
@@ -0,0 +1,29 @@
+/*
+ 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.
+*/
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class FileExistsException extends Exception {
+
+ public FileExistsException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/FileHelper.java b/plugins/org.apache.cordova.file/src/android/FileHelper.java
new file mode 100644
index 0000000..9e8b626
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/FileHelper.java
@@ -0,0 +1,158 @@
+/*
+ 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.
+ */
+package org.apache.cordova.file;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.webkit.MimeTypeMap;
+
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.LOG;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+public class FileHelper {
+ private static final String LOG_TAG = "FileUtils";
+ private static final String _DATA = "_data";
+
+ /**
+ * Returns the real path of the given URI string.
+ * If the given URI string represents a content:// URI, the real path is retrieved from the media store.
+ *
+ * @param uriString the URI string of the audio/image/video
+ * @param cordova the current application context
+ * @return the full path to the file
+ */
+ @SuppressWarnings("deprecation")
+ public static String getRealPath(String uriString, CordovaInterface cordova) {
+ String realPath = null;
+
+ if (uriString.startsWith("content://")) {
+ String[] proj = { _DATA };
+ Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
+ int column_index = cursor.getColumnIndexOrThrow(_DATA);
+ cursor.moveToFirst();
+ realPath = cursor.getString(column_index);
+ if (realPath == null) {
+ LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
+ }
+ } else if (uriString.startsWith("file://")) {
+ realPath = uriString.substring(7);
+ if (realPath.startsWith("/android_asset/")) {
+ LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
+ realPath = null;
+ }
+ } else {
+ realPath = uriString;
+ }
+
+ return realPath;
+ }
+
+ /**
+ * Returns the real path of the given URI.
+ * If the given URI is a content:// URI, the real path is retrieved from the media store.
+ *
+ * @param uri the URI of the audio/image/video
+ * @param cordova the current application context
+ * @return the full path to the file
+ */
+ public static String getRealPath(Uri uri, CordovaInterface cordova) {
+ return FileHelper.getRealPath(uri.toString(), cordova);
+ }
+
+ /**
+ * Returns an input stream based on given URI string.
+ *
+ * @param uriString the URI string from which to obtain the input stream
+ * @param cordova the current application context
+ * @return an input stream into the data at the given URI or null if given an invalid URI string
+ * @throws IOException
+ */
+ public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
+ if (uriString.startsWith("content")) {
+ Uri uri = Uri.parse(uriString);
+ return cordova.getActivity().getContentResolver().openInputStream(uri);
+ } else if (uriString.startsWith("file://")) {
+ int question = uriString.indexOf("?");
+ if (question > -1) {
+ uriString = uriString.substring(0,question);
+ }
+ if (uriString.startsWith("file:///android_asset/")) {
+ Uri uri = Uri.parse(uriString);
+ String relativePath = uri.getPath().substring(15);
+ return cordova.getActivity().getAssets().open(relativePath);
+ } else {
+ return new FileInputStream(getRealPath(uriString, cordova));
+ }
+ } else {
+ return new FileInputStream(getRealPath(uriString, cordova));
+ }
+ }
+
+ /**
+ * Removes the "file://" prefix from the given URI string, if applicable.
+ * If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
+ *
+ * @param uriString the URI string to operate on
+ * @return a path without the "file://" prefix
+ */
+ public static String stripFileProtocol(String uriString) {
+ if (uriString.startsWith("file://")) {
+ uriString = uriString.substring(7);
+ }
+ return uriString;
+ }
+
+ public static String getMimeTypeForExtension(String path) {
+ String extension = path;
+ int lastDot = extension.lastIndexOf('.');
+ if (lastDot != -1) {
+ extension = extension.substring(lastDot + 1);
+ }
+ // Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
+ extension = extension.toLowerCase(Locale.getDefault());
+ if (extension.equals("3ga")) {
+ return "audio/3gpp";
+ }
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ }
+
+ /**
+ * Returns the mime type of the data specified by the given URI string.
+ *
+ * @param uriString the URI string of the data
+ * @return the mime type of the specified data
+ */
+ public static String getMimeType(String uriString, CordovaInterface cordova) {
+ String mimeType = null;
+
+ Uri uri = Uri.parse(uriString);
+ if (uriString.startsWith("content://")) {
+ mimeType = cordova.getActivity().getContentResolver().getType(uri);
+ } else {
+ mimeType = getMimeTypeForExtension(uri.getPath());
+ }
+
+ return mimeType;
+ }
+}
diff --git a/plugins/org.apache.cordova.file/src/android/FileUtils.java b/plugins/org.apache.cordova.file/src/android/FileUtils.java
new file mode 100644
index 0000000..08205d1
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/FileUtils.java
@@ -0,0 +1,1059 @@
+/*
+ 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.
+ */
+package org.apache.cordova.file;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Base64;
+import android.util.Log;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.PluginResult;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class provides file and directory services to JavaScript.
+ */
+public class FileUtils extends CordovaPlugin {
+ private static final String LOG_TAG = "FileUtils";
+
+ public static int NOT_FOUND_ERR = 1;
+ public static int SECURITY_ERR = 2;
+ public static int ABORT_ERR = 3;
+
+ public static int NOT_READABLE_ERR = 4;
+ public static int ENCODING_ERR = 5;
+ public static int NO_MODIFICATION_ALLOWED_ERR = 6;
+ public static int INVALID_STATE_ERR = 7;
+ public static int SYNTAX_ERR = 8;
+ public static int INVALID_MODIFICATION_ERR = 9;
+ public static int QUOTA_EXCEEDED_ERR = 10;
+ public static int TYPE_MISMATCH_ERR = 11;
+ public static int PATH_EXISTS_ERR = 12;
+
+ public static int UNKNOWN_ERR = 1000;
+
+ private boolean configured = false;
+
+ // This field exists only to support getEntry, below, which has been deprecated
+ private static FileUtils filePlugin;
+
+ private interface FileOp {
+ void run( ) throws Exception;
+ }
+
+ private ArrayList filesystems;
+
+ public void registerFilesystem(Filesystem fs) {
+ if (fs != null && filesystemForName(fs.name)== null) {
+ this.filesystems.add(fs);
+ }
+ }
+
+ private Filesystem filesystemForName(String name) {
+ for (Filesystem fs:filesystems) {
+ if (fs != null && fs.name != null && fs.name.equals(name)) {
+ return fs;
+ }
+ }
+ return null;
+ }
+
+ protected String[] getExtraFileSystemsPreference(Activity activity) {
+ String fileSystemsStr = activity.getIntent().getStringExtra("androidextrafilesystems");
+ if (fileSystemsStr == null) {
+ fileSystemsStr = "files,files-external,documents,sdcard,cache,cache-external,root";
+ }
+ return fileSystemsStr.split(",");
+ }
+
+ protected void registerExtraFileSystems(String[] filesystems, HashMap availableFileSystems) {
+ HashSet installedFileSystems = new HashSet();
+
+ /* Register filesystems in order */
+ for (String fsName : filesystems) {
+ if (!installedFileSystems.contains(fsName)) {
+ String fsRoot = availableFileSystems.get(fsName);
+ if (fsRoot != null) {
+ File newRoot = new File(fsRoot);
+ if (newRoot.mkdirs() || newRoot.isDirectory()) {
+ registerFilesystem(new LocalFilesystem(fsName, cordova, Uri.fromFile(newRoot)));
+ installedFileSystems.add(fsName);
+ } else {
+ Log.d(LOG_TAG, "Unable to create root dir for filesystem \"" + fsName + "\", skipping");
+ }
+ } else {
+ Log.d(LOG_TAG, "Unrecognized extra filesystem identifier: " + fsName);
+ }
+ }
+ }
+ }
+
+ protected HashMap getAvailableFileSystems(Activity activity) {
+ Context context = activity.getApplicationContext();
+ HashMap availableFileSystems = new HashMap();
+
+ availableFileSystems.put("files", context.getFilesDir().getAbsolutePath());
+ availableFileSystems.put("documents", new File(context.getFilesDir(), "Documents").getAbsolutePath());
+ availableFileSystems.put("cache", context.getCacheDir().getAbsolutePath());
+ availableFileSystems.put("root", "/");
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ try {
+ availableFileSystems.put("files-external", context.getExternalFilesDir(null).getAbsolutePath());
+ availableFileSystems.put("sdcard", Environment.getExternalStorageDirectory().getAbsolutePath());
+ availableFileSystems.put("cache-external", context.getExternalCacheDir().getAbsolutePath());
+ }
+ catch(NullPointerException e) {
+ Log.d(LOG_TAG, "External storage unavailable, check to see if USB Mass Storage Mode is on");
+ }
+ }
+
+ return availableFileSystems;
+ }
+
+ @Override
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+ super.initialize(cordova, webView);
+ this.filesystems = new ArrayList();
+
+ String tempRoot = null;
+ String persistentRoot = null;
+
+ Activity activity = cordova.getActivity();
+ String packageName = activity.getPackageName();
+
+ String location = activity.getIntent().getStringExtra("androidpersistentfilelocation");
+ if (location == null) {
+ location = "compatibility";
+ }
+ tempRoot = activity.getCacheDir().getAbsolutePath();
+ if ("internal".equalsIgnoreCase(location)) {
+ persistentRoot = activity.getFilesDir().getAbsolutePath() + "/files/";
+ this.configured = true;
+ } else if ("compatibility".equalsIgnoreCase(location)) {
+ /*
+ * Fall-back to compatibility mode -- this is the logic implemented in
+ * earlier versions of this plugin, and should be maintained here so
+ * that apps which were originally deployed with older versions of the
+ * plugin can continue to provide access to files stored under those
+ * versions.
+ */
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ persistentRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
+ tempRoot = Environment.getExternalStorageDirectory().getAbsolutePath() +
+ "/Android/data/" + packageName + "/cache/";
+ } else {
+ persistentRoot = "/data/data/" + packageName;
+ }
+ this.configured = true;
+ }
+
+ if (this.configured) {
+ // Create the directories if they don't exist.
+ new File(tempRoot).mkdirs();
+ new File(persistentRoot).mkdirs();
+
+ // Register initial filesystems
+ // Note: The temporary and persistent filesystems need to be the first two
+ // registered, so that they will match window.TEMPORARY and window.PERSISTENT,
+ // per spec.
+ this.registerFilesystem(new LocalFilesystem("temporary", cordova, tempRoot));
+ this.registerFilesystem(new LocalFilesystem("persistent", cordova, persistentRoot));
+ this.registerFilesystem(new ContentFilesystem(cordova, webView));
+
+ registerExtraFileSystems(getExtraFileSystemsPreference(activity), getAvailableFileSystems(activity));
+
+ // Initialize static plugin reference for deprecated getEntry method
+ if (filePlugin == null) {
+ FileUtils.filePlugin = this;
+ }
+ } else {
+ Log.e(LOG_TAG, "File plugin configuration error: Please set AndroidPersistentFileLocation in config.xml to one of \"internal\" (for new applications) or \"compatibility\" (for compatibility with previous versions)");
+ activity.finish();
+ }
+ }
+
+ public static FileUtils getFilePlugin() {
+ return filePlugin;
+ }
+
+ private Filesystem filesystemForURL(LocalFilesystemURL localURL) {
+ if (localURL == null) return null;
+ return filesystemForName(localURL.filesystemName);
+ }
+
+ @Override
+ public Uri remapUri(Uri uri) {
+ // Remap only cdvfile: URLs (not content:).
+ if (!LocalFilesystemURL.FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
+ return null;
+ }
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(uri);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ return null;
+ }
+ String path = fs.filesystemPathForURL(inputURL);
+ if (path != null) {
+ return Uri.parse("file://" + fs.filesystemPathForURL(inputURL));
+ }
+ return null;
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Executes the request and returns whether the action was valid.
+ *
+ * @param action The action to execute.
+ * @param args JSONArray of arguments for the plugin.
+ * @param callbackContext The callback context used when calling back into JavaScript.
+ * @return True if the action was valid, false otherwise.
+ */
+ public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ if (!configured) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "File plugin is not configured. Please see the README.md file for details on how to update config.xml"));
+ return true;
+ }
+ if (action.equals("testSaveLocationExists")) {
+ threadhelper( new FileOp( ){
+ public void run() {
+ boolean b = DirectoryManager.testSaveLocationExists();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getFreeDiskSpace")) {
+ threadhelper( new FileOp( ){
+ public void run() {
+ long l = DirectoryManager.getFreeDiskSpace(false);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
+ }
+ },callbackContext);
+ }
+ else if (action.equals("testFileExists")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() {
+ boolean b = DirectoryManager.testFileExists(fname);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("testDirectoryExists")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() {
+ boolean b = DirectoryManager.testFileExists(fname);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("readAsText")) {
+ final String encoding = args.getString(1);
+ final int start = args.getInt(2);
+ final int end = args.getInt(3);
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws MalformedURLException {
+ readFileAs(fname, start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("readAsDataURL")) {
+ final int start = args.getInt(1);
+ final int end = args.getInt(2);
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws MalformedURLException {
+ readFileAs(fname, start, end, callbackContext, null, -1);
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("readAsArrayBuffer")) {
+ final int start = args.getInt(1);
+ final int end = args.getInt(2);
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws MalformedURLException {
+ readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("readAsBinaryString")) {
+ final int start = args.getInt(1);
+ final int end = args.getInt(2);
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws MalformedURLException {
+ readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("write")) {
+ final String fname=args.getString(0);
+ final String data=args.getString(1);
+ final int offset=args.getInt(2);
+ final Boolean isBinary=args.getBoolean(3);
+ threadhelper( new FileOp( ){
+ public void run() throws FileNotFoundException, IOException, NoModificationAllowedException {
+ long fileSize = write(fname, data, offset, isBinary);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("truncate")) {
+ final String fname=args.getString(0);
+ final int offset=args.getInt(1);
+ threadhelper( new FileOp( ){
+ public void run( ) throws FileNotFoundException, IOException, NoModificationAllowedException {
+ long fileSize = truncateFile(fname, offset);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("requestAllFileSystems")) {
+ threadhelper( new FileOp( ){
+ public void run() throws IOException, JSONException {
+ callbackContext.success(requestAllFileSystems());
+ }
+ }, callbackContext);
+ } else if (action.equals("requestAllPaths")) {
+ cordova.getThreadPool().execute(
+ new Runnable() {
+ public void run() {
+ try {
+ callbackContext.success(requestAllPaths());
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ );
+ } else if (action.equals("requestFileSystem")) {
+ final int fstype=args.getInt(0);
+ final long size = args.optLong(1);
+ threadhelper( new FileOp( ){
+ public void run() throws IOException, JSONException {
+ if (size != 0 && size > (DirectoryManager.getFreeDiskSpace(true) * 1024)) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR));
+ } else {
+ JSONObject obj = requestFileSystem(fstype);
+ callbackContext.success(obj);
+ }
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("resolveLocalFileSystemURI")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws IOException, JSONException {
+ JSONObject obj = resolveLocalFileSystemURI(fname);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getFileMetadata")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws FileNotFoundException, JSONException, MalformedURLException {
+ JSONObject obj = getFileMetadata(fname);
+ callbackContext.success(obj);
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("getParent")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws JSONException, IOException {
+ JSONObject obj = getParent(fname);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getDirectory")) {
+ final String dirname=args.getString(0);
+ final String path=args.getString(1);
+ threadhelper( new FileOp( ){
+ public void run() throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getFile")) {
+ final String dirname=args.getString(0);
+ final String path=args.getString(1);
+ threadhelper( new FileOp( ){
+ public void run() throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("remove")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws NoModificationAllowedException, InvalidModificationException, MalformedURLException {
+ boolean success = remove(fname);
+ if (success) {
+ callbackContext.success();
+ } else {
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+ }
+ }
+ },callbackContext);
+ }
+ else if (action.equals("removeRecursively")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws FileExistsException, MalformedURLException, NoModificationAllowedException {
+ boolean success = removeRecursively(fname);
+ if (success) {
+ callbackContext.success();
+ } else {
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+ }
+ }
+ },callbackContext);
+ }
+ else if (action.equals("moveTo")) {
+ final String fname=args.getString(0);
+ final String newParent=args.getString(1);
+ final String newName=args.getString(2);
+ threadhelper( new FileOp( ){
+ public void run() throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ JSONObject entry = transferTo(fname, newParent, newName, true);
+ callbackContext.success(entry);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("copyTo")) {
+ final String fname=args.getString(0);
+ final String newParent=args.getString(1);
+ final String newName=args.getString(2);
+ threadhelper( new FileOp( ){
+ public void run() throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ JSONObject entry = transferTo(fname, newParent, newName, false);
+ callbackContext.success(entry);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("readEntries")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws FileNotFoundException, JSONException, MalformedURLException {
+ JSONArray entries = readEntries(fname);
+ callbackContext.success(entries);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("_getLocalFilesystemPath")) {
+ // Internal method for testing: Get the on-disk location of a local filesystem url.
+ // [Currently used for testing file-transfer]
+ final String localURLstr = args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws FileNotFoundException, JSONException, MalformedURLException {
+ String fname = filesystemPathForURL(localURLstr);
+ callbackContext.success(fname);
+ }
+ },callbackContext);
+ }
+ else {
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * These two native-only methods can be used by other plugins to translate between
+ * device file system paths and URLs. By design, there is no direct JavaScript
+ * interface to these methods.
+ */
+
+ public String filesystemPathForURL(String localURLstr) throws MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(localURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.filesystemPathForURL(inputURL);
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ }
+
+ public LocalFilesystemURL filesystemURLforLocalPath(String localPath) {
+ LocalFilesystemURL localURL = null;
+ int shortestFullPath = 0;
+
+ // Try all installed filesystems. Return the best matching URL
+ // (determined by the shortest resulting URL)
+ for (Filesystem fs: filesystems) {
+ if (fs != null) {
+ LocalFilesystemURL url = fs.URLforFilesystemPath(localPath);
+ if (url != null) {
+ // A shorter fullPath implies that the filesystem is a better
+ // match for the local path than the previous best.
+ if (localURL == null || (url.fullPath.length() < shortestFullPath)) {
+ localURL = url;
+ shortestFullPath = url.fullPath.length();
+ }
+ }
+ }
+ }
+ return localURL;
+ }
+
+
+ /* helper to execute functions async and handle the result codes
+ *
+ */
+ private void threadhelper(final FileOp f, final CallbackContext callbackContext){
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ f.run();
+ } catch ( Exception e) {
+ e.printStackTrace();
+ if( e instanceof EncodingException){
+ callbackContext.error(FileUtils.ENCODING_ERR);
+ } else if(e instanceof FileNotFoundException) {
+ callbackContext.error(FileUtils.NOT_FOUND_ERR);
+ } else if(e instanceof FileExistsException) {
+ callbackContext.error(FileUtils.PATH_EXISTS_ERR);
+ } else if(e instanceof NoModificationAllowedException ) {
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+ } else if(e instanceof InvalidModificationException ) {
+ callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
+ } else if(e instanceof MalformedURLException ) {
+ callbackContext.error(FileUtils.ENCODING_ERR);
+ } else if(e instanceof IOException ) {
+ callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
+ } else if(e instanceof EncodingException ) {
+ callbackContext.error(FileUtils.ENCODING_ERR);
+ } else if(e instanceof TypeMismatchException ) {
+ callbackContext.error(FileUtils.TYPE_MISMATCH_ERR);
+ } else {
+ callbackContext.error(FileUtils.UNKNOWN_ERR);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Allows the user to look up the Entry for a file or directory referred to by a local URI.
+ *
+ * @param url of the file/directory to look up
+ * @return a JSONObject representing a Entry from the filesystem
+ * @throws MalformedURLException if the url is not valid
+ * @throws FileNotFoundException if the file does not exist
+ * @throws IOException if the user can't read the file
+ * @throws JSONException
+ */
+ private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException {
+ LocalFilesystemURL inputURL;
+ if (url == null) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+
+ /* Backwards-compatibility: Check for file:// urls */
+ if (url.startsWith("file:/")) {
+ if (!url.startsWith("file://")) {
+ url = "file:///" + url.substring(6);
+ }
+ String decoded = URLDecoder.decode(url, "UTF-8");
+ /* This looks like a file url. Get the path, and see if any handlers recognize it. */
+ String path;
+ int questionMark = decoded.indexOf("?");
+ int pathEnd;
+ if (questionMark < 0) {
+ pathEnd = decoded.length();
+ } else {
+ pathEnd = questionMark;
+ }
+
+ int thirdSlash = decoded.indexOf("/", 7);
+ if (thirdSlash < 0 || thirdSlash > pathEnd) {
+ path = "";
+ } else {
+ path = decoded.substring(thirdSlash, pathEnd);
+ }
+ inputURL = this.filesystemURLforLocalPath(path);
+ } else {
+ inputURL = new LocalFilesystemURL(url);
+ }
+
+ try {
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.getEntryForLocalURL(inputURL);
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ }
+
+ /**
+ * Read the list of files from this directory.
+ *
+ * @param fileName the directory to read from
+ * @return a JSONArray containing JSONObjects that represent Entry objects.
+ * @throws FileNotFoundException if the directory is not found.
+ * @throws JSONException
+ * @throws MalformedURLException
+ */
+ private JSONArray readEntries(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(baseURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.readEntriesAtLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ }
+
+ /**
+ * A setup method that handles the move/copy of files/directories
+ *
+ * @param fileName to be copied/moved
+ * @param newParent is the location where the file will be copied/moved to
+ * @param newName for the file directory to be called, if null use existing file name
+ * @param move if false do a copy, if true do a move
+ * @return a Entry object
+ * @throws NoModificationAllowedException
+ * @throws IOException
+ * @throws InvalidModificationException
+ * @throws EncodingException
+ * @throws JSONException
+ * @throws FileExistsException
+ */
+ private JSONObject transferTo(String srcURLstr, String destURLstr, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ if (srcURLstr == null || destURLstr == null) {
+ // either no source or no destination provided
+ throw new FileNotFoundException();
+ }
+
+ LocalFilesystemURL srcURL = new LocalFilesystemURL(srcURLstr);
+ LocalFilesystemURL destURL = new LocalFilesystemURL(destURLstr);
+
+ Filesystem srcFs = this.filesystemForURL(srcURL);
+ Filesystem destFs = this.filesystemForURL(destURL);
+
+ // Check for invalid file name
+ if (newName != null && newName.contains(":")) {
+ throw new EncodingException("Bad file name");
+ }
+
+ return destFs.copyFileToURL(destURL, newName, srcFs, srcURL, move);
+ }
+
+ /**
+ * Deletes a directory and all of its contents, if any. In the event of an error
+ * [e.g. trying to delete a directory that contains a file that cannot be removed],
+ * some of the contents of the directory may be deleted.
+ * It is an error to attempt to delete the root directory of a filesystem.
+ *
+ * @param filePath the directory to be removed
+ * @return a boolean representing success of failure
+ * @throws FileExistsException
+ * @throws NoModificationAllowedException
+ * @throws MalformedURLException
+ */
+ private boolean removeRecursively(String baseURLstr) throws FileExistsException, NoModificationAllowedException, MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(baseURLstr);
+ // You can't delete the root directory.
+ if ("".equals(inputURL.fullPath) || "/".equals(inputURL.fullPath)) {
+ throw new NoModificationAllowedException("You can't delete the root directory");
+ }
+
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.recursiveRemoveFileAtLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ }
+
+
+ /**
+ * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty.
+ * It is an error to attempt to delete the root directory of a filesystem.
+ *
+ * @param filePath file or directory to be removed
+ * @return a boolean representing success of failure
+ * @throws NoModificationAllowedException
+ * @throws InvalidModificationException
+ * @throws MalformedURLException
+ */
+ private boolean remove(String baseURLstr) throws NoModificationAllowedException, InvalidModificationException, MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(baseURLstr);
+ // You can't delete the root directory.
+ if ("".equals(inputURL.fullPath) || "/".equals(inputURL.fullPath)) {
+
+ throw new NoModificationAllowedException("You can't delete the root directory");
+ }
+
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.removeFileAtLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ }
+
+ /**
+ * Creates or looks up a file.
+ *
+ * @param baseURLstr base directory
+ * @param path file/directory to lookup or create
+ * @param options specify whether to create or not
+ * @param directory if true look up directory, if false look up file
+ * @return a Entry object
+ * @throws FileExistsException
+ * @throws IOException
+ * @throws TypeMismatchException
+ * @throws EncodingException
+ * @throws JSONException
+ */
+ private JSONObject getFile(String baseURLstr, String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(baseURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.getFileForLocalURL(inputURL, path, options, directory);
+
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+
+ }
+
+ /**
+ * Look up the parent DirectoryEntry containing this Entry.
+ * If this Entry is the root of its filesystem, its parent is itself.
+ *
+ * @param filePath
+ * @return
+ * @throws JSONException
+ * @throws IOException
+ */
+ private JSONObject getParent(String baseURLstr) throws JSONException, IOException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(baseURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.getParentForLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ }
+
+ /**
+ * Returns a File that represents the current state of the file that this FileEntry represents.
+ *
+ * @param filePath to entry
+ * @return returns a JSONObject represent a W3C File object
+ * @throws FileNotFoundException
+ * @throws JSONException
+ * @throws MalformedURLException
+ */
+ private JSONObject getFileMetadata(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(baseURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.getFileMetadataForLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ }
+
+ /**
+ * Requests a filesystem in which to store application data.
+ *
+ * @param type of file system requested
+ * @return a JSONObject representing the file system
+ * @throws IOException
+ * @throws JSONException
+ */
+ private JSONObject requestFileSystem(int type) throws IOException, JSONException {
+ JSONObject fs = new JSONObject();
+ Filesystem rootFs = null;
+ try {
+ rootFs = this.filesystems.get(type);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // Pass null through
+ }
+ if (rootFs == null) {
+ throw new IOException("No filesystem of type requested");
+ }
+ fs.put("name", rootFs.name);
+ fs.put("root", rootFs.getRootEntry());
+ return fs;
+ }
+
+
+ /**
+ * Requests a filesystem in which to store application data.
+ *
+ * @param type of file system requested
+ * @return a JSONObject representing the file system
+ * @throws IOException
+ * @throws JSONException
+ */
+ private JSONArray requestAllFileSystems() throws IOException, JSONException {
+ JSONArray ret = new JSONArray();
+ for (Filesystem fs : filesystems) {
+ ret.put(fs.getRootEntry());
+ }
+ return ret;
+ }
+
+ private static String toDirUrl(File f) {
+ return Uri.fromFile(f).toString() + '/';
+ }
+
+ private JSONObject requestAllPaths() throws JSONException {
+ Context context = cordova.getActivity();
+ JSONObject ret = new JSONObject();
+ ret.put("applicationDirectory", "file:///android_asset/");
+ ret.put("applicationStorageDirectory", toDirUrl(context.getFilesDir().getParentFile()));
+ ret.put("dataDirectory", toDirUrl(context.getFilesDir()));
+ ret.put("cacheDirectory", toDirUrl(context.getCacheDir()));
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ try {
+ ret.put("externalApplicationStorageDirectory", toDirUrl(context.getExternalFilesDir(null).getParentFile()));
+ ret.put("externalDataDirectory", toDirUrl(context.getExternalFilesDir(null)));
+ ret.put("externalCacheDirectory", toDirUrl(context.getExternalCacheDir()));
+ ret.put("externalRootDirectory", toDirUrl(Environment.getExternalStorageDirectory()));
+ }
+ catch(NullPointerException e) {
+ /* If external storage is unavailable, context.getExternal* returns null */
+ Log.d(LOG_TAG, "Unable to access these paths, most liklely due to USB storage");
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a JSON object representing the given File. Internal APIs should be modified
+ * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
+ *
+ * @param file the File to convert
+ * @return a JSON representation of the given File
+ * @throws JSONException
+ */
+ public JSONObject getEntryForFile(File file) throws JSONException {
+ JSONObject entry;
+
+ for (Filesystem fs : filesystems) {
+ entry = fs.makeEntryForFile(file);
+ if (entry != null) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a JSON object representing the given File. Deprecated, as this is only used by
+ * FileTransfer, and because it is a static method that should really be an instance method,
+ * since it depends on the actual filesystem roots in use. Internal APIs should be modified
+ * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
+ *
+ * @param file the File to convert
+ * @return a JSON representation of the given File
+ * @throws JSONException
+ */
+ @Deprecated
+ public static JSONObject getEntry(File file) throws JSONException {
+ if (getFilePlugin() != null) {
+ return getFilePlugin().getEntryForFile(file);
+ }
+ return null;
+ }
+
+ /**
+ * Read the contents of a file.
+ * This is done in a background thread; the result is sent to the callback.
+ *
+ * @param filename The name of the file.
+ * @param start Start position in the file.
+ * @param end End position to stop at (exclusive).
+ * @param callbackContext The context through which to send the result.
+ * @param encoding The encoding to return contents as. Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets)
+ * @param resultType The desired type of data to send to the callback.
+ * @return Contents of file.
+ * @throws MalformedURLException
+ */
+ public void readFileAs(final String srcURLstr, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) throws MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(srcURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+
+ fs.readFileAtURL(inputURL, start, end, new Filesystem.ReadFileCallback() {
+ public void handleData(InputStream inputStream, String contentType) {
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ final int BUFFER_SIZE = 8192;
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ for (;;) {
+ int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+
+ if (bytesRead <= 0) {
+ break;
+ }
+ os.write(buffer, 0, bytesRead);
+ }
+
+ PluginResult result;
+ switch (resultType) {
+ case PluginResult.MESSAGE_TYPE_STRING:
+ result = new PluginResult(PluginResult.Status.OK, os.toString(encoding));
+ break;
+ case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
+ result = new PluginResult(PluginResult.Status.OK, os.toByteArray());
+ break;
+ case PluginResult.MESSAGE_TYPE_BINARYSTRING:
+ result = new PluginResult(PluginResult.Status.OK, os.toByteArray(), true);
+ break;
+ default: // Base64.
+ byte[] base64 = Base64.encode(os.toByteArray(), Base64.NO_WRAP);
+ String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
+ result = new PluginResult(PluginResult.Status.OK, s);
+ }
+
+ callbackContext.sendPluginResult(result);
+ } catch (IOException e) {
+ Log.d(LOG_TAG, e.getLocalizedMessage());
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
+ }
+ }
+ });
+
+
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ } catch (FileNotFoundException e) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR));
+ } catch (IOException e) {
+ Log.d(LOG_TAG, e.getLocalizedMessage());
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
+ }
+ }
+
+
+ /**
+ * Write contents of file.
+ *
+ * @param filename The name of the file.
+ * @param data The contents of the file.
+ * @param offset The position to begin writing the file.
+ * @param isBinary True if the file contents are base64-encoded binary data
+ * @throws FileNotFoundException, IOException
+ * @throws NoModificationAllowedException
+ */
+ /**/
+ public long write(String srcURLstr, String data, int offset, boolean isBinary) throws FileNotFoundException, IOException, NoModificationAllowedException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(srcURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+
+ long x = fs.writeToFileAtURL(inputURL, data, offset, isBinary); Log.d("TEST",srcURLstr + ": "+x); return x;
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+
+ }
+
+ /**
+ * Truncate the file to size
+ *
+ * @param filename
+ * @param size
+ * @throws FileNotFoundException, IOException
+ * @throws NoModificationAllowedException
+ */
+ private long truncateFile(String srcURLstr, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
+ try {
+ LocalFilesystemURL inputURL = new LocalFilesystemURL(srcURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+
+ return fs.truncateFileAtURL(inputURL, size);
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ }
+}
diff --git a/plugins/org.apache.cordova.file/src/android/Filesystem.java b/plugins/org.apache.cordova.file/src/android/Filesystem.java
new file mode 100644
index 0000000..c4a0f8a
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/Filesystem.java
@@ -0,0 +1,229 @@
+/*
+ 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.
+ */
+package org.apache.cordova.file;
+
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public abstract class Filesystem {
+
+ protected final Uri rootUri;
+ public final String name;
+ private final JSONObject rootEntry;
+
+ public Filesystem(Uri rootUri, String name) {
+ this.rootUri = rootUri;
+ this.name = name;
+ rootEntry = makeEntryForPath("/", name, true, rootUri.toString());
+ }
+
+ public interface ReadFileCallback {
+ public void handleData(InputStream inputStream, String contentType) throws IOException;
+ }
+
+ public static JSONObject makeEntryForPath(String path, String fsName, Boolean isDir, String nativeURL) {
+ try {
+ JSONObject entry = new JSONObject();
+
+ int end = path.endsWith("/") ? 1 : 0;
+ String[] parts = path.substring(0, path.length() - end).split("/+");
+ String fileName = parts[parts.length - 1];
+ entry.put("isFile", !isDir);
+ entry.put("isDirectory", isDir);
+ entry.put("name", fileName);
+ entry.put("fullPath", path);
+ // The file system can't be specified, as it would lead to an infinite loop,
+ // but the filesystem name can be.
+ entry.put("filesystemName", fsName);
+ // Backwards compatibility
+ entry.put("filesystem", "temporary".equals(fsName) ? 0 : 1);
+
+ if (isDir && !nativeURL.endsWith("/")) {
+ nativeURL += "/";
+ }
+ entry.put("nativeURL", nativeURL);
+ return entry;
+ } catch (JSONException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Boolean isDir, String nativeURL) {
+ return makeEntryForPath(inputURL.fullPath, inputURL.filesystemName, isDir, nativeURL);
+ }
+
+ abstract JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException;
+
+ abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
+ JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
+
+ abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
+
+ abstract boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException, NoModificationAllowedException;
+
+ abstract JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
+
+ abstract JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
+
+ public Uri getRootUri() {
+ return rootUri;
+ }
+
+ public JSONObject getRootEntry() {
+ return rootEntry;
+ }
+
+ public JSONObject getParentForLocalURL(LocalFilesystemURL inputURL) throws IOException {
+ LocalFilesystemURL newURL = new LocalFilesystemURL(inputURL.URL);
+
+ if (!("".equals(inputURL.fullPath) || "/".equals(inputURL.fullPath))) {
+ String dirURL = inputURL.fullPath.replaceAll("/+$", "");
+ int lastPathStartsAt = dirURL.lastIndexOf('/')+1;
+ newURL.fullPath = newURL.fullPath.substring(0,lastPathStartsAt);
+ }
+ return getEntryForLocalURL(newURL);
+ }
+
+ protected LocalFilesystemURL makeDestinationURL(String newName, LocalFilesystemURL srcURL, LocalFilesystemURL destURL) {
+ // I know this looks weird but it is to work around a JSON bug.
+ if ("null".equals(newName) || "".equals(newName)) {
+ newName = srcURL.URL.getLastPathSegment();;
+ }
+
+ String newDest = destURL.URL.toString();
+ if (newDest.endsWith("/")) {
+ newDest = newDest + newName;
+ } else {
+ newDest = newDest + "/" + newName;
+ }
+ return new LocalFilesystemURL(newDest);
+ }
+
+ /* Read a source URL (possibly from a different filesystem, srcFs,) and copy it to
+ * the destination URL on this filesystem, optionally with a new filename.
+ * If move is true, then this method should either perform an atomic move operation
+ * or remove the source file when finished.
+ */
+ JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
+ Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
+ // This is "the hard way" -- transfer data between arbitrary filesystem urls/
+ // Gets an input stream from src, and writes its contents to an output stream
+ // from dest.
+
+ // First, check to see that we can do it
+ if (!move || srcFs.canRemoveFileAtLocalURL(srcURL)) {
+ final LocalFilesystemURL destination = makeDestinationURL(newName, srcURL, destURL);
+ srcFs.readFileAtURL(srcURL, 0, -1, new ReadFileCallback() {
+ public void handleData(InputStream inputStream, String contentType) throws IOException {
+ if (inputStream != null) {
+ //write data to file
+ OutputStream os = getOutputStreamForURL(destination);
+ final int BUFFER_SIZE = 8192;
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ for (;;) {
+ int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+
+ if (bytesRead <= 0) {
+ break;
+ }
+ os.write(buffer, 0, bytesRead);
+ }
+ os.close();
+ } else {
+ throw new IOException("Cannot read file at source URL");
+ }
+ }
+ });
+ if (move) {
+ // Delete original
+ srcFs.removeFileAtLocalURL(srcURL);
+ }
+ return getEntryForLocalURL(destination);
+ } else {
+ throw new NoModificationAllowedException("Cannot move file at source URL");
+ }
+ }
+
+ abstract OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException;
+
+ abstract void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
+ ReadFileCallback readFileCallback) throws IOException;
+
+ abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
+ boolean isBinary) throws NoModificationAllowedException, IOException;
+
+ abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
+ throws IOException, NoModificationAllowedException;
+
+ // This method should return null if filesystem urls cannot be mapped to paths
+ abstract String filesystemPathForURL(LocalFilesystemURL url);
+
+ abstract LocalFilesystemURL URLforFilesystemPath(String path);
+
+ abstract boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL);
+
+ protected class LimitedInputStream extends FilterInputStream {
+ long numBytesToRead;
+ public LimitedInputStream(InputStream in, long numBytesToRead) {
+ super(in);
+ this.numBytesToRead = numBytesToRead;
+ }
+ @Override
+ public int read() throws IOException {
+ if (numBytesToRead <= 0) {
+ return -1;
+ }
+ numBytesToRead--;
+ return in.read();
+ }
+ @Override
+ public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ if (numBytesToRead <= 0) {
+ return -1;
+ }
+ int bytesToRead = byteCount;
+ if (byteCount > numBytesToRead) {
+ bytesToRead = (int)numBytesToRead; // Cast okay; long is less than int here.
+ }
+ int numBytesRead = in.read(buffer, byteOffset, bytesToRead);
+ numBytesToRead -= numBytesRead;
+ return numBytesRead;
+ }
+ }
+
+ /* Create a FileEntry or DirectoryEntry given an actual file on device.
+ * Return null if the file does not exist within this filesystem.
+ */
+ public JSONObject makeEntryForFile(File file) throws JSONException {
+ return null;
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java b/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java
new file mode 100644
index 0000000..8f6bec5
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java
@@ -0,0 +1,30 @@
+/*
+ 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.
+*/
+
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class InvalidModificationException extends Exception {
+
+ public InvalidModificationException(String message) {
+ super(message);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/LocalFilesystem.java b/plugins/org.apache.cordova.file/src/android/LocalFilesystem.java
new file mode 100644
index 0000000..28985fa
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/LocalFilesystem.java
@@ -0,0 +1,647 @@
+/*
+ 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.
+ */
+package org.apache.cordova.file;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.apache.cordova.CordovaInterface;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.util.Base64;
+import android.net.Uri;
+import android.content.Context;
+import android.content.Intent;
+import android.app.Activity;
+
+public class LocalFilesystem extends Filesystem {
+
+ private CordovaInterface cordova;
+
+ public LocalFilesystem(String name, CordovaInterface cordova, String rootPath) {
+ this(name, cordova, Uri.fromFile(new File(rootPath)));
+ }
+ public LocalFilesystem(String name, CordovaInterface cordova, Uri rootUri) {
+ super(rootUri, name);
+ this.cordova = cordova;
+ }
+
+ public String filesystemPathForFullPath(String fullPath) {
+ String path = new File(rootUri.getPath(), fullPath).toString();
+ int questionMark = path.indexOf("?");
+ if (questionMark >= 0) {
+ path = path.substring(0, questionMark);
+ }
+ if (path.length() > 1 && path.endsWith("/")) {
+ path = path.substring(0, path.length()-1);
+ }
+ return path;
+ }
+
+ @Override
+ public String filesystemPathForURL(LocalFilesystemURL url) {
+ return filesystemPathForFullPath(url.fullPath);
+ }
+
+ private String fullPathForFilesystemPath(String absolutePath) {
+ if (absolutePath != null && absolutePath.startsWith(rootUri.getPath())) {
+ return absolutePath.substring(rootUri.getPath().length());
+ }
+ return null;
+ }
+
+ protected LocalFilesystemURL URLforFullPath(String fullPath) {
+ if (fullPath != null) {
+ if (fullPath.startsWith("/")) {
+ return new LocalFilesystemURL(LocalFilesystemURL.FILESYSTEM_PROTOCOL + "://localhost/"+this.name+fullPath);
+ }
+ return new LocalFilesystemURL(LocalFilesystemURL.FILESYSTEM_PROTOCOL + "://localhost/"+this.name+"/"+fullPath);
+ }
+ return null;
+
+ }
+
+ @Override
+ public LocalFilesystemURL URLforFilesystemPath(String path) {
+ return this.URLforFullPath(this.fullPathForFilesystemPath(path));
+ }
+
+ protected String normalizePath(String rawPath) {
+ // If this is an absolute path, trim the leading "/" and replace it later
+ boolean isAbsolutePath = rawPath.startsWith("/");
+ if (isAbsolutePath) {
+ rawPath = rawPath.substring(1);
+ }
+ ArrayList components = new ArrayList(Arrays.asList(rawPath.split("/+")));
+ for (int index = 0; index < components.size(); ++index) {
+ if (components.get(index).equals("..")) {
+ components.remove(index);
+ if (index > 0) {
+ components.remove(index-1);
+ --index;
+ }
+ }
+ }
+ StringBuilder normalizedPath = new StringBuilder();
+ for(String component: components) {
+ normalizedPath.append("/");
+ normalizedPath.append(component);
+ }
+ if (isAbsolutePath) {
+ return normalizedPath.toString();
+ } else {
+ return normalizedPath.toString().substring(1);
+ }
+
+
+ }
+
+
+ @Override
+ public JSONObject makeEntryForFile(File file) throws JSONException {
+ String path = this.fullPathForFilesystemPath(file.getAbsolutePath());
+ if (path != null) {
+ return makeEntryForPath(path, this.name, file.isDirectory(), Uri.fromFile(file).toString());
+ }
+ return null;
+ }
+
+ @Override
+ public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
+ File fp = new File(filesystemPathForURL(inputURL));
+
+ if (!fp.exists()) {
+ throw new FileNotFoundException();
+ }
+ if (!fp.canRead()) {
+ throw new IOException();
+ }
+ return LocalFilesystem.makeEntryForURL(inputURL, fp.isDirectory(), Uri.fromFile(fp).toString());
+ }
+
+ @Override
+ public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
+ String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ boolean create = false;
+ boolean exclusive = false;
+
+ if (options != null) {
+ create = options.optBoolean("create");
+ if (create) {
+ exclusive = options.optBoolean("exclusive");
+ }
+ }
+
+ // Check for a ":" character in the file to line up with BB and iOS
+ if (path.contains(":")) {
+ throw new EncodingException("This path has an invalid \":\" in it.");
+ }
+
+ LocalFilesystemURL requestedURL;
+
+ // Check whether the supplied path is absolute or relative
+ if (path.startsWith("/")) {
+ requestedURL = URLforFilesystemPath(path);
+ } else {
+ requestedURL = URLforFullPath(normalizePath(inputURL.fullPath + "/" + path));
+ }
+
+ File fp = new File(this.filesystemPathForURL(requestedURL));
+
+ if (create) {
+ if (exclusive && fp.exists()) {
+ throw new FileExistsException("create/exclusive fails");
+ }
+ if (directory) {
+ fp.mkdir();
+ } else {
+ fp.createNewFile();
+ }
+ if (!fp.exists()) {
+ throw new FileExistsException("create fails");
+ }
+ }
+ else {
+ if (!fp.exists()) {
+ throw new FileNotFoundException("path does not exist");
+ }
+ if (directory) {
+ if (fp.isFile()) {
+ throw new TypeMismatchException("path doesn't exist or is file");
+ }
+ } else {
+ if (fp.isDirectory()) {
+ throw new TypeMismatchException("path doesn't exist or is directory");
+ }
+ }
+ }
+
+ // Return the directory
+ return makeEntryForPath(requestedURL.fullPath, requestedURL.filesystemName, directory, Uri.fromFile(fp).toString());
+ }
+
+ @Override
+ public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException {
+
+ File fp = new File(filesystemPathForURL(inputURL));
+
+ // You can't delete a directory that is not empty
+ if (fp.isDirectory() && fp.list().length > 0) {
+ throw new InvalidModificationException("You can't delete a directory that is not empty.");
+ }
+
+ return fp.delete();
+ }
+
+ @Override
+ public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException {
+ File directory = new File(filesystemPathForURL(inputURL));
+ return removeDirRecursively(directory);
+ }
+
+ protected boolean removeDirRecursively(File directory) throws FileExistsException {
+ if (directory.isDirectory()) {
+ for (File file : directory.listFiles()) {
+ removeDirRecursively(file);
+ }
+ }
+
+ if (!directory.delete()) {
+ throw new FileExistsException("could not delete: " + directory.getName());
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ File fp = new File(filesystemPathForURL(inputURL));
+
+ if (!fp.exists()) {
+ // The directory we are listing doesn't exist so we should fail.
+ throw new FileNotFoundException();
+ }
+
+ JSONArray entries = new JSONArray();
+
+ if (fp.isDirectory()) {
+ File[] files = fp.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].canRead()) {
+ entries.put(makeEntryForPath(fullPathForFilesystemPath(files[i].getAbsolutePath()), inputURL.filesystemName, files[i].isDirectory(), Uri.fromFile(files[i]).toString()));
+ }
+ }
+ }
+
+ return entries;
+ }
+
+ @Override
+ public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ File file = new File(filesystemPathForURL(inputURL));
+
+ if (!file.exists()) {
+ throw new FileNotFoundException("File at " + inputURL.URL + " does not exist.");
+ }
+
+ JSONObject metadata = new JSONObject();
+ try {
+ // Ensure that directories report a size of 0
+ metadata.put("size", file.isDirectory() ? 0 : file.length());
+ metadata.put("type", FileHelper.getMimeType(file.getAbsolutePath(), cordova));
+ metadata.put("name", file.getName());
+ metadata.put("fullPath", inputURL.fullPath);
+ metadata.put("lastModifiedDate", file.lastModified());
+ } catch (JSONException e) {
+ return null;
+ }
+ return metadata;
+ }
+
+ /**
+ * Check to see if the user attempted to copy an entry into its parent without changing its name,
+ * or attempted to copy a directory into a directory that it contains directly or indirectly.
+ */
+ private boolean isCopyOnItself(String src, String dest) {
+
+ // This weird test is to determine if we are copying or moving a directory into itself.
+ // Copy /sdcard/myDir to /sdcard/myDir-backup is okay but
+ // Copy /sdcard/myDir to /sdcard/myDir/backup should throw an INVALID_MODIFICATION_ERR
+ return dest.equals(src) || dest.startsWith(src + File.separator);
+ }
+
+ /**
+ * Copy a file
+ *
+ * @param srcFile file to be copied
+ * @param destFile destination to be copied to
+ * @return a FileEntry object
+ * @throws IOException
+ * @throws InvalidModificationException
+ * @throws JSONException
+ */
+ private JSONObject copyFile(File srcFile, File destFile) throws IOException, InvalidModificationException, JSONException {
+ // Renaming a file to an existing directory should fail
+ if (destFile.exists() && destFile.isDirectory()) {
+ throw new InvalidModificationException("Can't rename a file to a directory");
+ }
+
+ copyAction(srcFile, destFile);
+
+ return makeEntryForFile(destFile);
+ }
+
+ /**
+ * Moved this code into it's own method so moveTo could use it when the move is across file systems
+ */
+ private void copyAction(File srcFile, File destFile)
+ throws FileNotFoundException, IOException {
+ FileInputStream istream = new FileInputStream(srcFile);
+ FileOutputStream ostream = new FileOutputStream(destFile);
+ FileChannel input = istream.getChannel();
+ FileChannel output = ostream.getChannel();
+
+ try {
+ input.transferTo(0, input.size(), output);
+ } finally {
+ istream.close();
+ ostream.close();
+ input.close();
+ output.close();
+ }
+ }
+
+ /**
+ * Copy a directory
+ *
+ * @param srcDir directory to be copied
+ * @param destinationDir destination to be copied to
+ * @return a DirectoryEntry object
+ * @throws JSONException
+ * @throws IOException
+ * @throws NoModificationAllowedException
+ * @throws InvalidModificationException
+ */
+ private JSONObject copyDirectory(File srcDir, File destinationDir) throws JSONException, IOException, NoModificationAllowedException, InvalidModificationException {
+ // Renaming a file to an existing directory should fail
+ if (destinationDir.exists() && destinationDir.isFile()) {
+ throw new InvalidModificationException("Can't rename a file to a directory");
+ }
+
+ // Check to make sure we are not copying the directory into itself
+ if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) {
+ throw new InvalidModificationException("Can't copy itself into itself");
+ }
+
+ // See if the destination directory exists. If not create it.
+ if (!destinationDir.exists()) {
+ if (!destinationDir.mkdir()) {
+ // If we can't create the directory then fail
+ throw new NoModificationAllowedException("Couldn't create the destination directory");
+ }
+ }
+
+
+ for (File file : srcDir.listFiles()) {
+ File destination = new File(destinationDir.getAbsoluteFile() + File.separator + file.getName());
+ if (file.isDirectory()) {
+ copyDirectory(file, destination);
+ } else {
+ copyFile(file, destination);
+ }
+ }
+
+ return makeEntryForFile(destinationDir);
+ }
+
+ /**
+ * Move a file
+ *
+ * @param srcFile file to be copied
+ * @param destFile destination to be copied to
+ * @return a FileEntry object
+ * @throws IOException
+ * @throws InvalidModificationException
+ * @throws JSONException
+ */
+ private JSONObject moveFile(File srcFile, File destFile) throws IOException, JSONException, InvalidModificationException {
+ // Renaming a file to an existing directory should fail
+ if (destFile.exists() && destFile.isDirectory()) {
+ throw new InvalidModificationException("Can't rename a file to a directory");
+ }
+
+ // Try to rename the file
+ if (!srcFile.renameTo(destFile)) {
+ // Trying to rename the file failed. Possibly because we moved across file system on the device.
+ // Now we have to do things the hard way
+ // 1) Copy all the old file
+ // 2) delete the src file
+ copyAction(srcFile, destFile);
+ if (destFile.exists()) {
+ srcFile.delete();
+ } else {
+ throw new IOException("moved failed");
+ }
+ }
+
+ return makeEntryForFile(destFile);
+ }
+
+ /**
+ * Move a directory
+ *
+ * @param srcDir directory to be copied
+ * @param destinationDir destination to be copied to
+ * @return a DirectoryEntry object
+ * @throws JSONException
+ * @throws IOException
+ * @throws InvalidModificationException
+ * @throws NoModificationAllowedException
+ * @throws FileExistsException
+ */
+ private JSONObject moveDirectory(File srcDir, File destinationDir) throws IOException, JSONException, InvalidModificationException, NoModificationAllowedException, FileExistsException {
+ // Renaming a file to an existing directory should fail
+ if (destinationDir.exists() && destinationDir.isFile()) {
+ throw new InvalidModificationException("Can't rename a file to a directory");
+ }
+
+ // Check to make sure we are not copying the directory into itself
+ if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) {
+ throw new InvalidModificationException("Can't move itself into itself");
+ }
+
+ // If the destination directory already exists and is empty then delete it. This is according to spec.
+ if (destinationDir.exists()) {
+ if (destinationDir.list().length > 0) {
+ throw new InvalidModificationException("directory is not empty");
+ }
+ }
+
+ // Try to rename the directory
+ if (!srcDir.renameTo(destinationDir)) {
+ // Trying to rename the directory failed. Possibly because we moved across file system on the device.
+ // Now we have to do things the hard way
+ // 1) Copy all the old files
+ // 2) delete the src directory
+ copyDirectory(srcDir, destinationDir);
+ if (destinationDir.exists()) {
+ removeDirRecursively(srcDir);
+ } else {
+ throw new IOException("moved failed");
+ }
+ }
+
+ return makeEntryForFile(destinationDir);
+ }
+
+ @Override
+ public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
+ Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
+
+ // Check to see if the destination directory exists
+ String newParent = this.filesystemPathForURL(destURL);
+ File destinationDir = new File(newParent);
+ if (!destinationDir.exists()) {
+ // The destination does not exist so we should fail.
+ throw new FileNotFoundException("The source does not exist");
+ }
+
+ if (LocalFilesystem.class.isInstance(srcFs)) {
+ /* Same FS, we can shortcut with NSFileManager operations */
+
+ // Figure out where we should be copying to
+ final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL);
+
+ String srcFilesystemPath = srcFs.filesystemPathForURL(srcURL);
+ File sourceFile = new File(srcFilesystemPath);
+ String destFilesystemPath = this.filesystemPathForURL(destinationURL);
+ File destinationFile = new File(destFilesystemPath);
+
+ if (!sourceFile.exists()) {
+ // The file/directory we are copying doesn't exist so we should fail.
+ throw new FileNotFoundException("The source does not exist");
+ }
+
+ // Check to see if source and destination are the same file
+ if (sourceFile.getAbsolutePath().equals(destinationFile.getAbsolutePath())) {
+ throw new InvalidModificationException("Can't copy a file onto itself");
+ }
+
+ if (sourceFile.isDirectory()) {
+ if (move) {
+ return moveDirectory(sourceFile, destinationFile);
+ } else {
+ return copyDirectory(sourceFile, destinationFile);
+ }
+ } else {
+ if (move) {
+ return moveFile(sourceFile, destinationFile);
+ } else {
+ return copyFile(sourceFile, destinationFile);
+ }
+ }
+
+ } else {
+ // Need to copy the hard way
+ return super.copyFileToURL(destURL, newName, srcFs, srcURL, move);
+ }
+ }
+
+ @Override
+ public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
+ ReadFileCallback readFileCallback) throws IOException {
+
+ File file = new File(this.filesystemPathForURL(inputURL));
+ String contentType = FileHelper.getMimeTypeForExtension(file.getAbsolutePath());
+
+ if (end < 0) {
+ end = file.length();
+ }
+ long numBytesToRead = end - start;
+
+ InputStream rawInputStream = new FileInputStream(file);
+ try {
+ if (start > 0) {
+ rawInputStream.skip(start);
+ }
+ LimitedInputStream inputStream = new LimitedInputStream(rawInputStream, numBytesToRead);
+ readFileCallback.handleData(inputStream, contentType);
+ } finally {
+ rawInputStream.close();
+ }
+ }
+
+ @Override
+ public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
+ int offset, boolean isBinary) throws IOException, NoModificationAllowedException {
+
+ boolean append = false;
+ if (offset > 0) {
+ this.truncateFileAtURL(inputURL, offset);
+ append = true;
+ }
+
+ byte[] rawData;
+ if (isBinary) {
+ rawData = Base64.decode(data, Base64.DEFAULT);
+ } else {
+ rawData = data.getBytes();
+ }
+ ByteArrayInputStream in = new ByteArrayInputStream(rawData);
+ try
+ {
+ byte buff[] = new byte[rawData.length];
+ FileOutputStream out = new FileOutputStream(this.filesystemPathForURL(inputURL), append);
+ try {
+ in.read(buff, 0, buff.length);
+ out.write(buff, 0, rawData.length);
+ out.flush();
+ } finally {
+ // Always close the output
+ out.close();
+ }
+ broadcastNewFile(inputURL);
+ }
+ catch (NullPointerException e)
+ {
+ // This is a bug in the Android implementation of the Java Stack
+ NoModificationAllowedException realException = new NoModificationAllowedException(inputURL.toString());
+ throw realException;
+ }
+
+ return rawData.length;
+ }
+
+ /**
+ * Send broadcast of new file so files appear over MTP
+ *
+ * @param inputURL
+ */
+ private void broadcastNewFile(LocalFilesystemURL inputURL) {
+ File file = new File(this.filesystemPathForURL(inputURL));
+ if (file.exists()) {
+ //Get the activity
+ Activity activity = this.cordova.getActivity();
+
+ //Get the context
+ Context context = activity.getApplicationContext();
+
+ //Create the URI
+ Uri uri = Uri.fromFile(file);
+
+ //Create the intent
+ Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
+
+ //Send broadcast of new file
+ context.sendBroadcast(intent);
+ }
+ }
+
+ @Override
+ public long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException {
+ File file = new File(filesystemPathForURL(inputURL));
+
+ if (!file.exists()) {
+ throw new FileNotFoundException("File at " + inputURL.URL + " does not exist.");
+ }
+
+ RandomAccessFile raf = new RandomAccessFile(filesystemPathForURL(inputURL), "rw");
+ try {
+ if (raf.length() >= size) {
+ FileChannel channel = raf.getChannel();
+ channel.truncate(size);
+ return size;
+ }
+
+ return raf.length();
+ } finally {
+ raf.close();
+ }
+
+
+ }
+
+ @Override
+ public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
+ String path = filesystemPathForURL(inputURL);
+ File file = new File(path);
+ return file.exists();
+ }
+
+ @Override
+ OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ String path = filesystemPathForURL(inputURL);
+ File file = new File(path);
+ FileOutputStream os = new FileOutputStream(file);
+ return os;
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/LocalFilesystemURL.java b/plugins/org.apache.cordova.file/src/android/LocalFilesystemURL.java
new file mode 100644
index 0000000..2bf40c3
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/LocalFilesystemURL.java
@@ -0,0 +1,74 @@
+/*
+ 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.
+ */
+package org.apache.cordova.file;
+
+import java.util.List;
+
+import android.net.Uri;
+
+public class LocalFilesystemURL {
+
+ public static final String FILESYSTEM_PROTOCOL = "cdvfile";
+
+ Uri URL;
+ String filesystemName;
+ String fullPath;
+
+ public LocalFilesystemURL(Uri URL) {
+ this.URL = URL;
+ this.filesystemName = this.filesystemNameForLocalURL(URL);
+ this.fullPath = this.fullPathForLocalURL(URL);
+ }
+
+ private String fullPathForLocalURL(Uri URL) {
+ if (FILESYSTEM_PROTOCOL.equals(URL.getScheme()) && "localhost".equals(URL.getHost())) {
+ String path = URL.getPath();
+ if (URL.getQuery() != null) {
+ path = path + "?" + URL.getQuery();
+ }
+ return path.substring(path.indexOf('/', 1));
+ } else if ("content".equals(URL.getScheme())) {
+ String path = '/' + URL.getHost() + URL.getPath();
+ // Re-encode path component to handle Android 4.4+ Content URLs
+ return Uri.encode(path,"/");
+ }
+ return null;
+ }
+
+ private String filesystemNameForLocalURL(Uri URL) {
+ if (FILESYSTEM_PROTOCOL.equals(URL.getScheme()) && "localhost".equals(URL.getHost())) {
+ List pathComponents = URL.getPathSegments();
+ if (pathComponents != null && pathComponents.size() > 0) {
+ return pathComponents.get(0);
+ }
+ return null;
+ } else if ("content".equals(URL.getScheme())) {
+ return "content";
+ }
+ return null;
+ }
+
+ public LocalFilesystemURL(String strURL) {
+ this(Uri.parse(strURL));
+ }
+
+ public String toString() {
+ return "cdvfile://localhost/" + this.filesystemName + this.fullPath;
+ }
+}
diff --git a/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java b/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java
new file mode 100644
index 0000000..627eafb
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java
@@ -0,0 +1,29 @@
+/*
+ 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.
+*/
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class NoModificationAllowedException extends Exception {
+
+ public NoModificationAllowedException(String message) {
+ super(message);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java b/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java
new file mode 100644
index 0000000..1315f9a
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java
@@ -0,0 +1,30 @@
+/*
+ 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.
+*/
+
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class TypeMismatchException extends Exception {
+
+ public TypeMismatchException(String message) {
+ super(message);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/blackberry10/index.js b/plugins/org.apache.cordova.file/src/blackberry10/index.js
new file mode 100644
index 0000000..913ab30
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/blackberry10/index.js
@@ -0,0 +1,44 @@
+/*
+ *
+ * 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.
+ *
+*/
+module.exports = {
+ setSandbox : function (success, fail, args, env) {
+ require("lib/webview").setSandbox(JSON.parse(decodeURIComponent(args[0])));
+ new PluginResult(args, env).ok();
+ },
+
+ getHomePath: function (success, fail, args, env) {
+ var homeDir = window.qnx.webplatform.getApplication().getEnv("HOME");
+ new PluginResult(args, env).ok(homeDir);
+ },
+
+ requestAllPaths: function (success, fail, args, env) {
+ var homeDir = 'file://' + window.qnx.webplatform.getApplication().getEnv("HOME").replace('/data', ''),
+ paths = {
+ applicationDirectory: homeDir + '/app/native/',
+ applicationStorageDirectory: homeDir + '/',
+ dataDirectory: homeDir + '/data/webviews/webfs/persistent/local__0/',
+ cacheDirectory: homeDir + '/data/webviews/webfs/temporary/local__0/',
+ externalRootDirectory: 'file:///accounts/1000/removable/sdcard/',
+ sharedDirectory: homeDir + '/shared/'
+ };
+ success(paths);
+ }
+};
diff --git a/plugins/org.apache.cordova.file/src/firefoxos/FileProxy.js b/plugins/org.apache.cordova.file/src/firefoxos/FileProxy.js
new file mode 100644
index 0000000..3662edc
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/firefoxos/FileProxy.js
@@ -0,0 +1,785 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+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) {
+ 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) {
+ 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 () {
+ errorCallback && errorCallback(FileError.NOT_READABLE_ERR);
+ };
+
+ xhr.send();
+ } else {
+ 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 () {
+ 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_)) {
+ var 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) {
+ 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) {
+ errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+
+ var results = [];
+
+ if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) {
+ storagePath = storagePath.substring(0, storagePath.length - 1);
+ }
+
+ 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) {
+ 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) {
+ 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);
diff --git a/plugins/org.apache.cordova.file/src/ios/CDVAssetLibraryFilesystem.h b/plugins/org.apache.cordova.file/src/ios/CDVAssetLibraryFilesystem.h
new file mode 100644
index 0000000..e09e225
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ios/CDVAssetLibraryFilesystem.h
@@ -0,0 +1,30 @@
+/*
+ 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.
+ */
+
+#import "CDVFile.h"
+
+extern NSString* const kCDVAssetsLibraryPrefix;
+extern NSString* const kCDVAssetsLibraryScheme;
+
+@interface CDVAssetLibraryFilesystem : NSObject {
+}
+
+- (id) initWithName:(NSString *)name;
+
+@end
diff --git a/plugins/org.apache.cordova.file/src/ios/CDVAssetLibraryFilesystem.m b/plugins/org.apache.cordova.file/src/ios/CDVAssetLibraryFilesystem.m
new file mode 100644
index 0000000..2354ce9
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ios/CDVAssetLibraryFilesystem.m
@@ -0,0 +1,247 @@
+/*
+ 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.
+ */
+
+#import "CDVFile.h"
+#import "CDVAssetLibraryFilesystem.h"
+#import
+#import
+#import
+#import
+#import
+
+NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
+NSString* const kCDVAssetsLibraryScheme = @"assets-library";
+
+@implementation CDVAssetLibraryFilesystem
+@synthesize name=_name;
+
+
+/*
+ The CDVAssetLibraryFilesystem works with resources which are identified
+ by iOS as
+ asset-library://
+ and represents them internally as URLs of the form
+ cdvfile://localhost/assets-library/
+ */
+
+- (NSURL *)assetLibraryURLForLocalURL:(CDVFilesystemURL *)url
+{
+ if ([url.url.scheme isEqualToString:kCDVFilesystemURLPrefix]) {
+ NSString *path = [[url.url absoluteString] substringFromIndex:[@"cdvfile://localhost/assets-library" length]];
+ return [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@", path]];
+ }
+ return url.url;
+}
+
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
+{
+ NSDictionary* entry = [self makeEntryForLocalURL:url];
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
+}
+
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
+ return [self makeEntryForPath:url.fullPath isDirectory:NO];
+}
+
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
+{
+ NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
+ NSString* lastPart = [fullPath lastPathComponent];
+ if (isDir && ![fullPath hasSuffix:@"/"]) {
+ fullPath = [fullPath stringByAppendingString:@"/"];
+ }
+ [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
+ [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
+ [dirEntry setObject:fullPath forKey:@"fullPath"];
+ [dirEntry setObject:lastPart forKey:@"name"];
+ [dirEntry setObject:self.name forKey: @"filesystemName"];
+ dirEntry[@"nativeURL"] = [NSString stringWithFormat:@"assets-library:/%@",fullPath];
+
+ return dirEntry;
+}
+
+/* helper function to get the mimeType from the file extension
+ * IN:
+ * NSString* fullPath - filename (may include path)
+ * OUT:
+ * NSString* the mime type as type/subtype. nil if not able to determine
+ */
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
+{
+ NSString* mimeType = nil;
+
+ if (fullPath) {
+ CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
+ if (typeId) {
+ mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
+ if (!mimeType) {
+ // special case for m4a
+ if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
+ mimeType = @"audio/mp4";
+ } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
+ mimeType = @"audio/wav";
+ } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
+ mimeType = @"text/css";
+ }
+ }
+ CFRelease(typeId);
+ }
+ }
+ return mimeType;
+}
+
+- (id)initWithName:(NSString *)name
+{
+ if (self) {
+ _name = name;
+ }
+ return self;
+}
+
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
+{
+ // return unsupported result for assets-library URLs
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."];
+}
+
+- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
+{
+ // we don't (yet?) support getting the parent of an asset
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR];
+}
+
+- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
+{
+ // setMetadata doesn't make sense for asset library files
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
+}
+
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
+{
+ // return error for assets-library URLs
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
+}
+
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
+{
+ // return error for assets-library URLs
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."];
+}
+
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
+{
+ // return unsupported result for assets-library URLs
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."];
+}
+
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
+{
+ // assets-library files can't be truncated
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+}
+
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
+{
+ // text can't be written into assets-library files
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+}
+
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
+{
+ // Copying to an assets library file is not doable, since we can't write it.
+ CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
+ callback(result);
+}
+
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
+{
+ NSString *path = nil;
+ if ([[url.url scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
+ path = [url.url path];
+ } else {
+ path = url.fullPath;
+ }
+ if ([path hasSuffix:@"/"]) {
+ path = [path substringToIndex:([path length]-1)];
+ }
+ return path;
+}
+
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
+{
+ ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
+ if (asset) {
+ // We have the asset! Get the data and send it off.
+ ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
+ Byte* buffer = (Byte*)malloc([assetRepresentation size]);
+ NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil];
+ NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES];
+ NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType);
+
+ callback(data, MIMEType, NO_ERROR);
+ } else {
+ callback(nil, nil, NOT_FOUND_ERR);
+ }
+ };
+
+ ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
+ // Retrieving the asset failed for some reason. Send the appropriate error.
+ NSLog(@"Error: %@", error);
+ callback(nil, nil, SECURITY_ERR);
+ };
+
+ ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
+ [assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
+}
+
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
+{
+ // In this case, we need to use an asynchronous method to retrieve the file.
+ // Because of this, we can't just assign to `result` and send it at the end of the method.
+ // Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
+ ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
+ if (asset) {
+ // We have the asset! Populate the dictionary and send it off.
+ NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
+ ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
+ [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[assetRepresentation size]] forKey:@"size"];
+ [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
+ NSString* filename = [assetRepresentation filename];
+ [fileInfo setObject:filename forKey:@"name"];
+ [fileInfo setObject:[CDVAssetLibraryFilesystem getMimeTypeFromPath:filename] forKey:@"type"];
+ NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate];
+ NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000];
+ [fileInfo setObject:msDate forKey:@"lastModifiedDate"];
+
+ callback([CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]);
+ } else {
+ // We couldn't find the asset. Send the appropriate error.
+ callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]);
+ }
+ };
+ ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
+ // Retrieving the asset failed for some reason. Send the appropriate error.
+ callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]);
+ };
+
+ ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
+ [assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
+ return;
+}
+@end
diff --git a/plugins/org.apache.cordova.file/src/ios/CDVFile.h b/plugins/org.apache.cordova.file/src/ios/CDVFile.h
new file mode 100644
index 0000000..74c0146
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ios/CDVFile.h
@@ -0,0 +1,156 @@
+/*
+ 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.
+ */
+
+#import
+#import
+
+NSString* const kCDVAssetsLibraryPrefix;
+NSString* const kCDVFilesystemURLPrefix;
+
+enum CDVFileError {
+ NO_ERROR = 0,
+ NOT_FOUND_ERR = 1,
+ SECURITY_ERR = 2,
+ ABORT_ERR = 3,
+ NOT_READABLE_ERR = 4,
+ ENCODING_ERR = 5,
+ NO_MODIFICATION_ALLOWED_ERR = 6,
+ INVALID_STATE_ERR = 7,
+ SYNTAX_ERR = 8,
+ INVALID_MODIFICATION_ERR = 9,
+ QUOTA_EXCEEDED_ERR = 10,
+ TYPE_MISMATCH_ERR = 11,
+ PATH_EXISTS_ERR = 12
+};
+typedef int CDVFileError;
+
+@interface CDVFilesystemURL : NSObject {
+ NSURL *_url;
+ NSString *_fileSystemName;
+ NSString *_fullPath;
+}
+
+- (id) initWithString:(NSString*)strURL;
+- (id) initWithURL:(NSURL*)URL;
++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL;
++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL;
+
+- (NSString *)absoluteURL;
+
+@property (atomic) NSURL *url;
+@property (atomic) NSString *fileSystemName;
+@property (atomic) NSString *fullPath;
+
+@end
+
+@interface CDVFilesystemURLProtocol : NSURLProtocol
+@end
+
+@protocol CDVFileSystem
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url;
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options;
+- (CDVPluginResult *)getParentForURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options;
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos;
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend;
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback;
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback;
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback;
+
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url;
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir;
+
+@property (nonatomic,strong) NSString *name;
+
+@optional
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURI;
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path;
+
+@end
+
+@interface CDVFile : CDVPlugin {
+ NSString* rootDocsPath;
+ NSString* appDocsPath;
+ NSString* appLibraryPath;
+ NSString* appTempPath;
+
+ NSMutableArray* fileSystems_;
+ BOOL userHasAllowed;
+}
+
+- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath;
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir;
+- (NSDictionary *)makeEntryForURL:(NSURL *)URL;
+- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath;
+
+- (NSObject *)filesystemForURL:(CDVFilesystemURL *)localURL;
+
+/* Native Registration API */
+- (void)registerFilesystem:(NSObject *)fs;
+- (NSObject *)fileSystemByName:(NSString *)fsName;
+
+/* Exec API */
+- (void)requestFileSystem:(CDVInvokedUrlCommand*)command;
+- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command;
+- (void)getDirectory:(CDVInvokedUrlCommand*)command;
+- (void)getFile:(CDVInvokedUrlCommand*)command;
+- (void)getParent:(CDVInvokedUrlCommand*)command;
+- (void)removeRecursively:(CDVInvokedUrlCommand*)command;
+- (void)remove:(CDVInvokedUrlCommand*)command;
+- (void)copyTo:(CDVInvokedUrlCommand*)command;
+- (void)moveTo:(CDVInvokedUrlCommand*)command;
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command;
+- (void)readEntries:(CDVInvokedUrlCommand*)command;
+- (void)readAsText:(CDVInvokedUrlCommand*)command;
+- (void)readAsDataURL:(CDVInvokedUrlCommand*)command;
+- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command;
+- (void)write:(CDVInvokedUrlCommand*)command;
+- (void)testFileExists:(CDVInvokedUrlCommand*)command;
+- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command;
+- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command;
+- (void)truncate:(CDVInvokedUrlCommand*)command;
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy;
+
+/* Compatibilty with older File API */
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
+- (NSDictionary *)getDirectoryEntry:(NSString *)target isDirectory:(BOOL)bDirRequest;
+
+/* Conversion between filesystem paths and URLs */
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)URL;
+
+/* Internal methods for testing */
+- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command;
+
+@property (nonatomic, strong) NSString* rootDocsPath;
+@property (nonatomic, strong) NSString* appDocsPath;
+@property (nonatomic, strong) NSString* appLibraryPath;
+@property (nonatomic, strong) NSString* appTempPath;
+@property (nonatomic, strong) NSString* persistentPath;
+@property (nonatomic, strong) NSString* temporaryPath;
+@property (nonatomic, strong) NSMutableArray* fileSystems;
+
+@property BOOL userHasAllowed;
+
+@end
+
+#define kW3FileTemporary @"temporary"
+#define kW3FilePersistent @"persistent"
diff --git a/plugins/org.apache.cordova.file/src/ios/CDVFile.m b/plugins/org.apache.cordova.file/src/ios/CDVFile.m
new file mode 100644
index 0000000..4e9db14
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ios/CDVFile.m
@@ -0,0 +1,1077 @@
+/*
+ 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.
+ */
+
+#import
+#import "CDVFile.h"
+#import "CDVLocalFilesystem.h"
+#import "CDVAssetLibraryFilesystem.h"
+
+CDVFile *filePlugin = nil;
+
+extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import));
+
+#ifndef __IPHONE_5_1
+ NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey";
+#endif
+
+NSString* const kCDVFilesystemURLPrefix = @"cdvfile";
+
+@implementation CDVFilesystemURL
+@synthesize url=_url;
+@synthesize fileSystemName=_fileSystemName;
+@synthesize fullPath=_fullPath;
+
+- (id) initWithString:(NSString *)strURL
+{
+ if ( self = [super init] ) {
+ NSURL *decodedURL = [NSURL URLWithString:strURL];
+ return [self initWithURL:decodedURL];
+ }
+ return nil;
+}
+
+-(id) initWithURL:(NSURL *)URL
+{
+ if ( self = [super init] ) {
+ _url = URL;
+ _fileSystemName = [self filesystemNameForLocalURI:URL];
+ _fullPath = [self fullPathForLocalURI:URL];
+ }
+ return self;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString FileSystem Name for this URI, or nil if it is not recognized.
+ */
+- (NSString *)filesystemNameForLocalURI:(NSURL *)uri
+{
+ if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) {
+ NSArray *pathComponents = [uri pathComponents];
+ if (pathComponents != nil && pathComponents.count > 1) {
+ return [pathComponents objectAtIndex:1];
+ }
+ } else if ([[uri scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
+ return @"assets-library";
+ }
+ return nil;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString fullPath component suitable for an Entry object.
+ * The incoming URI should be properly escaped. The returned fullPath is unescaped.
+ */
+- (NSString *)fullPathForLocalURI:(NSURL *)uri
+{
+ if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) {
+ NSString *path = [uri path];
+ if ([uri query]) {
+ path = [NSString stringWithFormat:@"%@?%@", path, [uri query]];
+ }
+ NSRange slashRange = [path rangeOfString:@"/" options:0 range:NSMakeRange(1, path.length-1)];
+ if (slashRange.location == NSNotFound) {
+ return @"";
+ }
+ return [path substringFromIndex:slashRange.location];
+ } else if ([[uri scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
+ return [[uri absoluteString] substringFromIndex:[kCDVAssetsLibraryScheme length]+2];
+ }
+ return nil;
+}
+
++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL
+{
+ return [[CDVFilesystemURL alloc] initWithString:strURL];
+}
+
++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL
+{
+ return [[CDVFilesystemURL alloc] initWithURL:URL];
+}
+
+- (NSString *)absoluteURL
+{
+ return [NSString stringWithFormat:@"cdvfile://localhost/%@%@", self.fileSystemName, self.fullPath];
+}
+
+@end
+
+@implementation CDVFilesystemURLProtocol
+
++ (BOOL)canInitWithRequest:(NSURLRequest*)request
+{
+ NSURL* url = [request URL];
+ return [[url scheme] isEqualToString:kCDVFilesystemURLPrefix];
+}
+
++ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request
+{
+ return request;
+}
+
++ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)requestA toRequest:(NSURLRequest*)requestB
+{
+ return [[[requestA URL] resourceSpecifier] isEqualToString:[[requestB URL] resourceSpecifier]];
+}
+
+- (void)startLoading
+{
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithURL:[[self request] URL]];
+ NSObject *fs = [filePlugin filesystemForURL:url];
+ [fs readFileAtURL:url start:0 end:-1 callback:^void(NSData *data, NSString *mimetype, CDVFileError error) {
+ NSMutableDictionary* responseHeaders = [[NSMutableDictionary alloc] init];
+ responseHeaders[@"Cache-Control"] = @"no-cache";
+
+ if (!error) {
+ responseHeaders[@"Content-Type"] = mimetype;
+ NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:200 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders];
+ [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+ [[self client] URLProtocol:self didLoadData:data];
+ [[self client] URLProtocolDidFinishLoading:self];
+ } else {
+ NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:404 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders];
+ [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+ [[self client] URLProtocolDidFinishLoading:self];
+ }
+ }];
+}
+
+- (void)stopLoading
+{}
+
+- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
+ willCacheResponse:(NSCachedURLResponse*)cachedResponse {
+ return nil;
+}
+
+@end
+
+
+@implementation CDVFile
+
+@synthesize rootDocsPath, appDocsPath, appLibraryPath, appTempPath, userHasAllowed, fileSystems=fileSystems_;
+
+- (void)registerFilesystem:(NSObject *)fs {
+ [fileSystems_ addObject:fs];
+}
+
+- (NSObject *)fileSystemByName:(NSString *)fsName
+{
+ if (self.fileSystems != nil) {
+ for (NSObject *fs in self.fileSystems) {
+ if ([fs.name isEqualToString:fsName]) {
+ return fs;
+ }
+ }
+ }
+ return nil;
+
+}
+
+- (NSObject *)filesystemForURL:(CDVFilesystemURL *)localURL {
+ if (localURL.fileSystemName == nil) return nil;
+ @try {
+ return [self fileSystemByName:localURL.fileSystemName];
+ }
+ @catch (NSException *e) {
+ return nil;
+ }
+}
+
+- (NSArray *)getExtraFileSystemsPreference:(UIViewController *)vc
+{
+ NSString *filesystemsStr = nil;
+ if([self.viewController isKindOfClass:[CDVViewController class]]) {
+ CDVViewController *vc = (CDVViewController *)self.viewController;
+ NSDictionary *settings = [vc settings];
+ filesystemsStr = [settings[@"iosextrafilesystems"] lowercaseString];
+ }
+ if (!filesystemsStr) {
+ filesystemsStr = @"library,library-nosync,documents,documents-nosync,cache,bundle,root";
+ }
+ return [filesystemsStr componentsSeparatedByString:@","];
+}
+
+- (void)makeNonSyncable:(NSString*)path {
+ [[NSFileManager defaultManager] createDirectoryAtPath:path
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:nil];
+ NSURL* url = [NSURL fileURLWithPath:path];
+ [url setResourceValue: [NSNumber numberWithBool: YES]
+ forKey: NSURLIsExcludedFromBackupKey error:nil];
+
+}
+
+- (void)registerExtraFileSystems:(NSArray *)filesystems fromAvailableSet:(NSDictionary *)availableFileSystems
+{
+ NSMutableSet *installedFilesystems = [[NSMutableSet alloc] initWithCapacity:7];
+
+ /* Build non-syncable directories as necessary */
+ for (NSString *nonSyncFS in @[@"library-nosync", @"documents-nosync"]) {
+ if ([filesystems containsObject:nonSyncFS]) {
+ [self makeNonSyncable:availableFileSystems[nonSyncFS]];
+ }
+ }
+
+ /* Register filesystems in order */
+ for (NSString *fsName in filesystems) {
+ if (![installedFilesystems containsObject:fsName]) {
+ NSString *fsRoot = availableFileSystems[fsName];
+ if (fsRoot) {
+ [filePlugin registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:fsName root:fsRoot]];
+ [installedFilesystems addObject:fsName];
+ } else {
+ NSLog(@"Unrecognized extra filesystem identifier: %@", fsName);
+ }
+ }
+ }
+}
+
+- (NSDictionary *)getAvailableFileSystems
+{
+ NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+ NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+ return @{
+ @"library": libPath,
+ @"library-nosync": [libPath stringByAppendingPathComponent:@"NoCloud"],
+ @"documents": docPath,
+ @"documents-nosync": [docPath stringByAppendingPathComponent:@"NoCloud"],
+ @"cache": [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0],
+ @"bundle": [[NSBundle mainBundle] bundlePath],
+ @"root": @"/"
+ };
+}
+
+- (void)pluginInitialize
+{
+ NSString *location = nil;
+ if([self.viewController isKindOfClass:[CDVViewController class]]) {
+ CDVViewController *vc = (CDVViewController *)self.viewController;
+ NSMutableDictionary *settings = vc.settings;
+ location = [[settings objectForKey:@"iospersistentfilelocation"] lowercaseString];
+ }
+ if (location == nil) {
+ // Compatibilty by default (if the config preference is not set, or
+ // if we're not embedded in a CDVViewController somehow.)
+ location = @"compatibility";
+ }
+
+ NSError *error;
+ if ([[NSFileManager defaultManager] createDirectoryAtPath:self.appTempPath
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&error]) {
+ [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"temporary" root:self.appTempPath]];
+ } else {
+ NSLog(@"Unable to create temporary directory: %@", error);
+ }
+ if ([location isEqualToString:@"library"]) {
+ if ([[NSFileManager defaultManager] createDirectoryAtPath:self.appLibraryPath
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&error]) {
+ [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"persistent" root:self.appLibraryPath]];
+ } else {
+ NSLog(@"Unable to create library directory: %@", error);
+ }
+ } else if ([location isEqualToString:@"compatibility"]) {
+ /*
+ * Fall-back to compatibility mode -- this is the logic implemented in
+ * earlier versions of this plugin, and should be maintained here so
+ * that apps which were originally deployed with older versions of the
+ * plugin can continue to provide access to files stored under those
+ * versions.
+ */
+ [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"persistent" root:self.rootDocsPath]];
+ } else {
+ NSAssert(false,
+ @"File plugin configuration error: Please set iosPersistentFileLocation in config.xml to one of \"library\" (for new applications) or \"compatibility\" (for compatibility with previous versions)");
+ }
+ [self registerFilesystem:[[CDVAssetLibraryFilesystem alloc] initWithName:@"assets-library"]];
+
+ [self registerExtraFileSystems:[self getExtraFileSystemsPreference:self.viewController]
+ fromAvailableSet:[self getAvailableFileSystems]];
+
+}
+
+
+- (id)initWithWebView:(UIWebView*)theWebView
+{
+ self = (CDVFile*)[super initWithWebView:theWebView];
+ if (self) {
+ filePlugin = self;
+ [NSURLProtocol registerClass:[CDVFilesystemURLProtocol class]];
+
+ fileSystems_ = [[NSMutableArray alloc] initWithCapacity:3];
+
+ // Get the Library directory path
+ NSArray* paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+ self.appLibraryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"files"];
+
+ // Get the Temporary directory path
+ self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory()
+
+ // Get the Documents directory path
+ paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ self.rootDocsPath = [paths objectAtIndex:0];
+ self.appDocsPath = [self.rootDocsPath stringByAppendingPathComponent:@"files"];
+
+ }
+
+ return self;
+}
+
+- (CDVFilesystemURL *)fileSystemURLforArg:(NSString *)urlArg
+{
+ CDVFilesystemURL* ret = nil;
+ if ([urlArg hasPrefix:@"file://"]) {
+ /* This looks like a file url. Get the path, and see if any handlers recognize it. */
+ NSURL *fileURL = [NSURL URLWithString:urlArg];
+ NSURL *resolvedFileURL = [fileURL URLByResolvingSymlinksInPath];
+ NSString *path = [resolvedFileURL path];
+ ret = [self fileSystemURLforLocalPath:path];
+ } else {
+ ret = [CDVFilesystemURL fileSystemURLWithString:urlArg];
+ }
+ return ret;
+}
+
+- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath
+{
+ CDVFilesystemURL *localURL = nil;
+ NSUInteger shortestFullPath = 0;
+
+ // Try all installed filesystems, in order. Return the most match url.
+ for (id object in self.fileSystems) {
+ if ([object respondsToSelector:@selector(URLforFilesystemPath:)]) {
+ CDVFilesystemURL *url = [object URLforFilesystemPath:localPath];
+ if (url){
+ // A shorter fullPath would imply that the filesystem is a better match for the local path
+ if (!localURL || ([[url fullPath] length] < shortestFullPath)) {
+ localURL = url;
+ shortestFullPath = [[url fullPath] length];
+ }
+ }
+ }
+ }
+ return localURL;
+}
+
+- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath
+{
+ NSFileManager* fMgr = [[NSFileManager alloc] init];
+
+ NSError* __autoreleasing pError = nil;
+
+ NSDictionary* pDict = [fMgr attributesOfFileSystemForPath:appPath error:&pError];
+ NSNumber* pNumAvail = (NSNumber*)[pDict objectForKey:NSFileSystemFreeSize];
+
+ return pNumAvail;
+}
+
+/* Request the File System info
+ *
+ * IN:
+ * arguments[0] - type (number as string)
+ * TEMPORARY = 0, PERSISTENT = 1;
+ * arguments[1] - size
+ *
+ * OUT:
+ * Dictionary representing FileSystem object
+ * name - the human readable directory name
+ * root = DirectoryEntry object
+ * bool isDirectory
+ * bool isFile
+ * string name
+ * string fullPath
+ * fileSystem = FileSystem object - !! ignored because creates circular reference !!
+ */
+
+- (void)requestFileSystem:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* strType = [command argumentAtIndex:0];
+ unsigned long long size = [[command argumentAtIndex:1] longLongValue];
+
+ int type = [strType intValue];
+ CDVPluginResult* result = nil;
+
+ if (type > self.fileSystems.count) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
+ NSLog(@"No filesystem of type requested");
+ } else {
+ NSString* fullPath = @"/";
+ // check for avail space for size request
+ NSNumber* pNumAvail = [self checkFreeDiskSpace:self.rootDocsPath];
+ // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]);
+ if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR];
+ } else {
+ NSObject *rootFs = [self.fileSystems objectAtIndex:type];
+ if (rootFs == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
+ NSLog(@"No filesystem of type requested");
+ } else {
+ NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2];
+ [fileSystem setObject:rootFs.name forKey:@"name"];
+ NSDictionary* dirEntry = [self makeEntryForPath:fullPath fileSystemName:rootFs.name isDirectory:YES];
+ [fileSystem setObject:dirEntry forKey:@"root"];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem];
+ }
+ }
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+
+- (void)requestAllFileSystems:(CDVInvokedUrlCommand*)command
+{
+ NSMutableArray* ret = [[NSMutableArray alloc] init];
+ for (NSObject* root in fileSystems_) {
+ [ret addObject:[self makeEntryForPath:@"/" fileSystemName:root.name isDirectory:YES]];
+ }
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:ret];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)requestAllPaths:(CDVInvokedUrlCommand*)command
+{
+ NSString* libPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0];
+ NSString* libPathSync = [libPath stringByAppendingPathComponent:@"Cloud"];
+ NSString* libPathNoSync = [libPath stringByAppendingPathComponent:@"NoCloud"];
+ NSString* docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+ NSString* storagePath = [libPath stringByDeletingLastPathComponent];
+ NSString* cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
+
+ // Create the directories if necessary.
+ [[NSFileManager defaultManager] createDirectoryAtPath:libPathSync withIntermediateDirectories:YES attributes:nil error:nil];
+ [[NSFileManager defaultManager] createDirectoryAtPath:libPathNoSync withIntermediateDirectories:YES attributes:nil error:nil];
+ // Mark NoSync as non-iCloud.
+ [[NSURL fileURLWithPath:libPathNoSync] setResourceValue: [NSNumber numberWithBool: YES]
+ forKey: NSURLIsExcludedFromBackupKey error:nil];
+
+ NSDictionary* ret = @{
+ @"applicationDirectory": [[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]] absoluteString],
+ @"applicationStorageDirectory": [[NSURL fileURLWithPath:storagePath] absoluteString],
+ @"dataDirectory": [[NSURL fileURLWithPath:libPathNoSync] absoluteString],
+ @"syncedDataDirectory": [[NSURL fileURLWithPath:libPathSync] absoluteString],
+ @"documentsDirectory": [[NSURL fileURLWithPath:docPath] absoluteString],
+ @"cacheDirectory": [[NSURL fileURLWithPath:cachePath] absoluteString],
+ @"tempDirectory": [[NSURL fileURLWithPath:NSTemporaryDirectory()] absoluteString]
+ };
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:ret];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* Creates and returns a dictionary representing an Entry Object
+ *
+ * IN:
+ * NSString* fullPath of the entry
+ * int fsType - FileSystem type
+ * BOOL isDirectory - YES if this is a directory, NO if is a file
+ * OUT:
+ * NSDictionary* Entry object
+ * bool as NSNumber isDirectory
+ * bool as NSNumber isFile
+ * NSString* name - last part of path
+ * NSString* fullPath
+ * NSString* filesystemName - FileSystem name -- actual filesystem will be created on the JS side if necessary, to avoid
+ * creating circular reference (FileSystem contains DirectoryEntry which contains FileSystem.....!!)
+ */
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir
+{
+ NSObject *fs = [self fileSystemByName:fsName];
+ return [fs makeEntryForPath:fullPath isDirectory:isDir];
+}
+
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)localURL
+{
+ NSObject *fs = [self filesystemForURL:localURL];
+ return [fs makeEntryForLocalURL:localURL];
+}
+
+- (NSDictionary *)makeEntryForURL:(NSURL *)URL
+{
+ CDVFilesystemURL* fsURL = [self fileSystemURLforArg:[URL absoluteString]];
+ return [self makeEntryForLocalURL:fsURL];
+}
+
+/*
+ * Given a URI determine the File System information associated with it and return an appropriate W3C entry object
+ * IN
+ * NSString* localURI: Should be an escaped local filesystem URI
+ * OUT
+ * Entry object
+ * bool isDirectory
+ * bool isFile
+ * string name
+ * string fullPath
+ * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!!
+ */
+- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* localURIstr = [command argumentAtIndex:0];
+ CDVPluginResult* result;
+
+ localURIstr = [self encodePath:localURIstr]; //encode path before resolving
+ CDVFilesystemURL* inputURI = [self fileSystemURLforArg:localURIstr];
+
+ if (inputURI == nil || inputURI.fileSystemName == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
+ } else {
+ NSObject *fs = [self filesystemForURL:inputURI];
+ if (fs == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
+ } else {
+ result = [fs entryForLocalURI:inputURI];
+ }
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+//encode path with percent escapes
+-(NSString *)encodePath:(NSString *)path
+{
+ NSString *decodedPath = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //decode incase it's already encoded to avoid encoding twice
+ return [decodedPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+}
+
+
+/* Part of DirectoryEntry interface, creates or returns the specified directory
+ * IN:
+ * NSString* localURI - local filesystem URI for this directory
+ * NSString* path - directory to be created/returned; may be full path or relative path
+ * NSDictionary* - Flags object
+ * boolean as NSNumber create -
+ * if create is true and directory does not exist, create dir and return directory entry
+ * if create is true and exclusive is true and directory does exist, return error
+ * if create is false and directory does not exist, return error
+ * if create is false and the path represents a file, return error
+ * boolean as NSNumber exclusive - used in conjunction with create
+ * if exclusive is true and create is true - specifies failure if directory already exists
+ *
+ *
+ */
+- (void)getDirectory:(CDVInvokedUrlCommand*)command
+{
+ NSMutableArray* arguments = [NSMutableArray arrayWithArray:command.arguments];
+ NSMutableDictionary* options = nil;
+
+ if ([arguments count] >= 3) {
+ options = [command argumentAtIndex:2 withDefault:nil];
+ }
+ // add getDir to options and call getFile()
+ if (options != nil) {
+ options = [NSMutableDictionary dictionaryWithDictionary:options];
+ } else {
+ options = [NSMutableDictionary dictionaryWithCapacity:1];
+ }
+ [options setObject:[NSNumber numberWithInt:1] forKey:@"getDir"];
+ if ([arguments count] >= 3) {
+ [arguments replaceObjectAtIndex:2 withObject:options];
+ } else {
+ [arguments addObject:options];
+ }
+ CDVInvokedUrlCommand* subCommand =
+ [[CDVInvokedUrlCommand alloc] initWithArguments:arguments
+ callbackId:command.callbackId
+ className:command.className
+ methodName:command.methodName];
+
+ [self getFile:subCommand];
+}
+
+/* Part of DirectoryEntry interface, creates or returns the specified file
+ * IN:
+ * NSString* baseURI - local filesytem URI for the base directory to search
+ * NSString* requestedPath - file to be created/returned; may be absolute path or relative path
+ * NSDictionary* options - Flags object
+ * boolean as NSNumber create -
+ * if create is true and file does not exist, create file and return File entry
+ * if create is true and exclusive is true and file does exist, return error
+ * if create is false and file does not exist, return error
+ * if create is false and the path represents a directory, return error
+ * boolean as NSNumber exclusive - used in conjunction with create
+ * if exclusive is true and create is true - specifies failure if file already exists
+ */
+- (void)getFile:(CDVInvokedUrlCommand*)command
+{
+ NSString* baseURIstr = [command argumentAtIndex:0];
+ CDVFilesystemURL* baseURI = [self fileSystemURLforArg:baseURIstr];
+ NSString* requestedPath = [command argumentAtIndex:1];
+ NSDictionary* options = [command argumentAtIndex:2 withDefault:nil];
+
+ NSObject *fs = [self filesystemForURL:baseURI];
+ CDVPluginResult* result = [fs getFileForURL:baseURI requestedPath:requestedPath options:options];
+
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/*
+ * Look up the parent Entry containing this Entry.
+ * If this Entry is the root of its filesystem, its parent is itself.
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI
+ * NSMutableDictionary* options
+ * empty
+ */
+- (void)getParent:(CDVInvokedUrlCommand*)command
+{
+ // arguments are URL encoded
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+
+ NSObject *fs = [self filesystemForURL:localURI];
+ CDVPluginResult* result = [fs getParentForURL:localURI];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/*
+ * set MetaData of entry
+ * Currently we only support "com.apple.MobileBackup" (boolean)
+ */
+- (void)setMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSDictionary* options = [command argumentAtIndex:1 withDefault:nil];
+ NSObject *fs = [self filesystemForURL:localURI];
+ CDVPluginResult* result = [fs setMetadataForURL:localURI withObject:options];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* removes the directory or file entry
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI
+ *
+ * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir
+ * returns INVALID_MODIFICATION_ERR if is non-empty dir or asset library file
+ * returns NOT_FOUND_ERR if file or dir is not found
+*/
+- (void)remove:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ CDVPluginResult* result = nil;
+
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // error if try to remove top level (documents or tmp) dir
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ } else {
+ NSObject *fs = [self filesystemForURL:localURI];
+ result = [fs removeFileAtURL:localURI];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* recursively removes the directory
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI
+ *
+ * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir
+ * returns NOT_FOUND_ERR if file or dir is not found
+ */
+- (void)removeRecursively:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ CDVPluginResult* result = nil;
+
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // error if try to remove top level (documents or tmp) dir
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ } else {
+ NSObject *fs = [self filesystemForURL:localURI];
+ result = [fs recursiveRemoveFileAtURL:localURI];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)copyTo:(CDVInvokedUrlCommand*)command
+{
+ [self doCopyMove:command isCopy:YES];
+}
+
+- (void)moveTo:(CDVInvokedUrlCommand*)command
+{
+ [self doCopyMove:command isCopy:NO];
+}
+
+/* Copy/move a file or directory to a new location
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* URL of entry to copy
+ * 1 - NSString* URL of the directory into which to copy/move the entry
+ * 2 - Optionally, the new name of the entry, defaults to the current name
+ * BOOL - bCopy YES if copy, NO if move
+ */
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy
+{
+ NSArray* arguments = command.arguments;
+
+ // arguments
+ NSString* srcURLstr = [command argumentAtIndex:0];
+ NSString* destURLstr = [command argumentAtIndex:1];
+
+ CDVPluginResult *result;
+
+ if (!srcURLstr || !destURLstr) {
+ // either no source or no destination provided
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ CDVFilesystemURL* srcURL = [self fileSystemURLforArg:srcURLstr];
+ CDVFilesystemURL* destURL = [self fileSystemURLforArg:destURLstr];
+
+ NSObject *srcFs = [self filesystemForURL:srcURL];
+ NSObject *destFs = [self filesystemForURL:destURL];
+
+ // optional argument; use last component from srcFullPath if new name not provided
+ NSString* newName = ([arguments count] > 2) ? [command argumentAtIndex:2] : [srcURL.url lastPathComponent];
+ if ([newName rangeOfString:@":"].location != NSNotFound) {
+ // invalid chars in new name
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ [destFs copyFileToURL:destURL withName:newName fromFileSystem:srcFs atURL:srcURL copy:bCopy callback:^(CDVPluginResult* result) {
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+
+}
+
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSObject *fs = [self filesystemForURL:localURI];
+
+ [fs getFileMetadataForURL:localURI callback:^(CDVPluginResult* result) {
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+}
+
+- (void)readEntries:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSObject *fs = [self filesystemForURL:localURI];
+ CDVPluginResult *result = [fs readEntriesAtURL:localURI];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* read and return file data
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ * 1 - NSString* encoding
+ * 2 - NSString* start
+ * 3 - NSString* end
+ */
+- (void)readAsText:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSString* encoding = [command argumentAtIndex:1];
+ NSInteger start = [[command argumentAtIndex:2] integerValue];
+ NSInteger end = [[command argumentAtIndex:3] integerValue];
+
+ NSObject *fs = [self filesystemForURL:localURI];
+
+ if (fs == nil) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ // TODO: implement
+ if ([@"UTF-8" caseInsensitiveCompare : encoding] != NSOrderedSame) {
+ NSLog(@"Only UTF-8 encodings are currently supported by readAsText");
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ NSString* str = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSUTF8StringEncoding freeWhenDone:NO];
+ // Check that UTF8 conversion did not fail.
+ if (str != nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:str];
+ result.associatedObject = data;
+ } else {
+ errorCode = ENCODING_ERR;
+ }
+ }
+ if (result == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+ }];
+}
+
+/* Read content of text file and return as base64 encoded data url.
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ * 1 - NSString* start
+ * 2 - NSString* end
+ *
+ * Determines the mime type from the file extension, returns ENCODING_ERR if mimetype can not be determined.
+ */
+
+- (void)readAsDataURL:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject *fs = [self filesystemForURL:localURI];
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ NSString* b64str = [data base64EncodedStringWithOptions:0];
+ NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, b64str];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+ }];
+}
+
+/* Read content of text file and return as an arraybuffer
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ * 1 - NSString* start
+ * 2 - NSString* end
+ */
+
+- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject *fs = [self filesystemForURL:localURI];
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+ }];
+}
+
+- (void)readAsBinaryString:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject *fs = [self filesystemForURL:localURI];
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ NSString* payload = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSASCIIStringEncoding freeWhenDone:NO];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload];
+ result.associatedObject = data;
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+ }];
+}
+
+
+- (void)truncate:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ unsigned long long pos = (unsigned long long)[[command argumentAtIndex:1] longLongValue];
+
+ NSObject *fs = [self filesystemForURL:localURI];
+ CDVPluginResult *result = [fs truncateFileAtURL:localURI atPosition:pos];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* write
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI of file to write to
+ * 1 - NSString* or NSData* data to write
+ * 2 - NSNumber* position to begin writing
+ */
+- (void)write:(CDVInvokedUrlCommand*)command
+{
+ [self.commandDelegate runInBackground:^ {
+ NSString* callbackId = command.callbackId;
+
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ id argData = [command argumentAtIndex:1];
+ unsigned long long pos = (unsigned long long)[[command argumentAtIndex:2] longLongValue];
+
+ NSObject *fs = [self filesystemForURL:localURI];
+
+
+ [fs truncateFileAtURL:localURI atPosition:pos];
+ CDVPluginResult *result;
+ if ([argData isKindOfClass:[NSString class]]) {
+ NSData *encData = [argData dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
+ result = [fs writeToFileAtURL:localURI withData:encData append:YES];
+ } else if ([argData isKindOfClass:[NSData class]]) {
+ result = [fs writeToFileAtURL:localURI withData:argData append:YES];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }];
+}
+
+#pragma mark Methods for converting between URLs and paths
+
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURL
+{
+ for (NSObject *fs in self.fileSystems) {
+ if ([fs.name isEqualToString:localURL.fileSystemName]) {
+ if ([fs respondsToSelector:@selector(filesystemPathForURL:)]) {
+ return [fs filesystemPathForURL:localURL];
+ }
+ }
+ }
+ return nil;
+}
+
+#pragma mark Undocumented Filesystem API
+
+- (void)testFileExists:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command argumentAtIndex:0];
+
+ // Get the file manager
+ NSFileManager* fMgr = [NSFileManager defaultManager];
+ NSString* appFile = argPath; // [ self getFullPath: argPath];
+
+ BOOL bExists = [fMgr fileExistsAtPath:appFile];
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1 : 0)];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command argumentAtIndex:0];
+
+ // Get the file manager
+ NSFileManager* fMgr = [[NSFileManager alloc] init];
+ NSString* appFile = argPath; // [self getFullPath: argPath];
+ BOOL bIsDir = NO;
+ BOOL bExists = [fMgr fileExistsAtPath:appFile isDirectory:&bIsDir];
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1 : 0)];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+// Returns number of bytes available via callback
+- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command
+{
+ // no arguments
+
+ NSNumber* pNumAvail = [self checkFreeDiskSpace:self.appDocsPath];
+
+ NSString* strFreeSpace = [NSString stringWithFormat:@"%qu", [pNumAvail unsignedLongLongValue]];
+ // NSLog(@"Free space is %@", strFreeSpace );
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:strFreeSpace];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+#pragma mark Compatibility with older File API
+
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath
+{
+ return [CDVLocalFilesystem getMimeTypeFromPath:fullPath];
+}
+
+- (NSDictionary *)getDirectoryEntry:(NSString *)localPath isDirectory:(BOOL)bDirRequest
+{
+ CDVFilesystemURL *localURL = [self fileSystemURLforLocalPath:localPath];
+ return [self makeEntryForPath:localURL.fullPath fileSystemName:localURL.fileSystemName isDirectory:bDirRequest];
+}
+
+#pragma mark Internal methods for testing
+// Internal methods for testing: Get the on-disk location of a local filesystem url.
+// [Currently used for testing file-transfer]
+
+- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURL = [self fileSystemURLforArg:command.arguments[0]];
+
+ NSString* fsPath = [self filesystemPathForURL:localURL];
+ CDVPluginResult* result;
+ if (fsPath) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:fsPath];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Cannot resolve URL to a file"];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+@end
diff --git a/plugins/org.apache.cordova.file/src/ios/CDVLocalFilesystem.h b/plugins/org.apache.cordova.file/src/ios/CDVLocalFilesystem.h
new file mode 100644
index 0000000..a0186c8
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ios/CDVLocalFilesystem.h
@@ -0,0 +1,32 @@
+/*
+ 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.
+ */
+
+#import "CDVFile.h"
+
+@interface CDVLocalFilesystem : NSObject {
+ NSString *_name;
+ NSString *_fsRoot;
+}
+
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot;
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
+
+@property (nonatomic,strong) NSString *fsRoot;
+
+@end
diff --git a/plugins/org.apache.cordova.file/src/ios/CDVLocalFilesystem.m b/plugins/org.apache.cordova.file/src/ios/CDVLocalFilesystem.m
new file mode 100644
index 0000000..932c0db
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ios/CDVLocalFilesystem.m
@@ -0,0 +1,728 @@
+/*
+ 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.
+ */
+
+#import "CDVFile.h"
+#import "CDVLocalFilesystem.h"
+#import
+#import
+#import
+
+@implementation CDVLocalFilesystem
+@synthesize name=_name, fsRoot=_fsRoot;
+
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
+{
+ if (self) {
+ _name = name;
+ _fsRoot = fsRoot;
+ }
+ return self;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
+ * URI represents a non-existent path, or is unrecognized or otherwise malformed.
+ */
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
+{
+ CDVPluginResult* result = nil;
+ NSDictionary* entry = [self makeEntryForLocalURL:url];
+ if (entry) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
+ } else {
+ // return NOT_FOUND_ERR
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ return result;
+}
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
+ NSString *path = [self filesystemPathForURL:url];
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL isDir = NO;
+ // see if exists and is file or dir
+ BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
+ if (bExists) {
+ return [self makeEntryForPath:url.fullPath isDirectory:isDir];
+ } else {
+ return nil;
+ }
+}
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
+{
+ NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
+ NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
+ if (isDir && ![fullPath hasSuffix:@"/"]) {
+ fullPath = [fullPath stringByAppendingString:@"/"];
+ }
+ [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
+ [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
+ [dirEntry setObject:fullPath forKey:@"fullPath"];
+ [dirEntry setObject:lastPart forKey:@"name"];
+ [dirEntry setObject:self.name forKey: @"filesystemName"];
+ dirEntry[@"nativeURL"] = [[NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]] absoluteString];
+
+ return dirEntry;
+}
+
+- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
+{
+ NSRange questionMark = [fullPath rangeOfString:@"?"];
+ if (questionMark.location != NSNotFound) {
+ return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
+ }
+ return fullPath;
+}
+
+- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
+{
+ NSString *path = nil;
+ NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
+ path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
+ if ([path length] > 1 && [path hasSuffix:@"/"]) {
+ path = [path substringToIndex:([path length]-1)];
+ }
+ return path;
+}
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
+ * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
+ * or if the URL is malformed.
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
+ */
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
+{
+ return [self filesystemPathForFullPath:url.fullPath];
+}
+
+- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
+{
+ if (fullPath) {
+ NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ if ([fullPath hasPrefix:@"/"]) {
+ return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
+ }
+ return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
+ }
+ return nil;
+}
+
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
+{
+ return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
+
+}
+
+- (NSString *)normalizePath:(NSString *)rawPath
+{
+ // If this is an absolute path, the first path component will be '/'. Skip it if that's the case
+ BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
+ if (isAbsolutePath) {
+ rawPath = [rawPath substringFromIndex:1];
+ }
+ NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
+ for (int index = 0; index < [components count]; ++index) {
+ if ([[components objectAtIndex:index] isEqualToString:@".."]) {
+ [components removeObjectAtIndex:index];
+ if (index > 0) {
+ [components removeObjectAtIndex:index-1];
+ --index;
+ }
+ }
+ }
+
+ if (isAbsolutePath) {
+ return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
+ } else {
+ return [components componentsJoinedByString:@"/"];
+ }
+
+
+}
+
+- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
+{
+ BOOL bNumber = NO;
+ NSObject* value = dict[key];
+ if (value) {
+ bNumber = [value isKindOfClass:[NSNumber class]];
+ }
+ return bNumber;
+}
+
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
+{
+ CDVPluginResult* result = nil;
+ BOOL bDirRequest = NO;
+ BOOL create = NO;
+ BOOL exclusive = NO;
+ int errorCode = 0; // !!! risky - no error code currently defined for 0
+
+ if ([self valueForKeyIsNumber:options key:@"create"]) {
+ create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
+ }
+ if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
+ exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
+ }
+ if ([self valueForKeyIsNumber:options key:@"getDir"]) {
+ // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method
+ bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue];
+ }
+ // see if the requested path has invalid characters - should we be checking for more than just ":"?
+ if ([requestedPath rangeOfString:@":"].location != NSNotFound) {
+ errorCode = ENCODING_ERR;
+ } else {
+ // Build new fullPath for the requested resource.
+ // We concatenate the two paths together, and then scan the resulting string to remove
+ // parent ("..") references. Any parent references at the beginning of the string are
+ // silently removed.
+ NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
+ combinedPath = [self normalizePath:combinedPath];
+ CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
+
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir;
+ BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
+ if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
+ // path exists and is not of requested type - return TYPE_MISMATCH_ERR
+ errorCode = TYPE_MISMATCH_ERR;
+ } else if (!bExists && (create == NO)) {
+ // path does not exist and create is false - return NOT_FOUND_ERR
+ errorCode = NOT_FOUND_ERR;
+ } else if (bExists && (create == YES) && (exclusive == YES)) {
+ // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR
+ errorCode = PATH_EXISTS_ERR;
+ } else {
+ // if bExists and create == YES - just return data
+ // if bExists and create == NO - just return data
+ // if !bExists and create == YES - create and return data
+ BOOL bSuccess = YES;
+ NSError __autoreleasing* pError = nil;
+ if (!bExists && (create == YES)) {
+ if (bDirRequest) {
+ // create the dir
+ bSuccess = [fileMgr createDirectoryAtPath:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
+ } else {
+ // create the empty file
+ bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] contents:nil attributes:nil];
+ }
+ }
+ if (!bSuccess) {
+ errorCode = ABORT_ERR;
+ if (pError) {
+ NSLog(@"error creating directory: %@", [pError localizedDescription]);
+ }
+ } else {
+ // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]);
+ // file existed or was created
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
+ }
+ } // are all possible conditions met?
+ }
+
+ if (errorCode > 0) {
+ // create error callback
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+ return result;
+
+}
+
+- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
+{
+ CDVPluginResult* result = nil;
+ CDVFilesystemURL *newURI = nil;
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // return self
+ newURI = localURI;
+ } else {
+ newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
+ }
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir;
+ BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
+ if (bExists) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
+ } else {
+ // invalid path or file does not exist
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ return result;
+}
+
+- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
+{
+ BOOL ok = NO;
+
+ NSString* filePath = [self filesystemPathForURL:localURI];
+ // we only care about this iCloud key for now.
+ // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute)
+ NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup";
+ id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey];
+
+ if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) {
+ if (IsAtLeastiOSVersion(@"5.1")) {
+ NSURL* url = [NSURL fileURLWithPath:filePath];
+ NSError* __autoreleasing error = nil;
+
+ ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error];
+ } else { // below 5.1 (deprecated - only really supported in 5.01)
+ u_int8_t value = [iCloudBackupExtendedAttributeValue intValue];
+ if (value == 0) { // remove the attribute (allow backup, the default)
+ ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0);
+ } else { // set the attribute (skip backup)
+ ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0);
+ }
+ }
+ }
+
+ if (ok) {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ } else {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
+ }
+}
+
+/* remove the file or directory (recursively)
+ * IN:
+ * NSString* fullPath - the full path to the file or directory to be removed
+ * NSString* callbackId
+ * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling
+ */
+
+- (CDVPluginResult*)doRemove:(NSString*)fullPath
+{
+ CDVPluginResult* result = nil;
+ BOOL bSuccess = NO;
+ NSError* __autoreleasing pError = nil;
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+
+ @try {
+ bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError];
+ if (bSuccess) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ } else {
+ // see if we can give a useful error
+ CDVFileError errorCode = ABORT_ERR;
+ NSLog(@"error removing filesystem entry at %@: %@", fullPath, [pError localizedDescription]);
+ if ([pError code] == NSFileNoSuchFileError) {
+ errorCode = NOT_FOUND_ERR;
+ } else if ([pError code] == NSFileWriteNoPermissionError) {
+ errorCode = NO_MODIFICATION_ALLOWED_ERR;
+ }
+
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+ } @catch(NSException* e) { // NSInvalidArgumentException if path is . or ..
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR];
+ }
+
+ return result;
+}
+
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
+{
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir = NO;
+ BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
+ if (!bExists) {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
+ // dir is not empty
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
+ }
+ return [self doRemove:fileSystemPath];
+}
+
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
+{
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+ return [self doRemove:fileSystemPath];
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
+ * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
+ * or if the URL is malformed.
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
+ */
+- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
+{
+ if ([fsPath hasPrefix:self.fsRoot]) {
+ return [fsPath substringFromIndex:[self.fsRoot length]];
+ }
+ return nil;
+}
+
+
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
+{
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSError* __autoreleasing error = nil;
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+
+ NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath error:&error];
+
+ if (contents) {
+ NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1];
+ if ([contents count] > 0) {
+ // create an Entry (as JSON) for each file/dir
+ for (NSString* name in contents) {
+ // see if is dir or file
+ NSString* entryPath = [fileSystemPath stringByAppendingPathComponent:name];
+ BOOL bIsDir = NO;
+ [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
+ NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
+ [entries addObject:entryDict];
+ }
+ }
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
+ } else {
+ // assume not found but could check error for more specific error conditions
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+}
+
+- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos
+{
+ unsigned long long newPos = 0UL;
+
+ NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath];
+
+ if (file) {
+ [file truncateFileAtOffset:(unsigned long long)pos];
+ newPos = [file offsetInFile];
+ [file synchronizeFile];
+ [file closeFile];
+ }
+ return newPos;
+}
+
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
+{
+ unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
+}
+
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
+{
+ NSString *filePath = [self filesystemPathForURL:localURL];
+
+ CDVPluginResult* result = nil;
+ CDVFileError errCode = INVALID_MODIFICATION_ERR;
+ int bytesWritten = 0;
+
+ if (filePath) {
+ NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
+ if (fileStream) {
+ NSUInteger len = [encData length];
+ if (len == 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
+ } else {
+ [fileStream open];
+
+ bytesWritten = (int)[fileStream write:[encData bytes] maxLength:len];
+
+ [fileStream close];
+ if (bytesWritten > 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten];
+ // } else {
+ // can probably get more detailed error info via [fileStream streamError]
+ // errCode already set to INVALID_MODIFICATION_ERR;
+ // bytesWritten = 0; // may be set to -1 on error
+ }
+ }
+ } // else fileStream not created return INVALID_MODIFICATION_ERR
+ } else {
+ // invalid filePath
+ errCode = NOT_FOUND_ERR;
+ }
+ if (!result) {
+ // was an error
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode];
+ }
+ return result;
+}
+
+/**
+ * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name,
+ * or attempted to copy a directory into a directory that it contains directly or indirectly.
+ *
+ * IN:
+ * NSString* srcDir
+ * NSString* destinationDir
+ * OUT:
+ * YES copy/ move is allows
+ * NO move is onto itself
+ */
+- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest
+{
+ // This weird test is to determine if we are copying or moving a directory into itself.
+ // Copy /Documents/myDir to /Documents/myDir-backup is okay but
+ // Copy /Documents/myDir to /Documents/myDir/backup not okay
+ BOOL copyOK = YES;
+ NSRange range = [dest rangeOfString:src];
+
+ if (range.location != NSNotFound) {
+ NSRange testRange = {range.length - 1, ([dest length] - range.length)};
+ NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange];
+ if (resultRange.location != NSNotFound) {
+ copyOK = NO;
+ }
+ }
+ return copyOK;
+}
+
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
+{
+ NSFileManager *fileMgr = [[NSFileManager alloc] init];
+ NSString *destRootPath = [self filesystemPathForURL:destURL];
+ BOOL bDestIsDir = NO;
+ BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
+
+ NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
+ NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
+
+ BOOL bNewIsDir = NO;
+ BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
+
+ CDVPluginResult *result = nil;
+ int errCode = 0;
+
+ if (!bDestExists) {
+ // the destination root does not exist
+ errCode = NOT_FOUND_ERR;
+ }
+
+ else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
+ /* Same FS, we can shortcut with NSFileManager operations */
+ NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
+
+ BOOL bSrcIsDir = NO;
+ BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
+
+ if (!bSrcExists) {
+ // the source does not exist
+ errCode = NOT_FOUND_ERR;
+ } else if ([newFileSystemPath isEqualToString:srcFullPath]) {
+ // source and destination can not be the same
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) {
+ // can't copy/move dir to file
+ errCode = INVALID_MODIFICATION_ERR;
+ } else { // no errors yet
+ NSError* __autoreleasing error = nil;
+ BOOL bSuccess = NO;
+ if (bCopy) {
+ if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
+ // can't copy dir into self
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bNewExists) {
+ // the full destination should NOT already exist if a copy
+ errCode = PATH_EXISTS_ERR;
+ } else {
+ bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
+ }
+ } else { // move
+ // iOS requires that destination must not exist before calling moveTo
+ // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents
+ //
+ if (!bSrcIsDir && (bNewExists && bNewIsDir)) {
+ // can't move a file to directory
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
+ // can't move a dir into itself
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bNewExists) {
+ if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
+ // can't move dir to a dir that is not empty
+ errCode = INVALID_MODIFICATION_ERR;
+ newFileSystemPath = nil; // so we won't try to move
+ } else {
+ // remove destination so can perform the moveItemAtPath
+ bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
+ if (!bSuccess) {
+ errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
+ newFileSystemPath = nil;
+ }
+ }
+ } else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
+ // can't move a directory inside itself or to any child at any depth;
+ errCode = INVALID_MODIFICATION_ERR;
+ newFileSystemPath = nil;
+ }
+
+ if (newFileSystemPath != nil) {
+ bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
+ }
+ }
+ if (bSuccess) {
+ // should verify it is there and of the correct type???
+ NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
+ } else {
+ if (error) {
+ if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) {
+ errCode = NOT_READABLE_ERR;
+ } else if ([error code] == NSFileWriteOutOfSpaceError) {
+ errCode = QUOTA_EXCEEDED_ERR;
+ } else if ([error code] == NSFileWriteNoPermissionError) {
+ errCode = NO_MODIFICATION_ALLOWED_ERR;
+ }
+ }
+ }
+ }
+ } else {
+ // Need to copy the hard way
+ [srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
+ if (bSuccess) {
+ // should verify it is there and of the correct type???
+ NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
+ }
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+ callback(result);
+ }];
+ return; // Async IO; return without callback.
+ }
+ if (result == nil) {
+ if (!errCode) {
+ errCode = INVALID_MODIFICATION_ERR; // Catch-all default
+ }
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
+ }
+ callback(result);
+}
+
+/* helper function to get the mimeType from the file extension
+ * IN:
+ * NSString* fullPath - filename (may include path)
+ * OUT:
+ * NSString* the mime type as type/subtype. nil if not able to determine
+ */
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
+{
+ NSString* mimeType = nil;
+
+ if (fullPath) {
+ CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
+ if (typeId) {
+ mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
+ if (!mimeType) {
+ // special case for m4a
+ if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
+ mimeType = @"audio/mp4";
+ } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
+ mimeType = @"audio/wav";
+ } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
+ mimeType = @"text/css";
+ }
+ }
+ CFRelease(typeId);
+ }
+ }
+ return mimeType;
+}
+
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
+{
+ NSString *path = [self filesystemPathForURL:localURL];
+
+ NSString* mimeType = [CDVLocalFilesystem getMimeTypeFromPath:path];
+ if (mimeType == nil) {
+ mimeType = @"*/*";
+ }
+ NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
+ if (start > 0) {
+ [file seekToFileOffset:start];
+ }
+
+ NSData* readData;
+ if (end < 0) {
+ readData = [file readDataToEndOfFile];
+ } else {
+ readData = [file readDataOfLength:(end - start)];
+ }
+ [file closeFile];
+
+ callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR);
+}
+
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
+{
+ NSString *path = [self filesystemPathForURL:localURL];
+ CDVPluginResult *result;
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+
+ NSError* __autoreleasing error = nil;
+ NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
+
+ if (fileAttrs) {
+
+ // create dictionary of file info
+ NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
+
+ [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
+ [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping
+ [fileInfo setObject:[path lastPathComponent] forKey:@"name"];
+
+ // Ensure that directories (and other non-regular files) report size of 0
+ unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
+ [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
+
+ NSDate* modDate = [fileAttrs fileModificationDate];
+ if (modDate) {
+ [fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
+ }
+
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
+
+ } else {
+ // didn't get fileAttribs
+ CDVFileError errorCode = ABORT_ERR;
+ NSLog(@"error getting metadata: %@", [error localizedDescription]);
+ if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
+ errorCode = NOT_FOUND_ERR;
+ }
+ // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
+ }
+
+ callback(result);
+}
+
+@end
diff --git a/plugins/org.apache.cordova.file/src/ubuntu/file.cpp b/plugins/org.apache.cordova.file/src/ubuntu/file.cpp
new file mode 100644
index 0000000..395ab2d
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ubuntu/file.cpp
@@ -0,0 +1,912 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "file.h"
+
+#include
+
+namespace {
+ class FileError {
+ public:
+ static const QString kEncodingErr;
+ static const QString kTypeMismatchErr;
+ static const QString kNotFoundErr;
+ static const QString kSecurityErr;
+ static const QString kAbortErr;
+ static const QString kNotReadableErr;
+ static const QString kNoModificationAllowedErr;
+ static const QString kInvalidStateErr;
+ static const QString kSyntaxErr;
+ static const QString kInvalidModificationErr;
+ static const QString kQuotaExceededErr;
+ static const QString kPathExistsErr;
+ };
+
+ bool checkFileName(const QString &name) {
+ if (name.contains(":")){
+ return false;
+ }
+ return true;
+ }
+};
+
+const QString FileError::kEncodingErr("FileError.ENCODING_ERR");
+const QString FileError::kTypeMismatchErr("FileError.TYPE_MISMATCH_ERR");
+const QString FileError::kNotFoundErr("FileError.NOT_FOUND_ERR");
+const QString FileError::kSecurityErr("FileError.SECURITY_ERR");
+const QString FileError::kAbortErr("FileError.ABORT_ERR");
+const QString FileError::kNotReadableErr("FileError.NOT_READABLE_ERR");
+const QString FileError::kNoModificationAllowedErr("FileError.NO_MODIFICATION_ALLOWED_ERR");
+const QString FileError::kInvalidStateErr("FileError.INVALID_STATE_ERR");
+const QString FileError::kSyntaxErr("FileError.SYNTAX_ERR");
+const QString FileError::kInvalidModificationErr("FileError.INVALID_MODIFICATION_ERR");
+const QString FileError::kQuotaExceededErr("FileError.QUOTA_EXCEEDED_ERR");
+const QString FileError::kPathExistsErr("FileError.PATH_EXISTS_ERR");
+
+File::File(Cordova *cordova) :
+ CPlugin(cordova),
+ _persistentDir(QString("%1/.local/share/%2/persistent").arg(QDir::homePath()).arg(QCoreApplication::applicationName())) {
+ QDir::root().mkpath(_persistentDir.absolutePath());
+}
+
+QVariantMap File::file2map(const QFileInfo &fileInfo) {
+ QVariantMap res;
+
+ res.insert("name", fileInfo.fileName());
+ QPair r = GetRelativePath(fileInfo);
+ res.insert("fullPath", QString("/") + r.second);
+ res.insert("filesystemName", r.first);
+
+ res.insert("nativeURL", QString("file://localhost") + fileInfo.absoluteFilePath());
+ res.insert("isDirectory", (int)fileInfo.isDir());
+ res.insert("isFile", (int)fileInfo.isFile());
+
+ return res;
+}
+
+QVariantMap File::dir2map(const QDir &dir) {
+ return file2map(QFileInfo(dir.absolutePath()));
+}
+
+QPair File::GetRelativePath(const QFileInfo &fileInfo) {
+ QString fullPath = fileInfo.isDir() ? QDir::cleanPath(fileInfo.absoluteFilePath()) : fileInfo.absoluteFilePath();
+
+ QString relativePath1 = _persistentDir.relativeFilePath(fullPath);
+ QString relativePath2 = QDir::temp().relativeFilePath(fullPath);
+
+ if (!(relativePath1[0] != '.' || relativePath2[0] != '.')) {
+ if (relativePath1.size() > relativePath2.size()) {
+ return QPair("temporary", relativePath2);
+ } else {
+ return QPair("persistent", relativePath1);
+ }
+ }
+
+ if (relativePath1[0] != '.')
+ return QPair("persistent", relativePath1);
+ return QPair("temporary", relativePath2);
+}
+
+void File::requestFileSystem(int scId, int ecId, unsigned short type, unsigned long long size) {
+ QDir dir;
+
+ if (size >= 1000485760){
+ this->callback(ecId, FileError::kQuotaExceededErr);
+ return;
+ }
+
+ if (type == 0)
+ dir = QDir::temp();
+ else
+ dir = _persistentDir;
+
+ if (type > 1) {
+ this->callback(ecId, FileError::kSyntaxErr);
+ return;
+ } else {
+ QVariantMap res;
+ res.insert("root", dir2map(dir));
+ if (type == 0)
+ res.insert("name", "temporary");
+ else
+ res.insert("name", "persistent");
+
+ this->cb(scId, res);
+ }
+}
+
+QPair File::resolveURI(int ecId, const QString &uri) {
+ QPair result;
+
+ result.first = false;
+
+ QUrl url = QUrl::fromUserInput(uri);
+
+ if (url.scheme() == "file" && url.isValid()) {
+ result.first = true;
+ result.second = QFileInfo(url.path());
+ return result;
+ }
+
+ if (url.scheme() != "cdvfile") {
+ if (ecId)
+ this->callback(ecId, FileError::kTypeMismatchErr);
+ return result;
+ }
+
+ QString path = url.path().replace("//", "/");
+ //NOTE: colon is not safe in url, it is not a valid path in Win and Mac, simple disable it here.
+ if (path.contains(":") || !url.isValid()){
+ if (ecId)
+ this->callback(ecId, FileError::kEncodingErr);
+ return result;
+ }
+ if (!path.startsWith("/persistent/") && !path.startsWith("/temporary/")) {
+ if (ecId)
+ this->callback(ecId, FileError::kEncodingErr);
+ return result;
+ }
+
+ result.first = true;
+ if (path.startsWith("/persistent/")) {
+ QString relativePath = path.mid(QString("/persistent/").size());
+ result.second = QFileInfo(_persistentDir.filePath(relativePath));
+ } else {
+ QString relativePath = path.mid(QString("/temporary/").size());
+ result.second = QFileInfo(QDir::temp().filePath(relativePath));
+ }
+ return result;
+}
+
+QPair File::resolveURI(const QString &uri) {
+ return resolveURI(0, uri);
+}
+
+
+void File::_getLocalFilesystemPath(int scId, int ecId, const QString& uri) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ this->cb(scId, f1.second.absoluteFilePath());
+}
+
+void File::resolveLocalFileSystemURI(int scId, int ecId, const QString &uri) {
+ if (uri[0] == '/' || uri[0] == '.') {
+ this->callback(ecId, FileError::kEncodingErr);
+ return;
+ }
+
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ QFileInfo fileInfo = f1.second;
+ if (!fileInfo.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ this->cb(scId, file2map(fileInfo));
+}
+
+void File::getFile(int scId, int ecId, const QString &parentPath, const QString &rpath, const QVariantMap &options) {
+ QPair f1 = resolveURI(ecId, parentPath + "/" + rpath);
+ if (!f1.first)
+ return;
+
+ bool create = options.value("create").toBool();
+ bool exclusive = options.value("exclusive").toBool();
+ QFile file(f1.second.absoluteFilePath());
+
+ // if create is false and the path represents a directory, return error
+ QFileInfo fileInfo = f1.second;
+ if ((!create) && fileInfo.isDir()) {
+ this->callback(ecId, FileError::kTypeMismatchErr);
+ return;
+ }
+
+ // if file does exist, and create is true and exclusive is true, return error
+ if (file.exists()) {
+ if (create && exclusive) {
+ this->callback(ecId, FileError::kPathExistsErr);
+ return;
+ }
+ }
+ else {
+ // if file does not exist and create is false, return error
+ if (!create) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ file.open(QIODevice::WriteOnly);
+ file.close();
+
+ // Check if creation was successfull
+ if (!file.exists()) {
+ this->callback(ecId, FileError::kNoModificationAllowedErr);
+ return;
+ }
+ }
+
+ this->cb(scId, file2map(QFileInfo(file)));
+}
+
+void File::getDirectory(int scId, int ecId, const QString &parentPath, const QString &rpath, const QVariantMap &options) {
+ QPair f1 = resolveURI(ecId, parentPath + "/" + rpath);
+ if (!f1.first)
+ return;
+
+ bool create = options.value("create").toBool();
+ bool exclusive = options.value("exclusive").toBool();
+ QDir dir(f1.second.absoluteFilePath());
+
+ QFileInfo &fileInfo = f1.second;
+ if ((!create) && fileInfo.isFile()) {
+ this->callback(ecId, FileError::kTypeMismatchErr);
+ return;
+ }
+
+ if (dir.exists()) {
+ if (create && exclusive) {
+ this->callback(ecId, FileError::kPathExistsErr);
+ return;
+ }
+ }
+ else {
+ if (!create) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ QString folderName = dir.dirName();
+ dir.cdUp();
+ dir.mkdir(folderName);
+ dir.cd(folderName);
+
+ if (!dir.exists()) {
+ this->callback(ecId, FileError::kNoModificationAllowedErr);
+ return;
+ }
+ }
+
+ this->cb(scId, dir2map(dir));
+}
+
+void File::removeRecursively(int scId, int ecId, const QString &uri) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ QDir dir(f1.second.absoluteFilePath());
+ if (File::rmDir(dir))
+ this->cb(scId);
+ else
+ this->callback(ecId, FileError::kNoModificationAllowedErr);
+}
+
+void File::write(int scId, int ecId, const QString &uri, const QString &_data, unsigned long long position, bool binary) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ QFile file(f1.second.absoluteFilePath());
+
+ file.open(QIODevice::WriteOnly);
+ file.close();
+
+ if (!file.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ QFileInfo fileInfo(file);
+ if (!file.open(QIODevice::ReadWrite)) {
+ this->callback(ecId, FileError::kNoModificationAllowedErr);
+ return;
+ }
+
+ if (!binary) {
+ QTextStream textStream(&file);
+ textStream.setCodec("UTF-8");
+ textStream.setAutoDetectUnicode(true);
+
+ if (!textStream.seek(position)) {
+ file.close();
+ fileInfo.refresh();
+
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ textStream << _data;
+ textStream.flush();
+ } else {
+ QByteArray data(_data.toUtf8());
+ if (!file.seek(position)) {
+ file.close();
+ fileInfo.refresh();
+
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ file.write(data.data(), data.length());
+ }
+
+ file.flush();
+ file.close();
+ fileInfo.refresh();
+
+ this->cb(scId, fileInfo.size() - position);
+}
+
+void File::truncate(int scId, int ecId, const QString &uri, unsigned long long size) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ QFile file(f1.second.absoluteFilePath());
+
+ if (!file.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ if (!file.resize(size)) {
+ this->callback(ecId, FileError::kNoModificationAllowedErr);
+ return;
+ }
+
+ this->cb(scId, size);
+}
+
+void File::getParent(int scId, int ecId, const QString &uri) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+ QDir dir(f1.second.absoluteFilePath());
+
+ //can't cdup more than app's root
+ // Try to change into upper directory
+ if (dir != _persistentDir && dir != QDir::temp()){
+ if (!dir.cdUp()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ }
+ this->cb(scId, dir2map(dir));
+}
+
+void File::remove(int scId, int ecId, const QString &uri) {
+ QPair f1 = resolveURI(ecId, uri);
+ if (!f1.first)
+ return;
+
+ QFileInfo &fileInfo = f1.second;
+ //TODO: fix
+ if (!fileInfo.exists() || (fileInfo.absoluteFilePath() == _persistentDir.absolutePath()) || (QDir::temp() == fileInfo.absoluteFilePath())) {
+ this->callback(ecId, FileError::kNoModificationAllowedErr);
+ return;
+ }
+
+ if (fileInfo.isDir()) {
+ QDir dir(fileInfo.absoluteFilePath());
+ if (dir.rmdir(dir.absolutePath())) {
+ this->cb(scId);
+ return;
+ }
+ } else {
+ QFile file(fileInfo.absoluteFilePath());
+ if (file.remove()) {
+ this->cb(scId);
+ return;
+ }
+ }
+
+ this->callback(ecId, FileError::kInvalidModificationErr);
+}
+
+void File::getFileMetadata(int scId, int ecId, const QString &uri) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+ QFileInfo &fileInfo = f1.second;
+
+ if (!fileInfo.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ } else {
+ QMimeType mime = _db.mimeTypeForFile(fileInfo.fileName());
+
+ QString args = QString("{name: %1, fullPath: %2, type: %3, lastModifiedDate: new Date(%4), size: %5}")
+ .arg(CordovaInternal::format(fileInfo.fileName())).arg(CordovaInternal::format(fileInfo.absoluteFilePath()))
+ .arg(CordovaInternal::format(mime.name())).arg(fileInfo.lastModified().toMSecsSinceEpoch())
+ .arg(fileInfo.size());
+
+ this->callback(scId, args);
+ }
+}
+
+void File::getMetadata(int scId, int ecId, const QString &uri) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+ QFileInfo &fileInfo = f1.second;
+
+ if (!fileInfo.exists())
+ this->callback(ecId, FileError::kNotFoundErr);
+ else {
+ QVariantMap obj;
+ obj.insert("modificationTime", fileInfo.lastModified().toMSecsSinceEpoch());
+ obj.insert("size", fileInfo.isDir() ? 0 : fileInfo.size());
+ this->cb(scId, obj);
+ }
+}
+
+void File::readEntries(int scId, int ecId, const QString &uri) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+ QDir dir(f1.second.absoluteFilePath());
+ QString entriesList;
+
+ if (!dir.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ for (const QFileInfo &fileInfo: dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) {
+ entriesList += CordovaInternal::format(file2map(fileInfo)) + ",";
+ }
+ // Remove trailing comma
+ if (entriesList.size() > 0)
+ entriesList.remove(entriesList.size() - 1, 1);
+
+ entriesList = "new Array(" + entriesList + ")";
+
+ this->callback(scId, entriesList);
+}
+
+void File::readAsText(int scId, int ecId, const QString &uri, const QString &/*encoding*/, int sliceStart, int sliceEnd) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ QFile file(f1.second.absoluteFilePath());
+
+ if (!file.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ this->callback(ecId, FileError::kNotReadableErr);
+ return;
+ }
+
+ QByteArray content = file.readAll();
+
+ if (sliceEnd == -1)
+ sliceEnd = content.size();
+ if (sliceEnd < 0) {
+ sliceEnd++;
+ sliceEnd = std::max(0, content.size() + sliceEnd);
+ }
+ if (sliceEnd > content.size())
+ sliceEnd = content.size();
+
+ if (sliceStart < 0)
+ sliceStart = std::max(0, content.size() + sliceStart);
+ if (sliceStart > content.size())
+ sliceStart = content.size();
+
+ if (sliceStart > sliceEnd)
+ sliceEnd = sliceStart;
+
+ //FIXME: encoding
+ content = content.mid(sliceStart, sliceEnd - sliceStart);
+
+ this->cb(scId, content);
+}
+
+void File::readAsArrayBuffer(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) {
+ const QString str2array("\
+ (function strToArray(str) { \
+ var res = new Uint8Array(str.length); \
+ for (var i = 0; i < str.length; i++) { \
+ res[i] = str.charCodeAt(i); \
+ } \
+ return res; \
+ })(\"%1\")");
+
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ QFile file(f1.second.absoluteFilePath());
+
+ if (!file.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ this->callback(ecId, FileError::kNotReadableErr);
+ return;
+ }
+ QString res;
+ QByteArray content = file.readAll();
+
+ if (sliceEnd == -1)
+ sliceEnd = content.size();
+ if (sliceEnd < 0) {
+ sliceEnd++;
+ sliceEnd = std::max(0, content.size() + sliceEnd);
+ }
+ if (sliceEnd > content.size())
+ sliceEnd = content.size();
+
+ if (sliceStart < 0)
+ sliceStart = std::max(0, content.size() + sliceStart);
+ if (sliceStart > content.size())
+ sliceStart = content.size();
+
+ if (sliceStart > sliceEnd)
+ sliceEnd = sliceStart;
+
+ content = content.mid(sliceStart, sliceEnd - sliceStart);
+
+ res.reserve(content.length() * 6);
+ for (uchar c: content) {
+ res += "\\x";
+ res += QString::number(c, 16).rightJustified(2, '0').toUpper();
+ }
+
+ this->callback(scId, str2array.arg(res));
+}
+
+void File::readAsBinaryString(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ QFile file(f1.second.absoluteFilePath());
+
+ if (!file.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ this->callback(ecId, FileError::kNotReadableErr);
+ return;
+ }
+ QString res;
+ QByteArray content = file.readAll();
+
+ if (sliceEnd == -1)
+ sliceEnd = content.size();
+ if (sliceEnd < 0) {
+ sliceEnd++;
+ sliceEnd = std::max(0, content.size() + sliceEnd);
+ }
+ if (sliceEnd > content.size())
+ sliceEnd = content.size();
+
+ if (sliceStart < 0)
+ sliceStart = std::max(0, content.size() + sliceStart);
+ if (sliceStart > content.size())
+ sliceStart = content.size();
+
+ if (sliceStart > sliceEnd)
+ sliceEnd = sliceStart;
+
+ content = content.mid(sliceStart, sliceEnd - sliceStart);
+
+ res.reserve(content.length() * 6);
+ for (uchar c: content) {
+ res += "\\x";
+ res += QString::number(c, 16).rightJustified(2, '0').toUpper();
+ }
+ this->callback(scId, "\"" + res + "\"");
+}
+
+void File::readAsDataURL(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) {
+ QPair f1 = resolveURI(ecId, uri);
+
+ if (!f1.first)
+ return;
+
+ QFile file(f1.second.absoluteFilePath());
+ QFileInfo &fileInfo = f1.second;
+
+ if (!file.exists()) {
+ this->callback(ecId, FileError::kNotReadableErr);
+ return;
+ }
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ this->callback(ecId, FileError::kNotReadableErr);
+ return;
+ }
+
+ QByteArray content = file.readAll();
+ QString contentType(_db.mimeTypeForFile(fileInfo.fileName()).name());
+
+ if (sliceEnd == -1)
+ sliceEnd = content.size();
+ if (sliceEnd < 0) {
+ sliceEnd++;
+ sliceEnd = std::max(0, content.size() + sliceEnd);
+ }
+ if (sliceEnd > content.size())
+ sliceEnd = content.size();
+
+ if (sliceStart < 0)
+ sliceStart = std::max(0, content.size() + sliceStart);
+ if (sliceStart > content.size())
+ sliceStart = content.size();
+
+ if (sliceStart > sliceEnd)
+ sliceEnd = sliceStart;
+
+ content = content.mid(sliceStart, sliceEnd - sliceStart);
+
+ this->cb(scId, QString("data:%1;base64,").arg(contentType) + content.toBase64());
+}
+
+bool File::rmDir(const QDir &dir) {
+ if (dir == _persistentDir || dir == QDir::temp()) {//can't remove root dir
+ return false;
+ }
+ bool result = true;
+ if (dir.exists()) {
+ // Iterate over entries and remove them
+ Q_FOREACH(const QFileInfo &fileInfo, dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) {
+ if (fileInfo.isDir()) {
+ result = rmDir(fileInfo.absoluteFilePath());
+ }
+ else {
+ result = QFile::remove(fileInfo.absoluteFilePath());
+ }
+
+ if (!result) {
+ return result;
+ }
+ }
+
+ // Finally remove the current dir
+ return dir.rmdir(dir.absolutePath());
+ }
+ return result;
+}
+
+bool File::copyFile(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) {
+ QPair destDir = resolveURI(ecId, destinationUri);
+ QPair sourceFile = resolveURI(ecId, sourceUri);
+
+ if (!destDir.first || !sourceFile.first)
+ return false;
+
+ if (!checkFileName(newName)) {
+ this->callback(ecId, FileError::kEncodingErr);
+ return false;
+ }
+
+ if (destDir.second.isFile()) {
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return false;
+ }
+
+ if (!destDir.second.isDir()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return false;
+ }
+
+ QFileInfo &fileInfo = sourceFile.second;
+ QString fileName((newName.isEmpty()) ? fileInfo.fileName() : newName);
+ QString destinationFile(QDir(destDir.second.absoluteFilePath()).filePath(fileName));
+ if (QFile::copy(fileInfo.absoluteFilePath(), destinationFile)){
+ this->cb(scId, file2map(QFileInfo(destinationFile)));
+ return true;
+ }
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return false;
+}
+
+void File::copyDir(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) {
+ QPair destDir = resolveURI(ecId, destinationUri);
+ QPair sourceDir = resolveURI(ecId, sourceUri);
+
+ if (!destDir.first || !sourceDir.first)
+ return;
+ if (!checkFileName(newName)) {
+ this->callback(ecId, FileError::kEncodingErr);
+ return;
+ }
+
+ QString targetName = ((newName.isEmpty()) ? sourceDir.second.fileName() : newName);
+ QString target(QDir(destDir.second.absoluteFilePath()).filePath(targetName));
+
+ if (QFileInfo(target).isFile()){
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ // check: copy directory into itself
+ if (QDir(sourceDir.second.absoluteFilePath()).relativeFilePath(target)[0] != '.'){
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ if (!QDir(target).exists()){
+ QDir(destDir.second.absoluteFilePath()).mkdir(target);;
+ } else{
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ if (copyFolder(sourceDir.second.absoluteFilePath(), target)){
+ this->cb(scId, dir2map(QDir(target)));
+ return;
+ }
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+}
+
+void File::copyTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName) {
+ QPair f1 = resolveURI(ecId, source);
+
+ if (!f1.first)
+ return;
+
+ if (f1.second.isDir())
+ copyDir(scId, ecId, source, destinationDir, newName);
+ else
+ copyFile(scId, ecId, source, destinationDir, newName);
+}
+
+void File::moveFile(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) {
+ QPair sourceFile = resolveURI(ecId, sourceUri);
+ QPair destDir = resolveURI(ecId, destinationUri);
+
+ if (!destDir.first || !sourceFile.first)
+ return;
+ if (!checkFileName(newName)) {
+ this->callback(ecId, FileError::kEncodingErr);
+ return;
+ }
+
+ QString fileName = ((newName.isEmpty()) ? sourceFile.second.fileName() : newName);
+ QString target = QDir(destDir.second.absoluteFilePath()).filePath(fileName);
+
+ if (sourceFile.second == QFileInfo(target)) {
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ if (!destDir.second.exists()) {
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+ if (!destDir.second.isDir()){
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ if (QFileInfo(target).exists()) {
+ if (!QFile::remove(target)) {
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+ }
+
+ QFile::rename(sourceFile.second.absoluteFilePath(), target);
+ this->cb(scId, file2map(QFileInfo(target)));
+}
+
+void File::moveDir(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName){
+ QPair sourceDir = resolveURI(ecId, sourceUri);
+ QPair destDir = resolveURI(ecId, destinationUri);
+
+ if (!destDir.first || !sourceDir.first)
+ return;
+ if (!checkFileName(newName)) {
+ this->callback(ecId, FileError::kEncodingErr);
+ return;
+ }
+
+ QString fileName = ((newName.isEmpty()) ? sourceDir.second.fileName() : newName);
+ QString target = QDir(destDir.second.absoluteFilePath()).filePath(fileName);
+
+ if (!destDir.second.exists()){
+ this->callback(ecId, FileError::kNotFoundErr);
+ return;
+ }
+
+ if (destDir.second.isFile()){
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ // check: copy directory into itself
+ if (QDir(sourceDir.second.absoluteFilePath()).relativeFilePath(target)[0] != '.'){
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ if (QFileInfo(target).exists() && !QDir(destDir.second.absoluteFilePath()).rmdir(fileName)) {
+ this->callback(ecId, FileError::kInvalidModificationErr);
+ return;
+ }
+
+ if (copyFolder(sourceDir.second.absoluteFilePath(), target)) {
+ rmDir(sourceDir.second.absoluteFilePath());
+ this->cb(scId, file2map(QFileInfo(target)));
+ } else {
+ this->callback(ecId, FileError::kNoModificationAllowedErr);
+ }
+}
+
+void File::moveTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName) {
+ QPair f1 = resolveURI(ecId, source);
+
+ if (!f1.first)
+ return;
+
+ if (f1.second.isDir())
+ moveDir(scId, ecId, source, destinationDir, newName);
+ else
+ moveFile(scId, ecId, source, destinationDir, newName);
+}
+
+bool File::copyFolder(const QString& sourceFolder, const QString& destFolder) {
+ QDir sourceDir(sourceFolder);
+ if (!sourceDir.exists())
+ return false;
+ QDir destDir(destFolder);
+ if (!destDir.exists()){
+ destDir.mkdir(destFolder);
+ }
+ QStringList files = sourceDir.entryList(QDir::Files);
+ for (int i = 0; i< files.count(); i++)
+ {
+ QString srcName = sourceFolder + "/" + files[i];
+ QString destName = destFolder + "/" + files[i];
+ QFile::copy(srcName, destName);
+ }
+ files.clear();
+ files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
+ for (int i = 0; i< files.count(); i++)
+ {
+ QString srcName = sourceFolder + "/" + files[i];
+ QString destName = destFolder + "/" + files[i];
+ copyFolder(srcName, destName);
+ }
+ return true;
+}
diff --git a/plugins/org.apache.cordova.file/src/ubuntu/file.h b/plugins/org.apache.cordova.file/src/ubuntu/file.h
new file mode 100644
index 0000000..de27762
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ubuntu/file.h
@@ -0,0 +1,81 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FILEAPI_H_SDASDASDAS
+#define FILEAPI_H_SDASDASDAS
+
+#include
+#include
+
+#include
+#include
+
+class File: public CPlugin {
+ Q_OBJECT
+public:
+ explicit File(Cordova *cordova);
+
+ virtual const QString fullName() override {
+ return File::fullID();
+ }
+
+ virtual const QString shortName() override {
+ return "File";
+ }
+
+ static const QString fullID() {
+ return "File";
+ }
+ QPair resolveURI(const QString &uri);
+ QPair resolveURI(int ecId, const QString &uri);
+ QVariantMap file2map(const QFileInfo &dir);
+
+public slots:
+ void requestFileSystem(int scId, int ecId, unsigned short type, unsigned long long size);
+ void resolveLocalFileSystemURI(int scId, int ecId, const QString&);
+ void getDirectory(int scId, int ecId, const QString&, const QString&, const QVariantMap&);
+ void getFile(int scId, int ecId, const QString &parentPath, const QString &rpath, const QVariantMap &options);
+ void readEntries(int scId, int ecId, const QString &uri);
+ void getParent(int scId, int ecId, const QString &uri);
+ void copyTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName);
+ void moveTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName);
+ void getFileMetadata(int scId, int ecId, const QString &);
+ void getMetadata(int scId, int ecId, const QString &);
+ void remove(int scId, int ecId, const QString &);
+ void removeRecursively(int scId, int ecId, const QString&);
+ void write(int scId, int ecId, const QString&, const QString&, unsigned long long position, bool binary);
+ void readAsText(int scId, int ecId, const QString&, const QString &encoding, int sliceStart, int sliceEnd);
+ void readAsDataURL(int scId, int ecId, const QString&, int sliceStart, int sliceEnd);
+ void readAsArrayBuffer(int scId, int ecId, const QString&, int sliceStart, int sliceEnd);
+ void readAsBinaryString(int scId, int ecId, const QString&, int sliceStart, int sliceEnd);
+ void truncate(int scId, int ecId, const QString&, unsigned long long size);
+
+ void _getLocalFilesystemPath(int scId, int ecId, const QString&);
+private:
+ void moveFile(int scId, int ecId,const QString&, const QString&, const QString&);
+ void moveDir(int scId, int ecId,const QString&, const QString&, const QString&);
+ bool copyFile(int scId, int ecId, const QString&, const QString&, const QString&);
+ void copyDir(int scId, int ecId, const QString&, const QString&, const QString&);
+ bool rmDir(const QDir &dir);
+ bool copyFolder(const QString&, const QString&);
+
+ QPair GetRelativePath(const QFileInfo &fileInfo);
+ QVariantMap dir2map(const QDir &dir);
+
+ QMimeDatabase _db;
+ const QDir _persistentDir;
+ QNetworkAccessManager _manager;
+};
+
+#endif
diff --git a/plugins/org.apache.cordova.file/src/windows/FileProxy.js b/plugins/org.apache.cordova.file/src/windows/FileProxy.js
new file mode 100644
index 0000000..4148948
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/windows/FileProxy.js
@@ -0,0 +1,1140 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var cordova = require('cordova');
+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;
+
+var writeBytesAsync = Windows.Storage.FileIO.writeBytesAsync;
+var writeTextAsync = Windows.Storage.FileIO.writeTextAsync;
+var writeBlobAsync = function writeBlobAsync(storageFile, data) {
+ return storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite)
+ .then(function (output) {
+ 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 () {
+ return output.flushAsync().then(function () {
+ input.close();
+ output.close();
+
+ return dataSize;
+ });
+ });
+ });
+};
+
+var writeArrayBufferAsync = function writeArrayBufferAsync(storageFile, data) {
+ return writeBlobAsync(storageFile, new Blob([data]));
+};
+
+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,''));
+}
+
+var slashesRE = new RegExp('/{2,}','g');
+var parentRE = new RegExp('/([^/]+/)\\.\\.(/|$)','g');
+function sanitize(path) {
+ return path.replace(slashesRE,'/').replace(parentRE,'$2');
+}
+
+var WinFS = function(name, root) {
+ this.winpath = root.winpath;
+ if (this.winpath && !/\/$/.test(this.winpath)) {
+ this.winpath += "/";
+ }
+ this.makeNativeURL = function(path) {
+ return encodeURI(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]==='/'?'':'/')+encodeURI(fullPath));
+ return 'cdvfile://localhost' + path;
+};
+
+var AllFileSystems;
+
+function getAllFS() {
+ if (!AllFileSystems) {
+ var storageFolderPermanent = Windows.Storage.ApplicationData.current.localFolder.path,
+ storageFolderTemporary = Windows.Storage.ApplicationData.current.temporaryFolder.path;
+ 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 = decodeURI(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('%3A',':').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) {
+ var pending = [];
+ 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();
+ },
+ 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) {
+ win(Windows.Security.Cryptography.CryptographicBuffer.convertBinaryToString(encoding, buffer));
+ },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).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 fullPath = storageFile.path;
+ 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 inputURL;
+
+ 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);
diff --git a/plugins/org.apache.cordova.file/src/wp/File.cs b/plugins/org.apache.cordova.file/src/wp/File.cs
new file mode 100644
index 0000000..c383372
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/wp/File.cs
@@ -0,0 +1,1724 @@
+/*
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Runtime.Serialization;
+using System.Security;
+using System.Text;
+using System.Windows;
+using System.Windows.Resources;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+ ///
+ /// Provides access to isolated storage
+ ///
+ public class File : BaseCommand
+ {
+ // Error codes
+ public const int NOT_FOUND_ERR = 1;
+ public const int SECURITY_ERR = 2;
+ public const int ABORT_ERR = 3;
+ public const int NOT_READABLE_ERR = 4;
+ public const int ENCODING_ERR = 5;
+ public const int NO_MODIFICATION_ALLOWED_ERR = 6;
+ public const int INVALID_STATE_ERR = 7;
+ public const int SYNTAX_ERR = 8;
+ public const int INVALID_MODIFICATION_ERR = 9;
+ public const int QUOTA_EXCEEDED_ERR = 10;
+ public const int TYPE_MISMATCH_ERR = 11;
+ public const int PATH_EXISTS_ERR = 12;
+
+ // File system options
+ public const int TEMPORARY = 0;
+ public const int PERSISTENT = 1;
+ public const int RESOURCE = 2;
+ public const int APPLICATION = 3;
+
+ ///
+ /// Temporary directory name
+ ///
+ private readonly string TMP_DIRECTORY_NAME = "tmp";
+
+ ///
+ /// Represents error code for callback
+ ///
+ [DataContract]
+ public class ErrorCode
+ {
+ ///
+ /// Error code
+ ///
+ [DataMember(IsRequired = true, Name = "code")]
+ public int Code { get; set; }
+
+ ///
+ /// Creates ErrorCode object
+ ///
+ public ErrorCode(int code)
+ {
+ this.Code = code;
+ }
+ }
+
+ ///
+ /// Represents File action options.
+ ///
+ [DataContract]
+ public class FileOptions
+ {
+ ///
+ /// File path
+ ///
+ ///
+ private string _fileName;
+ [DataMember(Name = "fileName")]
+ public string FilePath
+ {
+ get
+ {
+ return this._fileName;
+ }
+
+ set
+ {
+ int index = value.IndexOfAny(new char[] { '#', '?' });
+ this._fileName = index > -1 ? value.Substring(0, index) : value;
+ }
+ }
+
+ ///
+ /// Full entryPath
+ ///
+ [DataMember(Name = "fullPath")]
+ public string FullPath { get; set; }
+
+ ///
+ /// Directory name
+ ///
+ [DataMember(Name = "dirName")]
+ public string DirectoryName { get; set; }
+
+ ///
+ /// Path to create file/directory
+ ///
+ [DataMember(Name = "path")]
+ public string Path { get; set; }
+
+ ///
+ /// The encoding to use to encode the file's content. Default is UTF8.
+ ///
+ [DataMember(Name = "encoding")]
+ public string Encoding { get; set; }
+
+ ///
+ /// Uri to get file
+ ///
+ ///
+ private string _uri;
+ [DataMember(Name = "uri")]
+ public string Uri
+ {
+ get
+ {
+ return this._uri;
+ }
+
+ set
+ {
+ int index = value.IndexOfAny(new char[] { '#', '?' });
+ this._uri = index > -1 ? value.Substring(0, index) : value;
+ }
+ }
+
+ ///
+ /// Size to truncate file
+ ///
+ [DataMember(Name = "size")]
+ public long Size { get; set; }
+
+ ///
+ /// Data to write in file
+ ///
+ [DataMember(Name = "data")]
+ public string Data { get; set; }
+
+ ///
+ /// Position the writing starts with
+ ///
+ [DataMember(Name = "position")]
+ public int Position { get; set; }
+
+ ///
+ /// Type of file system requested
+ ///
+ [DataMember(Name = "type")]
+ public int FileSystemType { get; set; }
+
+ ///
+ /// New file/directory name
+ ///
+ [DataMember(Name = "newName")]
+ public string NewName { get; set; }
+
+ ///
+ /// Destination directory to copy/move file/directory
+ ///
+ [DataMember(Name = "parent")]
+ public string Parent { get; set; }
+
+ ///
+ /// Options for getFile/getDirectory methods
+ ///
+ [DataMember(Name = "options")]
+ public CreatingOptions CreatingOpt { get; set; }
+
+ ///
+ /// Creates options object with default parameters
+ ///
+ public FileOptions()
+ {
+ this.SetDefaultValues(new StreamingContext());
+ }
+
+ ///
+ /// Initializes default values for class fields.
+ /// Implemented in separate method because default constructor is not invoked during deserialization.
+ ///
+ ///
+ [OnDeserializing()]
+ public void SetDefaultValues(StreamingContext context)
+ {
+ this.Encoding = "UTF-8";
+ this.FilePath = "";
+ this.FileSystemType = -1;
+ }
+ }
+
+ ///
+ /// Stores image info
+ ///
+ [DataContract]
+ public class FileMetadata
+ {
+ [DataMember(Name = "fileName")]
+ public string FileName { get; set; }
+
+ [DataMember(Name = "fullPath")]
+ public string FullPath { get; set; }
+
+ [DataMember(Name = "type")]
+ public string Type { get; set; }
+
+ [DataMember(Name = "lastModifiedDate")]
+ public string LastModifiedDate { get; set; }
+
+ [DataMember(Name = "size")]
+ public long Size { get; set; }
+
+ public FileMetadata(string filePath)
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ throw new FileNotFoundException("File doesn't exist");
+ }
+
+ this.FullPath = filePath;
+ this.Size = 0;
+ this.FileName = string.Empty;
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ bool IsFile = isoFile.FileExists(filePath);
+ bool IsDirectory = isoFile.DirectoryExists(filePath);
+
+ if (!IsDirectory)
+ {
+ if (!IsFile) // special case, if isoFile cannot find it, it might still be part of the app-package
+ {
+ // attempt to get it from the resources
+
+ Uri fileUri = new Uri(filePath, UriKind.Relative);
+ StreamResourceInfo streamInfo = Application.GetResourceStream(fileUri);
+ if (streamInfo != null)
+ {
+ this.Size = streamInfo.Stream.Length;
+ this.FileName = filePath.Substring(filePath.LastIndexOf("/") + 1);
+ }
+ else
+ {
+ throw new FileNotFoundException("File doesn't exist");
+ }
+ }
+ else
+ {
+ using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.Read, isoFile))
+ {
+ this.Size = stream.Length;
+ }
+
+ this.FileName = System.IO.Path.GetFileName(filePath);
+ this.LastModifiedDate = isoFile.GetLastWriteTime(filePath).DateTime.ToString();
+ }
+ }
+
+ this.Type = MimeTypeMapper.GetMimeType(this.FileName);
+ }
+ }
+ }
+
+ ///
+ /// Represents file or directory modification metadata
+ ///
+ [DataContract]
+ public class ModificationMetadata
+ {
+ ///
+ /// Modification time
+ ///
+ [DataMember]
+ public string modificationTime { get; set; }
+ }
+
+ ///
+ /// Represents file or directory entry
+ ///
+ [DataContract]
+ public class FileEntry
+ {
+
+ ///
+ /// File type
+ ///
+ [DataMember(Name = "isFile")]
+ public bool IsFile { get; set; }
+
+ ///
+ /// Directory type
+ ///
+ [DataMember(Name = "isDirectory")]
+ public bool IsDirectory { get; set; }
+
+ ///
+ /// File/directory name
+ ///
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ ///
+ /// Full path to file/directory
+ ///
+ [DataMember(Name = "fullPath")]
+ public string FullPath { get; set; }
+
+ public bool IsResource { get; set; }
+
+ public static FileEntry GetEntry(string filePath, bool bIsRes=false)
+ {
+ FileEntry entry = null;
+ try
+ {
+ entry = new FileEntry(filePath, bIsRes);
+
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Exception in GetEntry for filePath :: " + filePath + " " + ex.Message);
+ }
+ return entry;
+ }
+
+ ///
+ /// Creates object and sets necessary properties
+ ///
+ ///
+ public FileEntry(string filePath, bool bIsRes = false)
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ throw new ArgumentException();
+ }
+
+ if(filePath.Contains(" "))
+ {
+ Debug.WriteLine("FilePath with spaces :: " + filePath);
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ IsResource = bIsRes;
+ IsFile = isoFile.FileExists(filePath);
+ IsDirectory = isoFile.DirectoryExists(filePath);
+ if (IsFile)
+ {
+ this.Name = Path.GetFileName(filePath);
+ }
+ else if (IsDirectory)
+ {
+ this.Name = this.GetDirectoryName(filePath);
+ if (string.IsNullOrEmpty(Name))
+ {
+ this.Name = "/";
+ }
+ }
+ else
+ {
+ if (IsResource)
+ {
+ this.Name = Path.GetFileName(filePath);
+ }
+ else
+ {
+ throw new FileNotFoundException();
+ }
+ }
+
+ try
+ {
+ this.FullPath = filePath.Replace('\\', '/'); // new Uri(filePath).LocalPath;
+ }
+ catch (Exception)
+ {
+ this.FullPath = filePath;
+ }
+ }
+ }
+
+ ///
+ /// Extracts directory name from path string
+ /// Path should refer to a directory, for example \foo\ or /foo.
+ ///
+ ///
+ ///
+ private string GetDirectoryName(string path)
+ {
+ if (String.IsNullOrEmpty(path))
+ {
+ return path;
+ }
+
+ string[] split = path.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
+ if (split.Length < 1)
+ {
+ return null;
+ }
+ else
+ {
+ return split[split.Length - 1];
+ }
+ }
+ }
+
+
+ ///
+ /// Represents info about requested file system
+ ///
+ [DataContract]
+ public class FileSystemInfo
+ {
+ ///
+ /// file system type
+ ///
+ [DataMember(Name = "name", IsRequired = true)]
+ public string Name { get; set; }
+
+ ///
+ /// Root directory entry
+ ///
+ [DataMember(Name = "root", EmitDefaultValue = false)]
+ public FileEntry Root { get; set; }
+
+ ///
+ /// Creates class instance
+ ///
+ ///
+ /// Root directory
+ public FileSystemInfo(string name, FileEntry rootEntry = null)
+ {
+ Name = name;
+ Root = rootEntry;
+ }
+ }
+
+ [DataContract]
+ public class CreatingOptions
+ {
+ ///
+ /// Create file/directory if is doesn't exist
+ ///
+ [DataMember(Name = "create")]
+ public bool Create { get; set; }
+
+ ///
+ /// Generate an exception if create=true and file/directory already exists
+ ///
+ [DataMember(Name = "exclusive")]
+ public bool Exclusive { get; set; }
+
+
+ }
+
+ // returns null value if it fails.
+ private string[] getOptionStrings(string options)
+ {
+ string[] optStings = null;
+ try
+ {
+ optStings = JSON.JsonHelper.Deserialize(options);
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), CurrentCommandCallbackId);
+ }
+ return optStings;
+ }
+
+ ///
+ /// Gets amount of free space available for Isolated Storage
+ ///
+ /// No options is needed for this method
+ public void getFreeDiskSpace(string options)
+ {
+ string callbackId = getOptionStrings(options)[0];
+
+ try
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isoFile.AvailableFreeSpace), callbackId);
+ }
+ }
+ catch (IsolatedStorageException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ ///
+ /// Check if file exists
+ ///
+ /// File path
+ public void testFileExists(string options)
+ {
+ IsDirectoryOrFileExist(options, false);
+ }
+
+ ///
+ /// Check if directory exists
+ ///
+ /// directory name
+ public void testDirectoryExists(string options)
+ {
+ IsDirectoryOrFileExist(options, true);
+ }
+
+ ///
+ /// Check if file or directory exist
+ ///
+ /// File path/Directory name
+ /// Flag to recognize what we should check
+ public void IsDirectoryOrFileExist(string options, bool isDirectory)
+ {
+ string[] args = getOptionStrings(options);
+ string callbackId = args[1];
+ FileOptions fileOptions = JSON.JsonHelper.Deserialize(args[0]);
+ string filePath = args[0];
+
+ if (fileOptions == null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ }
+
+ try
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ bool isExist;
+ if (isDirectory)
+ {
+ isExist = isoFile.DirectoryExists(fileOptions.DirectoryName);
+ }
+ else
+ {
+ isExist = isoFile.FileExists(fileOptions.FilePath);
+ }
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isExist), callbackId);
+ }
+ }
+ catch (IsolatedStorageException) // default handler throws INVALID_MODIFICATION_ERR
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+
+ }
+
+ public void readAsDataURL(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string filePath = optStrings[0];
+ int startPos = int.Parse(optStrings[1]);
+ int endPos = int.Parse(optStrings[2]);
+ string callbackId = optStrings[3];
+
+ if (filePath != null)
+ {
+ try
+ {
+ string base64URL = null;
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoFile.FileExists(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+ string mimeType = MimeTypeMapper.GetMimeType(filePath);
+
+ using (IsolatedStorageFileStream stream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read))
+ {
+ string base64String = GetFileContent(stream);
+ base64URL = "data:" + mimeType + ";base64," + base64String;
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, base64URL), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+ }
+
+ public void readAsArrayBuffer(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string filePath = optStrings[0];
+ int startPos = int.Parse(optStrings[1]);
+ int endPos = int.Parse(optStrings[2]);
+ string callbackId = optStrings[3];
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR), callbackId);
+ }
+
+ public void readAsBinaryString(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string filePath = optStrings[0];
+ int startPos = int.Parse(optStrings[1]);
+ int endPos = int.Parse(optStrings[2]);
+ string callbackId = optStrings[3];
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR), callbackId);
+ }
+
+ public void readAsText(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string filePath = optStrings[0];
+ string encStr = optStrings[1];
+ int startPos = int.Parse(optStrings[2]);
+ int endPos = int.Parse(optStrings[3]);
+ string callbackId = optStrings[4];
+
+ try
+ {
+ string text = "";
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoFile.FileExists(filePath))
+ {
+ readResourceAsText(options);
+ return;
+ }
+ Encoding encoding = Encoding.GetEncoding(encStr);
+
+ using (IsolatedStorageFileStream reader = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read))
+ {
+ if (startPos < 0)
+ {
+ startPos = Math.Max((int)reader.Length + startPos, 0);
+ }
+ else if (startPos > 0)
+ {
+ startPos = Math.Min((int)reader.Length, startPos);
+ }
+
+ if (endPos > 0)
+ {
+ endPos = Math.Min((int)reader.Length, endPos);
+ }
+ else if (endPos < 0)
+ {
+ endPos = Math.Max(endPos + (int)reader.Length, 0);
+ }
+
+
+ var buffer = new byte[endPos - startPos];
+
+ reader.Seek(startPos, SeekOrigin.Begin);
+ reader.Read(buffer, 0, buffer.Length);
+
+ text = encoding.GetString(buffer, 0, buffer.Length);
+
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ ///
+ /// Reads application resource as a text
+ ///
+ /// Path to a resource
+ public void readResourceAsText(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string pathToResource = optStrings[0];
+ string encStr = optStrings[1];
+ int start = int.Parse(optStrings[2]);
+ int endMarker = int.Parse(optStrings[3]);
+ string callbackId = optStrings[4];
+
+ try
+ {
+ if (pathToResource.StartsWith("/"))
+ {
+ pathToResource = pathToResource.Remove(0, 1);
+ }
+
+ var resource = Application.GetResourceStream(new Uri(pathToResource, UriKind.Relative));
+
+ if (resource == null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+
+ string text;
+ StreamReader streamReader = new StreamReader(resource.Stream);
+ text = streamReader.ReadToEnd();
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ public void truncate(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+
+ string filePath = optStrings[0];
+ int size = int.Parse(optStrings[1]);
+ string callbackId = optStrings[2];
+
+ try
+ {
+ long streamLength = 0;
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoFile.FileExists(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+
+ using (FileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.ReadWrite, isoFile))
+ {
+ if (0 <= size && size <= stream.Length)
+ {
+ stream.SetLength(size);
+ }
+ streamLength = stream.Length;
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, streamLength), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ //write:[filePath,data,position,isBinary,callbackId]
+ public void write(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+
+ string filePath = optStrings[0];
+ string data = optStrings[1];
+ int position = int.Parse(optStrings[2]);
+ bool isBinary = bool.Parse(optStrings[3]);
+ string callbackId = optStrings[4];
+
+ try
+ {
+ if (string.IsNullOrEmpty(data))
+ {
+ Debug.WriteLine("Expected some data to be send in the write command to {0}", filePath);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+
+ byte[] dataToWrite = isBinary ? JSON.JsonHelper.Deserialize(data) :
+ System.Text.Encoding.UTF8.GetBytes(data);
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ // create the file if not exists
+ if (!isoFile.FileExists(filePath))
+ {
+ var file = isoFile.CreateFile(filePath);
+ file.Close();
+ }
+
+ using (FileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.ReadWrite, isoFile))
+ {
+ if (0 <= position && position <= stream.Length)
+ {
+ stream.SetLength(position);
+ }
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.Seek(0, SeekOrigin.End);
+ writer.Write(dataToWrite);
+ }
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, dataToWrite.Length), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ ///
+ /// Look up metadata about this entry.
+ ///
+ /// filePath to entry
+ public void getMetadata(string options)
+ {
+ string[] optStings = getOptionStrings(options);
+ string filePath = optStings[0];
+ string callbackId = optStings[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.FileExists(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK,
+ new ModificationMetadata() { modificationTime = isoFile.GetLastWriteTime(filePath).DateTime.ToString() }), callbackId);
+ }
+ else if (isoFile.DirectoryExists(filePath))
+ {
+ string modTime = isoFile.GetLastWriteTime(filePath).DateTime.ToString();
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new ModificationMetadata() { modificationTime = modTime }), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+
+ }
+ }
+ catch (IsolatedStorageException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ }
+
+
+ ///
+ /// Returns a File that represents the current state of the file that this FileEntry represents.
+ ///
+ /// filePath to entry
+ ///
+ public void getFileMetadata(string options)
+ {
+ string[] optStings = getOptionStrings(options);
+ string filePath = optStings[0];
+ string callbackId = optStings[1];
+
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ try
+ {
+ FileMetadata metaData = new FileMetadata(filePath);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, metaData), callbackId);
+ }
+ catch (IsolatedStorageException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+
+ ///
+ /// Look up the parent DirectoryEntry containing this Entry.
+ /// If this Entry is the root of IsolatedStorage, its parent is itself.
+ ///
+ ///
+ public void getParent(string options)
+ {
+ string[] optStings = getOptionStrings(options);
+ string filePath = optStings[0];
+ string callbackId = optStings[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId);
+ return;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ FileEntry entry;
+
+ if (isoFile.FileExists(filePath) || isoFile.DirectoryExists(filePath))
+ {
+
+
+ string path = this.GetParentDirectory(filePath);
+ entry = FileEntry.GetEntry(path);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry),callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ }
+
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ }
+ }
+ }
+ }
+
+ public void remove(string options)
+ {
+ string[] args = getOptionStrings(options);
+ string filePath = args[0];
+ string callbackId = args[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ if (filePath == "/" || filePath == "" || filePath == @"\")
+ {
+ throw new Exception("Cannot delete root file system") ;
+ }
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.FileExists(filePath))
+ {
+ isoFile.DeleteFile(filePath);
+ }
+ else
+ {
+ if (isoFile.DirectoryExists(filePath))
+ {
+ isoFile.DeleteDirectory(filePath);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ return;
+ }
+ }
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK),callbackId);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId);
+ }
+ }
+ }
+ }
+
+ public void removeRecursively(string options)
+ {
+ string[] args = getOptionStrings(options);
+ string filePath = args[0];
+ string callbackId = args[1];
+
+ if (filePath != null)
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId);
+ }
+ else
+ {
+ if (removeDirRecursively(filePath, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
+ }
+ }
+ }
+ }
+
+ public void readEntries(string options)
+ {
+ string[] args = getOptionStrings(options);
+ string filePath = args[0];
+ string callbackId = args[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId);
+ return;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.DirectoryExists(filePath))
+ {
+ string path = File.AddSlashToDirectory(filePath);
+ List entries = new List();
+ string[] files = isoFile.GetFileNames(path + "*");
+ string[] dirs = isoFile.GetDirectoryNames(path + "*");
+ foreach (string file in files)
+ {
+ entries.Add(FileEntry.GetEntry(path + file));
+ }
+ foreach (string dir in dirs)
+ {
+ entries.Add(FileEntry.GetEntry(path + dir + "/"));
+ }
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entries),callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId);
+ }
+ }
+ }
+ }
+
+ public void requestFileSystem(string options)
+ {
+ // TODO: try/catch
+ string[] optVals = getOptionStrings(options);
+ //FileOptions fileOptions = new FileOptions();
+ int fileSystemType = int.Parse(optVals[0]);
+ double size = double.Parse(optVals[1]);
+ string callbackId = optVals[2];
+
+
+ IsolatedStorageFile.GetUserStoreForApplication();
+
+ if (size > (10 * 1024 * 1024)) // 10 MB, compier will clean this up!
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, QUOTA_EXCEEDED_ERR), callbackId);
+ return;
+ }
+
+ try
+ {
+ if (size != 0)
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ long availableSize = isoFile.AvailableFreeSpace;
+ if (size > availableSize)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, QUOTA_EXCEEDED_ERR), callbackId);
+ return;
+ }
+ }
+ }
+
+ if (fileSystemType == PERSISTENT)
+ {
+ // TODO: this should be in it's own folder to prevent overwriting of the app assets, which are also in ISO
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("persistent", FileEntry.GetEntry("/"))), callbackId);
+ }
+ else if (fileSystemType == TEMPORARY)
+ {
+ using (IsolatedStorageFile isoStorage = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoStorage.FileExists(TMP_DIRECTORY_NAME))
+ {
+ isoStorage.CreateDirectory(TMP_DIRECTORY_NAME);
+ }
+ }
+
+ string tmpFolder = "/" + TMP_DIRECTORY_NAME + "/";
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("temporary", FileEntry.GetEntry(tmpFolder))), callbackId);
+ }
+ else if (fileSystemType == RESOURCE)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("resource")), callbackId);
+ }
+ else if (fileSystemType == APPLICATION)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("application")), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+ }
+ }
+
+ public void resolveLocalFileSystemURI(string options)
+ {
+
+ string[] optVals = getOptionStrings(options);
+ string uri = optVals[0].Split('?')[0];
+ string callbackId = optVals[1];
+
+ if (uri != null)
+ {
+ // a single '/' is valid, however, '/someDir' is not, but '/tmp//somedir' and '///someDir' are valid
+ if (uri.StartsWith("/") && uri.IndexOf("//") < 0 && uri != "/")
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ return;
+ }
+ try
+ {
+ // fix encoded spaces
+ string path = Uri.UnescapeDataString(uri);
+
+ FileEntry uriEntry = FileEntry.GetEntry(path);
+ if (uriEntry != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, uriEntry), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+ }
+ }
+ }
+
+ public void copyTo(string options)
+ {
+ TransferTo(options, false);
+ }
+
+ public void moveTo(string options)
+ {
+ TransferTo(options, true);
+ }
+
+ public void getFile(string options)
+ {
+ GetFileOrDirectory(options, false);
+ }
+
+ public void getDirectory(string options)
+ {
+ GetFileOrDirectory(options, true);
+ }
+
+ #region internal functionality
+
+ ///
+ /// Retrieves the parent directory name of the specified path,
+ ///
+ /// Path
+ /// Parent directory name
+ private string GetParentDirectory(string path)
+ {
+ if (String.IsNullOrEmpty(path) || path == "/")
+ {
+ return "/";
+ }
+
+ if (path.EndsWith(@"/") || path.EndsWith(@"\"))
+ {
+ return this.GetParentDirectory(Path.GetDirectoryName(path));
+ }
+
+ string result = Path.GetDirectoryName(path);
+ if (result == null)
+ {
+ result = "/";
+ }
+
+ return result;
+ }
+
+ private bool removeDirRecursively(string fullPath,string callbackId)
+ {
+ try
+ {
+ if (fullPath == "/")
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId);
+ return false;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.DirectoryExists(fullPath))
+ {
+ string tempPath = File.AddSlashToDirectory(fullPath);
+ string[] files = isoFile.GetFileNames(tempPath + "*");
+ if (files.Length > 0)
+ {
+ foreach (string file in files)
+ {
+ isoFile.DeleteFile(tempPath + file);
+ }
+ }
+ string[] dirs = isoFile.GetDirectoryNames(tempPath + "*");
+ if (dirs.Length > 0)
+ {
+ foreach (string dir in dirs)
+ {
+ if (!removeDirRecursively(tempPath + dir, callbackId))
+ {
+ return false;
+ }
+ }
+ }
+ isoFile.DeleteDirectory(fullPath);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private bool CanonicalCompare(string pathA, string pathB)
+ {
+ string a = pathA.Replace("//", "/");
+ string b = pathB.Replace("//", "/");
+
+ return a.Equals(b, StringComparison.OrdinalIgnoreCase);
+ }
+
+ /*
+ * copyTo:["fullPath","parent", "newName"],
+ * moveTo:["fullPath","parent", "newName"],
+ */
+ private void TransferTo(string options, bool move)
+ {
+ // TODO: try/catch
+ string[] optStrings = getOptionStrings(options);
+ string fullPath = optStrings[0];
+ string parent = optStrings[1];
+ string newFileName = optStrings[2];
+ string callbackId = optStrings[3];
+
+ char[] invalids = Path.GetInvalidPathChars();
+
+ if (newFileName.IndexOfAny(invalids) > -1 || newFileName.IndexOf(":") > -1 )
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ return;
+ }
+
+ try
+ {
+ if ((parent == null) || (string.IsNullOrEmpty(parent)) || (string.IsNullOrEmpty(fullPath)))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+
+ string parentPath = File.AddSlashToDirectory(parent);
+ string currentPath = fullPath;
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ bool isFileExist = isoFile.FileExists(currentPath);
+ bool isDirectoryExist = isoFile.DirectoryExists(currentPath);
+ bool isParentExist = isoFile.DirectoryExists(parentPath);
+
+ if ( ( !isFileExist && !isDirectoryExist ) || !isParentExist )
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+ string newName;
+ string newPath;
+ if (isFileExist)
+ {
+ newName = (string.IsNullOrEmpty(newFileName))
+ ? Path.GetFileName(currentPath)
+ : newFileName;
+
+ newPath = Path.Combine(parentPath, newName);
+
+ // sanity check ..
+ // cannot copy file onto itself
+ if (CanonicalCompare(newPath,currentPath)) //(parent + newFileName))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId);
+ return;
+ }
+ else if (isoFile.DirectoryExists(newPath))
+ {
+ // there is already a folder with the same name, operation is not allowed
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId);
+ return;
+ }
+ else if (isoFile.FileExists(newPath))
+ { // remove destination file if exists, in other case there will be exception
+ isoFile.DeleteFile(newPath);
+ }
+
+ if (move)
+ {
+ isoFile.MoveFile(currentPath, newPath);
+ }
+ else
+ {
+ isoFile.CopyFile(currentPath, newPath, true);
+ }
+ }
+ else
+ {
+ newName = (string.IsNullOrEmpty(newFileName))
+ ? currentPath
+ : newFileName;
+
+ newPath = Path.Combine(parentPath, newName);
+
+ if (move)
+ {
+ // remove destination directory if exists, in other case there will be exception
+ // target directory should be empty
+ if (!newPath.Equals(currentPath) && isoFile.DirectoryExists(newPath))
+ {
+ isoFile.DeleteDirectory(newPath);
+ }
+
+ isoFile.MoveDirectory(currentPath, newPath);
+ }
+ else
+ {
+ CopyDirectory(currentPath, newPath, isoFile);
+ }
+ }
+ FileEntry entry = FileEntry.GetEntry(newPath);
+ if (entry != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+ }
+ }
+
+ private bool HandleException(Exception ex, string cbId="")
+ {
+ bool handled = false;
+ string callbackId = String.IsNullOrEmpty(cbId) ? this.CurrentCommandCallbackId : cbId;
+ if (ex is SecurityException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR), callbackId);
+ handled = true;
+ }
+ else if (ex is FileNotFoundException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ handled = true;
+ }
+ else if (ex is ArgumentException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ handled = true;
+ }
+ else if (ex is IsolatedStorageException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId);
+ handled = true;
+ }
+ else if (ex is DirectoryNotFoundException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ handled = true;
+ }
+ return handled;
+ }
+
+ private void CopyDirectory(string sourceDir, string destDir, IsolatedStorageFile isoFile)
+ {
+ string path = File.AddSlashToDirectory(sourceDir);
+
+ bool bExists = isoFile.DirectoryExists(destDir);
+
+ if (!bExists)
+ {
+ isoFile.CreateDirectory(destDir);
+ }
+
+ destDir = File.AddSlashToDirectory(destDir);
+
+ string[] files = isoFile.GetFileNames(path + "*");
+
+ if (files.Length > 0)
+ {
+ foreach (string file in files)
+ {
+ isoFile.CopyFile(path + file, destDir + file,true);
+ }
+ }
+ string[] dirs = isoFile.GetDirectoryNames(path + "*");
+ if (dirs.Length > 0)
+ {
+ foreach (string dir in dirs)
+ {
+ CopyDirectory(path + dir, destDir + dir, isoFile);
+ }
+ }
+ }
+
+ private string RemoveExtraSlash(string path) {
+ if (path.StartsWith("//")) {
+ path = path.Remove(0, 1);
+ path = RemoveExtraSlash(path);
+ }
+ return path;
+ }
+
+ private string ResolvePath(string parentPath, string path)
+ {
+ string absolutePath = null;
+
+ if (path.Contains(".."))
+ {
+ if (parentPath.Length > 1 && parentPath.StartsWith("/") && parentPath !="/")
+ {
+ parentPath = RemoveExtraSlash(parentPath);
+ }
+
+ string fullPath = Path.GetFullPath(Path.Combine(parentPath, path));
+ absolutePath = fullPath.Replace(Path.GetPathRoot(fullPath), @"//");
+ }
+ else
+ {
+ absolutePath = Path.Combine(parentPath + "/", path);
+ }
+ return absolutePath;
+ }
+
+ private void GetFileOrDirectory(string options, bool getDirectory)
+ {
+ FileOptions fOptions = new FileOptions();
+ string[] args = getOptionStrings(options);
+
+ fOptions.FullPath = args[0];
+ fOptions.Path = args[1];
+
+ string callbackId = args[3];
+
+ try
+ {
+ fOptions.CreatingOpt = JSON.JsonHelper.Deserialize(args[2]);
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+
+ try
+ {
+ if ((string.IsNullOrEmpty(fOptions.Path)) || (string.IsNullOrEmpty(fOptions.FullPath)))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+
+ string path;
+
+ if (fOptions.Path.Split(':').Length > 2)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ return;
+ }
+
+ try
+ {
+ path = ResolvePath(fOptions.FullPath, fOptions.Path);
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ return;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ bool isFile = isoFile.FileExists(path);
+ bool isDirectory = isoFile.DirectoryExists(path);
+ bool create = (fOptions.CreatingOpt == null) ? false : fOptions.CreatingOpt.Create;
+ bool exclusive = (fOptions.CreatingOpt == null) ? false : fOptions.CreatingOpt.Exclusive;
+ if (create)
+ {
+ if (exclusive && (isoFile.FileExists(path) || isoFile.DirectoryExists(path)))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, PATH_EXISTS_ERR), callbackId);
+ return;
+ }
+
+ // need to make sure the parent exists
+ // it is an error to create a directory whose immediate parent does not yet exist
+ // see issue: https://issues.apache.org/jira/browse/CB-339
+ string[] pathParts = path.Split('/');
+ string builtPath = pathParts[0];
+ for (int n = 1; n < pathParts.Length - 1; n++)
+ {
+ builtPath += "/" + pathParts[n];
+ if (!isoFile.DirectoryExists(builtPath))
+ {
+ Debug.WriteLine(String.Format("Error :: Parent folder \"{0}\" does not exist, when attempting to create \"{1}\"",builtPath,path));
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+ }
+
+ if ((getDirectory) && (!isDirectory))
+ {
+ isoFile.CreateDirectory(path);
+ }
+ else
+ {
+ if ((!getDirectory) && (!isFile))
+ {
+
+ IsolatedStorageFileStream fileStream = isoFile.CreateFile(path);
+ fileStream.Close();
+ }
+ }
+ }
+ else // (not create)
+ {
+ if ((!isFile) && (!isDirectory))
+ {
+ if (path.IndexOf("//www") == 0)
+ {
+ Uri fileUri = new Uri(path.Remove(0,2), UriKind.Relative);
+ StreamResourceInfo streamInfo = Application.GetResourceStream(fileUri);
+ if (streamInfo != null)
+ {
+ FileEntry _entry = FileEntry.GetEntry(fileUri.OriginalString,true);
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, _entry), callbackId);
+
+ //using (BinaryReader br = new BinaryReader(streamInfo.Stream))
+ //{
+ // byte[] data = br.ReadBytes((int)streamInfo.Stream.Length);
+
+ //}
+
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+
+
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ return;
+ }
+ if (((getDirectory) && (!isDirectory)) || ((!getDirectory) && (!isFile)))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, TYPE_MISMATCH_ERR), callbackId);
+ return;
+ }
+ }
+ FileEntry entry = FileEntry.GetEntry(path);
+ if (entry != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+ }
+ }
+
+ private static string AddSlashToDirectory(string dirPath)
+ {
+ if (dirPath.EndsWith("/"))
+ {
+ return dirPath;
+ }
+ else
+ {
+ return dirPath + "/";
+ }
+ }
+
+ ///
+ /// Returns file content in a form of base64 string
+ ///
+ /// File stream
+ /// Base64 representation of the file
+ private string GetFileContent(Stream stream)
+ {
+ int streamLength = (int)stream.Length;
+ byte[] fileData = new byte[streamLength + 1];
+ stream.Read(fileData, 0, streamLength);
+ stream.Close();
+ return Convert.ToBase64String(fileData);
+ }
+
+ #endregion
+
+ }
+}
diff --git a/plugins/org.apache.cordova.file/tests/plugin.xml b/plugins/org.apache.cordova.file/tests/plugin.xml
new file mode 100644
index 0000000..7faccd7
--- /dev/null
+++ b/plugins/org.apache.cordova.file/tests/plugin.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ Cordova File Plugin Tests
+ Apache 2.0
+
+
+
+
diff --git a/plugins/org.apache.cordova.file/tests/tests.js b/plugins/org.apache.cordova.file/tests/tests.js
new file mode 100644
index 0000000..1aa9d9c
--- /dev/null
+++ b/plugins/org.apache.cordova.file/tests/tests.js
@@ -0,0 +1,3198 @@
+/*
+ *
+ * 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.
+ *
+ */
+exports.defineAutoTests = function () {
+ var isWindows = (cordova.platformId == "windows") || (navigator.appVersion.indexOf("MSAppHost/1.0") !== -1);
+
+ describe('File API', function () {
+ // Adding a Jasmine helper matcher, to report errors when comparing to FileError better.
+ var fileErrorMap = {
+ 1 : 'NOT_FOUND_ERR',
+ 2 : 'SECURITY_ERR',
+ 3 : 'ABORT_ERR',
+ 4 : 'NOT_READABLE_ERR',
+ 5 : 'ENCODING_ERR',
+ 6 : 'NO_MODIFICATION_ALLOWED_ERR',
+ 7 : 'INVALID_STATE_ERR',
+ 8 : 'SYNTAX_ERR',
+ 9 : 'INVALID_MODIFICATION_ERR',
+ 10 : 'QUOTA_EXCEEDED_ERR',
+ 11 : 'TYPE_MISMATCH_ERR',
+ 12 : 'PATH_EXISTS_ERR'
+ },
+ root,
+ temp_root,
+ persistent_root;
+ beforeEach(function (done) {
+ // Custom Matchers
+ jasmine.Expectation.addMatchers({
+ toBeFileError : function () {
+ return {
+ compare : function (error, code) {
+ var pass = error.code == code;
+ return {
+ pass : pass,
+ message : 'Expected FileError with code ' + fileErrorMap[error.code] + ' (' + error.code + ') to be ' + fileErrorMap[code] + '(' + code + ')'
+ };
+ }
+ };
+ },
+ toCanonicallyMatch : function () {
+ return {
+ compare : function (currentPath, path) {
+ var a = path.split("/").join("").split("\\").join(""),
+ b = currentPath.split("/").join("").split("\\").join(""),
+ pass = a == b;
+ return {
+ pass : pass,
+ message : 'Expected paths to match : ' + path + ' should be ' + currentPath
+ };
+ }
+ };
+ },
+ toFailWithMessage : function () {
+ return {
+ compare : function (error, message) {
+ var pass = false;
+ return {
+ pass : pass,
+ message : message
+ };
+ }
+ };
+ }
+ });
+ //Define global variables
+ var onError = function (e) {
+ console.log('[ERROR] Problem setting up root filesystem for test running! Error to follow.');
+ console.log(JSON.stringify(e));
+ };
+ window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) {
+ root = fileSystem.root;
+ // set in file.tests.js
+ persistent_root = root;
+ window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, function (fileSystem) {
+ temp_root = fileSystem.root;
+ // set in file.tests.js
+ done();
+ }, onError);
+ }, onError);
+ });
+ // HELPER FUNCTIONS
+ // deletes specified file or directory
+ var deleteEntry = function (name, success, error) {
+ // deletes entry, if it exists
+ window.resolveLocalFileSystemURL(root.toURL() + '/' + name, function (entry) {
+ if (entry.isDirectory === true) {
+ entry.removeRecursively(success, error);
+ } else {
+ entry.remove(success, error);
+ }
+ }, success);
+ };
+ // deletes file, if it exists, then invokes callback
+ var deleteFile = function (fileName, callback) {
+ root.getFile(fileName, null, // remove file system entry
+ function (entry) {
+ entry.remove(callback, function () {
+ console.log('[ERROR] deleteFile cleanup method invoked fail callback.');
+ });
+ }, // doesn't exist
+ callback);
+ };
+ // deletes and re-creates the specified file
+ var createFile = function (fileName, success, error) {
+ deleteEntry(fileName, function () {
+ root.getFile(fileName, {
+ create : true
+ }, success, error);
+ }, error);
+ };
+ // deletes and re-creates the specified directory
+ var createDirectory = function (dirName, success, error) {
+ deleteEntry(dirName, function () {
+ root.getDirectory(dirName, {
+ create : true
+ }, success, error);
+ }, error);
+ };
+ var failed = function (done, msg, error) {
+ var info = typeof msg == 'undefined' ? 'Unexpected error callback' : msg;
+ expect(true).toFailWithMessage(info + '\n' + JSON.stringify(error));
+ done();
+ };
+ var succeed = function (done, msg) {
+ var info = typeof msg == 'undefined' ? 'Unexpected success callback' : msg;
+ expect(true).toFailWithMessage(info);
+ done();
+ };
+ var joinURL = function (base, extension) {
+ if (base.charAt(base.length - 1) !== '/' && extension.charAt(0) !== '/') {
+ return base + '/' + extension;
+ }
+ if (base.charAt(base.length - 1) === '/' && extension.charAt(0) === '/') {
+ return base + exension.substring(1);
+ }
+ return base + extension;
+ };
+ describe('FileError object', function () {
+ it("file.spec.1 should define FileError constants", function () {
+ expect(FileError.NOT_FOUND_ERR).toBe(1);
+ expect(FileError.SECURITY_ERR).toBe(2);
+ expect(FileError.ABORT_ERR).toBe(3);
+ expect(FileError.NOT_READABLE_ERR).toBe(4);
+ expect(FileError.ENCODING_ERR).toBe(5);
+ expect(FileError.NO_MODIFICATION_ALLOWED_ERR).toBe(6);
+ expect(FileError.INVALID_STATE_ERR).toBe(7);
+ expect(FileError.SYNTAX_ERR).toBe(8);
+ expect(FileError.INVALID_MODIFICATION_ERR).toBe(9);
+ expect(FileError.QUOTA_EXCEEDED_ERR).toBe(10);
+ expect(FileError.TYPE_MISMATCH_ERR).toBe(11);
+ expect(FileError.PATH_EXISTS_ERR).toBe(12);
+ });
+ });
+ describe('LocalFileSystem', function () {
+ it("file.spec.2 should define LocalFileSystem constants", function () {
+ expect(LocalFileSystem.TEMPORARY).toBe(0);
+ expect(LocalFileSystem.PERSISTENT).toBe(1);
+ });
+ describe('window.requestFileSystem', function () {
+ it("file.spec.3 should be defined", function () {
+ expect(window.requestFileSystem).toBeDefined();
+ });
+ it("file.spec.4 should be able to retrieve a PERSISTENT file system", function (done) {
+ var win = function (fileSystem) {
+ expect(fileSystem).toBeDefined();
+ expect(fileSystem.name).toBeDefined();
+ expect(fileSystem.name).toBe("persistent");
+ expect(fileSystem.root).toBeDefined();
+ expect(fileSystem.root.filesystem).toBeDefined();
+ // Shouldn't use cdvfile by default.
+ expect(fileSystem.root.toURL()).not.toMatch(/^cdvfile:/);
+ // All DirectoryEntry URLs should always have a trailing slash.
+ expect(fileSystem.root.toURL()).toMatch(/\/$/);
+ done();
+ };
+ // retrieve PERSISTENT file system
+ window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, win, failed.bind(null, done, 'window.requestFileSystem - Error retrieving PERSISTENT file system'));
+ });
+ it("file.spec.5 should be able to retrieve a TEMPORARY file system", function (done) {
+ var win = function (fileSystem) {
+ expect(fileSystem).toBeDefined();
+ expect(fileSystem.name).toBeDefined();
+ expect(fileSystem.name).toBe("temporary");
+ expect(fileSystem.root).toBeDefined();
+ expect(fileSystem.root.filesystem).toBeDefined();
+ expect(fileSystem.root.filesystem).toBe(fileSystem);
+ done();
+ };
+ //retrieve TEMPORARY file system
+ window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, win, failed.bind(null, done, 'window.requestFileSystem - Error retrieving TEMPORARY file system'));
+ });
+ it("file.spec.6 should error if you request a file system that is too large", function (done) {
+ var fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.QUOTA_EXCEEDED_ERR);
+ done();
+ };
+ //win = createWin('window.requestFileSystem');
+ // Request the file system
+ window.requestFileSystem(LocalFileSystem.TEMPORARY, 1000000000000000, failed.bind(null, done, 'window.requestFileSystem - Error retrieving TEMPORARY file system'), fail);
+ });
+ it("file.spec.7 should error out if you request a file system that does not exist", function (done) {
+ var fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.SYNTAX_ERR);
+ done();
+ };
+ // Request the file system
+ window.requestFileSystem(-1, 0, succeed.bind(null, done, 'window.requestFileSystem'), fail);
+ });
+ });
+ describe('window.resolveLocalFileSystemURL', function () {
+ it("file.spec.8 should be defined", function () {
+ expect(window.resolveLocalFileSystemURL).toBeDefined();
+ });
+ it("file.spec.9 should resolve a valid file name", function (done) {
+ var fileName = 'file.spec.9';
+ var win = function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.name).toCanonicallyMatch(fileName);
+ expect(fileEntry.toURL()).not.toMatch(/^cdvfile:/, 'should not use cdvfile URL');
+ expect(fileEntry.toURL()).not.toMatch(/\/$/, 'URL should not end with a slash');
+ // Clean-up
+ deleteEntry(fileName);
+ //End
+ done();
+ };
+ createFile(fileName, function (entry) {
+ window.resolveLocalFileSystemURL(entry.toURL(), win, failed.bind(null, done, 'window.resolveLocalFileSystemURL - Error resolving file URL: ' + entry.toURL()));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName), failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.9.5 should resolve a directory", function (done) {
+ var fileName = 'file.spec.9.5';
+ var win = function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.name).toCanonicallyMatch(fileName);
+ expect(fileEntry.toURL()).not.toMatch(/^cdvfile:/, 'should not use cdvfile URL');
+ expect(fileEntry.toURL()).toMatch(/\/$/, 'URL end with a slash');
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ };
+ function gotDirectory(entry) {
+ // lookup file system entry
+ window.resolveLocalFileSystemURL(entry.toURL(), win, failed.bind(null, done, 'window.resolveLocalFileSystemURL - Error resolving directory URL: ' + entry.toURL()));
+ }
+ createDirectory(fileName, gotDirectory, failed.bind(null, done, 'createDirectory - Error creating directory: ' + fileName), failed.bind(null, done, 'createDirectory - Error creating directory: ' + fileName));
+ });
+ it("file.spec.10 resolve valid file name with parameters", function (done) {
+ var fileName = "resolve.file.uri.params",
+ win = function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ if (fileEntry.toURL().toLowerCase().substring(0, 10) === "cdvfile://") {
+ expect(fileEntry.fullPath).toBe("/" + fileName + "?1234567890");
+ }
+ expect(fileEntry.name).toBe(fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ };
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ window.resolveLocalFileSystemURL(entry.toURL() + "?1234567890", win, failed.bind(null, done, 'window.resolveLocalFileSystemURL - Error resolving file URI: ' + entry.toURL()));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.11 should error (NOT_FOUND_ERR) when resolving (non-existent) invalid file name", function (done) {
+ var fileName = cordova.platformId === 'windowsphone' ? root.toURL() + "/" + "this.is.not.a.valid.file.txt" : joinURL(root.toURL(), "this.is.not.a.valid.file.txt");
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ };
+ // lookup file system entry
+ window.resolveLocalFileSystemURL(fileName, succeed.bind(null, done, 'window.resolveLocalFileSystemURL - Error unexpected callback resolving file URI: ' + fileName), fail);
+ });
+ it("file.spec.12 should error (ENCODING_ERR) when resolving invalid URI with leading /", function (done) {
+ var fileName = "/this.is.not.a.valid.url",
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.ENCODING_ERR);
+ done();
+ };
+ // lookup file system entry
+ window.resolveLocalFileSystemURL(fileName, succeed.bind(null, done, 'window.resolveLocalFileSystemURL - Error unexpected callback resolving file URI: ' + fileName), fail);
+ });
+ });
+ });
+ //LocalFileSystem
+ describe('Metadata interface', function () {
+ it("file.spec.13 should exist and have the right properties", function () {
+ var metadata = new Metadata();
+ expect(metadata).toBeDefined();
+ expect(metadata.modificationTime).toBeDefined();
+ });
+ });
+ describe('Flags interface', function () {
+ it("file.spec.14 should exist and have the right properties", function () {
+ var flags = new Flags(false, true);
+ expect(flags).toBeDefined();
+ expect(flags.create).toBeDefined();
+ expect(flags.create).toBe(false);
+ expect(flags.exclusive).toBeDefined();
+ expect(flags.exclusive).toBe(true);
+ });
+ });
+ describe('FileSystem interface', function () {
+ it("file.spec.15 should have a root that is a DirectoryEntry", function (done) {
+ var win = function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(false);
+ expect(entry.isDirectory).toBe(true);
+ expect(entry.name).toBeDefined();
+ expect(entry.fullPath).toBeDefined();
+ expect(entry.getMetadata).toBeDefined();
+ expect(entry.moveTo).toBeDefined();
+ expect(entry.copyTo).toBeDefined();
+ expect(entry.toURL).toBeDefined();
+ expect(entry.remove).toBeDefined();
+ expect(entry.getParent).toBeDefined();
+ expect(entry.createReader).toBeDefined();
+ expect(entry.getFile).toBeDefined();
+ expect(entry.getDirectory).toBeDefined();
+ expect(entry.removeRecursively).toBeDefined();
+ done();
+ };
+ window.resolveLocalFileSystemURL(root.toURL(), win, failed.bind(null, done, 'window.resolveLocalFileSystemURL - Error resolving file URI: ' + root.toURL()));
+ });
+ });
+ describe('DirectoryEntry', function () {
+ it("file.spec.16 getFile: get Entry for file that does not exist", function (done) {
+ var fileName = "de.no.file",
+ filePath = joinURL(root.fullPath, fileName),
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ };
+ // create:false, exclusive:false, file does not exist
+ root.getFile(fileName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getFile - Error unexpected callback, file should not exists: ' + fileName), fail);
+ });
+ it("file.spec.17 getFile: create new file", function (done) {
+ var fileName = "de.create.file",
+ filePath = joinURL(root.fullPath, fileName),
+ win = function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toCanonicallyMatch(fileName);
+ expect(entry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ entry.remove(null, null);
+ done();
+ };
+ // create:true, exclusive:false, file does not exist
+ root.getFile(fileName, {
+ create : true
+ }, win, succeed.bind(null, done, 'root.getFile - Error unexpected callback, file should not exists: ' + fileName));
+ });
+ it("file.spec.18 getFile: create new file (exclusive)", function (done) {
+ var fileName = "de.create.exclusive.file",
+ filePath = joinURL(root.fullPath, fileName),
+ win = function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toBe(fileName);
+ expect(entry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ entry.remove(null, null);
+ done();
+ };
+ // create:true, exclusive:true, file does not exist
+ root.getFile(fileName, {
+ create : true,
+ exclusive : true
+ }, win, failed.bind(null, done, 'root.getFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.19 getFile: create file that already exists", function (done) {
+ var fileName = "de.create.existing.file",
+ filePath = joinURL(root.fullPath, fileName),
+ getFile = function (file) {
+ // create:true, exclusive:false, file exists
+ root.getFile(fileName, {
+ create : true
+ }, win, failed.bind(null, done, 'root.getFile - Error creating file: ' + fileName));
+ },
+ win = function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toCanonicallyMatch(fileName);
+ expect(entry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ entry.remove(null, fail);
+ done();
+ };
+ // create file to kick off it
+ root.getFile(fileName, {
+ create : true
+ }, getFile, fail);
+ });
+ it("file.spec.20 getFile: create file that already exists (exclusive)", function (done) {
+ var fileName = "de.create.exclusive.existing.file",
+ filePath = joinURL(root.fullPath, fileName),
+ existingFile,
+ getFile = function (file) {
+ existingFile = file;
+ // create:true, exclusive:true, file exists
+ root.getFile(fileName, {
+ create : true,
+ exclusive : true
+ }, succeed.bind(null, done, 'root.getFile - getFile function - Error unexpected callback, file should exists: ' + fileName), fail);
+ },
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.PATH_EXISTS_ERR);
+ // cleanup
+ existingFile.remove(null, null);
+ done();
+ };
+ // create file to kick off it
+ root.getFile(fileName, {
+ create : true
+ }, getFile, failed.bind(null, done, 'root.getFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.21 DirectoryEntry.getFile: get Entry for existing file", function (done) {
+ var fileName = "de.get.file",
+ filePath = joinURL(root.fullPath, fileName),
+ win = function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toCanonicallyMatch(fileName);
+ expect(entry.fullPath).toCanonicallyMatch(filePath);
+ expect(entry.filesystem).toBeDefined();
+ expect(entry.filesystem).toBe(root.filesystem);
+ //clean up
+ entry.remove(null, null);
+ done();
+ },
+ getFile = function (file) {
+ // create:false, exclusive:false, file exists
+ root.getFile(fileName, {
+ create : false
+ }, win, failed.bind(null, done, 'root.getFile - Error getting file entry: ' + fileName));
+ };
+ // create file to kick off it
+ root.getFile(fileName, {
+ create : true
+ }, getFile, failed.bind(null, done, 'root.getFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.22 DirectoryEntry.getFile: get FileEntry for invalid path", function (done) {
+ var fileName = "de:invalid:path",
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.ENCODING_ERR);
+ done();
+ };
+ // create:false, exclusive:false, invalid path
+ root.getFile(fileName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getFile - Error unexpected callback, file should not exists: ' + fileName), fail);
+ });
+ it("file.spec.23 DirectoryEntry.getDirectory: get Entry for directory that does not exist", function (done) {
+ var dirName = "de.no.dir",
+ dirPath = joinURL(root.fullPath, dirName),
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ };
+ // create:false, exclusive:false, directory does not exist
+ root.getDirectory(dirName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getDirectory - Error unexpected callback, directory should not exists: ' + dirName), fail);
+ });
+ it("file.spec.24 DirectoryEntry.getDirectory: create new dir with space then resolveLocalFileSystemURL", function (done) {
+ var dirName = "de create dir",
+ dirPath = joinURL(root.fullPath, encodeURIComponent(dirName)),
+ getDir = function (dirEntry) {
+ expect(dirEntry.filesystem).toBeDefined();
+ expect(dirEntry.filesystem).toBe(root.filesystem);
+ var dirURI = dirEntry.toURL();
+ // now encode URI and try to resolve
+ window.resolveLocalFileSystemURL(dirURI, win, failed.bind(null, done, 'window.resolveLocalFileSystemURL - getDir function - Error resolving directory: ' + dirURI));
+ },
+ win = function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.name).toCanonicallyMatch(dirName);
+ expect(directory.fullPath).toCanonicallyMatch(joinURL(root.fullPath, dirName));
+ // cleanup
+ directory.remove(null, null);
+ done();
+ };
+ // create:true, exclusive:false, directory does not exist
+ root.getDirectory(dirName, {
+ create : true
+ }, getDir, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ // This test is excluded, and should probably be removed. Filesystem
+ // should always be properly encoded URLs, and *not* raw paths, and it
+ // doesn't make sense to double-encode the URLs and expect that to be
+ // handled by the implementation.
+ // If a particular platform uses paths internally rather than URLs, // then that platform should careful to pass them correctly to its
+ // backend.
+ xit("file.spec.25 DirectoryEntry.getDirectory: create new dir with space resolveLocalFileSystemURL with encoded URI", function (done) {
+ var dirName = "de create dir2",
+ dirPath = joinURL(root.fullPath, dirName),
+ getDir = function (dirEntry) {
+ var dirURI = dirEntry.toURL();
+ // now encode URI and try to resolve
+ window.resolveLocalFileSystemURL(encodeURI(dirURI), win, failed.bind(null, done, 'window.resolveLocalFileSystemURL - getDir function - Error resolving directory: ' + dirURI));
+ },
+ win = function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.name).toCanonicallyMatch(dirName);
+ expect(directory.fullPath).toCanonicallyMatch(dirPath);
+ // cleanup
+ directory.remove(null, null);
+ done();
+ };
+ // create:true, exclusive:false, directory does not exist
+ root.getDirectory(dirName, {
+ create : true
+ }, getDir, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.26 DirectoryEntry.getDirectory: create new directory", function (done) {
+ var dirName = "de.create.dir",
+ dirPath = joinURL(root.fullPath, dirName),
+ win = function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.name).toCanonicallyMatch(dirName);
+ expect(directory.fullPath).toCanonicallyMatch(dirPath);
+ expect(directory.filesystem).toBeDefined();
+ expect(directory.filesystem).toBe(root.filesystem);
+ // cleanup
+ directory.remove(null, null);
+ done();
+ };
+ // create:true, exclusive:false, directory does not exist
+ root.getDirectory(dirName, {
+ create : true
+ }, win, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.27 DirectoryEntry.getDirectory: create new directory (exclusive)", function (done) {
+ var dirName = "de.create.exclusive.dir",
+ dirPath = joinURL(root.fullPath, dirName),
+ win = function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.name).toCanonicallyMatch(dirName);
+ expect(directory.fullPath).toCanonicallyMatch(dirPath);
+ expect(directory.filesystem).toBeDefined();
+ expect(directory.filesystem).toBe(root.filesystem);
+ // cleanup
+ directory.remove(null, null);
+ done();
+ };
+ // create:true, exclusive:true, directory does not exist
+ root.getDirectory(dirName, {
+ create : true,
+ exclusive : true
+ }, win, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.28 DirectoryEntry.getDirectory: create directory that already exists", function (done) {
+ var dirName = "de.create.existing.dir",
+ dirPath = joinURL(root.fullPath, dirName),
+ win = function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.name).toCanonicallyMatch(dirName);
+ expect(directory.fullPath).toCanonicallyMatch(dirPath);
+ // cleanup
+ directory.remove(null, null);
+ done();
+ };
+ // create directory to kick off it
+ root.getDirectory(dirName, {
+ create : true
+ }, function () {
+ root.getDirectory(dirName, {
+ create : true
+ }, win, failed.bind(null, done, 'root.getDirectory - Error creating existent second directory : ' + dirName));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.29 DirectoryEntry.getDirectory: create directory that already exists (exclusive)", function (done) {
+ var dirName = "de.create.exclusive.existing.dir",
+ dirPath = joinURL(root.fullPath, dirName),
+ existingDir,
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.PATH_EXISTS_ERR);
+ // cleanup
+ existingDir.remove(null, null);
+ done();
+ };
+ // create directory to kick off it
+ root.getDirectory(dirName, {
+ create : true
+ }, function (directory) {
+ existingDir = directory;
+ // create:true, exclusive:true, directory exists
+ root.getDirectory(dirName, {
+ create : true,
+ exclusive : true
+ }, failed.bind(null, done, 'root.getDirectory - Unexpected success callback, second directory should not be created : ' + dirName), fail);
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.30 DirectoryEntry.getDirectory: get Entry for existing directory", function (done) {
+ var dirName = "de.get.dir",
+ dirPath = joinURL(root.fullPath, dirName),
+ win = function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.name).toCanonicallyMatch(dirName);
+ expect(directory.fullPath).toCanonicallyMatch(dirPath);
+ // cleanup
+ directory.remove(null, null);
+ done();
+ };
+ // create directory to kick it off
+ root.getDirectory(dirName, {
+ create : true
+ }, function () {
+ root.getDirectory(dirName, {
+ create : false
+ }, win, failed.bind(null, done, 'root.getDirectory - Error getting directory entry : ' + dirName));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.31 DirectoryEntry.getDirectory: get DirectoryEntry for invalid path", function (done) {
+ var dirName = "de:invalid:path",
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.ENCODING_ERR);
+ done();
+ };
+ // create:false, exclusive:false, invalid path
+ root.getDirectory(dirName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getDirectory - Unexpected success callback, directory should not exists: ' + dirName), fail);
+ });
+ it("file.spec.32 DirectoryEntry.getDirectory: get DirectoryEntry for existing file", function (done) {
+ var fileName = "de.existing.file",
+ existingFile,
+ filePath = joinURL(root.fullPath, fileName),
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.TYPE_MISMATCH_ERR);
+ // cleanup
+ existingFile.remove(null, null);
+ done();
+ };
+ // create file to kick off it
+ root.getFile(fileName, {
+ create : true
+ }, function (file) {
+ existingFile = file;
+ root.getDirectory(fileName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getDirectory - Unexpected success callback, directory should not exists: ' + fileName), fail);
+ }, failed.bind(null, done, 'root.getFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.33 DirectoryEntry.getFile: get FileEntry for existing directory", function (done) {
+ var dirName = "de.existing.dir",
+ existingDir,
+ dirPath = joinURL(root.fullPath, dirName),
+ fail = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.TYPE_MISMATCH_ERR);
+ // cleanup
+ existingDir.remove(null, null);
+ done();
+ };
+ // create directory to kick off it
+ root.getDirectory(dirName, {
+ create : true
+ }, function (directory) {
+ existingDir = directory;
+ root.getFile(dirName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getFile - Unexpected success callback, file should not exists: ' + dirName), fail);
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.34 DirectoryEntry.removeRecursively on directory", function (done) {
+ var dirName = "de.removeRecursively",
+ subDirName = "dir",
+ dirPath = joinURL(root.fullPath, dirName),
+ subDirPath = joinURL(dirPath, subDirName),
+ dirExists = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ };
+ // create a new directory entry to kick off it
+ root.getDirectory(dirName, {
+ create : true
+ }, function (entry) {
+ entry.getDirectory(subDirName, {
+ create : true
+ }, function (dir) {
+ entry.removeRecursively(function () {
+ root.getDirectory(dirName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getDirectory - Unexpected success callback, directory should not exists: ' + dirName), dirExists);
+ }, failed.bind(null, done, 'entry.removeRecursively - Error removing directory recursively : ' + dirName));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + subDirName));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.35 createReader: create reader on existing directory", function () {
+ // create reader for root directory
+ var reader = root.createReader();
+ expect(reader).toBeDefined();
+ expect(typeof reader.readEntries).toBe('function');
+ });
+ it("file.spec.36 removeRecursively on root file system", function (done) {
+ var remove = function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ done();
+ };
+ // remove root file system
+ root.removeRecursively(succeed.bind(null, done, 'root.removeRecursively - Unexpected success callback, root cannot be removed'), remove);
+ });
+ });
+ describe('DirectoryReader interface', function () {
+ describe("readEntries", function () {
+ it("file.spec.37 should read contents of existing directory", function (done) {
+ var reader,
+ win = function (entries) {
+ expect(entries).toBeDefined();
+ expect(entries instanceof Array).toBe(true);
+ done();
+ };
+ // create reader for root directory
+ reader = root.createReader();
+ // read entries
+ reader.readEntries(win, failed.bind(null, done, 'reader.readEntries - Error reading entries'));
+ });
+ it("file.spec.37.1 should read contents of existing directory", function (done) {
+ var dirName = 'readEntries.dir',
+ fileName = 'readeEntries.file';
+ root.getDirectory(dirName, {
+ create : true
+ }, function (directory) {
+ directory.getFile(fileName, {
+ create : true
+ }, function (fileEntry) {
+ var reader = directory.createReader();
+ reader.readEntries(function (entries) {
+ expect(entries).toBeDefined();
+ expect(entries instanceof Array).toBe(true);
+ expect(entries.length).toBe(1);
+ expect(entries[0].fullPath).toCanonicallyMatch(fileEntry.fullPath);
+ expect(entries[0].filesystem).not.toBe(null);
+ expect(entries[0].filesystem instanceof FileSystem).toBe(true);
+ // cleanup
+ directory.removeRecursively(null, null);
+ done();
+ }, failed.bind(null, done, 'reader.readEntries - Error reading entries from directory: ' + dirName));
+ }, failed.bind(null, done, 'directory.getFile - Error creating file : ' + fileName));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.109 should return an empty entry list on the second call", function (done) {
+ var reader,
+ fileName = 'test109.txt';
+ // Add a file to ensure the root directory is non-empty and then read the contents of the directory.
+ root.getFile(fileName, {
+ create : true
+ }, function (entry) {
+ reader = root.createReader();
+ //First read
+ reader.readEntries(function (entries) {
+ expect(entries).toBeDefined();
+ expect(entries instanceof Array).toBe(true);
+ expect(entries.length).not.toBe(0);
+ //Second read
+ reader.readEntries(function (entries_) {
+ expect(entries_).toBeDefined();
+ expect(entries_ instanceof Array).toBe(true);
+ expect(entries_.length).toBe(0);
+ //Clean up
+ entry.remove();
+ done();
+ }, failed.bind(null, done, 'reader.readEntries - Error during SECOND reading of entries from [root] directory'));
+ }, failed.bind(null, done, 'reader.readEntries - Error during FIRST reading of entries from [root] directory'));
+ }, failed.bind(null, done, 'root.getFile - Error creating file : ' + fileName));
+ });
+ });
+ it("file.spec.38 should read contents of directory that has been removed", function (done) {
+ var dirName = "de.createReader.notfound",
+ dirPath = joinURL(root.fullPath, dirName);
+ // create a new directory entry to kick off it
+ root.getDirectory(dirName, {
+ create : true
+ }, function (directory) {
+ directory.removeRecursively(function () {
+ var reader = directory.createReader();
+ reader.readEntries(succeed.bind(null, done, 'reader.readEntries - Unexpected success callback, it should not read entries from deleted dir: ' + dirName), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ root.getDirectory(dirName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getDirectory - Unexpected success callback, it should not get deleted directory: ' + dirName), function (err) {
+ expect(err).toBeDefined();
+ expect(err).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ });
+ });
+ }, failed.bind(null, done, 'directory.removeRecursively - Error removing directory recursively : ' + dirName));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dirName));
+ });
+ });
+ //DirectoryReader interface
+ describe('File', function () {
+ it("file.spec.39 constructor should be defined", function () {
+ expect(File).toBeDefined();
+ expect(typeof File).toBe('function');
+ });
+ it("file.spec.40 should be define File attributes", function () {
+ var file = new File();
+ expect(file.name).toBeDefined();
+ expect(file.type).toBeDefined();
+ expect(file.lastModifiedDate).toBeDefined();
+ expect(file.size).toBeDefined();
+ });
+ });
+ //File
+ describe('FileEntry', function () {
+ it("file.spec.41 should be define FileEntry methods", function (done) {
+ var fileName = "fe.methods",
+ testFileEntry = function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(typeof fileEntry.createWriter).toBe('function');
+ expect(typeof fileEntry.file).toBe('function');
+ // cleanup
+ fileEntry.remove(null, null);
+ done();
+ };
+ // create a new file entry to kick off it
+ root.getFile(fileName, {
+ create : true
+ }, testFileEntry, failed.bind(null, done, 'root.getFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.42 createWriter should return a FileWriter object", function (done) {
+ var fileName = "fe.createWriter",
+ testFile,
+ testWriter = function (writer) {
+ expect(writer).toBeDefined();
+ expect(writer instanceof FileWriter).toBe(true);
+ // cleanup
+ testFile.remove(null, null);
+ done();
+ };
+ // create a new file entry to kick off it
+ root.getFile(fileName, {
+ create : true
+ }, function (fileEntry) {
+ testFile = fileEntry;
+ fileEntry.createWriter(testWriter, failed.bind(null, done, 'fileEntry.createWriter - Error creating Writer from entry'));
+ }, failed.bind(null, done, 'root.getFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.43 file should return a File object", function (done) {
+ var fileName = "fe.file",
+ newFile,
+ testFile = function (file) {
+ expect(file).toBeDefined();
+ expect(file instanceof File).toBe(true);
+ // cleanup
+ newFile.remove(null, null);
+ done();
+ };
+ // create a new file entry to kick off it
+ root.getFile(fileName, {
+ create : true
+ }, function (fileEntry) {
+ newFile = fileEntry;
+ fileEntry.file(testFile, failed.bind(null, done, 'fileEntry.file - Error reading file using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'root.getFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.44 file: on File that has been removed", function (done) {
+ var fileName = "fe.no.file";
+ // create a new file entry to kick off it
+ root.getFile(fileName, {
+ create : true
+ }, function (fileEntry) {
+ fileEntry.remove(function () {
+ fileEntry.file(succeed.bind(null, done, 'fileEntry.file - Unexpected success callback, file it should not be created from removed entry'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ });
+ }, failed.bind(null, done, 'fileEntry.remove - Error removing entry : ' + fileName));
+ }, failed.bind(null, done, 'root.getFile - Error creating file : ' + fileName));
+ });
+ });
+ //FileEntry
+ describe('Entry', function () {
+ it("file.spec.45 Entry object", function (done) {
+ var fileName = "entry",
+ fullPath = joinURL(root.fullPath, fileName),
+ winEntry = function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toCanonicallyMatch(fileName);
+ expect(entry.fullPath).toCanonicallyMatch(fullPath);
+ expect(typeof entry.getMetadata).toBe('function');
+ expect(typeof entry.setMetadata).toBe('function');
+ expect(typeof entry.moveTo).toBe('function');
+ expect(typeof entry.copyTo).toBe('function');
+ expect(typeof entry.toURL).toBe('function');
+ expect(typeof entry.remove).toBe('function');
+ expect(typeof entry.getParent).toBe('function');
+ expect(typeof entry.createWriter).toBe('function');
+ expect(typeof entry.file).toBe('function');
+ // Clean up
+ deleteEntry(fileName);
+ done();
+ };
+ // create a new file entry
+ createFile(fileName, winEntry, failed.bind(null, done, 'createFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.46 Entry.getMetadata on file", function (done) {
+ var fileName = "entry.metadata.file";
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ entry.getMetadata(function (metadata) {
+ expect(metadata).toBeDefined();
+ expect(metadata.modificationTime instanceof Date).toBe(true);
+ expect(typeof metadata.size).toBe("number");
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'entry.getMetadata - Error getting metadata from entry : ' + fileName));
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.47 Entry.getMetadata on directory", function (done) {
+ var dirName = "entry.metadata.dir";
+ // create a new directory entry
+ createDirectory(dirName, function (entry) {
+ entry.getMetadata(function (metadata) {
+ expect(metadata).toBeDefined();
+ expect(metadata.modificationTime instanceof Date).toBe(true);
+ expect(typeof metadata.size).toBe("number");
+ expect(metadata.size).toBe(0);
+ // cleanup
+ deleteEntry(dirName);
+ done();
+ }, failed.bind(null, done, 'entry.getMetadata - Error getting metadata from entry : ' + dirName));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.48 Entry.getParent on file in root file system", function (done) {
+ var fileName = "entry.parent.file",
+ rootPath = root.fullPath;
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ entry.getParent(function (parent) {
+ expect(parent).toBeDefined();
+ expect(parent.fullPath).toCanonicallyMatch(rootPath);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'entry.getParent - Error getting parent directory of file : ' + fileName));
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.49 Entry.getParent on directory in root file system", function (done) {
+ var dirName = "entry.parent.dir",
+ rootPath = root.fullPath;
+ // create a new directory entry
+ createDirectory(dirName, function (entry) {
+ entry.getParent(function (parent) {
+ expect(parent).toBeDefined();
+ expect(parent.fullPath).toCanonicallyMatch(rootPath);
+ // cleanup
+ deleteEntry(dirName);
+ done();
+ }, failed.bind(null, done, 'entry.getParent - Error getting parent directory of directory : ' + dirName));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.50 Entry.getParent on root file system", function (done) {
+ var rootPath = root.fullPath,
+ winParent = function (parent) {
+ expect(parent).toBeDefined();
+ expect(parent.fullPath).toCanonicallyMatch(rootPath);
+ done();
+ };
+ // create a new directory entry
+ root.getParent(winParent, failed.bind(null, done, 'root.getParent - Error getting parent directory of root'));
+ });
+ it("file.spec.51 Entry.toURL on file", function (done) {
+ var fileName = "entry.uri.file",
+ rootPath = root.fullPath,
+ winURI = function (entry) {
+ var uri = entry.toURL();
+ expect(uri).toBeDefined();
+ expect(uri.indexOf(rootPath)).not.toBe(-1);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ };
+ // create a new file entry
+ createFile(fileName, winURI, failed.bind(null, done, 'createFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.52 Entry.toURL on directory", function (done) {
+ var dirName_1 = "num 1",
+ dirName_2 = "num 2",
+ rootPath = root.fullPath;
+ createDirectory(dirName_1, function (entry) {
+ entry.getDirectory(dirName_2, {
+ create : true
+ }, function (entryFile) {
+ var uri = entryFile.toURL();
+ expect(uri).toBeDefined();
+ expect(uri).toContain('/num%201/num%202/');
+ expect(uri.indexOf(rootPath)).not.toBe(-1);
+ // cleanup
+ deleteEntry(dirName_1);
+ done();
+ }, failed.bind(null, done, 'entry.getDirectory - Error creating directory : ' + dirName_2));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + dirName_1));
+ });
+ it("file.spec.53 Entry.remove on file", function (done) {
+ var fileName = "entr .rm.file";
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ expect(entry).toBeDefined();
+ entry.remove(function () {
+ root.getFile(fileName, null, succeed.bind(null, done, 'root.getFile - Unexpected success callback, it should not get deleted file : ' + fileName), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ });
+ }, failed.bind(null, done, 'entry.remove - Error removing entry : ' + fileName));
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + fileName));
+ });
+ it("file.spec.54 remove on empty directory", function (done) {
+ var dirName = "entry.rm.dir";
+ // create a new directory entry
+ createDirectory(dirName, function (entry) {
+ expect(entry).toBeDefined();
+ entry.remove(function () {
+ root.getDirectory(dirName, null, succeed.bind(null, done, 'root.getDirectory - Unexpected success callback, it should not get deleted directory : ' + dirName), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(dirName);
+ done();
+ });
+ }, failed.bind(null, done, 'entry.remove - Error removing entry : ' + dirName));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.55 remove on non-empty directory", function (done) {
+ var dirName = "ent y.rm.dir.not.empty",
+ fileName = "re ove.txt",
+ fullPath = joinURL(root.fullPath, dirName);
+ // create a new directory entry
+ createDirectory(dirName, function (entry) {
+ entry.getFile(fileName, {
+ create : true
+ }, function (fileEntry) {
+ entry.remove(succeed.bind(null, done, 'entry.remove - Unexpected success callback, it should not remove a directory that contains files : ' + dirName), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ root.getDirectory(dirName, null, function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.fullPath).toCanonicallyMatch(fullPath);
+ // cleanup
+ deleteEntry(dirName);
+ done();
+ }, failed.bind(null, done, 'root.getDirectory - Error getting directory : ' + dirName));
+ });
+ }, failed.bind(null, done, 'entry.getFile - Error creating file : ' + fileName + ' inside of ' + dirName));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + dirName));
+ });
+ it("file.spec.56 remove on root file system", function (done) {
+ // remove entry that doesn't exist
+ root.remove(succeed.bind(null, done, 'entry.remove - Unexpected success callback, it should not remove entry that it does not exists'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ done();
+ });
+ });
+ it("file.spec.57 copyTo: file", function (done) {
+ var file1 = "entry copy.file1",
+ file2 = "entry copy.file2",
+ fullPath = joinURL(root.fullPath, file2);
+ // create a new file entry to kick off it
+ deleteEntry(file2, function () {
+ createFile(file1, function (fileEntry) {
+ // copy file1 to file2
+ fileEntry.copyTo(root, file2, function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.fullPath).toCanonicallyMatch(fullPath);
+ expect(entry.name).toCanonicallyMatch(file2);
+ root.getFile(file2, {
+ create : false
+ }, function (entry2) {
+ expect(entry2).toBeDefined();
+ expect(entry2.isFile).toBe(true);
+ expect(entry2.isDirectory).toBe(false);
+ expect(entry2.fullPath).toCanonicallyMatch(fullPath);
+ expect(entry2.name).toCanonicallyMatch(file2);
+ // cleanup
+ deleteEntry(file1);
+ deleteEntry(file2);
+ done();
+ }, failed.bind(null, done, 'root.getFile - Error getting copied file : ' + file2));
+ }, failed.bind(null, done, 'fileEntry.copyTo - Error copying file : ' + file2));
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'deleteEntry - Error removing file : ' + file2));
+ });
+ it("file.spec.58 copyTo: file onto itself", function (done) {
+ var file1 = "entry.copy.fos.file1";
+ // create a new file entry to kick off it
+ createFile(file1, function (entry) {
+ // copy file1 onto itself
+ entry.copyTo(root, null, succeed.bind(null, done, 'entry.copyTo - Unexpected success callback, it should not copy a null file'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ // cleanup
+ deleteEntry(file1);
+ done();
+ });
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + file1));
+ });
+ it("file.spec.59 copyTo: directory", function (done) {
+ var file1 = "file1",
+ srcDir = "entry.copy.srcDir",
+ dstDir = "entry.copy.dstDir",
+ dstPath = joinURL(root.fullPath, dstDir),
+ filePath = joinURL(dstPath, file1);
+ // create a new directory entry to kick off it
+ deleteEntry(dstDir, function () {
+ createDirectory(srcDir, function (directory) {
+ // create a file within new directory
+ directory.getFile(file1, {
+ create : true
+ }, function () {
+ directory.copyTo(root, dstDir, function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.fullPath).toCanonicallyMatch(dstPath);
+ expect(directory.name).toCanonicallyMatch(dstDir);
+ root.getDirectory(dstDir, {
+ create : false
+ }, function (dirEntry) {
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.isFile).toBe(false);
+ expect(dirEntry.isDirectory).toBe(true);
+ expect(dirEntry.fullPath).toCanonicallyMatch(dstPath);
+ expect(dirEntry.name).toCanonicallyMatch(dstDir);
+ dirEntry.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.isFile).toBe(true);
+ expect(fileEntry.isDirectory).toBe(false);
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ expect(fileEntry.name).toCanonicallyMatch(file1);
+ // cleanup
+ deleteEntry(srcDir);
+ deleteEntry(dstDir);
+ done();
+ }, failed.bind(null, done, 'dirEntry.getFile - Error getting file : ' + file1));
+ }, failed.bind(null, done, 'root.getDirectory - Error getting copied directory : ' + dstDir));
+ }, failed.bind(null, done, 'directory.copyTo - Error copying directory : ' + srcDir + ' to :' + dstDir));
+ }, failed.bind(null, done, 'directory.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dstDir));
+ });
+ it("file.spec.60 copyTo: directory to backup at same root directory", function (done) {
+ var file1 = "file1",
+ srcDir = "entry.copy srcDirSame",
+ dstDir = "entry.copy srcDirSame-backup",
+ dstPath = joinURL(root.fullPath, dstDir),
+ filePath = joinURL(dstPath, file1);
+ // create a new directory entry to kick off it
+ deleteEntry(dstDir, function () {
+ createDirectory(srcDir, function (directory) {
+ directory.getFile(file1, {
+ create : true
+ }, function () {
+ directory.copyTo(root, dstDir, function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.fullPath).toCanonicallyMatch(dstPath);
+ expect(directory.name).toCanonicallyMatch(dstDir);
+ root.getDirectory(dstDir, {
+ create : false
+ }, function (dirEntry) {
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.isFile).toBe(false);
+ expect(dirEntry.isDirectory).toBe(true);
+ expect(dirEntry.fullPath).toCanonicallyMatch(dstPath);
+ expect(dirEntry.name).toCanonicallyMatch(dstDir);
+ dirEntry.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.isFile).toBe(true);
+ expect(fileEntry.isDirectory).toBe(false);
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ expect(fileEntry.name).toCanonicallyMatch(file1);
+ // cleanup
+ deleteEntry(srcDir);
+ deleteEntry(dstDir);
+ done();
+ }, failed.bind(null, done, 'dirEntry.getFile - Error getting file : ' + file1));
+ }, failed.bind(null, done, 'root.getDirectory - Error getting copied directory : ' + dstDir));
+ }, failed.bind(null, done, 'directory.copyTo - Error copying directory : ' + srcDir + ' to :' + dstDir));
+ }, failed.bind(null, done, 'directory.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dstDir));
+ });
+ it("file.spec.61 copyTo: directory onto itself", function (done) {
+ var file1 = "file1",
+ srcDir = "entry.copy.dos.srcDir",
+ srcPath = joinURL(root.fullPath, srcDir),
+ filePath = joinURL(srcPath, file1);
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (directory) {
+ // create a file within new directory
+ directory.getFile(file1, {
+ create : true
+ }, function (fileEntry) {
+ // copy srcDir onto itself
+ directory.copyTo(root, null, succeed.bind(null, done, 'directory.copyTo - Unexpected success callback, it should not copy file: ' + srcDir + ' to a null destination'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ root.getDirectory(srcDir, {
+ create : false
+ }, function (dirEntry) {
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.fullPath).toCanonicallyMatch(srcPath);
+ dirEntry.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ deleteEntry(srcDir);
+ done();
+ }, failed.bind(null, done, 'dirEntry.getFile - Error getting file : ' + file1));
+ }, failed.bind(null, done, 'root.getDirectory - Error getting directory : ' + srcDir));
+ });
+ }, failed.bind(null, done, 'directory.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ });
+ it("file.spec.62 copyTo: directory into itself", function (done) {
+ var srcDir = "entry.copy.dis.srcDir",
+ dstDir = "entry.copy.dis.dstDir",
+ srcPath = joinURL(root.fullPath, srcDir);
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (directory) {
+ // copy source directory into itself
+ directory.copyTo(directory, dstDir, succeed.bind(null, done, 'directory.copyTo - Unexpected success callback, it should not copy a directory ' + srcDir + ' into itself'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ root.getDirectory(srcDir, {
+ create : false
+ }, function (dirEntry) {
+ // returning confirms existence so just check fullPath entry
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.fullPath).toCanonicallyMatch(srcPath);
+ // cleanup
+ deleteEntry(srcDir);
+ done();
+ }, failed.bind(null, done, 'root.getDirectory - Error getting directory : ' + srcDir));
+ });
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ });
+ it("file.spec.63 copyTo: directory that does not exist", function (done) {
+ var file1 = "entry.copy.dnf.file1",
+ dirName = 'dir-foo';
+ createFile(file1, function (fileEntry) {
+ createDirectory(dirName, function (dirEntry) {
+ dirEntry.remove(function () {
+ fileEntry.copyTo(dirEntry, null, succeed.bind(null, done, 'fileEntry.copyTo - Unexpected success callback, it should not copy a file ' + file1 + ' into a removed directory'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ });
+ }, failed.bind(null, done, 'dirEntry.remove - Error removing directory : ' + dirName));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + dirName));
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + file1));
+ });
+ it("file.spec.64 copyTo: invalid target name", function (done) {
+ var file1 = "entry.copy.itn.file1",
+ file2 = "bad:file:name";
+ // create a new file entry
+ createFile(file1, function (entry) {
+ // copy file1 to file2
+ entry.copyTo(root, file2, succeed.bind(null, done, 'entry.copyTo - Unexpected success callback, it should not copy a file ' + file1 + ' to an invalid file name: ' + file2), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.ENCODING_ERR);
+ // cleanup
+ deleteEntry(file1);
+ done();
+ });
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + file1));
+ });
+ it("file.spec.65 moveTo: file to same parent", function (done) {
+ var file1 = "entry.move.fsp.file1",
+ file2 = "entry.move.fsp.file2",
+ dstPath = joinURL(root.fullPath, file2);
+ // create a new file entry to kick off it
+ createFile(file1, function (entry) {
+ // move file1 to file2
+ entry.moveTo(root, file2, function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.fullPath).toCanonicallyMatch(dstPath);
+ expect(entry.name).toCanonicallyMatch(file2);
+ root.getFile(file2, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(dstPath);
+ root.getFile(file1, {
+ create : false
+ }, succeed.bind(null, done, 'root.getFile - Unexpected success callback, it should not get invalid or moved file: ' + file1), function (error) {
+ //expect(navigator.fileMgr.testFileExists(srcPath) === false, "original file should not exist.");
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(file1);
+ deleteEntry(file2);
+ done();
+ });
+ }, failed.bind(null, done, 'root.getFile - Error getting file : ' + file2));
+ }, failed.bind(null, done, 'entry.moveTo - Error moving file : ' + file1 + ' to root as: ' + file2));
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + file1));
+ });
+ it("file.spec.66 moveTo: file to new parent", function (done) {
+ var file1 = "entry.move.fnp.file1",
+ dir = "entry.move.fnp.dir",
+ dstPath = joinURL(joinURL(root.fullPath, dir), file1);
+ // ensure destination directory is cleaned up first
+ deleteEntry(dir, function () {
+ // create a new file entry to kick off it
+ createFile(file1, function (entry) {
+ // create a parent directory to move file to
+ root.getDirectory(dir, {
+ create : true
+ }, function (directory) {
+ // move file1 to new directory
+ // move the file
+ entry.moveTo(directory, null, function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.fullPath).toCanonicallyMatch(dstPath);
+ expect(entry.name).toCanonicallyMatch(file1);
+ // test the moved file exists
+ directory.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(dstPath);
+ root.getFile(file1, {
+ create : false
+ }, succeed.bind(null, done, 'root.getFile - Unexpected success callback, it should not get invalid or moved file: ' + file1), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(file1);
+ deleteEntry(dir);
+ done();
+ });
+ }, failed.bind(null, done, 'directory.getFile - Error getting file : ' + file1 + ' from: ' + dir));
+ }, failed.bind(null, done, 'entry.moveTo - Error moving file : ' + file1 + ' to: ' + dir + ' with the same name'));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dir));
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dir));
+ });
+ it("file.spec.67 moveTo: directory to same parent", function (done) {
+ var file1 = "file1",
+ srcDir = "entry.move.dsp.srcDir",
+ dstDir = "entry.move.dsp.dstDir",
+ srcPath = joinURL(root.fullPath, srcDir),
+ dstPath = joinURL(root.fullPath, dstDir),
+ filePath = joinURL(dstPath, file1);
+ // ensure destination directory is cleaned up before it
+ deleteEntry(dstDir, function () {
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (directory) {
+ // create a file within directory
+ directory.getFile(file1, {
+ create : true
+ }, function () {
+ // move srcDir to dstDir
+ directory.moveTo(root, dstDir, function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.fullPath).toCanonicallyMatch(dstPath);
+ expect(directory.name).toCanonicallyMatch(dstDir);
+ // test that moved file exists in destination dir
+ directory.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // check that the moved file no longer exists in original dir
+ root.getFile(file1, {
+ create : false
+ }, succeed.bind(null, done, 'directory.getFile - Unexpected success callback, it should not get invalid or moved file: ' + file1), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(srcDir);
+ deleteEntry(dstDir);
+ done();
+ });
+ }, failed.bind(null, done, 'directory.getFile - Error getting file : ' + file1 + ' from: ' + srcDir));
+ }, failed.bind(null, done, 'entry.moveTo - Error moving directory : ' + srcDir + ' to root as: ' + dstDir));
+ }, failed.bind(null, done, 'directory.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dstDir));
+ });
+ it("file.spec.68 moveTo: directory to same parent with same name", function (done) {
+ var file1 = "file1",
+ srcDir = "entry.move.dsp.srcDir",
+ dstDir = "entry.move.dsp.srcDir-backup",
+ srcPath = joinURL(root.fullPath, srcDir),
+ dstPath = joinURL(root.fullPath, dstDir),
+ filePath = joinURL(dstPath, file1);
+ // ensure destination directory is cleaned up before it
+ deleteEntry(dstDir, function () {
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (directory) {
+ // create a file within directory
+ directory.getFile(file1, {
+ create : true
+ }, function () {
+ // move srcDir to dstDir
+ directory.moveTo(root, dstDir, function (directory) {
+ expect(directory).toBeDefined();
+ expect(directory.isFile).toBe(false);
+ expect(directory.isDirectory).toBe(true);
+ expect(directory.fullPath).toCanonicallyMatch(dstPath);
+ expect(directory.name).toCanonicallyMatch(dstDir);
+ // check that moved file exists in destination dir
+ directory.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // check that the moved file no longer exists in original dir
+ root.getFile(file1, {
+ create : false
+ }, succeed.bind(null, done, 'directory.getFile - Unexpected success callback, it should not get invalid or moved file: ' + file1), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(srcDir);
+ deleteEntry(dstDir);
+ done();
+ });
+ }, failed.bind(null, done, 'directory.getFile - Error getting file : ' + file1 + ' from: ' + srcDir));
+ }, failed.bind(null, done, 'entry.moveTo - Error moving directory : ' + srcDir + ' to root as: ' + dstDir));
+ }, failed.bind(null, done, 'directory.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dstDir));
+ });
+ it("file.spec.69 moveTo: directory to new parent", function (done) {
+ var file1 = "file1",
+ srcDir = "entry.move.dnp.srcDir",
+ dstDir = "entry.move.dnp.dstDir",
+ srcPath = joinURL(root.fullPath, srcDir),
+ dstPath = joinURL(root.fullPath, dstDir),
+ filePath = joinURL(dstPath, file1);
+ // ensure destination directory is cleaned up before it
+ deleteEntry(dstDir, function () {
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (directory) {
+ // create a file within directory
+ directory.getFile(file1, {
+ create : true
+ }, function () {
+ // move srcDir to dstDir
+ directory.moveTo(root, dstDir, function (dirEntry) {
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.isFile).toBe(false);
+ expect(dirEntry.isDirectory).toBe(true);
+ expect(dirEntry.fullPath).toCanonicallyMatch(dstPath);
+ expect(dirEntry.name).toCanonicallyMatch(dstDir);
+ // test that moved file exists in destination dir
+ dirEntry.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // test that the moved file no longer exists in original dir
+ root.getFile(file1, {
+ create : false
+ }, succeed.bind(null, done, 'root.getFile - Unexpected success callback, it should not get invalid or moved file: ' + file1), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(srcDir);
+ deleteEntry(dstDir);
+ done();
+ });
+ }, failed.bind(null, done, 'directory.getFile - Error getting file : ' + file1 + ' from: ' + dstDir));
+ }, failed.bind(null, done, 'directory.moveTo - Error moving directory : ' + srcDir + ' to root as: ' + dstDir));
+ }, failed.bind(null, done, 'directory.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dstDir));
+ });
+ it("file.spec.70 moveTo: directory onto itself", function (done) {
+ var file1 = "file1",
+ srcDir = "entry.move.dos.srcDir",
+ srcPath = joinURL(root.fullPath, srcDir),
+ filePath = joinURL(srcPath, file1);
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (directory) {
+ // create a file within new directory
+ directory.getFile(file1, {
+ create : true
+ }, function () {
+ // move srcDir onto itself
+ directory.moveTo(root, null, succeed.bind(null, done, 'directory.moveTo - Unexpected success callback, it should not move directory to invalid path'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ // test that original dir still exists
+ root.getDirectory(srcDir, {
+ create : false
+ }, function (dirEntry) {
+ // returning confirms existence so just check fullPath entry
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.fullPath).toCanonicallyMatch(srcPath);
+ dirEntry.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ deleteEntry(srcDir);
+ done();
+ }, failed.bind(null, done, 'dirEntry.getFile - Error getting file : ' + file1 + ' from: ' + srcDir));
+ }, failed.bind(null, done, 'root.getDirectory - Error getting directory : ' + srcDir));
+ });
+ }, failed.bind(null, done, 'directory.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ });
+ it("file.spec.71 moveTo: directory into itself", function (done) {
+ var srcDir = "entry.move.dis.srcDir",
+ dstDir = "entry.move.dis.dstDir",
+ srcPath = joinURL(root.fullPath, srcDir);
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (directory) {
+ // move source directory into itself
+ directory.moveTo(directory, dstDir, succeed.bind(null, done, 'directory.moveTo - Unexpected success callback, it should not move a directory into itself: ' + srcDir), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ // make sure original directory still exists
+ root.getDirectory(srcDir, {
+ create : false
+ }, function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.fullPath).toCanonicallyMatch(srcPath);
+ // cleanup
+ deleteEntry(srcDir);
+ done();
+ }, failed.bind(null, done, 'root.getDirectory - Error getting directory, making sure that original directory still exists: ' + srcDir));
+ });
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ });
+ it("file.spec.130 moveTo: directory into similar directory", function (done) {
+ var srcDir = "entry.move.dis.srcDir",
+ dstDir = "entry.move.dis.srcDir-backup",
+ srcPath = joinURL(root.fullPath, srcDir);
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (srcDirEntry) {
+ deleteEntry(dstDir, function () {
+ createDirectory(dstDir, function (dstDirEntry) {
+ // move source directory into itself
+ srcDirEntry.moveTo(dstDirEntry, 'file', function (newDirEntry) {
+ expect(newDirEntry).toBeDefined();
+ deleteEntry(dstDir);
+ done();
+ }, failed.bind(null, done, 'directory.moveTo - Error moving a directory into a similarly-named directory: ' + srcDir));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + dstDir));
+ }, failed.bind(null, done, 'deleteEntry - Error deleting directory : ' + dstDir));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ });
+ it("file.spec.72 moveTo: file onto itself", function (done) {
+ var file1 = "entry.move.fos.file1",
+ filePath = joinURL(root.fullPath, file1);
+ // create a new file entry to kick off it
+ createFile(file1, function (entry) {
+ // move file1 onto itself
+ entry.moveTo(root, null, succeed.bind(null, done, 'entry.moveTo - Unexpected success callback, it should not move a file: ' + file1 + ' into the same parent'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ //test that original file still exists
+ root.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ deleteEntry(file1);
+ done();
+ }, failed.bind(null, done, 'root.getFile - Error getting file, making sure that original file still exists: ' + file1));
+ });
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + file1));
+ });
+ it("file.spec.73 moveTo: file onto existing directory", function (done) {
+ var file1 = "entry.move.fod.file1",
+ dstDir = "entry.move.fod.dstDir",
+ subDir = "subDir",
+ dirPath = joinURL(joinURL(root.fullPath, dstDir), subDir),
+ filePath = joinURL(root.fullPath, file1);
+ // ensure destination directory is cleaned up before it
+ deleteEntry(dstDir, function () {
+ // create a new file entry to kick off it
+ createFile(file1, function (entry) {
+ // create top level directory
+ root.getDirectory(dstDir, {
+ create : true
+ }, function (directory) {
+ // create sub-directory
+ directory.getDirectory(subDir, {
+ create : true
+ }, function (subDirectory) {
+ // move file1 onto sub-directory
+ entry.moveTo(directory, subDir, succeed.bind(null, done, 'entry.moveTo - Unexpected success callback, it should not move a file: ' + file1 + ' into directory: ' + dstDir + '\n' + subDir + ' directory already exists'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ // check that original dir still exists
+ directory.getDirectory(subDir, {
+ create : false
+ }, function (dirEntry) {
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.fullPath).toCanonicallyMatch(dirPath);
+ // check that original file still exists
+ root.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ deleteEntry(file1);
+ deleteEntry(dstDir);
+ done();
+ }, failed.bind(null, done, 'root.getFile - Error getting file, making sure that original file still exists: ' + file1));
+ }, failed.bind(null, done, 'directory.getDirectory - Error getting directory, making sure that original directory still exists: ' + subDir));
+ });
+ }, failed.bind(null, done, 'directory.getDirectory - Error creating directory : ' + subDir));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory : ' + dstDir));
+ }, failed.bind(null, done, 'createFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dstDir));
+ });
+ it("file.spec.74 moveTo: directory onto existing file", function (done) {
+ var file1 = "entry.move.dof.file1",
+ srcDir = "entry.move.dof.srcDir",
+ dirPath = joinURL(root.fullPath, srcDir),
+ filePath = joinURL(root.fullPath, file1);
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (entry) {
+ // create file
+ root.getFile(file1, {
+ create : true
+ }, function (fileEntry) {
+ // move directory onto file
+ entry.moveTo(root, file1, succeed.bind(null, done, 'entry.moveTo - Unexpected success callback, it should not move : \n' + srcDir + ' into root directory renamed as ' + file1 + '\n' + file1 + ' file already exists'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ // test that original directory exists
+ root.getDirectory(srcDir, {
+ create : false
+ }, function (dirEntry) {
+ // returning confirms existence so just check fullPath entry
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.fullPath).toCanonicallyMatch(dirPath);
+ // test that original file exists
+ root.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ deleteEntry(file1);
+ deleteEntry(srcDir);
+ done();
+ }, failed.bind(null, done, 'root.getFile - Error getting file, making sure that original file still exists: ' + file1));
+ }, failed.bind(null, done, 'directory.getDirectory - Error getting directory, making sure that original directory still exists: ' + srcDir));
+ });
+ }, failed.bind(null, done, 'root.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ });
+ it("file.spec.75 copyTo: directory onto existing file", function (done) {
+ var file1 = "entry.copy.dof.file1",
+ srcDir = "entry.copy.dof.srcDir",
+ dirPath = joinURL(root.fullPath, srcDir),
+ filePath = joinURL(root.fullPath, file1);
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (entry) {
+ // create file
+ root.getFile(file1, {
+ create : true
+ }, function () {
+ // copy directory onto file
+ entry.copyTo(root, file1, succeed.bind(null, done, 'entry.copyTo - Unexpected success callback, it should not copy : \n' + srcDir + ' into root directory renamed as ' + file1 + '\n' + file1 + ' file already exists'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ //check that original dir still exists
+ root.getDirectory(srcDir, {
+ create : false
+ }, function (dirEntry) {
+ // returning confirms existence so just check fullPath entry
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.fullPath).toCanonicallyMatch(dirPath);
+ // test that original file still exists
+ root.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // cleanup
+ deleteEntry(file1);
+ deleteEntry(srcDir);
+ done();
+ }, failed.bind(null, done, 'root.getFile - Error getting file, making sure that original file still exists: ' + file1));
+ }, failed.bind(null, done, 'root.getDirectory - Error getting directory, making sure that original directory still exists: ' + srcDir));
+ });
+ }, failed.bind(null, done, 'root.getFile - Error creating file : ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ });
+ it("file.spec.76 moveTo: directory onto directory that is not empty", function (done) {
+ var srcDir = "entry.move.dod.srcDir",
+ dstDir = "entry.move.dod.dstDir",
+ subDir = "subDir",
+ srcPath = joinURL(root.fullPath, srcDir),
+ dstPath = joinURL(joinURL(root.fullPath, dstDir), subDir);
+ // ensure destination directory is cleaned up before it
+ deleteEntry(dstDir, function () {
+ // create a new file entry to kick off it
+ createDirectory(srcDir, function (entry) {
+ // create top level directory
+ root.getDirectory(dstDir, {
+ create : true
+ }, function (directory) {
+ // create sub-directory
+ directory.getDirectory(subDir, {
+ create : true
+ }, function () {
+ // move srcDir onto dstDir (not empty)
+ entry.moveTo(root, dstDir, succeed.bind(null, done, 'entry.moveTo - Unexpected success callback, it should not copy : \n' + srcDir + ' into root directory renamed as ' + dstDir + '\n' + dstDir + ' directory already exists'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR);
+ // making sure destination directory still exists
+ directory.getDirectory(subDir, {
+ create : false
+ }, function (dirEntry) {
+ // returning confirms existence so just check fullPath entry
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.fullPath).toCanonicallyMatch(dstPath);
+ // making sure source directory exists
+ root.getDirectory(srcDir, {
+ create : false
+ }, function (srcEntry) {
+ expect(srcEntry).toBeDefined();
+ expect(srcEntry.fullPath).toCanonicallyMatch(srcPath);
+ // cleanup
+ deleteEntry(srcDir);
+ deleteEntry(dstDir);
+ done();
+ }, failed.bind(null, done, 'root.getDirectory - Error getting directory, making sure that original directory still exists: ' + srcDir));
+ }, failed.bind(null, done, 'directory.getDirectory - Error getting directory, making sure that original directory still exists: ' + subDir));
+ });
+ }, failed.bind(null, done, 'directory.getDirectory - Error creating directory : ' + subDir));
+ }, failed.bind(null, done, 'directory.getDirectory - Error creating directory : ' + subDir));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory : ' + srcDir));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dstDir));
+ });
+ it("file.spec.77 moveTo: file replace existing file", function (done) {
+ var file1 = "entry.move.frf.file1",
+ file2 = "entry.move.frf.file2",
+ file1Path = joinURL(root.fullPath, file1),
+ file2Path = joinURL(root.fullPath, file2);
+ // create a new directory entry to kick off it
+ createFile(file1, function (entry) {
+ // create file
+ root.getFile(file2, {
+ create : true
+ }, function () {
+ // replace file2 with file1
+ entry.moveTo(root, file2, function (entry2) {
+ expect(entry2).toBeDefined();
+ expect(entry2.isFile).toBe(true);
+ expect(entry2.isDirectory).toBe(false);
+ expect(entry2.fullPath).toCanonicallyMatch(file2Path);
+ expect(entry2.name).toCanonicallyMatch(file2);
+ // old file should not exists
+ root.getFile(file1, {
+ create : false
+ }, succeed.bind(null, done, 'root.getFile - Unexpected success callback, file: ' + file1 + ' should not exists'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // test that new file exists
+ root.getFile(file2, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(file2Path);
+ // cleanup
+ deleteEntry(file1);
+ deleteEntry(file2);
+ done();
+ }, failed.bind(null, done, 'root.getFile - Error getting moved file: ' + file2));
+ });
+ }, failed.bind(null, done, 'entry.moveTo - Error moving file : ' + file1 + ' to root as: ' + file2));
+ }, failed.bind(null, done, 'root.getFile - Error creating file: ' + file2));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + file1));
+ });
+ it("file.spec.78 moveTo: directory replace empty directory", function (done) {
+ var file1 = "file1",
+ srcDir = "entry.move.drd.srcDir",
+ dstDir = "entry.move.drd.dstDir",
+ srcPath = joinURL(root.fullPath, srcDir),
+ dstPath = joinURL(root.fullPath, dstDir),
+ filePath = dstPath + '/' + file1;
+ // ensure destination directory is cleaned up before it
+ deleteEntry(dstDir, function () {
+ // create a new directory entry to kick off it
+ createDirectory(srcDir, function (directory) {
+ // create a file within source directory
+ directory.getFile(file1, {
+ create : true
+ }, function () {
+ // create destination directory
+ root.getDirectory(dstDir, {
+ create : true
+ }, function () {
+ // move srcDir to dstDir
+ directory.moveTo(root, dstDir, function (dirEntry) {
+ expect(dirEntry).toBeDefined();
+ expect(dirEntry.isFile).toBe(false);
+ expect(dirEntry.isDirectory).toBe(true);
+ expect(dirEntry.fullPath).toCanonicallyMatch(dstPath);
+ expect(dirEntry.name).toCanonicallyMatch(dstDir);
+ // check that old directory contents have been moved
+ dirEntry.getFile(file1, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.fullPath).toCanonicallyMatch(filePath);
+ // check that old directory no longer exists
+ root.getDirectory(srcDir, {
+ create : false
+ }, succeed.bind(null, done, 'root.getDirectory - Unexpected success callback, directory: ' + srcDir + ' should not exists'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(srcDir);
+ deleteEntry(dstDir);
+ done();
+ });
+ }, failed.bind(null, done, 'dirEntry.getFile - Error getting moved file: ' + file1));
+ }, failed.bind(null, done, 'entry.moveTo - Error moving directory : ' + srcDir + ' to root as: ' + dstDir));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory: ' + dstDir));
+ }, failed.bind(null, done, 'root.getFile - Error creating file: ' + file1));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory: ' + srcDir));
+ }, failed.bind(null, done, 'deleteEntry - Error removing directory : ' + dstDir));
+ });
+ it("file.spec.79 moveTo: directory that does not exist", function (done) {
+ var file1 = "entry.move.dnf.file1",
+ dstDir = "entry.move.dnf.dstDir",
+ dstPath = joinURL(root.fullPath, dstDir);
+ // create a new file entry to kick off it
+ createFile(file1, function (entry) {
+ // move file to directory that does not exist
+ directory = new DirectoryEntry();
+ directory.filesystem = root.filesystem;
+ directory.fullPath = dstPath;
+ entry.moveTo(directory, null, succeed.bind(null, done, 'entry.moveTo - Unexpected success callback, parent directory: ' + dstPath + ' should not exists'), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ // cleanup
+ deleteEntry(file1);
+ done();
+ });
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + file1));
+ });
+ it("file.spec.80 moveTo: invalid target name", function (done) {
+ var file1 = "entry.move.itn.file1",
+ file2 = "bad:file:name";
+ // create a new file entry to kick off it
+ createFile(file1, function (entry) {
+ // move file1 to file2
+ entry.moveTo(root, file2, succeed.bind(null, done, 'entry.moveTo - Unexpected success callback, : ' + file1 + ' to root as: ' + file2), function (error) {
+ expect(error).toBeDefined();
+ expect(error).toBeFileError(FileError.ENCODING_ERR);
+ // cleanup
+ deleteEntry(file1);
+ done();
+ });
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + file1));
+ });
+ });
+ //Entry
+ describe('FileReader', function () {
+ it("file.spec.81 should have correct methods", function () {
+ var reader = new FileReader();
+ expect(reader).toBeDefined();
+ expect(typeof reader.readAsBinaryString).toBe('function');
+ expect(typeof reader.readAsDataURL).toBe('function');
+ expect(typeof reader.readAsText).toBe('function');
+ expect(typeof reader.readAsArrayBuffer).toBe('function');
+ expect(typeof reader.abort).toBe('function');
+ expect(reader.result).toBe(null);
+ });
+ });
+ //FileReader
+ describe('Read method', function () {
+ it("file.spec.82 should error out on non-existent file", function (done) {
+ var fileName = cordova.platformId === 'windowsphone' ? root.toURL() + "/" + "somefile.txt" : "somefile.txt",
+ verifier = function (evt) {
+ expect(evt).toBeDefined();
+ expect(evt.target.error).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ };
+ root.getFile(fileName, {
+ create : true
+ }, function (entry) {
+ entry.file(function (file) {
+ deleteEntry(fileName, function () {
+ //Create FileReader
+ var reader = new FileReader();
+ reader.onerror = verifier;
+ reader.onload = succeed.bind(null, done, 'reader.onload - Unexpected success callback, file: ' + fileName + ' it should not exists');
+ reader.readAsText(file);
+ }, failed.bind(null, done, 'deleteEntry - Error removing file: ' + fileName));
+ }, failed.bind(null, done, 'entry.file - Error reading file: ' + fileName));
+ }, failed.bind(null, done, 'root.getFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.83 should be able to read native blob objects", function (done) {
+ // Skip test if blobs are not supported (e.g.: Android 2.3).
+ if (typeof window.Blob == 'undefined' || typeof window.Uint8Array == 'undefined') {
+ expect(true).toFailWithMessage('Platform does not supported this feature');
+ done();
+ }
+ var contents = 'asdf';
+ var uint8Array = new Uint8Array(contents.length);
+ for (var i = 0; i < contents.length; ++i) {
+ uint8Array[i] = contents.charCodeAt(i);
+ }
+ var Builder = window.BlobBuilder || window.WebKitBlobBuilder;
+ var blob;
+ if (Builder) {
+ var builder = new Builder();
+ builder.append(uint8Array.buffer);
+ builder.append(contents);
+ blob = builder.getBlob("text/plain");
+ } else {
+ try {
+ // iOS 6 does not support Views, so pass in the buffer.
+ blob = new Blob([uint8Array.buffer, contents]);
+ } catch (e) {
+ // Skip the test if we can't create a blob (e.g.: iOS 5).
+ if (e instanceof TypeError) {
+ expect(true).toFailWithMessage('Platform does not supported this feature');
+ done();
+ }
+ throw e;
+ }
+ }
+ var verifier = function (evt) {
+ expect(evt).toBeDefined();
+ expect(evt.target.result).toBe('asdfasdf');
+ done();
+ };
+ var reader = new FileReader();
+ reader.onloadend = verifier;
+ reader.onerror = failed.bind(null, done, 'reader.onerror - Error reading file: ' + blob);
+ reader.readAsText(blob);
+ });
+ function writeDummyFile(writeBinary, callback, done) {
+ var fileName = "dummy.txt",
+ fileEntry = null,
+ fileData = '\u20AC\xEB - There is an exception to every rule. Except this one.',
+ fileDataAsBinaryString = '\xe2\x82\xac\xc3\xab - There is an exception to every rule. Except this one.',
+ createWriter = function (fe) {
+ fileEntry = fe;
+ fileEntry.createWriter(writeFile, failed.bind(null, done, 'fileEntry.createWriter - Error reading file: ' + fileName));
+ }, // writes file and reads it back in
+ writeFile = function (writer) {
+ writer.onwriteend = function () {
+ fileEntry.file(function (f) {
+ callback(fileEntry, f, fileData, fileDataAsBinaryString);
+ }, failed.bind(null, done, 'writer.onwriteend - Error writing data on file: ' + fileName));
+ };
+ writer.write(fileData);
+ };
+ fileData += writeBinary ? 'bin:\x01\x00' : '';
+ fileDataAsBinaryString += writeBinary ? 'bin:\x01\x00' : '';
+ // create a file, write to it, and read it in again
+ createFile(fileName, createWriter, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ }
+ function runReaderTest(funcName, writeBinary, done, verifierFunc, sliceStart, sliceEnd) {
+ writeDummyFile(writeBinary, function (fileEntry, file, fileData, fileDataAsBinaryString) {
+ var verifier = function (evt) {
+ expect(evt).toBeDefined();
+ verifierFunc(evt, fileData, fileDataAsBinaryString);
+ };
+ var reader = new FileReader();
+ reader.onload = verifier;
+ reader.onerror = failed.bind(null, done, 'reader.onerror - Error reading file: ' + file + ' using function: ' + funcName);
+ if (sliceEnd !== undefined) {
+ file = file.slice(sliceStart, sliceEnd);
+ } else if (sliceStart !== undefined) {
+ file = file.slice(sliceStart);
+ }
+ reader[funcName](file);
+ }, done);
+ }
+ function arrayBufferEqualsString(ab, str) {
+ var buf = new Uint8Array(ab);
+ var match = buf.length == str.length;
+ for (var i = 0; match && i < buf.length; i++) {
+ match = buf[i] == str.charCodeAt(i);
+ }
+ return match;
+ }
+ it("file.spec.84 should read file properly, readAsText", function (done) {
+ runReaderTest('readAsText', false, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result).toBe(fileData);
+ done();
+ });
+ });
+ it("file.spec.85 should read file properly, Data URI", function (done) {
+ runReaderTest('readAsDataURL', true, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result.substr(0, 23)).toBe("data:text/plain;base64,");
+ //The atob function it is completely ignored during mobilespec execution, besides the returned object: evt
+ //it is encoded and the atob function is aimed to decode a string. Even with btoa (encode) the function it gets stucked
+ //because of the Unicode characters that contains the fileData object.
+ //Issue reported at JIRA with all the details: CB-7095
+
+ //expect(evt.target.result.slice(23)).toBe(atob(fileData));
+ done();
+ });
+ });
+ it("file.spec.86 should read file properly, readAsBinaryString", function (done) {
+ runReaderTest('readAsBinaryString', true, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result).toBe(fileDataAsBinaryString);
+ done();
+ });
+ });
+ it("file.spec.87 should read file properly, readAsArrayBuffer", function (done) {
+ // Skip test if ArrayBuffers are not supported (e.g.: Android 2.3).
+ if (typeof window.ArrayBuffer == 'undefined') {
+ expect(true).toFailWithMessage('Platform does not supported this feature');
+ done();
+ }
+ runReaderTest('readAsArrayBuffer', true, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(arrayBufferEqualsString(evt.target.result, fileDataAsBinaryString)).toBe(true);
+ done();
+ });
+ });
+ it("file.spec.88 should read sliced file: readAsText", function (done) {
+ runReaderTest('readAsText', false, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result).toBe(fileDataAsBinaryString.slice(10, 40));
+ done();
+ }, 10, 40);
+ });
+ it("file.spec.89 should read sliced file: slice past eof", function (done) {
+ runReaderTest('readAsText', false, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result).toBe(fileData.slice(-5, 9999));
+ done();
+ }, -5, 9999);
+ });
+ it("file.spec.90 should read sliced file: slice to eof", function (done) {
+ runReaderTest('readAsText', false, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result).toBe(fileData.slice(-5));
+ done();
+ }, -5);
+ });
+ it("file.spec.91 should read empty slice", function (done) {
+ runReaderTest('readAsText', false, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result).toBe('');
+ done();
+ }, 0, 0);
+ });
+ it("file.spec.92 should read sliced file properly, readAsDataURL", function (done) {
+ runReaderTest('readAsDataURL', true, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result.slice(0, 23)).toBe("data:text/plain;base64,");
+ //The atob function it is completely ignored during mobilespec execution, besides the returned object: evt
+ //it is encoded and the atob function is aimed to decode a string. Even with btoa (encode) the function it gets stucked
+ //because of the Unicode characters that contains the fileData object.
+ //Issue reported at JIRA with all the details: CB-7095
+
+ //expect(evt.target.result.slice(23)).toBe(atob(fileDataAsBinaryString.slice(10, -3)));
+ done();
+ }, 10, -3);
+ });
+ it("file.spec.93 should read sliced file properly, readAsBinaryString", function (done) {
+ runReaderTest('readAsBinaryString', true, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(evt.target.result).toBe(fileDataAsBinaryString.slice(-10, -5));
+ done();
+ }, -10, -5);
+ });
+ it("file.spec.94 should read sliced file properly, readAsArrayBuffer", function (done) {
+ // Skip test if ArrayBuffers are not supported (e.g.: Android 2.3).
+ if (typeof window.ArrayBuffer == 'undefined') {
+ expect(true).toFailWithMessage('Platform does not supported this feature');
+ done();
+ }
+ runReaderTest('readAsArrayBuffer', true, done, function (evt, fileData, fileDataAsBinaryString) {
+ expect(arrayBufferEqualsString(evt.target.result, fileDataAsBinaryString.slice(0, -1))).toBe(true);
+ done();
+ }, 0, -1);
+ });
+ });
+ //Read method
+ describe('FileWriter', function () {
+ it("file.spec.95 should have correct methods", function (done) {
+ // retrieve a FileWriter object
+ var fileName = "writer.methods";
+ // FileWriter
+ root.getFile(fileName, {
+ create : true
+ }, function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+ expect(writer).toBeDefined();
+ expect(typeof writer.write).toBe('function');
+ expect(typeof writer.seek).toBe('function');
+ expect(typeof writer.truncate).toBe('function');
+ expect(typeof writer.abort).toBe('function');
+ // cleanup
+ deleteFile(fileName);
+ done();
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'root.getFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.96 should be able to write and append to file, createWriter", function (done) {
+ var fileName = "writer.append.createWriter", // file content
+ content = "There is an exception to every rule.", // for checkin file length
+ length = content.length;
+ // create file, then write and append to it
+ createFile(fileName, function (fileEntry) {
+ // writes initial file content
+ fileEntry.createWriter(function (writer) {
+ //Verifiers declaration
+ var verifier = function (evt) {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // Append some more data
+ var exception = " Except this one.";
+ writer.onwriteend = secondVerifier;
+ length += exception.length;
+ writer.seek(writer.length);
+ writer.write(exception);
+ },
+ secondVerifier = function (evt) {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.write(content);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.97 should be able to write and append to file, File object", function (done) {
+ var fileName = "writer.append.File", // file content
+ content = "There is an exception to every rule.", // for checking file length
+ length = content.length;
+ root.getFile(fileName, {
+ create : true
+ }, function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+ //Verifiers declaration
+ var verifier = function () {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // Append some more data
+ var exception = " Except this one.";
+ writer.onwriteend = secondVerifier;
+ length += exception.length;
+ writer.seek(writer.length);
+ writer.write(exception);
+ },
+ secondVerifier = function () {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.write(content);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'root.getFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.98 should be able to seek to the middle of the file and write more data than file.length", function (done) {
+ var fileName = "writer.seek.write", // file content
+ content = "This is our sentence.", // for checking file length
+ length = content.length;
+ // create file, then write and append to it
+ createFile(fileName, function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+ //Verifiers declaration
+ var verifier = function (evt) {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // Append some more data
+ var exception = "newer sentence.";
+ writer.onwriteend = secondVerifier;
+ length = 12 + exception.length;
+ writer.seek(12);
+ writer.write(exception);
+ },
+ secondVerifier = function (evt) {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.write(content);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.99 should be able to seek to the middle of the file and write less data than file.length", function (done) {
+ var fileName = "writer.seek.write2", // file content
+ content = "This is our sentence.", // for checking file length
+ length = content.length;
+ // create file, then write and append to it
+ createFile(fileName, function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+ // Verifiers declaration
+ var verifier = function (evt) {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // Append some more data
+ var exception = "new.";
+ writer.onwriteend = secondVerifier;
+ length = 8 + exception.length;
+ writer.seek(8);
+ writer.write(exception);
+ },
+ secondVerifier = function (evt) {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.write(content);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.100 should be able to write XML data", function (done) {
+ var fileName = "writer.xml", // file content
+ content = '\n\nData\n\n', // for testing file length
+ length = content.length;
+ // creates file, then write XML data
+ createFile(fileName, function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+ //Verifier content
+ var verifier = function (evt) {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.write(content);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.101 should be able to write JSON data", function (done) {
+ var fileName = "writer.json", // file content
+ content = '{ "name": "Guy Incognito", "email": "here@there.com" }', // for testing file length
+ length = content.length;
+ // creates file, then write JSON content
+ createFile(fileName, function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+ //Verifier declaration
+ var verifier = function (evt) {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.write(content);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.102 should be able to seek", function (done) {
+ var fileName = "writer.seek", // file content
+ content = "There is an exception to every rule. Except this one.", // for testing file length
+ length = content.length;
+ // creates file, then write JSON content
+ createFile(fileName, function (fileEntry) {
+ // writes file content and tests writer.seek
+ fileEntry.createWriter(function (writer) {
+ //Verifier declaration
+ var verifier = function () {
+ expect(writer.position).toBe(length);
+ writer.seek(-5);
+ expect(writer.position).toBe(length - 5);
+ writer.seek(length + 100);
+ expect(writer.position).toBe(length);
+ writer.seek(10);
+ expect(writer.position).toBe(10);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.seek(-100);
+ expect(writer.position).toBe(0);
+ writer.write(content);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.103 should be able to truncate", function (done) {
+ var fileName = "writer.truncate",
+ content = "There is an exception to every rule. Except this one.";
+ // creates file, writes to it, then truncates it
+ createFile(fileName, function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+ // Verifier declaration
+ var verifier = function () {
+ expect(writer.length).toBe(36);
+ expect(writer.position).toBe(36);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = function () {
+ //Truncate process after write
+ writer.onwriteend = verifier;
+ writer.truncate(36);
+ };
+ writer.write(content);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.104 should be able to write binary data from an ArrayBuffer", function (done) {
+ // Skip test if ArrayBuffers are not supported (e.g.: Android 2.3).
+ if (typeof window.ArrayBuffer == 'undefined') {
+ expect(true).toFailWithMessage('Platform does not supported this feature');
+ done();
+ return;
+ }
+ var fileName = "bufferwriter.bin", // file content
+ data = new ArrayBuffer(32),
+ dataView = new Int8Array(data), // for verifying file length
+ length = 32;
+ for (i = 0; i < dataView.length; i++) {
+ dataView[i] = i;
+ }
+ // creates file, then write content
+ createFile(fileName, function (fileEntry) {
+ // writes file content
+ fileEntry.createWriter(function (writer) {
+ //Verifier declaration
+ var verifier = function () {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.write(data);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.105 should be able to write binary data from a Blob", function (done) {
+ // Skip test if Blobs are not supported (e.g.: Android 2.3).
+ if ((typeof window.Blob == 'undefined' && typeof window.WebKitBlobBuilder == 'undefined') || typeof window.ArrayBuffer == 'undefined') {
+ expect(true).toFailWithMessage('Platform does not supported this feature');
+ done();
+ return;
+ }
+ var fileName = "blobwriter.bin", // file content
+ data = new ArrayBuffer(32),
+ dataView = new Int8Array(data),
+ blob, // for verifying file length
+ length = 32;
+ for (i = 0; i < dataView.length; i++) {
+ dataView[i] = i;
+ }
+ try {
+ // Mobile Safari: Use Blob constructor
+ blob = new Blob([data], {
+ "type" : "application/octet-stream"
+ });
+ } catch (e) {
+ if (window.WebKitBlobBuilder) {
+ // Android Browser: Use deprecated BlobBuilder
+ var builder = new WebKitBlobBuilder();
+ builder.append(data);
+ blob = builder.getBlob('application/octet-stream');
+ } else {
+ // We have no way defined to create a Blob, so fail
+ fail();
+ }
+ }
+ if (typeof blob !== 'undefined') {
+ // creates file, then write content
+ createFile(fileName, function (fileEntry) {
+ fileEntry.createWriter(function (writer) {
+ //Verifier declaration
+ var verifier = function () {
+ expect(writer.length).toBe(length);
+ expect(writer.position).toBe(length);
+ // cleanup
+ deleteFile(fileName);
+ done();
+ };
+ //Write process
+ writer.onwriteend = verifier;
+ writer.write(blob);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ }
+ });
+ it("file.spec.106 should be able to write a File to a FileWriter", function (done) {
+ var dummyFileName = 'dummy.txt',
+ outputFileName = 'verify.txt',
+ dummyFileText = 'This text should be written to two files',
+ verifier = function (outputFileWriter) {
+ expect(outputFileWriter.length).toBe(dummyFileText.length);
+ expect(outputFileWriter.position).toBe(dummyFileText.length);
+ deleteFile(outputFileName);
+ done();
+ },
+ writeFile = function (fileName, fileData, win) {
+ var theWriter,
+ filePath = joinURL(root.fullPath, fileName), // writes file content to new file
+ write_file = function (fileEntry) {
+ writerEntry = fileEntry;
+ fileEntry.createWriter(function (writer) {
+ theWriter = writer;
+ writer.onwriteend = function (ev) {
+ if (typeof fileData.length !== "undefined") {
+ expect(theWriter.length).toBe(fileData.length);
+ expect(theWriter.position).toBe(fileData.length);
+ }
+ win(theWriter);
+ };
+ writer.onerror = failed.bind(null, done, 'writer.onerror - Error writing content on file: ' + fileName);
+ writer.write(fileData);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ };
+ createFile(fileName, write_file, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ },
+ openFile = function (fileName, callback) {
+ root.getFile(fileName, {
+ create : false
+ }, function (fileEntry) {
+ fileEntry.file(callback, failed.bind(null, done, 'fileEntry.file - Error reading file using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'root.getFile - Error getting file: ' + fileName));
+ };
+ writeFile(dummyFileName, dummyFileText, function (dummyFileWriter) {
+ openFile(dummyFileName, function (file) {
+ writeFile(outputFileName, file, verifier);
+ });
+ });
+ });
+ it("file.spec.107 should be able to write a sliced File to a FileWriter", function (done) {
+ var dummyFileName = 'dummy2.txt',
+ outputFileName = 'verify2.txt',
+ dummyFileText = 'This text should be written to two files',
+ verifier = function (outputFileWriter) {
+ expect(outputFileWriter.length).toBe(10);
+ expect(outputFileWriter.position).toBe(10);
+ deleteFile(outputFileName);
+ done();
+ },
+ writeFile = function (fileName, fileData, win) {
+ var theWriter,
+ filePath = joinURL(root.fullPath, fileName), // writes file content to new file
+ write_file = function (fileEntry) {
+ writerEntry = fileEntry;
+ fileEntry.createWriter(function (writer) {
+ theWriter = writer;
+ writer.onwriteend = function (ev) {
+ if (typeof fileData.length !== "undefined") {
+ expect(theWriter.length).toBe(fileData.length);
+ expect(theWriter.position).toBe(fileData.length);
+ }
+ win(theWriter);
+ };
+ writer.onerror = failed.bind(null, done, 'writer.onerror - Error writing content on file: ' + fileName);
+ writer.write(fileData);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ };
+ createFile(fileName, write_file, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ },
+ openFile = function (fileName, callback) {
+ root.getFile(fileName, {
+ create : false
+ }, function (fileEntry) {
+ fileEntry.file(callback, failed.bind(null, done, 'fileEntry.file - Error reading file using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'root.getFile - Error getting file: ' + fileName));
+ };
+ writeFile(dummyFileName, dummyFileText, function (dummyFileWriter) {
+ openFile(dummyFileName, function (file) {
+ writeFile(outputFileName, file.slice(10, 20), verifier);
+ });
+ });
+ });
+ it("file.spec.108 should be able to write binary data from a File", function (done) {
+ // Skip test if Blobs are not supported (e.g.: Android 2.3).
+ if (typeof window.Blob == 'undefined' && typeof window.WebKitBlobBuilder == 'undefined') {
+ expect(true).toFailWithMessage('Platform does not supported this feature');
+ done();
+ }
+ var dummyFileName = "blobwriter.bin",
+ outputFileName = 'verify.bin', // file content
+ data = new ArrayBuffer(32),
+ dataView = new Int8Array(data),
+ blob, // for verifying file length
+ length = 32,
+ verifier = function (outputFileWriter) {
+ expect(outputFileWriter.length).toBe(length);
+ expect(outputFileWriter.position).toBe(length);
+ // cleanup
+ deleteFile(outputFileName);
+ done();
+ },
+ writeFile = function (fileName, fileData, win) {
+ var theWriter,
+ filePath = joinURL(root.fullPath, fileName), // writes file content to new file
+ write_file = function (fileEntry) {
+ writerEntry = fileEntry;
+ fileEntry.createWriter(function (writer) {
+ theWriter = writer;
+ writer.onwriteend = function (ev) {
+ if (typeof fileData.length !== "undefined") {
+ expect(theWriter.length).toBe(fileData.length);
+ expect(theWriter.position).toBe(fileData.length);
+ }
+ win(theWriter);
+ };
+ writer.onerror = failed.bind(null, done, 'writer.onerror - Error writing content on file: ' + fileName);
+ writer.write(fileData);
+ }, failed.bind(null, done, 'fileEntry.createWriter - Error creating writer using fileEntry: ' + fileEntry.name));
+ };
+ createFile(fileName, write_file, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ },
+ openFile = function (fileName, callback) {
+ root.getFile(fileName, {
+ create : false
+ }, function (fileEntry) {
+ fileEntry.file(callback, failed.bind(null, done, 'fileEntry.file - Error reading file using fileEntry: ' + fileEntry.name));
+ }, failed.bind(null, done, 'root.getFile - Error getting file: ' + fileName));
+ };
+ for (i = 0; i < dataView.length; i++) {
+ dataView[i] = i;
+ }
+ try {
+ // Mobile Safari: Use Blob constructor
+ blob = new Blob([data], {
+ "type" : "application/octet-stream"
+ });
+ } catch (e) {
+ if (window.WebKitBlobBuilder) {
+ // Android Browser: Use deprecated BlobBuilder
+ var builder = new WebKitBlobBuilder();
+ builder.append(data);
+ blob = builder.getBlob('application/octet-stream');
+ } else {
+ // We have no way defined to create a Blob, so fail
+ fail();
+ }
+ }
+ if (typeof blob !== 'undefined') {
+ // creates file, then write content
+ writeFile(dummyFileName, blob, function (dummyFileWriter) {
+ openFile(dummyFileName, function (file) {
+ writeFile(outputFileName, file, verifier);
+ });
+ });
+ }
+ });
+ });
+ //FileWritter
+ describe('Backwards compatibility', function () {
+ /* These specs exist to test that the File plugin can still recognize file:///
+ * URLs, and can resolve them to FileEntry and DirectoryEntry objects.
+ * They rely on an undocumented interface to File which provides absolute file
+ * paths, which are not used internally anymore.
+ * If that interface is not present, then these tests will silently succeed.
+ */
+ it("file.spec.109 should be able to resolve a file:/// URL", function (done) {
+ var localFilename = 'file.txt';
+ var originalEntry;
+ root.getFile(localFilename, {
+ create : true
+ }, function (entry) {
+ originalEntry = entry;
+ /* This is an undocumented interface to File which exists only for testing
+ * backwards compatibilty. By obtaining the raw filesystem path of the download
+ * location, we can pass that to ft.download() to make sure that previously-stored
+ * paths are still valid.
+ */
+ cordova.exec(function (localPath) {
+ window.resolveLocalFileSystemURL("file://" + encodeURI(localPath), function (fileEntry) {
+ expect(fileEntry.toURL()).toEqual(originalEntry.toURL());
+ // cleanup
+ deleteFile(localFilename);
+ done();
+ }, failed.bind(null, done, 'window.resolveLocalFileSystemURL - Error resolving URI: file://' + encodeURI(localPath)));
+ }, done, 'File', '_getLocalFilesystemPath', [entry.toURL()]);
+ }, failed.bind(null, done, 'root.getFile - Error creating file: ' + localFilename));
+ });
+ });
+ //Backwards Compatibility
+ describe('Parent References', function () {
+ /* These specs verify that paths with parent references i("..") in them
+ * work correctly, and do not cause the application to crash.
+ */
+ it("file.spec.110 should not throw exception resolving parent refefences", function (done) {
+ /* This is a direct copy of file.spec.9, with the filename changed, * as reported in CB-5721.
+ */
+ var fileName = "resolve.file.uri";
+ var dirName = "resolve.dir.uri";
+ // create a new file entry
+ createDirectory(dirName, function () {
+ createFile(dirName+"/../" + fileName, function (entry) {
+ // lookup file system entry
+ window.resolveLocalFileSystemURL(entry.toURL(), function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.name).toCanonicallyMatch(fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'window.resolveLocalFileSystemURL - Error resolving URI: ' + entry.toURL()));
+ }, failed.bind(null, done, 'createFile - Error creating file: ../' + fileName));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory: ' + dirName));
+ });
+ it("file.spec.111 should not traverse above above the root directory", function (done) {
+ var fileName = "traverse.file.uri";
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ // lookup file system entry
+ root.getFile('../' + fileName, {
+ create : false
+ }, function (fileEntry) {
+ // Note: we expect this to still resolve, as the correct behaviour is to ignore the ../, not to fail out.
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.name).toBe(fileName);
+ expect(fileEntry.fullPath).toCanonicallyMatch(root.fullPath +'/' + fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'root.getFile - Error getting file: ../' + fileName));
+ }, failed.bind(null, done, 'createFile - Error creating file: ../' + fileName));
+ });
+ it("file.spec.112 should traverse above above the current directory", function (done) {
+ var fileName = "traverse2.file.uri",
+ dirName = "traverse2.subdir";
+ // create a new directory and a file entry
+ createFile(fileName, function () {
+ createDirectory(dirName, function (entry) {
+ // lookup file system entry
+ entry.getFile('../' + fileName, {
+ create : false
+ }, function (fileEntry) {
+ expect(fileEntry).toBeDefined();
+ expect(fileEntry.name).toBe(fileName);
+ expect(fileEntry.fullPath).toCanonicallyMatch('/' + fileName);
+ // cleanup
+ deleteEntry(fileName);
+ deleteEntry(dirName);
+ done();
+ }, failed.bind(null, done, 'entry.getFile - Error getting file: ' + fileName + ' recently created above: ' + dirName));
+ }, failed.bind(null, done, 'createDirectory - Error creating directory: ' + dirName));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.113 getFile: get Entry should error for missing file above root directory", function (done) {
+ var fileName = "../missing.file";
+ // create:false, exclusive:false, file does not exist
+ root.getFile(fileName, {
+ create : false
+ }, succeed.bind(null, done, 'root.getFile - Unexpected success callback, it should not locate nonexistent file: ' + fileName), function (error) {
+ expect(error).toBeDefined();
+ if (cordova.platformId == "windows8" || cordova.platformId == "windows")
+ expect(error).toBeFileError(FileError.SECURITY_ERR);
+ else
+ expect(error).toBeFileError(FileError.NOT_FOUND_ERR);
+ done();
+ });
+ });
+ });
+ //Parent References
+ describe('toNativeURL interface', function () {
+ /* These specs verify that FileEntries have a toNativeURL method
+ * which appears to be sane.
+ */
+ var pathExpect = cordova.platformId === 'windowsphone' ? "//nativ" :
+ (cordova.platformId == "windows8" || cordova.platformId == "windows")?
+ "ms-appdata:/":
+ "file://";
+ it("file.spec.114 fileEntry should have a toNativeURL method", function (done) {
+ var fileName = "native.file.uri";
+ if (isWindows) {
+ var rootPath = root.fullPath;
+ pathExpect = rootPath.substr(0, rootPath.indexOf(":"));
+ }
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ expect(entry.toNativeURL).toBeDefined();
+ expect(entry.name).toCanonicallyMatch(fileName);
+ expect(typeof entry.toNativeURL).toBe('function');
+ var nativeURL = entry.toNativeURL();
+ var indexOfRoot = isWindows ? rootPath.indexOf(":") : 7;
+ expect(typeof nativeURL).toBe("string");
+ expect(nativeURL.substring(0, pathExpect.length)).toEqual(pathExpect);
+ expect(nativeURL.substring(nativeURL.length - fileName.length)).toEqual(fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.115 DirectoryReader should return entries with toNativeURL method", function (done) {
+ var dirName = 'nativeEntries.dir',
+ fileName = 'nativeEntries.file',
+ checkEntries = function (entries) {
+ expect(entries).toBeDefined();
+ expect(entries instanceof Array).toBe(true);
+ expect(entries.length).toBe(1);
+ expect(entries[0].toNativeURL).toBeDefined();
+ expect(typeof entries[0].toNativeURL).toBe('function');
+ var nativeURL = entries[0].toNativeURL();
+ var indexOfRoot = (isWindows) ? nativeURL.indexOf(":") : 7;
+ expect(typeof nativeURL).toBe("string");
+ expect(nativeURL.substring(0, pathExpect.length)).toEqual(pathExpect);
+ expect(nativeURL.substring(nativeURL.length - fileName.length)).toEqual(fileName);
+ // cleanup
+ directory.removeRecursively(null, null);
+ done();
+ };
+ // create a new file entry
+ root.getDirectory(dirName, {
+ create : true
+ }, function (directory) {
+ directory.getFile(fileName, {
+ create : true
+ }, function (fileEntry) {
+ var reader = directory.createReader();
+ reader.readEntries(checkEntries, failed.bind(null, done, 'reader.readEntries - Error reading entries from directory: ' + dirName));
+ }, failed.bind(null, done, 'directory.getFile - Error creating file: ' + fileName));
+ }, failed.bind(null, done, 'root.getDirectory - Error creating directory: ' + dirName));
+ });
+ it("file.spec.116 resolveLocalFileSystemURL should return entries with toNativeURL method", function (done) {
+ var fileName = "native.resolve.uri";
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ resolveLocalFileSystemURL(entry.toURL(), function (entry) {
+ expect(entry.toNativeURL).toBeDefined();
+ expect(entry.name).toCanonicallyMatch(fileName);
+ expect(typeof entry.toNativeURL).toBe('function');
+ var nativeURL = entry.toNativeURL();
+ var indexOfRoot = (isWindows) ? nativeURL.indexOf(":") : 7;
+ expect(typeof nativeURL).toBe("string");
+ expect(nativeURL.substring(0, pathExpect.length)).toEqual(pathExpect);
+ expect(nativeURL.substring(nativeURL.length - fileName.length)).toEqual(fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'resolveLocalFileSystemURL - Error resolving file URL: ' + entry.toURL()));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ });
+ //toNativeURL interface
+ describe('resolveLocalFileSystemURL on file://', function () {
+ /* These specs verify that window.resolveLocalFileSystemURL works correctly on file:// URLs
+ */
+ it("file.spec.117 should not resolve native URLs outside of FS roots", function (done) {
+ // lookup file system entry
+ window.resolveLocalFileSystemURL("file:///this.is.an.invalid.url", succeed.bind(null, done, 'window.resolveLocalFileSystemURL - Unexpected success callback, it should not resolve invalid URL: file:///this.is.an.invalid.url'), function (error) {
+ expect(error).toBeDefined();
+ done();
+ });
+ });
+ it("file.spec.118 should not resolve native URLs outside of FS roots", function (done) {
+ // lookup file system entry
+ window.resolveLocalFileSystemURL("file://localhost/this.is.an.invalid.url", succeed.bind(null, done, 'window.resolveLocalFileSystemURL - Unexpected success callback, it should not resolve invalid URL: file://localhost/this.is.an.invalid.url'), function (error) {
+ expect(error).toBeDefined();
+ done();
+ });
+ });
+ it("file.spec.119 should not resolve invalid native URLs", function (done) {
+ // lookup file system entry
+ window.resolveLocalFileSystemURL("file://localhost", succeed.bind(null, done, 'window.resolveLocalFileSystemURL - Unexpected success callback, it should not resolve invalid URL: file://localhost'), function (error) {
+ expect(error).toBeDefined();
+ done();
+ });
+ });
+ it("file.spec.120 should not resolve invalid native URLs with query strings", function (done) {
+ // lookup file system entry
+ window.resolveLocalFileSystemURL("file://localhost?test/test", succeed.bind(null, done, 'window.resolveLocalFileSystemURL - Unexpected success callback, it should not resolve invalid URL: file://localhost?test/test'), function (error) {
+ expect(error).toBeDefined();
+ done();
+ });
+ });
+ it("file.spec.121 should resolve native URLs returned by API", function (done) {
+ var fileName = "native.resolve.uri1";
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ resolveLocalFileSystemURL(entry.toNativeURL(), function (fileEntry) {
+ expect(fileEntry.fullPath).toCanonicallyMatch(root.fullPath + "/" + fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'resolveLocalFileSystemURL - Error resolving file URL: ' + entry.toNativeURL()));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.122 should resolve native URLs returned by API with localhost", function (done) {
+ var fileName = "native.resolve.uri2";
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ var url = entry.toNativeURL();
+ url = url.replace("///", "//localhost/");
+ resolveLocalFileSystemURL(url, function (fileEntry) {
+ expect(fileEntry.fullPath).toCanonicallyMatch(root.fullPath + "/" + fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'resolveLocalFileSystemURL - Error resolving file URL: ' + url));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.123 should resolve native URLs returned by API with query string", function (done) {
+ var fileName = "native.resolve.uri3";
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ var url = entry.toNativeURL();
+ url = url + "?test/test";
+ resolveLocalFileSystemURL(url, function (fileEntry) {
+ expect(fileEntry.fullPath).toCanonicallyMatch(root.fullPath + "/" + fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'resolveLocalFileSystemURL - Error resolving file URL: ' + url));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ it("file.spec.124 should resolve native URLs returned by API with localhost and query string", function (done) {
+ var fileName = "native.resolve.uri4";
+ // create a new file entry
+ createFile(fileName, function (entry) {
+ var url = entry.toNativeURL();
+ url = url.replace("///", "//localhost/") + "?test/test";
+ resolveLocalFileSystemURL(url, function (fileEntry) {
+ expect(fileEntry.fullPath).toCanonicallyMatch(root.fullPath + "/" + fileName);
+ // cleanup
+ deleteEntry(fileName);
+ done();
+ }, failed.bind(null, done, 'resolveLocalFileSystemURL - Error resolving file URL: ' + url));
+ }, failed.bind(null, done, 'createFile - Error creating file: ' + fileName));
+ });
+ });
+ //resolveLocalFileSystemURL on file://
+ describe('cross-file-system copy and move', function () {
+ /* These specs verify that Entry.copyTo and Entry.moveTo work correctly
+ * when crossing filesystem boundaries.
+ */
+ it("file.spec.125 copyTo: temporary -> persistent", function (done) {
+ var file1 = "entry.copy.file1a",
+ file2 = "entry.copy.file2a",
+ sourceEntry,
+ fullPath = joinURL(root.fullPath, file2),
+ validateFile = function (entry) {
+ // a bit redundant since copy returned this entry already
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toCanonicallyMatch(file2);
+ expect(entry.fullPath).toCanonicallyMatch(fullPath);
+ expect(entry.filesystem).toBeDefined();
+ expect(entry.filesystem.name).toEqual("persistent");
+ // cleanup
+ entry.remove();
+ sourceEntry.remove();
+ done();
+ },
+ createSourceAndTransfer = function () {
+ temp_root.getFile(file1, {
+ create : true
+ }, function (entry) {
+ expect(entry.filesystem).toBeDefined();
+ expect(entry.filesystem.name).toEqual("temporary");
+ sourceEntry = entry;
+ // Save for later cleanup
+ entry.copyTo(persistent_root, file2, validateFile, failed.bind(null, done, 'entry.copyTo - Error copying file: ' + file1 + ' to PERSISTENT root as: ' + file2));
+ }, failed.bind(null, done, 'temp_root.getFile - Error creating file: ' + file1 + 'at TEMPORAL root'));
+ };
+ // Delete any existing file to start things off
+ persistent_root.getFile(file2, {}, function (entry) {
+ entry.remove(createSourceAndTransfer, failed.bind(null, done, 'entry.remove - Error removing file: ' + file2));
+ }, createSourceAndTransfer);
+ });
+ it("file.spec.126 copyTo: persistent -> temporary", function (done) {
+ var file1 = "entry.copy.file1b",
+ file2 = "entry.copy.file2b",
+ sourceEntry,
+ fullPath = joinURL(temp_root.fullPath, file2),
+ validateFile = function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toCanonicallyMatch(file2);
+ expect(entry.fullPath).toCanonicallyMatch(fullPath);
+ expect(entry.filesystem.name).toEqual("temporary");
+ // cleanup
+ entry.remove();
+ sourceEntry.remove();
+ done();
+ },
+ createSourceAndTransfer = function () {
+ persistent_root.getFile(file1, {
+ create : true
+ }, function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.filesystem).toBeDefined();
+ expect(entry.filesystem.name).toEqual("persistent");
+ sourceEntry = entry;
+ // Save for later cleanup
+ entry.copyTo(temp_root, file2, validateFile, failed.bind(null, done, 'entry.copyTo - Error copying file: ' + file1 + ' to TEMPORAL root as: ' + file2));
+ }, failed.bind(null, done, 'persistent_root.getFile - Error creating file: ' + file1 + 'at PERSISTENT root'));
+ };
+ // Delete any existing file to start things off
+ temp_root.getFile(file2, {}, function (entry) {
+ entry.remove(createSourceAndTransfer, failed.bind(null, done, 'entry.remove - Error removing file: ' + file2));
+ }, createSourceAndTransfer);
+ });
+ it("file.spec.127 moveTo: temporary -> persistent", function (done) {
+ var file1 = "entry.copy.file1a",
+ file2 = "entry.copy.file2a",
+ sourceEntry,
+ fullPath = joinURL(root.fullPath, file2),
+ validateFile = function (entry) {
+ // a bit redundant since copy returned this entry already
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toCanonicallyMatch(file2);
+ expect(entry.fullPath).toCanonicallyMatch(fullPath);
+ expect(entry.filesystem).toBeDefined();
+ expect(entry.filesystem.name).toEqual("persistent");
+ // cleanup
+ entry.remove();
+ sourceEntry.remove();
+ done();
+ },
+ createSourceAndTransfer = function () {
+ temp_root.getFile(file1, {
+ create : true
+ }, function (entry) {
+ expect(entry.filesystem).toBeDefined();
+ expect(entry.filesystem.name).toEqual("temporary");
+ sourceEntry = entry;
+ // Save for later cleanup
+ entry.moveTo(persistent_root, file2, validateFile, failed.bind(null, done, 'entry.moveTo - Error moving file: ' + file1 + ' to PERSISTENT root as: ' + file2));
+ }, failed.bind(null, done, 'temp_root.getFile - Error creating file: ' + file1 + 'at TEMPORAL root'));
+ };
+ // Delete any existing file to start things off
+ persistent_root.getFile(file2, {}, function (entry) {
+ entry.remove(createSourceAndTransfer, failed.bind(null, done, 'entry.remove - Error removing file: ' + file2));
+ }, createSourceAndTransfer);
+ });
+ it("file.spec.128 moveTo: persistent -> temporary", function (done) {
+ var file1 = "entry.copy.file1b",
+ file2 = "entry.copy.file2b",
+ sourceEntry,
+ fullPath = joinURL(temp_root.fullPath, file2),
+ validateFile = function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.isFile).toBe(true);
+ expect(entry.isDirectory).toBe(false);
+ expect(entry.name).toCanonicallyMatch(file2);
+ expect(entry.fullPath).toCanonicallyMatch(fullPath);
+ expect(entry.filesystem.name).toEqual("temporary");
+ // cleanup
+ entry.remove();
+ sourceEntry.remove();
+ done();
+ },
+ createSourceAndTransfer = function () {
+ persistent_root.getFile(file1, {
+ create : true
+ }, function (entry) {
+ expect(entry).toBeDefined();
+ expect(entry.filesystem).toBeDefined();
+ expect(entry.filesystem.name).toEqual("persistent");
+ sourceEntry = entry;
+ // Save for later cleanup
+ entry.moveTo(temp_root, file2, validateFile, failed.bind(null, done, 'entry.moveTo - Error moving file: ' + file1 + ' to TEMPORAL root as: ' + file2));
+ }, failed.bind(null, done, 'persistent_root.getFile - Error creating file: ' + file1 + 'at PERSISTENT root'));
+ };
+ // Delete any existing file to start things off
+ temp_root.getFile(file2, {}, function (entry) {
+ entry.remove(createSourceAndTransfer, failed.bind(null, done, 'entry.remove - Error removing file: ' + file2));
+ }, createSourceAndTransfer);
+ });
+ it("file.spec.129 cordova.file.*Directory are set", function () {
+ var expectedPaths = ['applicationDirectory', 'applicationStorageDirectory', 'dataDirectory', 'cacheDirectory'];
+ if (cordova.platformId == 'android' || cordova.platformId == 'amazon-fireos') {
+ expectedPaths.push('externalApplicationStorageDirectory', 'externalRootDirectory', 'externalCacheDirectory', 'externalDataDirectory');
+ } else if (cordova.platformId == 'blackberry10') {
+ expectedPaths.push('externalRootDirectory', 'sharedDirectory');
+ } else if (cordova.platformId == 'ios') {
+ expectedPaths.push('syncedDataDirectory', 'documentsDirectory', 'tempDirectory');
+ } else {
+ console.log('Skipping test due on unsupported platform.');
+ return;
+ }
+ for (var i = 0; i < expectedPaths.length; ++i) {
+ expect(typeof cordova.file[expectedPaths[i]]).toBe('string');
+ expect(cordova.file[expectedPaths[i]]).toMatch(/\/$/, 'Path should end with a slash');
+ }
+ });
+ });
+ //cross-file-system copy and move
+
+ });
+ //File API describe
+
+};
+//******************************************************************************************
+//***************************************Manual Tests***************************************
+//******************************************************************************************
+
+exports.defineManualTests = function (contentEl, createActionButton) {
+
+ function resolveFs(fsname) {
+ var fsURL = "cdvfile://localhost/" + fsname + "/";
+ logMessage("Resolving URL: " + fsURL);
+ resolveLocalFileSystemURL(fsURL, function (entry) {
+ logMessage("Success", 'green');
+ logMessage(entry.toURL(), 'blue');
+ logMessage(entry.toInternalURL(), 'blue');
+ logMessage("Resolving URL: " + entry.toURL());
+ resolveLocalFileSystemURL(entry.toURL(), function (entry2) {
+ logMessage("Success", 'green');
+ logMessage(entry2.toURL(), 'blue');
+ logMessage(entry2.toInternalURL(), 'blue');
+ }, logError("resolveLocalFileSystemURL"));
+ }, logError("resolveLocalFileSystemURL"));
+ }
+
+ function testPrivateURL() {
+ requestFileSystem(TEMPORARY, 0, function (fileSystem) {
+ logMessage("Temporary root is at " + fileSystem.root.toNativeURL());
+ fileSystem.root.getFile("testfile", {
+ create : true
+ }, function (entry) {
+ logMessage("Temporary file is at " + entry.toNativeURL());
+ if (entry.toNativeURL().substring(0, 12) == "file:///var/") {
+ logMessage("File starts with /var/, trying /private/var");
+ var newURL = "file://localhost/private/var/" + entry.toNativeURL().substring(12) + "?and=another_thing";
+ //var newURL = entry.toNativeURL();
+ logMessage(newURL, 'blue');
+ resolveLocalFileSystemURL(newURL, function (newEntry) {
+ logMessage("Successfully resolved.", 'green');
+ logMessage(newEntry.toURL(), 'blue');
+ logMessage(newEntry.toNativeURL(), 'blue');
+ }, logError("resolveLocalFileSystemURL"));
+ }
+ }, logError("getFile"));
+ }, logError("requestFileSystem"));
+ }
+
+ function clearLog() {
+ var log = document.getElementById("info");
+ log.innerHTML = "";
+ }
+
+ function logMessage(message, color) {
+ var log = document.getElementById("info");
+ var logLine = document.createElement('div');
+ if (color) {
+ logLine.style.color = color;
+ }
+ logLine.innerHTML = message;
+ log.appendChild(logLine);
+ }
+
+ function logError(serviceName) {
+ return function (err) {
+ logMessage("ERROR: " + serviceName + " " + JSON.stringify(err), "red");
+ };
+ }
+
+ var fsRoots = {
+ "ios" : "library,library-nosync,documents,documents-nosync,cache,bundle,root,private",
+ "android" : "files,files-external,documents,sdcard,cache,cache-external,root",
+ "amazon-fireos" : "files,files-external,documents,sdcard,cache,cache-external,root"
+ };
+
+ //Add title and align to content
+ var div = document.createElement('h2');
+ div.appendChild(document.createTextNode('File Systems'));
+ div.setAttribute("align", "center");
+ contentEl.appendChild(div);
+
+ div = document.createElement('h3');
+ div.appendChild(document.createTextNode('Results are displayed in yellow status box below with expected results noted under that'));
+ div.setAttribute("align", "center");
+ contentEl.appendChild(div);
+
+ div = document.createElement('div');
+ div.setAttribute("id", "button");
+ div.setAttribute("align", "center");
+ contentEl.appendChild(div);
+ if (fsRoots.hasOwnProperty(cordova.platformId)) {
+ (fsRoots[cordova.platformId].split(',')).forEach(function (fs) {
+ if (cordova.platformId === 'ios' && fs === 'private') {
+ createActionButton("Test private URL (iOS)", function () {
+ clearLog();
+ testPrivateURL();
+ }, 'button');
+ } else {
+ createActionButton(fs, function () {
+ clearLog();
+ resolveFs(fs);
+ }, 'button');
+ }
+ });
+ }
+
+
+ div = document.createElement('div');
+ div.setAttribute("id", "info");
+ div.setAttribute("align", "center");
+ contentEl.appendChild(div);
+
+ div = document.createElement('h3');
+ div.appendChild(document.createTextNode('For each test above, file or directory should be successfully found. ' +
+ 'Status box should say Resolving URL was Success. The first URL resolved is the internal URL. ' +
+ 'The second URL resolved is the absolute URL. Blue URLs must match.'));
+ contentEl.appendChild(div);
+
+ div = document.createElement('h3');
+ div.appendChild(document.createTextNode('For Test private URL (iOS), the private URL (first blue URL in status box) ' +
+ 'should be successfully resolved. Status box should say Successfully resolved. Both blue URLs below ' +
+ 'that should match.'));
+ contentEl.appendChild(div);
+};
diff --git a/plugins/org.apache.cordova.file/www/DirectoryEntry.js b/plugins/org.apache.cordova.file/www/DirectoryEntry.js
new file mode 100644
index 0000000..62f468a
--- /dev/null
+++ b/plugins/org.apache.cordova.file/www/DirectoryEntry.js
@@ -0,0 +1,117 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var argscheck = require('cordova/argscheck'),
+ utils = require('cordova/utils'),
+ exec = require('cordova/exec'),
+ Entry = require('./Entry'),
+ FileError = require('./FileError'),
+ DirectoryReader = require('./DirectoryReader');
+
+/**
+ * An interface representing a directory on the file system.
+ *
+ * {boolean} isFile always false (readonly)
+ * {boolean} isDirectory always true (readonly)
+ * {DOMString} name of the directory, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the directory (readonly)
+ * {FileSystem} filesystem on which the directory resides (readonly)
+ */
+var DirectoryEntry = function(name, fullPath, fileSystem, nativeURL) {
+
+ // add trailing slash if it is missing
+ if ((fullPath) && !/\/$/.test(fullPath)) {
+ fullPath += "/";
+ }
+ // add trailing slash if it is missing
+ if (nativeURL && !/\/$/.test(nativeURL)) {
+ nativeURL += "/";
+ }
+ DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem, nativeURL);
+};
+
+utils.extend(DirectoryEntry, Entry);
+
+/**
+ * Creates a new DirectoryReader to read entries from this directory
+ */
+DirectoryEntry.prototype.createReader = function() {
+ return new DirectoryReader(this.toInternalURL());
+};
+
+/**
+ * Creates or looks up a directory
+ *
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
+ * @param {Flags} options to create or exclusively create the directory
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) {
+ argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments);
+ var fs = this.filesystem;
+ var win = successCallback && function(result) {
+ var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
+ successCallback(entry);
+ };
+ var fail = errorCallback && function(code) {
+ errorCallback(new FileError(code));
+ };
+ exec(win, fail, "File", "getDirectory", [this.toInternalURL(), path, options]);
+};
+
+/**
+ * Deletes a directory and all of it's contents
+ *
+ * @param {Function} successCallback is called with no parameters
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) {
+ argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments);
+ var fail = errorCallback && function(code) {
+ errorCallback(new FileError(code));
+ };
+ exec(successCallback, fail, "File", "removeRecursively", [this.toInternalURL()]);
+};
+
+/**
+ * Creates or looks up a file
+ *
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
+ * @param {Flags} options to create or exclusively create the file
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) {
+ argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments);
+ var fs = this.filesystem;
+ var win = successCallback && function(result) {
+ var FileEntry = require('./FileEntry');
+ var entry = new FileEntry(result.name, result.fullPath, fs, result.nativeURL);
+ successCallback(entry);
+ };
+ var fail = errorCallback && function(code) {
+ errorCallback(new FileError(code));
+ };
+ exec(win, fail, "File", "getFile", [this.toInternalURL(), path, options]);
+};
+
+module.exports = DirectoryEntry;
diff --git a/plugins/org.apache.cordova.file/www/DirectoryReader.js b/plugins/org.apache.cordova.file/www/DirectoryReader.js
new file mode 100644
index 0000000..2894c9a
--- /dev/null
+++ b/plugins/org.apache.cordova.file/www/DirectoryReader.js
@@ -0,0 +1,73 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var exec = require('cordova/exec'),
+ FileError = require('./FileError') ;
+
+/**
+ * An interface that lists the files and directories in a directory.
+ */
+function DirectoryReader(localURL) {
+ this.localURL = localURL || null;
+ this.hasReadEntries = false;
+}
+
+/**
+ * Returns a list of entries from a directory.
+ *
+ * @param {Function} successCallback is called with a list of entries
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) {
+ // If we've already read and passed on this directory's entries, return an empty list.
+ if (this.hasReadEntries) {
+ successCallback([]);
+ return;
+ }
+ var reader = this;
+ var win = typeof successCallback !== 'function' ? null : function(result) {
+ var retVal = [];
+ for (var i=0; i or
+ *