Rafael Arce Nazario 5 years ago
commit
7634e2ccd0
100 changed files with 32002 additions and 0 deletions
  1. 8
    0
      .gitignore
  2. 5
    0
      .monaca/project_info.json
  3. 9
    0
      .monacaignore
  4. 21
    0
      LICENSE
  5. 2
    0
      Makefile
  6. 93
    0
      config.xml
  7. 7607
    0
      package-lock.json
  8. 39
    0
      package.json
  9. 30
    0
      plugins/android.json
  10. 30
    0
      plugins/browser.json
  11. 89
    0
      plugins/cordova-custom-config/.jshintrc
  12. 14
    0
      plugins/cordova-custom-config/.travis.yml
  13. 208
    0
      plugins/cordova-custom-config/CHANGELOG.md
  14. 726
    0
      plugins/cordova-custom-config/README.md
  15. 1186
    0
      plugins/cordova-custom-config/hooks/applyCustomConfig.js
  16. 114
    0
      plugins/cordova-custom-config/hooks/fileUtils.js
  17. 87
    0
      plugins/cordova-custom-config/hooks/logger.js
  18. 150
    0
      plugins/cordova-custom-config/hooks/restoreBackups.js
  19. 49
    0
      plugins/cordova-custom-config/hooks/triggerExampleProjBuild.js
  20. 82
    0
      plugins/cordova-custom-config/package.json
  21. 27
    0
      plugins/cordova-custom-config/plugin.xml
  22. 7
    0
      plugins/cordova-custom-config/templates/ios/Contents.json
  23. 17
    0
      plugins/cordova-plugin-email/.npmignore
  24. 135
    0
      plugins/cordova-plugin-email/CHANGELOG.md
  25. 13
    0
      plugins/cordova-plugin-email/CONTRIBUTING.MD
  26. 202
    0
      plugins/cordova-plugin-email/LICENSE
  27. 74
    0
      plugins/cordova-plugin-email/PLUGIN_USAGE.MD
  28. 332
    0
      plugins/cordova-plugin-email/README.md
  29. 91
    0
      plugins/cordova-plugin-email/package.json
  30. 83
    0
      plugins/cordova-plugin-email/plugin.xml
  31. 177
    0
      plugins/cordova-plugin-email/src/android/EmailComposer.java
  32. 681
    0
      plugins/cordova-plugin-email/src/android/EmailComposerImpl.java
  33. 76
    0
      plugins/cordova-plugin-email/src/browser/EmailComposerProxy.js
  34. 33
    0
      plugins/cordova-plugin-email/src/ios/APPEmailComposer.h
  35. 205
    0
      plugins/cordova-plugin-email/src/ios/APPEmailComposer.m
  36. 58
    0
      plugins/cordova-plugin-email/src/ios/APPEmailComposerImpl.h
  37. 483
    0
      plugins/cordova-plugin-email/src/ios/APPEmailComposerImpl.m
  38. 68
    0
      plugins/cordova-plugin-email/src/windows/EmailComposerProxy.js
  39. 259
    0
      plugins/cordova-plugin-email/src/windows/EmailComposerProxyImpl.js
  40. 133
    0
      plugins/cordova-plugin-email/src/wp8/EmailComposer.cs
  41. 77
    0
      plugins/cordova-plugin-email/src/wp8/Options.cs
  42. 204
    0
      plugins/cordova-plugin-email/www/email_composer.js
  43. 30
    0
      plugins/cordova-plugin-file/.jshintrc
  44. 1
    0
      plugins/cordova-plugin-file/.ratignore
  45. 37
    0
      plugins/cordova-plugin-file/CONTRIBUTING.md
  46. 202
    0
      plugins/cordova-plugin-file/LICENSE
  47. 5
    0
      plugins/cordova-plugin-file/NOTICE
  48. 833
    0
      plugins/cordova-plugin-file/README.md
  49. 496
    0
      plugins/cordova-plugin-file/RELEASENOTES.md
  50. 120
    0
      plugins/cordova-plugin-file/doc/plugins.md
  51. 87
    0
      plugins/cordova-plugin-file/package.json
  52. 260
    0
      plugins/cordova-plugin-file/plugin.xml
  53. 294
    0
      plugins/cordova-plugin-file/src/android/AssetFilesystem.java
  54. 223
    0
      plugins/cordova-plugin-file/src/android/ContentFilesystem.java
  55. 134
    0
      plugins/cordova-plugin-file/src/android/DirectoryManager.java
  56. 29
    0
      plugins/cordova-plugin-file/src/android/EncodingException.java
  57. 29
    0
      plugins/cordova-plugin-file/src/android/FileExistsException.java
  58. 1225
    0
      plugins/cordova-plugin-file/src/android/FileUtils.java
  59. 331
    0
      plugins/cordova-plugin-file/src/android/Filesystem.java
  60. 30
    0
      plugins/cordova-plugin-file/src/android/InvalidModificationException.java
  61. 513
    0
      plugins/cordova-plugin-file/src/android/LocalFilesystem.java
  62. 64
    0
      plugins/cordova-plugin-file/src/android/LocalFilesystemURL.java
  63. 29
    0
      plugins/cordova-plugin-file/src/android/NoModificationAllowedException.java
  64. 94
    0
      plugins/cordova-plugin-file/src/android/PendingRequests.java
  65. 30
    0
      plugins/cordova-plugin-file/src/android/TypeMismatchException.java
  66. 47
    0
      plugins/cordova-plugin-file/src/android/build-extras.gradle
  67. 1059
    0
      plugins/cordova-plugin-file/src/browser/FileProxy.js
  68. 30
    0
      plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h
  69. 253
    0
      plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m
  70. 157
    0
      plugins/cordova-plugin-file/src/ios/CDVFile.h
  71. 1119
    0
      plugins/cordova-plugin-file/src/ios/CDVFile.m
  72. 32
    0
      plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h
  73. 750
    0
      plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m
  74. 189
    0
      plugins/cordova-plugin-file/src/osx/CDVFile.h
  75. 1056
    0
      plugins/cordova-plugin-file/src/osx/CDVFile.m
  76. 32
    0
      plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.h
  77. 733
    0
      plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.m
  78. 1190
    0
      plugins/cordova-plugin-file/src/windows/FileProxy.js
  79. 17
    0
      plugins/cordova-plugin-file/tests/package.json
  80. 43
    0
      plugins/cordova-plugin-file/tests/plugin.xml
  81. 93
    0
      plugins/cordova-plugin-file/tests/src/android/TestContentProvider.java
  82. 4163
    0
      plugins/cordova-plugin-file/tests/tests.js
  83. 1
    0
      plugins/cordova-plugin-file/tests/www/fixtures/asset-test/asset-test.txt
  84. 378
    0
      plugins/cordova-plugin-file/types/index.d.ts
  85. 117
    0
      plugins/cordova-plugin-file/www/DirectoryEntry.js
  86. 72
    0
      plugins/cordova-plugin-file/www/DirectoryReader.js
  87. 260
    0
      plugins/cordova-plugin-file/www/Entry.js
  88. 78
    0
      plugins/cordova-plugin-file/www/File.js
  89. 92
    0
      plugins/cordova-plugin-file/www/FileEntry.js
  90. 46
    0
      plugins/cordova-plugin-file/www/FileError.js
  91. 298
    0
      plugins/cordova-plugin-file/www/FileReader.js
  92. 55
    0
      plugins/cordova-plugin-file/www/FileSystem.js
  93. 41
    0
      plugins/cordova-plugin-file/www/FileUploadOptions.js
  94. 30
    0
      plugins/cordova-plugin-file/www/FileUploadResult.js
  95. 325
    0
      plugins/cordova-plugin-file/www/FileWriter.js
  96. 36
    0
      plugins/cordova-plugin-file/www/Flags.js
  97. 23
    0
      plugins/cordova-plugin-file/www/LocalFileSystem.js
  98. 40
    0
      plugins/cordova-plugin-file/www/Metadata.js
  99. 67
    0
      plugins/cordova-plugin-file/www/ProgressEvent.js
  100. 0
    0
      plugins/cordova-plugin-file/www/android/FileSystem.js

+ 8
- 0
.gitignore View File

@@ -0,0 +1,8 @@
1
+/.monaca/*
2
+!/.monaca/project_info.json
3
+/platforms
4
+.DS_Store
5
+*.swp
6
+.vscode/
7
+typings/
8
+node_modules

+ 5
- 0
.monaca/project_info.json View File

@@ -0,0 +1,5 @@
1
+{
2
+    "framework_version": "3.5",
3
+    "xcode_version": "10.2.1",
4
+    "cordova_version": "9.0"
5
+}

+ 9
- 0
.monacaignore View File

@@ -0,0 +1,9 @@
1
+/.monaca/*
2
+!/.monaca/project_info.json
3
+/platforms
4
+.DS_Store
5
+*.swp
6
+.vscode/
7
+typings/
8
+node_modules
9
+.git

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2016 Asial Corporation
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 2
- 0
Makefile View File

@@ -0,0 +1,2 @@
1
+all:
2
+	cordova build android; /home/rarce/Android/Sdk/platform-tools/adb install ./platforms/android/app/build/outputs/apk/debug/app-debug.apk

+ 93
- 0
config.xml View File

@@ -0,0 +1,93 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<widget xmlns="http://www.w3.org/ns/widgets" id="com.example.helloworld" version="1.0.0">
3
+  <name>Asistencia</name>
4
+  <description/>
5
+  <author/>
6
+  <content src="index.html"/>
7
+  <access origin="*"/>
8
+  <allow-navigation href="*"/>
9
+  <allow-intent href="itms:*"/>
10
+  <allow-intent href="itms-apps:*"/>
11
+
12
+  <preference name="DisallowOverscroll" value="true"/>
13
+  <preference name="Orientation" value="default"/>
14
+
15
+  <preference name="loglevel" value="DEBUG"/>
16
+  <preference name="AndroidLaunchMode" value="singleTop"/>
17
+  <preference name="ErrorUrl" value=""/>
18
+  <preference name="Fullscreen" value="false"/>
19
+  <preference name="KeepRunning" value="true"/>
20
+  <preference name="SplashScreen" value="screen"/>
21
+  <preference name="SplashScreenDelay" value="1000"/>
22
+
23
+  <preference name="AllowInlineMediaPlayback" value="false"/>
24
+  <preference name="AutoHideSplashScreen" value="true"/>
25
+  <preference name="BackupWebStorage" value="cloud"/>
26
+  <preference name="EnableViewportScale" value="false"/>
27
+  <preference name="FadeSplashScreen" value="true"/>
28
+  <preference name="FadeSplashScreenDuration" value="250"/>
29
+  <preference name="KeyboardDisplayRequiresUserAction" value="true"/>
30
+  <preference name="MediaPlaybackRequiresUserAction" value="false"/>
31
+  <preference name="ShowSplashScreenSpinner" value="false"/>
32
+  <preference name="SuppressesIncrementalRendering" value="false"/>
33
+  <preference name="TopActivityIndicator" value="gray"/>
34
+  <preference name="GapBetweenPages" value="0"/>
35
+  <preference name="PageLength" value="0"/>
36
+  <preference name="PaginationBreakingMode" value="page"/>
37
+  <preference name="PaginationMode" value="unpaginated"/>
38
+  <feature name="LocalStorage">
39
+    <param name="ios-package" value="CDVLocalStorage"/>
40
+  </feature>
41
+  <preference name="UIWebViewDecelerationSpeed" value="normal"/>
42
+
43
+  <preference name="monaca:AndroidIsPackageNameSeparate" value="false"/>
44
+  <preference name="ios-XCBuildConfiguration-TARGETED_DEVICE_FAMILY" value="1,2"/>
45
+  <platform name="ios">
46
+    <splash src="/res/ios/screen/Default@2x~universal~anyany.png"/>
47
+    <icon src="/res/ios/icon/icon.png" width="57" height="57"/>
48
+    <icon src="/res/ios/icon/icon@2x.png" width="114" height="114"/>
49
+    <icon src="/res/ios/icon/icon-72.png" width="72" height="72"/>
50
+    <icon src="/res/ios/icon/icon-72@2x.png" width="144" height="144"/>
51
+    <icon src="/res/ios/icon/icon-60.png" width="60" height="60"/>
52
+    <icon src="/res/ios/icon/icon-60@2x.png" width="120" height="120"/>
53
+    <icon src="/res/ios/icon/icon-60@3x.png" width="180" height="180"/>
54
+    <icon src="/res/ios/icon/icon-76.png" width="76" height="76"/>
55
+    <icon src="/res/ios/icon/icon-76@2x.png" width="152" height="152"/>
56
+    <icon src="/res/ios/icon/icon-40.png" width="40" height="40"/>
57
+    <icon src="/res/ios/icon/icon-40@2x.png" width="80" height="80"/>
58
+    <icon src="/res/ios/icon/icon-50.png" width="50" height="50"/>
59
+    <icon src="/res/ios/icon/icon-50@2x.png" width="100" height="100"/>
60
+    <icon src="/res/ios/icon/icon-small.png" width="29" height="29"/>
61
+    <icon src="/res/ios/icon/icon-small@2x.png" width="58" height="58"/>
62
+    <icon src="/res/ios/icon/icon-small@3x.png" width="87" height="87"/>
63
+    <icon src="/res/ios/icon/icon-83.5@2x~ipad.png" width="167" height="167"/>
64
+    <icon src="/res/ios/icon/icon-1024.png" width="1024" height="1024"/>
65
+    <config-file platform="ios" parent="CFBundleLocalizations" target="*-Info.plist">
66
+      <array>
67
+        <string>en</string>
68
+      </array>
69
+    </config-file>
70
+  </platform>
71
+  <platform name="android">
72
+    <icon src="/res/android/icon/ldpi.png" density="ldpi"/>
73
+    <icon src="/res/android/icon/mdpi.png" density="mdpi"/>
74
+    <icon src="/res/android/icon/hdpi.png" density="hdpi"/>
75
+    <icon src="/res/android/icon/xhdpi.png" density="xhdpi"/>
76
+    <icon src="/res/android/icon/xxhdpi.png" density="xxhdpi"/>
77
+    <icon src="/res/android/icon/xxxhdpi.png" density="xxxhdpi"/>
78
+    <splash src="/res/android/screen/splash-port-ldpi.9.png" density="port-ldpi"/>
79
+    <splash src="/res/android/screen/splash-port-mdpi.9.png" density="port-mdpi"/>
80
+    <splash src="/res/android/screen/splash-port-hdpi.9.png" density="port-hdpi"/>
81
+    <splash src="/res/android/screen/splash-port-xhdpi.9.png" density="port-xhdpi"/>
82
+    <splash src="/res/android/screen/splash-port-xxhdpi.9.png" density="port-xxhdpi"/>
83
+    <splash src="/res/android/screen/splash-port-xxxhdpi.9.png" density="port-xxxhdpi"/>
84
+    <splash src="/res/android/screen/splash-mdpi.png" density="mdpi"/>
85
+  </platform>
86
+  <platform name="electron">
87
+    <icon src="/res/electron/icon/icon_electron_512.png" width="512" height="512"/>
88
+    <splash src="/res/electron/screen/electron_splash_image.png" width="620" height="300"/>
89
+    <preference name="SplashScreenWidth" value="620"/>
90
+    <preference name="SplashScreenHeight" value="300"/>
91
+    <preference name="ShowSplashScreen" value="false"/>
92
+  </platform>
93
+</widget>

+ 7607
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 39
- 0
package.json View File

@@ -0,0 +1,39 @@
1
+{
2
+    "name": "monaca-template-onsenui-v2-js-minimum",
3
+    "version": "2.0.4",
4
+    "dependencies": {
5
+        "cordova-android": "^8.1.0",
6
+        "cordova-browser": "^6.0.0",
7
+        "cordova-custom-config": "5.1.0",
8
+        "cordova-plugin-email": "^1.2.7",
9
+        "cordova-plugin-file": "^6.0.2",
10
+        "cordova-plugin-filechooser": "git+ssh://git@github.com/ihadeed/cordova-filechooser.git",
11
+        "cordova-plugin-splashscreen": "5.0.2",
12
+        "cordova-plugin-whitelist": "1.3.3",
13
+        "monaca-plugin-monaca-core": "3.3.0",
14
+        "nw": "^0.26.6"
15
+    },
16
+    "cordova": {
17
+        "plugins": {
18
+            "cordova-custom-config": {},
19
+            "cordova-plugin-splashscreen": {},
20
+            "cordova-plugin-whitelist": {},
21
+            "monaca-plugin-monaca-core": {},
22
+            "cordova-plugin-filechooser": {},
23
+            "cordova-plugin-file": {},
24
+            "cordova-plugin-email": {}
25
+        },
26
+        "platforms": [
27
+            "android",
28
+            "browser"
29
+        ]
30
+    },
31
+    "scripts": {
32
+        "monaca:preview": "npm run dev",
33
+        "dev": "browser-sync start -s www/ --watch --port 8080 --ui-port 8081"
34
+    },
35
+    "devDependencies": {
36
+        "browser-sync": "^2.26.7",
37
+        "cordova": "^9.0.0"
38
+    }
39
+}

+ 30
- 0
plugins/android.json View File

@@ -0,0 +1,30 @@
1
+{
2
+  "prepare_queue": {
3
+    "installed": [],
4
+    "uninstalled": []
5
+  },
6
+  "config_munge": {
7
+    "files": {}
8
+  },
9
+  "installed_plugins": {
10
+    "cordova-custom-config": {
11
+      "PACKAGE_NAME": "com.example.helloworld"
12
+    },
13
+    "cordova-plugin-splashscreen": {
14
+      "PACKAGE_NAME": "com.example.helloworld"
15
+    },
16
+    "cordova-plugin-whitelist": {
17
+      "PACKAGE_NAME": "com.example.helloworld"
18
+    },
19
+    "cordova-plugin-filechooser": {
20
+      "PACKAGE_NAME": "com.example.helloworld"
21
+    },
22
+    "cordova-plugin-file": {
23
+      "PACKAGE_NAME": "com.example.helloworld"
24
+    },
25
+    "cordova-plugin-email": {
26
+      "PACKAGE_NAME": "com.example.helloworld"
27
+    }
28
+  },
29
+  "dependent_plugins": {}
30
+}

+ 30
- 0
plugins/browser.json View File

@@ -0,0 +1,30 @@
1
+{
2
+  "prepare_queue": {
3
+    "installed": [],
4
+    "uninstalled": []
5
+  },
6
+  "config_munge": {
7
+    "files": {}
8
+  },
9
+  "installed_plugins": {
10
+    "cordova-custom-config": {
11
+      "PACKAGE_NAME": "com.example.helloworld"
12
+    },
13
+    "cordova-plugin-file": {
14
+      "PACKAGE_NAME": "com.example.helloworld"
15
+    },
16
+    "cordova-plugin-filechooser": {
17
+      "PACKAGE_NAME": "com.example.helloworld"
18
+    },
19
+    "cordova-plugin-splashscreen": {
20
+      "PACKAGE_NAME": "com.example.helloworld"
21
+    },
22
+    "cordova-plugin-whitelist": {
23
+      "PACKAGE_NAME": "com.example.helloworld"
24
+    },
25
+    "cordova-plugin-email": {
26
+      "PACKAGE_NAME": "com.example.helloworld"
27
+    }
28
+  },
29
+  "dependent_plugins": {}
30
+}

+ 89
- 0
plugins/cordova-custom-config/.jshintrc View File

@@ -0,0 +1,89 @@
1
+{
2
+    // JSHint Default Configuration File (as on JSHint website)
3
+    // See http://jshint.com/docs/ for more details
4
+
5
+    "maxerr"        : 50,       // {int} Maximum error before stopping
6
+
7
+    // Enforcing
8
+    "bitwise"       : true,     // true: Prohibit bitwise operators (&, |, ^, etc.)
9
+    "camelcase"     : false,    // true: Identifiers must be in camelCase
10
+    "curly"         : false,     // true: Require {} for every new block or scope
11
+    "eqeqeq"        : true,     // true: Require triple equals (===) for comparison
12
+    "forin"         : false,     // true: Require filtering for..in loops with obj.hasOwnProperty()
13
+    "freeze"        : false,     // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
14
+    "immed"         : false,    // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
15
+    "indent"        : 4,        // {int} Number of spaces to use for indentation
16
+    "latedef"       : false,    // true: Require variables/functions to be defined before being used
17
+    "newcap"        : false,    // true: Require capitalization of all constructor functions e.g. `new F()`
18
+    "noarg"         : false,     // true: Prohibit use of `arguments.caller` and `arguments.callee`
19
+    "noempty"       : false,     // true: Prohibit use of empty blocks
20
+    "nonbsp"        : false,     // true: Prohibit "non-breaking whitespace" characters.
21
+    "nonew"         : false,    // true: Prohibit use of constructors for side-effects (without assignment)
22
+    "plusplus"      : false,    // true: Prohibit use of `++` & `--`
23
+    "quotmark"      : false,    // Quotation mark consistency:
24
+    //   false    : do nothing (default)
25
+    //   true     : ensure whatever is used is consistent
26
+    //   "single" : require single quotes
27
+    //   "double" : require double quotes
28
+    "undef"         : false,     // true: Require all non-global variables to be declared (prevents global leaks)
29
+    "unused"        : false,     // true: Require all defined variables be used
30
+    "strict"        : false,     // true: Requires all functions run in ES5 Strict Mode
31
+    "maxparams"     : false,    // {int} Max number of formal params allowed per function
32
+    "maxdepth"      : false,    // {int} Max depth of nested blocks (within functions)
33
+    "maxstatements" : false,    // {int} Max number statements per function
34
+    "maxcomplexity" : false,    // {int} Max cyclomatic complexity per function
35
+    "maxlen"        : false,    // {int} Max number of characters per line
36
+
37
+    // Relaxing
38
+    "asi"           : false,     // true: Tolerate Automatic Semicolon Insertion (no semicolons)
39
+    "boss"          : false,     // true: Tolerate assignments where comparisons would be expected
40
+    "debug"         : false,     // true: Allow debugger statements e.g. browser breakpoints.
41
+    "eqnull"        : true,     // true: Tolerate use of `== null`
42
+    "es5"           : false,     // true: Allow ES5 syntax (ex: getters and setters)
43
+    "esversion"     : 6,
44
+    "esnext"        : false,     // true: Allow ES.next (ES6) syntax (ex: `const`)
45
+    "moz"           : false,     // true: Allow Mozilla specific syntax (extends and overrides esnext features)
46
+    // (ex: `for each`, multiple try/catch, function expression�)
47
+    "evil"          : true,     // true: Tolerate use of `eval` and `new Function()`
48
+    "expr"          : true,     // true: Tolerate `ExpressionStatement` as Programs
49
+    "funcscope"     : true,     // true: Tolerate defining variables inside control statements
50
+    "globalstrict"  : true,     // true: Allow global "use strict" (also enables 'strict')
51
+    "iterator"      : false,     // true: Tolerate using the `__iterator__` property
52
+    "lastsemic"     : false,     // true: Tolerate omitting a semicolon for the last statement of a 1-line block
53
+    "laxbreak"      : true,     // true: Tolerate possibly unsafe line breakings
54
+    "laxcomma"      : false,     // true: Tolerate comma-first style coding
55
+    "loopfunc"      : true,     // true: Tolerate functions being defined in loops
56
+    "multistr"      : true,     // true: Tolerate multi-line strings
57
+    "noyield"       : false,     // true: Tolerate generator functions with no yield statement in them.
58
+    "notypeof"      : false,     // true: Tolerate invalid typeof operator values
59
+    "proto"         : false,     // true: Tolerate using the `__proto__` property
60
+    "scripturl"     : false,     // true: Tolerate script-targeted URLs
61
+    "shadow"        : true,     // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
62
+    "sub"           : true,     // true: Tolerate using `[]` notation when it can still be expressed in dot notation
63
+    "supernew"      : true,     // true: Tolerate `new function () { ... };` and `new Object;`
64
+    "validthis"     : true,     // true: Tolerate using this in a non-constructor function
65
+    "-W100"         : true,     // true: Disable unsafe characters check
66
+
67
+    // Environments
68
+    "browser"       : true,     // Web Browser (window, document, etc)
69
+    "browserify"    : false,    // Browserify (node.js code in the browser)
70
+    "couch"         : false,    // CouchDB
71
+    "devel"         : true,     // Development/debugging (alert, confirm, etc)
72
+    "dojo"          : false,    // Dojo Toolkit
73
+    "jasmine"       : true,    // Jasmine
74
+    "jquery"        : true,    // jQuery
75
+    "mocha"         : false,     // Mocha
76
+    "mootools"      : false,    // MooTools
77
+    "node"          : false,    // Node.js
78
+    "nonstandard"   : true,    // Widely adopted globals (escape, unescape, etc)
79
+    "prototypejs"   : false,    // Prototype and Scriptaculous
80
+    "qunit"         : false,    // QUnit
81
+    "rhino"         : false,    // Rhino
82
+    "shelljs"       : false,    // ShellJS
83
+    "worker"        : false,    // Web Workers
84
+    "wsh"           : false,    // Windows Scripting Host
85
+    "yui"           : false,    // Yahoo User Interface
86
+
87
+    // Custom Globals
88
+    "globals"       : {}        // additional predefined global variables
89
+}

+ 14
- 0
plugins/cordova-custom-config/.travis.yml View File

@@ -0,0 +1,14 @@
1
+branches:
2
+  only:
3
+  - master
4
+language: node_js
5
+node_js:
6
+- 6
7
+sudo: false
8
+notifications:
9
+  email: false
10
+script:
11
+- node hooks/triggerExampleProjBuild.js
12
+env:
13
+  global:
14
+    secure: V39f11cf2y24hTZdhq0w4POXA/tyjfL/7Tebp3TBJ5q/OP6uq2NOxwv8CeftEtzpUuT2t9w9x5lNVTsIUNlvt1C8on7Vhl8SnDKF10eQsjzaplmYsJN+6CBNNof1dWI/k/u5ov5PCD3DQfJn4ikVxN1zIlfizXiVnBF9h15PVhRFWltjYS/yc/hWtyfHMEItqZtOcPB3w1Hc7SPzImRsn/ClK6EN8lQA5H8q2tmHDMGdbx+lGQYdlXR3RHE4lZvq3GAWGLkzVDM4qK/uxwFMrvf909OEFJnVkn+IjGSjxQbcHWQaO6wYZW3hMFcDKZ/zjv7rmIXjoSwsa+zfnvY47PAK9s5rOYNF4zgJnEmFwEeDzPx5vvuSttgkcxiBBcgvddwNJsSRSD94rSiFLE5Nwek9iNEPXasVCaWEevBb0ZK40Ef59keupB0B94t0jRz5055sdcywwL8eBgwdP2RgOgS/BXIfzjKItvjFDPR+aMTbPKkq0UQpsE+26oVV4fMHFkFVbAdgBYbeA9BKlWNjnNyAxWN4kmBtMGyspJGtPfim2E5uJA6fryppib6W4jhVKzsfXPVinwYs8UARJYW3OduRDFM27XD6p435F1FUZ8JXj81Tw9u/fD58RXz1zIej5rqmkNx9Uz2q0J5FFyv8arhWP+exxdLkMzZyU7qkqCA=

+ 208
- 0
plugins/cordova-custom-config/CHANGELOG.md View File

@@ -0,0 +1,208 @@
1
+# CHANGELOG
2
+
3
+**v5.1.0**
4
+* Replace requireCordovaModule() with require() due to breaking changes in cordova@9.0.0. Resolves [#152](https://github.com/dpa99c/cordova-custom-config/issues/152).
5
+
6
+**v5.0.3**
7
+* Switch to using latest `plist` release. Resolves [#151](https://github.com/dpa99c/cordova-custom-config/issues/151).
8
+* Update version of `lodash` dependency.
9
+* Add support for #include statements in `xcconfig` files on iOS.
10
+
11
+**v5.0.2**
12
+* Improve handling of errors caused by missing dependencies or during script running.
13
+
14
+**v5.0.1**
15
+* Update `plist` and `xcode` dependencies to resolve issues caused by PR [#119](https://github.com/dpa99c/cordova-custom-config/issues/119). Resolves [#136](https://github.com/dpa99c/cordova-custom-config/issues/136).
16
+
17
+**v5.0.0** Major update for `cordova-android@7`
18
+* Support the new Android project structure introduced with the [release of cordova@7.0.0](http://cordova.apache.org/announcements/2017/12/04/cordova-android-7.0.0.html) . Resolves [#135](https://github.com/dpa99c/cordova-custom-config/issues/135).
19
+* Expect custom config elements to be prefixed with `<custom-` to avoid build issues now `cordova-android@7` attempts to parse `<config-file>` blocks, but continue to support unprefixed elements by default for `cordova-android@6`.
20
+
21
+**v4.0.2**
22
+* Fix iOS bug where a `<config-file>` block with `mode=delete` causes an error if the plist doesn't contain the specified parent key.
23
+
24
+**v4.0.0**
25
+* Remove manual dependency resolution logic and require cordova-fetch for installation.
26
+
27
+**v3.3.0**
28
+* Enable deleting of existing iOS plist entries.
29
+
30
+**v3.2.0**
31
+* Add support for iOS asset catalogs as image resources. 
32
+
33
+**v3.1.4**
34
+* Add missing before_prepare and before_compile plugin hooks. Fixes [#110](https://github.com/dpa99c/cordova-custom-config/issues/110).
35
+
36
+**v3.1.3**
37
+* Wait for async processing of project.pbxproj to finish before resolving exported promise. Addresses [#108](https://github.com/dpa99c/cordova-custom-config/issues/108).
38
+* Initial documentation regarding precompile headers
39
+* Support for precompile headers (*-Prefix.pch) on iOS
40
+
41
+**v3.1.2**
42
+* Fix relative paths in xcode-func preferences
43
+
44
+**v3.1.1**
45
+* Remove engines restriction of npm version to see if it affects [#94](https://github.com/dpa99c/cordova-custom-config/issues/94).
46
+
47
+**v3.1.0**
48
+* Add cordova-fetch as preferred install method
49
+* Add support for mode attribute on config-file blocks
50
+* Dump out datastructures if --dump CLI arg is specified
51
+* Update Phonegap Build issue for no hooks support
52
+* Fix merging of plist array values.
53
+* Prevent insertion of multiple duplicate elements from config-file blocks if no top-level attributes.
54
+* When removing preferences, if element is not found under parent element, search from root as well.
55
+* Update jshint rules to allow ES6 syntax
56
+* Fix missing ; for jshint
57
+* Specify npm@>=3.0.0 via engines in package.json. Resolves [#76](https://github.com/dpa99c/cordova-custom-config/issues/76) while hopefully not breaking [#79](https://github.com/dpa99c/cordova-custom-config/issues/79) and [#80](https://github.com/dpa99c/cordova-custom-config/issues/80) again.
58
+* Merge plist arrays instead of overriding them
59
+* If loading dependencies fails, try to resolve them again. Addresses [#89](https://github.com/dpa99c/cordova-custom-config/issues/89).
60
+* Prevent problem with NSMainNibFile. Change as suggested in [#90](https://github.com/dpa99c/cordova-custom-config/issues/90).
61
+* Declare globals in example project build trigger script to prevent issues with typescript compilers. Fixes [#88](https://github.com/dpa99c/cordova-custom-config/issues/88).
62
+* Add documentation for xcodefunc blocks
63
+* ios preference to apply node-xcode functions to modify project.pbxproj
64
+* Added logic for updating Entitlements-Release.plist and Entitlements-Debug.plist files that were added with cordova-ios 4.3
65
+* Note the addition for support of <edit-config> in config.xml in cordova@6.4.0. Resolves [#81](https://github.com/dpa99c/cordova-custom-config/issues/81).
66
+
67
+**v3.0.14**
68
+* Remove npm version check due to quoting problems between Windows vs OSX. Fixes [#80](https://github.com/dpa99c/cordova-custom-config/issues/80) (and [#79](https://github.com/dpa99c/cordova-custom-config/issues/79)).
69
+
70
+**v3.0.13**
71
+* Use double quotes (instead of single quotes) around version in preinstall version check to avoid problems in Windows env. Fixes [#79](https://github.com/dpa99c/cordova-custom-config/issues/79).
72
+
73
+**v3.0.12**
74
+* Add preinstall hook to check npm version is >= 3 and fail installation if not.
75
+* Clarify npm requirements
76
+* Add Travis config and hook script to trigger build of example project on commit
77
+* Add build, version and downloads badges
78
+* Add note regarding npm version requirements. Fixes [#76](https://github.com/dpa99c/cordova-custom-config/issues/76).
79
+
80
+**v3.0.11**
81
+* Locally implement _.keyBy to avoid issues where local version of lodash < 2.0.0
82
+
83
+**v3.0.10**
84
+* Add backward compatibility for lodash < 4.0.0
85
+
86
+**v3.0.9**
87
+* Eliminate race condition when resolving npm dependencies
88
+
89
+**v3.0.8**
90
+* Add jshint validation
91
+
92
+**v3.0.7**
93
+* Only attempt to remove an element from AndroidManifest.xml if it actually exists
94
+* Dump error details when exception is raised applying custom config
95
+
96
+**v3.0.6**
97
+* Revise the plugin installation process to make it more robust
98
+
99
+**v3.0.5**
100
+* Add relative copy
101
+
102
+**v3.0.1 to v3.0.4**
103
+* Various bug fixes
104
+
105
+**v3.0.0**
106
+* Change dependency resolution to rely on cordova-fetch (cordova@6.2.0+).
107
+* Require latest xcode@0.8.9 to resolve security issue. Fixes [#74](https://github.com/dpa99c/cordova-custom-config/issues/74)
108
+* Fix jshint errors
109
+* When <preference delete="true">, ensure the child is deleted rather than the parent node. Fixes [#65](https://github.com/dpa99c/cordova-custom-config/issues/65).
110
+* Add attribute for config-file
111
+* Multiple meta-data inside application
112
+* android: create parent path if not exist
113
+
114
+**v2.0.3**
115
+* Remove useless platform parameter.
116
+* Rationalise and fix the handling of multiple sibling homogeneous elements in Android manifest. Fixes [#64](https://github.com/dpa99c/cordova-custom-config/issues/64).
117
+* Add debug tools to logger
118
+* Rename logger.debug() to logger.verbose()
119
+
120
+**v2.0.2**
121
+* Fix bug introduced by pull request [#59](https://github.com/dpa99c/cordova-custom-config/issues/59). Resolves [#61](https://github.com/dpa99c/cordova-custom-config/issues/61).
122
+
123
+**v2.0.1**
124
+* Add shelljs as dependency
125
+* adding ability to remove nodes from AndroidManifest.xml
126
+* Only apply config to hook context platforms
127
+* fix: instead of going through all the prepared platforms use hook context to find out which config update to apply
128
+* Update docs to relect non-support of Phonegap Build / Intel XDK
129
+* Update Android examples to use actual existing default theme
130
+* Add a preference to allow control of which hook the plugin uses to apply custom config
131
+* Added ability for multiple intent-filter blocks 
132
+    * Previously intent-filter blocks where duplicated in the AndroidManifest when added to a config.xml config-block. Now checks for intent-filter by label. 
133
+    * Fixed indexOf syntax error in updateAndroidManifest()
134
+* Patched weird behaviour: "shell" is undefined if android platform has been removed and added with a new package id but ios stayed the same. Patched by checking if shell is defined and using logger.error if it isn't
135
+
136
+**v2.0.0**
137
+* Remove deprecated pre-defined preferences for Android.
138
+* Make auto-restore OFF by default - resolves [#42](https://github.com/dpa99c/cordova-custom-config/issues/42).
139
+* Merge pull request [#41](https://github.com/dpa99c/cordova-custom-config/issues/41) from hilkeheremans/master. Fix for iOS issue with some types of keys.
140
+
141
+**v1.2.6**
142
+* Fix typo causing item value to always be quoted in XC build config blocks. Fixes [#40](https://github.com/dpa99c/cordova-custom-config/issues/40)
143
+
144
+**v1.2.5**
145
+* During backup/restore and apply custom config, only attempt to resolve dependencies if an error occurs while trying to load them.
146
+* Add MIT license.
147
+* Support for all root-level tags that can appear multiple times. Fixes [#34](https://github.com/dpa99c/cordova-custom-config/issues/34).
148
+
149
+**v1.2.4**
150
+* Remove lodash compatibility hack now version numbers is package.json dependencies are respected.
151
+* Fix non-resolution of promise if config is set to skip auto-backup/restore. Fixes [#32](https://github.com/dpa99c/cordova-custom-config/issues/32).
152
+
153
+**v1.2.3**
154
+* fix removal of project-level package.json during prepare operation due to concurrency in dependency resolution script by ensuring synchronisation of restoreBackups.js and applyCustomConfig.js using deferred promises
155
+
156
+**v1.2.2**
157
+* properly fix dependency resolution using promises to defer async progression of hook scripts. Fixes [#23](https://github.com/dpa99c/cordova-custom-config/issues/23) and fixes [#29](https://github.com/dpa99c/cordova-custom-config/issues/29).
158
+
159
+**v1.2.1**
160
+* Rework dependency resolution to eliminate race conditions.
161
+
162
+**v1.2.0**
163
+* Enable preference quote attribute to control quoting in project.pbxproj
164
+* Don't quote keys and values in .xcconfig files
165
+* Only replace settings in xcconfig files that are of the same build type as specified in config.
166
+    * Add special case handling of Debug CODE_SIGNING_IDENTITY for which Cordova defaults reside in build.xcconfig (not build-debug.xcconfig).
167
+    * Enable forced addition of settings (if they don't already exist) to relevant xcconfig file using xcconfigEnforce attribute.
168
+
169
+**v1.1.11**
170
+* Run dependency resolution script on 'after_platform_add` hook to avoid issues if plugin is added to a project with no platforms then platforms are subsequently added.
171
+
172
+**v1.1.10**
173
+* Document necessity of android namespace attribute in config.xml. Fixes [#24](https://github.com/dpa99c/cordova-custom-config/issues/24).
174
+* wp8 config-file append mode
175
+* Added quote attribute for iOS prefs
176
+* Update to fix indexBy() which is renamed keyBy() in lodash@4.0.0. Fixes [#20](https://github.com/dpa99c/cordova-custom-config/issues/20) and [#21](https://github.com/dpa99c/cordova-custom-config/issues/21)
177
+
178
+**v1.1.8**
179
+* Update with details of XPath preferences
180
+* Add support for xpath-style Android manifest preferences
181
+* Add warning message regarding deprecation of pre-defined preferences
182
+* Change verbosity of log messages when successfully resolved dependencies to debug so it only show up with --verbose
183
+* Add colours to log messages
184
+
185
+**v1.1.7**
186
+* Overwrite build settings if they are present in Cordova's xcconfig files. Fixes [#6](https://github.com/dpa99c/cordova-custom-config/issues/6).
187
+* Don't try to resolve dependencies when plugin is being removed
188
+* Rework dependency resolution to eliminate race conditions. Fixes [#15](https://github.com/dpa99c/cordova-custom-config/issues/15). Fixes [#11](https://github.com/dpa99c/cordova-custom-config/issues/11).
189
+    * Also rework and homogenise hook scripts.
190
+
191
+**v1.1.6**
192
+* If error occurs while processing a platform, log the error and proceed with other platforms/cordova operations by default unless cordova-custom-config-stoponerror preference is set. Fixes [#12](https://github.com/dpa99c/cordova-custom-config/issues/12).
193
+* Remove read-package-json from list of modules to install. Fixes [#10](https://github.com/dpa99c/cordova-custom-config/issues/10).
194
+
195
+**v1.1.5**
196
+* Document preference to prevent auto-restoring of backups
197
+* Add support for preference to disable auto-restore of config file backups
198
+* escape values from preferences (closes [#4](https://github.com/dpa99c/cordova-custom-config/issues/4))
199
+
200
+**v1.1.4**
201
+* Add missing license field. Fixes [#3](https://github.com/dpa99c/cordova-custom-config/issues/3).
202
+* Replace 'after_plugin_add' and 'before_plugin_rm' hooks with 'after_plugin_install' and 'before_plugin_uninstall'. Fixes [#2](https://github.com/dpa99c/cordova-custom-config/issues/2).
203
+
204
+**v1.1.2**
205
+* Replace the hard-coded old manifest name in windowSoftInputMode parent. Fixes [#1](https://github.com/dpa99c/cordova-custom-config/issues/1)
206
+
207
+**v1.1.1**
208
+* Add support for new Cordova activity name on Android

+ 726
- 0
plugins/cordova-custom-config/README.md View File

@@ -0,0 +1,726 @@
1
+cordova-custom-config plugin [![Build Status](https://travis-ci.org/dpa99c/cordova-custom-config-example.svg?branch=master)](https://travis-ci.org/dpa99c/cordova-custom-config-example/branches) [![Latest Stable Version](https://img.shields.io/npm/v/cordova-custom-config.svg)](https://www.npmjs.com/package/cordova-custom-config) [![Total Downloads](https://img.shields.io/npm/dt/cordova-custom-config.svg)](https://npm-stat.com/charts.html?package=cordova-custom-config)
2
+============================
3
+
4
+
5
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
6
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
7
+**Table of Contents**
8
+
9
+- [Overview](#overview)
10
+  - [Do I need it?](#do-i-need-it)
11
+- [Important notes](#important-notes)
12
+  - [Changes in `cordova-custom-config@5`](#changes-in-cordova-custom-config5)
13
+  - [Remote build environments](#remote-build-environments)
14
+- [Installation](#installation)
15
+- [Usage](#usage)
16
+  - [Removable preferences via backup/restore](#removable-preferences-via-backuprestore)
17
+  - [Preferences](#preferences)
18
+  - [Config blocks](#config-blocks)
19
+  - [Android](#android)
20
+    - [Android preferences](#android-preferences)
21
+    - [Android config blocks](#android-config-blocks)
22
+    - [Android example](#android-example)
23
+  - [iOS](#ios)
24
+    - [iOS preferences](#ios-preferences)
25
+    - [iOS config blocks](#ios-config-blocks)
26
+    - [iOS image resources](#ios-image-resources)
27
+    - [iOS example](#ios-example)
28
+  - [Plugin preferences](#plugin-preferences)
29
+  - [Log output](#log-output)
30
+- [Example project](#example-project)
31
+- [TODO](#todo)
32
+- [Credits](#credits)
33
+- [License](#license)
34
+
35
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
36
+
37
+# Overview
38
+
39
+The purpose of this plugin is to enable manipulation of native platform configuration files that are not supported out-of-the-box by Cordova/Phonegap CLI.
40
+
41
+The plugin uses hook scripts to update iOS and Android platform configuration files based on custom data defined in `config.xml`.
42
+
43
+<!-- DONATE -->
44
+[![donate](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG_global.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZRD3W47HQ3EMJ)
45
+
46
+I dedicate a considerable amount of my free time to developing and maintaining this Cordova plugin, along with my other Open Source software.
47
+To help ensure this plugin is kept updated, new features are added and bugfixes are implemented quickly, please donate a couple of dollars (or a little more if you can stretch) as this will help me to afford to dedicate time to its maintenance. Please consider donating if you're using this plugin in an app that makes you money, if you're being paid to make the app, if you're asking for new features or priority bug fixes.
48
+<!-- END DONATE -->
49
+
50
+## Do I need it?
51
+
52
+**Manual editing** of the platform configuration files in the `platforms/` directory is one solution to setting of custom platform configuration. 
53
+But this is not maintainable across multiple development machines or a CI environment where subsequent build operations may overwrite your changes.
54
+
55
+This plugin reads custom preferences from `config.xml`, which can be committed to version control and therefore applied across multiple development machines, CI environments,
56
+and maintained between builds, even if a platform is removed and re-added.
57
+
58
+**However:** recent versions of the Cordova/Phonegap CLI have added official support for [`<edit-config>`](https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#edit-config) and [`<config-file>`](https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#config-file) blocks in the `config.xml` (previously they only worked in `plugin.xml`).
59
+
60
+So if all you want to do is insert a block of native config or change a native preference, you probably don't need this plugin at all.
61
+
62
+I hope that eventually the Cordova/Phonegap CLI will support all the functionality that this plugin provides and it can be retired.
63
+
64
+**Until then:** there are still some operations that can be performed by this plugin which are not supported by the latest Cordova/Phonegap CLI versions. These include:
65
+ 
66
+ - Overriding default platform preferences set during the `cordova prepare` operation.
67
+ - Deletion of existing elements/attributes in `AndroidManifest.xml`
68
+ - Manipulation of build settings in the native iOS Xcode project file `project.pbxproj` via [XCBuildConfiguration](#xcbuildconfiguration) blocks.
69
+ - Manipulation of iOS Precompile header files via [iOS Precompile Header config blocks](#ios-precompile-header-config-blocks)
70
+ - Advanced manipulation of iOS Xcode project using [xcodefunc](#xcodefunc).
71
+
72
+# Important notes
73
+
74
+## Changes in `cordova-custom-config@5`
75
+
76
+The recent [release of cordova@7.0.0](http://cordova.apache.org/announcements/2017/12/04/cordova-android-7.0.0.html) has introduced backwardly-incompatible breaking changes to the structure of Android projects generated by Cordova.
77
+
78
+Therefore a new major version of this plugin (v5) has been released to support these changes. Here are things to be aware of:
79
+
80
+- The location of `AndroidManifest.xml` has changed in `cordova-android@7` but `cordova-custom-config@5` should detect which version of `cordova-android` platform is present in the project and use the correct path.
81
+- All custom config elements supported by this plugin in `config.xml` should now be prefixed with `custom-` for use with `cordova-custom-config@5`
82
+    - `<config-file>` => `<custom-config-file>`
83
+    - `<preference>` => `<custom-preference>`
84
+    - `<resource>` => `<custom-resource>`
85
+    - This is because `cordova-android@7` now attempts to parse `<config-file>` blocks in the `config.xml`, so `<config-file>` blocks intended for this plugin to parse will be picked up by Cordova and can cause build errors (see [#135](https://github.com/dpa99c/cordova-custom-config/issues/135)) for an example.
86
+    - [Plugin preferences](#plugin-preferences) should still however be specified as `<preference>`
87
+        - e.g. `<preference name="cordova-custom-config-autorestore" value="true" />`
88
+    - The plugin will detect if the platform project is `cordova-android@7` or `cordova-android@6` (or below)
89
+        - If `cordova-android@6`, by default the plugin will support non-prefixed custom config elements
90
+        - If `cordova-android@7`, by default the plugin will NOT support non-prefixed custom config elements
91
+        - This can be overridden by explicitly setting the `parse_unprefixed` preference
92
+            - `<preference name="cordova-custom-config-parse_unprefixed" value="true" />`
93
+
94
+## Remote build environments
95
+
96
+This plugin is intended for the automated application of custom configuration to native platform projects in a **local build environment**.
97
+
98
+This plugin **WILL NOT WORK** with remote ("Cloud") build environments that do not support the execution of this plugin's [hook scripts](https://cordova.apache.org/docs/en/latest/guide/appdev/hooks/). This includes:
99
+
100
+- [Phonegap Build](https://github.com/phonegap/build/issues/425) 
101
+- [Intel XDK](https://software.intel.com/en-us/xdk/docs/add-manage-project-plugins)
102
+- [Telerik Appbuilder](http://docs.telerik.com/platform/appbuilder/cordova/using-plugins/using-custom-plugins/using-custom-plugins)
103
+- [Ionic Cloud](https://docs.ionic.io/services/package/#hooks)
104
+ 
105
+If you are using another cloud-based Cordova/Phonegap build service and find this plugin doesn't work, the reason is probably also the same.
106
+
107
+FWIW: if you are professionally developing Cordova/Phonegap apps, you are eventually going to find it preferable to build locally.
108
+
109
+# Installation
110
+
111
+The plugin is registered as `cordova-custom-config` on [npm](https://www.npmjs.com/package/cordova-custom-config) (requires Cordova CLI 5.0.0+)
112
+
113
+`cordova-custom-config@4+` requires the plugin to be installed via the [`cordova-fetch`](https://cordova.apache.org/news/2016/05/24/tools-release.html) mechanism in order to satisfy its package dependencies by installing it via npm.
114
+
115
+Therefore a Cordova CLI version of `cordova@7+` is required to install the plugin: 
116
+
117
+    $ cordova plugin add cordova-custom-config
118
+
119
+Or `cordova@6.2+` if the `--fetch` option is specified explicitly:
120
+
121
+    $ cordova plugin add cordova-custom-config --fetch
122
+
123
+# Usage
124
+
125
+The hook scripts included in this plugin are run after each platform `prepare` operation and apply preferences dictated by custom keys in the project `config.xml` file to the relevant platform config files.
126
+As such, all you need to do to "use" this plugin is include the relevant keys in your `config.xml` and the scripts will take care of the rest when you build your project.
127
+
128
+**IMPORTANT**: As of `cordova-custom-config@5`, this plugin expects that custom configuration keys will be prefixed with `<custom-` in order to distinguish them from the Cordova configuration keys. For example, for a preference intended for this plugin to parse you should use `<custom-preference>` but for a preference intended for Cordova to parse you should use `<preference>` 
129
+
130
+NOTE: There are no run-time source files included in this plugin - it is simply a convenient package of hook scripts.
131
+
132
+## Removable preferences via backup/restore
133
+
134
+By default, any changes made by this plugin to platform config files are irreversible - i.e. if you want to undo changes made by the plugin, you'll need to remove then re-add the Cordova platform, for example:
135
+
136
+    cordova platform rm android && cordova platform add android
137
+
138
+However, if you want the changes made to be reversible, you can enable auto-backup/restore functionality by adding the following preference inside the top-level `<widget>` element of your `config.xml`:
139
+                                                                                                                 
140
+    <preference name="cordova-custom-config-autorestore" value="true" />
141
+
142
+When the first `prepare` operation runs after the plugin is installed, it will make backup copies of the original configuration files before it makes any modifications. 
143
+These backup copies are stored in `plugins/cordova-custom-config/backup/` and will be restored before each `prepare` operation, allowing Cordova to make modifications and then the plugin to make further modifications after the `prepare`.
144
+
145
+This means changes made by the plugin are reversible, so removing a custom element from the `config.xml` will remove it from the platform configuration file on the next `prepare` operation and uninstalling the plugin will restore the configuration files to their original state (before the plugin made any modifications).
146
+
147
+Consequently, any manual changes made to the platform configuration files in `platforms/` **after** installing the plugin will be overwritten by the plugin on the next `prepare` operation.
148
+
149
+To prevent auto-restoring of backups and make manual changes to platform configuration files persist, remove the `autorestore` preference from the `config.xml`
150
+
151
+## Preferences
152
+
153
+Preferences are set by defining a `<custom-preference>` element in the config.xml, e.g. `<custom-preference name="android-launchMode" value="singleTop" />`
154
+
155
+1.  Preferences defined outside of the platform element will apply to all platforms
156
+2.  Preferences defined inside a platform element will apply only to the specified platform
157
+3.  Platform preferences take precedence over common preferences
158
+4.  Platform-specific preferences must be prefixed with the platform name (e.g. `name="ios-somepref"`) and be defined inside a platform element.
159
+
160
+
161
+## Config blocks
162
+
163
+`<custom-config-file>` blocks allow platform-specific chunks of config to be defined as an XML subtree in the `config.xml`, which is then applied to the appropriate platform configuration file by the plugin.
164
+
165
+1.  config-file elements MUST be defined inside a platform element, otherwise they will be ignored.
166
+2.  config-file target attributes specify the target file to update. (AndroidManifest.xml or *-Info.plist)
167
+3.  config-file parent attributes specify the parent element (AndroidManifest.xml) or parent key (*-Info.plist) that the child data will replace or be appended to.
168
+4.  config-file elements are uniquely indexed by target AND parent for each platform.
169
+5.  If there are multiple config-file's defined with the same target AND parent, the last config-file will be used
170
+6.  Elements defined WITHIN a config-file will replace or be appended to the same elements relative to the parent element
171
+7.  If a unique config-file contains multiples of the same elements (other than uses-permission elements which are selected by by the uses-permission name attribute), the last defined element will be retrieved.
172
+
173
+## Android
174
+
175
+The plugin currently supports setting of custom config only in `platforms/android/AndroidManifest.xml`.
176
+For a list of possible manifest values see [http://developer.android.com/guide/topics/manifest/manifest-intro.html](http://developer.android.com/guide/topics/manifest/manifest-intro.html)
177
+
178
+### Android preferences
179
+
180
+Note: [cordova@6.4.0](https://cordova.apache.org/news/2016/10/25/tools-release.html) adds support for [`<edit-config>`](http://cordova.apache.org/docs/en/6.x/plugin_ref/spec.html#edit-config) blocks in `config.xml`, which enables you to achieve similar manipulation of Android preferences without needing this plugin.
181
+
182
+- `<custom-preference>` elements in `config.xml` are used to set set attributes on existing elements in the `AndroidManifest.xml`.
183
+    - e.g. `<custom-preference name="android-manifest/@android:hardwareAccelerated" value="false" />`
184
+    - will result in `AndroidManifest.xml`: `<manifest android:hardwareAccelerated="false">`
185
+- Sometimes there plugins set some defaults in AndroidManifest.xml that you may not want.
186
+  It is also possible to delete nodes using the preferences and the `delete="true"` attribute.
187
+  - e.g. `<custom-preference name="android-manifest/uses-permission/[@android:name='android.permission.WRITE_CONTACTS']" delete="true" />`
188
+  - will delete the existing node `<uses-permission android:name="android.permission.WRITE_CONTACTS" />`
189
+
190
+#### Android namespace attribute
191
+
192
+__Important:__ In order to user the `android:` namespace in preferences within your `config.xml`, you must include the android namespace attribute on the root `<widget>` element.
193
+The namespace attribute fragment is:
194
+
195
+    xmlns:android="http://schemas.android.com/apk/res/android"
196
+
197
+so your `<widget>` element should look something like:
198
+
199
+    <widget
200
+        id="com.my.app"
201
+        version="0.0.1"
202
+        xmlns="http://www.w3.org/ns/widgets"
203
+        xmlns:cdv="http://cordova.apache.org/ns/1.0"
204
+        xmlns:android="http://schemas.android.com/apk/res/android">
205
+
206
+#### XPath preferences
207
+
208
+Android manifest preferences are set by using XPaths in the preference name to define which element attribute the value should be applied to.
209
+
210
+The preference name should be prefixed with `android-manifest` then follow with an XPath which specifies the element and attribute to apple the value to.
211
+
212
+For example:
213
+
214
+    <custom-preference name="android-manifest/application/activity/@android:launchMode" value="singleTask" />
215
+
216
+This preference specifies that the `launchMode` attribute should be given a value of `singleTask`:
217
+
218
+    <activity android:launchMode="singleTask">
219
+
220
+If your manifest contains other activities, you should specify the activity name in the XPath. Note that the activity name for Cordova 4.2.0 and below was "CordovaApp" whereas Cordova 4.3.0 and above is "MainActivity". For example:
221
+
222
+    <custom-preference name="android-manifest/application/activity[@android:name='MainActivity']/@android:launchMode" value="singleTask" />
223
+
224
+If the attribute you are setting is on the root `<manifest>` element, just omit the element name and specify the attribute. For example:
225
+
226
+    <custom-preference name="android-manifest/@android:installLocation" value="auto" />
227
+
228
+
229
+### Android config blocks
230
+
231
+- `<custom-config-file>` blocks are use to define chunks of config an XML subtree, to be inserted into `AndroidManifest.xml`
232
+- the `target` attribute must be set to `AndroidManifest.xml`: `<custom-config-file target="AndroidManifest.xml">`
233
+- the `parent` attribute defines an Xpath to the parent element in the `AndroidManifest.xml` under which the XML subtree block should be inserted
234
+    - to insert a block under the root `<manifest>` element, use `parent="/*"`
235
+    - to insert a block under a descendant of `<manifest>`, use an Xpath prefixed with `./`
236
+        e.g `parent="./application/activity"` will insert the block under `/manifest/application/activity`
237
+- the child elements inside the `<custom-config-file>` block will be inserted under the parent element.
238
+
239
+For example:
240
+
241
+    <custom-config-file target="AndroidManifest.xml" parent="./application">
242
+        <some-element />
243
+    </custom-config-file>
244
+
245
+will result in `AndroidManifest.xml` with:
246
+
247
+    <manifest ...>
248
+        <application ...>
249
+            <some-element />
250
+        </application>
251
+    </manifest>
252
+
253
+**NOTE:** By default, if the specified parent element contains an existing child element of the same name as that defined in the XML subtree, the existing element will be overwritten.
254
+For example:
255
+
256
+    <custom-config-file target="AndroidManifest.xml">
257
+        <application android:name="MyApp" />
258
+    </custom-config-file>
259
+
260
+will replace the existing `<application>` element(s).
261
+    
262
+To force the preservation (rather than replacement) of existing child elements, you can use the `mode="add"` attribute.
263
+So for the example above:
264
+
265
+    <custom-config-file target="AndroidManifest.xml" mode="add">
266
+        <application android:name="MyApp" />
267
+    </custom-config-file>
268
+
269
+will preserve the existing `<application>` element(s).
270
+
271
+
272
+### Android example
273
+
274
+config.xml:
275
+
276
+    <platform name="android">
277
+        <!-- custom preferences examples -->
278
+        <custom-preference name="android-manifest/application/activity/@android:windowSoftInputMode" value="stateVisible" />
279
+        <custom-preference name="android-manifest/@android:installLocation" value="auto" />
280
+        <custom-preference name="android-manifest/application/@android:hardwareAccelerated" value="false" />
281
+        <custom-preference name="android-manifest/@android:hardwareAccelerated" value="false" />
282
+        <custom-preference name="android-manifest/application/activity/@android:configChanges" value="orientation" />
283
+        <custom-preference name="android-manifest/application/activity/@android:theme" value="@android:style/Theme.Material" />
284
+
285
+        <!-- specify activity name -->
286
+        <custom-preference name="android-manifest/application/activity[@android:name='MainActivity']/@android:launchMode" value="singleTask" />
287
+        
288
+        <!-- Delete an element -->
289
+        <custom-preference name="android-manifest/application/activity[@android:name='DeleteMe']" delete="true" />
290
+
291
+
292
+        <!-- These preferences are actually available in Cordova by default although not currently documented -->
293
+        <custom-preference name="android-minSdkVersion" value="10" />
294
+        <custom-preference name="android-maxSdkVersion" value="22" />
295
+        <custom-preference name="android-targetSdkVersion" value="21" />
296
+
297
+        <!-- Or you can use a custom-config-file element for them -->
298
+        <custom-config-file target="AndroidManifest.xml" parent="/*">
299
+            <uses-sdk android:maxSdkVersion="22" android:minSdkVersion="10" android:targetSdkVersion="21" />
300
+        </custom-config-file>
301
+
302
+
303
+        <!-- custom config example -->
304
+         <custom-config-file target="AndroidManifest.xml" parent="/*">
305
+            <supports-screens
306
+                android:xlargeScreens="false"
307
+                android:largeScreens="false"
308
+                android:smallScreens="false" />
309
+
310
+            <uses-permission android:name="android.permission.READ_CONTACTS" android:maxSdkVersion="15" />
311
+            <uses-permission android:name="android.permission.WRITE_CONTACTS" />
312
+        </custom-config-file>
313
+        
314
+        <!-- Add (rather than overwrite) a custom-config-file block -->
315
+        <custom-config-file target="AndroidManifest.xml" parent="./" mode="add">
316
+            <application android:name="customApplication"></application>
317
+        </custom-config-file>
318
+        
319
+    </platform>
320
+
321
+## iOS
322
+
323
+- The plugin currently supports custom configuration of:
324
+    - the project plist (`*-Info.plist`) using `<custom-config-file>` blocks
325
+    - build settings using `<custom-preference>` elements
326
+    - image asset catalogs using `<custom-resource>` elements
327
+- All iOS-specific config should be placed inside the `<platform name="ios">` in `config.xml`.
328
+
329
+### iOS preferences
330
+
331
+- Preferences for iOS can be used to define build configuration settings
332
+- The plugin currently supports:
333
+    - setting of `XCBuildConfiguration` block keys in the `project.pbxproj` file
334
+    - `xcodefunc` as an interface to apply functions from [node-xcode](https://github.com/alunny/node-xcode)
335
+
336
+#### XCBuildConfiguration
337
+
338
+- XCBuildConfiguration `<custom-preference>` elements are used to set preferences in the project settings file `platforms/ios/{PROJECT_NAME}/{PROJECT_NAME}.xcodeproj/project.pbxproj`
339
+- Currently, `XCBuildConfiguration` is the only supported block type.
340
+- However, there is no constraint on the list of keys for which values may be set.
341
+- If an entry already exists in an `XCBuildConfiguration` block for the specified key, the existing value will be overwritten with the specified value.
342
+- If no entry exists in any `XCBuildConfiguration` block for the specified key, a new key entry will be created in each `XCBuildConfiguration` block with the specified value.
343
+- By default, values will be applied to both "Release" and "Debug" `XCBuildConfiguration` blocks.
344
+- However, the block type can be specified by adding a `buildType` attribute to the `<custom-preference>` element in the config.xml: value is either `debug` or `release`
345
+    - e.g `<custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="7.0" buildType="release" />`
346
+- By default, both the key (preference name) and value will be quote-escaped when inserted into the `XCBuildConfiguration` block.
347
+    - e.g. `<custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="7.0" buildType="release" />`
348
+    - will appear in `project.pbxproj` as: `"IPHONEOS_DEPLOYMENT_TARGET" = "7.0";`
349
+- The default quoting can be override by setting the `quote` attribute on the `<custom-preference>` element.
350
+    - Valid values are:
351
+        - "none" - don't quote key or value
352
+        - "key" - quote key but not value
353
+        - "value" - quote value but not key
354
+        - "both" - quote both key and value
355
+    - e.g. `<custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="7.0" buildType="release" quote="none" />`
356
+    - will appear in `project.pbxproj` as: `IPHONEOS_DEPLOYMENT_TARGET = 7.0;`
357
+
358
+- Preferences should be defined in the format `<custom-preference name="ios-SOME_BLOCK_TYPE-SOME_KEY" value="SOME_VALUE" />`
359
+- Therefore, the preference name should be prefixed with `ios-XCBuildConfiguration`, for example:
360
+
361
+    <custom-preference name="ios-XCBuildConfiguration-ENABLE_BITCODE" value="YES" />
362
+
363
+##### .xcconfig files
364
+
365
+- Cordova uses `.xcconfig` files in `/platforms/ios/cordova/` to override Xcode project settings in `project.pbxproj` with build-type specific values.
366
+    - `build.xcconfig` is overriden by settings in `build-debug.xcconfig` and `build-release.xcconfig` for the corresponding build type.
367
+- When applying a custom preference, the plugin will look for an existing entry in the `.xcconfig` file that corresponds to the buildType attribute.
368
+    - If buildType attribute is "debug" or "release", the plugin will look in `build-debug.xcconfig` or `build-release.xcconfig` respectively.
369
+    - If buildType is not specified or set to "none", the plugin will look in `build.xcconfig`.
370
+- By default, if an entry is found in the `.xcconfig` file which corresponds to the custom preference name in the `config.xml`, the value in the `.xcconfig` file will be overwritten with the value in the `config.xml`.
371
+- To prevent the plugin from overwriting the value of a specific preference in the corresponding `.xcconfig` file, set the preference attribute `xcconfigEnforce="false"`.
372
+     - e.g `<custom-preference name="ios-XCBuildConfiguration-SOME_PREFERENCE" value="Some value" buildType="debug" xcconfigEnforce="false" />`
373
+- If a preference value doesn't already exist in the corresponding `.xcconfig` file, you can force its addition by setting the preference attribute `xcconfigEnforce="true"`.
374
+This will append it to the corresponding .xcconfig` file.
375
+     - e.g `<custom-preference name="ios-XCBuildConfiguration-SOME_PREFERENCE" value="Some value" buildType="debug" xcconfigEnforce="true" />`
376
+- Dependencies on additional `.xcconfig` files be added to a project by using `#include` statements. Note, the buildType attribute applies as stated above, however, xcconfigEnforce has no effect on `#include` statements as it is possible to have multiple `#include` statemnets in a single `.xcconfig` file.
377
+    - e.g. `<custom-preference name="ios-XCBuildConfiguration-#INCLUDE" value="/path/to/external/dependency/additional_config.xcconfig" />`. 
378
+- A backup copy of any modified `.xcconfig` file will be made in 'plugins/cordova-custom-config/backup/ios'. By default, these backups will be restored prior to the next `prepare` operation.
379
+- Auto-restore of the backups can be disabled by setting `<custom-preference name="cordova-custom-config-autorestore" value="false" />` in the `config.xml`.
380
+- Preference names and values will not be quote-escaped in `.xcconfig` files, so the `quote` attribute has no effect on them.
381
+
382
+##### CODE\_SIGN\_IDENTITY preferences
383
+
384
+- Cordova places its default CODE\_SIGN\_IDENTITY for Release builds in `build-release.xcconfig` but for Debug builds in `build.xcconfig.
385
+- If you set a CODE\_SIGN\_IDENTITY preference in the `config.xml` with `buildType="release"`, the plugin will overwrite the defaults in `build-release.xcconfig`.
386
+    - e.g. `<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: My Release Profile (A1B2C3D4)" buildType="release" />`
387
+- If you set a CODE\_SIGN\_IDENTITY preference in the `config.xml` with `buildType="debug"`, the plugin will overwrite the defaults in `build.xcconfig`.
388
+    - e.g. `<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: My Debug Profile (A1B2C3D4)" buildType="debug" />`
389
+- You can prevent the CODE\_SIGN\_IDENTITY preferences being overwritten by setting `xcconfigEnforce="false"`.
390
+    - e.g. `<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: My Release Profile (A1B2C3D4)" buildType="release" xcconfigEnforce="false" />`
391
+- You can force the plugin to add a new entry for CODE\_SIGN\_IDENTITY preference with `buildType="debug"` to `build-debug.xcconfig`, rather than overwriting the defaults in `build.xcconfig` by setting `xcconfigEnforce="true"`.
392
+This will still override the defaults in `build.xcconfig`, because `build-debug.xcconfig` overrides `build.xcconfig`.
393
+    - e.g. `<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: My Debug Profile (A1B2C3D4)" buildType="debug" xcconfigEnforce="true" />`
394
+
395
+#### xcodefunc
396
+
397
+- `xcode-func` preferences enable functions within [node-xcode](https://github.com/alunny/node-xcode) to be called to edit different block types (such as Sources, Resources or Frameworks) in the `project.pbxproj`.
398
+- The preference name should be `ios-xcodefunc`, i.e. `name="ios-xcodefunc"`
399
+- The function to call in [node-xcode](https://github.com/alunny/node-xcode) should be specified using the `func` attribute, e.g. `func="addResourceFile"`
400
+- Function arguments should be specified using `<arg />` child elements. It supports the following attributes:
401
+    - `value`-  the value of the argument, e.g. `value="src/content/image.png"`
402
+    - `type` - the type of the value, e.g. `type="String"`.
403
+        - Supported types are:
404
+            - `Null` - evaluates to `null`
405
+            - `Undefined` - evaluates to `undefined`
406
+            - `Object` - a stringified JSON object that will be parsed back into its object form
407
+            - `Number` - a Javascript Number
408
+            - `String` - a Javascript String
409
+            - `Symbol` - a Javascript Symbol
410
+        - If `type` is not specified, the argument value will be passed exactly as defined in the `value` attribute
411
+    - `flag` - a modifier for specific types of argument, e.g. `flag="path"`
412
+        - Currently, the only supported value is `path` which forces the path to be resolved either as an absolute path or relative to the project root.
413
+
414
+For example:
415
+
416
+    <custom-preference name="ios-xcodefunc" func="addResourceFile">
417
+        <arg type="String" value="src/content/image.png" flag="path" />
418
+    </custom-preference>
419
+
420
+will add resource `image.png` from `./src/content` (i.e. `../../src/content/image.png` relative to `./platforms/ios/`)
421
+
422
+
423
+### iOS config blocks
424
+
425
+- `<custom-config-file>` elements are currently used to:
426
+    - set preferences in the project .plist file (`platforms/ios/{PROJECT_NAME}/{PROJECT_NAME}-Info.plist`).
427
+    - add to Precompiled Headers file (`platforms/ios/{PROJECT_NAME}/{PROJECT_NAME}-Prefix.pch`).
428
+- all `<custom-config-file>` elements should have the  `platform` attribute set to `ios`: `<custom-config-file platform="ios">`
429
+
430
+#### iOS project plist config blocks
431
+
432
+- the `target` attribute of the `<custom-config-file>` should be set to `*-Info.plist`: `<custom-config-file platform="ios" target="*-Info.plist">`
433
+- the `parent` attribute is used to determine which key name to use for the custom preference
434
+    - e.g. `<custom-config-file platform="ios" target="*-Info.plist" parent="NSLocationAlwaysUsageDescription">`
435
+    - will appear in `{PROJECT_NAME}-Info.plist` as `<key>NSLocationAlwaysUsageDescription</key>` under `/plist/dict`
436
+- the value of the preference is set by the child elements of the `<custom-config-file>` element. These will appear directly below the preference `<key>` in the .plist file.
437
+    - For example:
438
+
439
+        `<custom-config-file platform="ios" target="*-Info.plist" parent="NSLocationAlwaysUsageDescription">
440
+            <string>This app requires constant access to your location in order to track your position, even when the screen is off.</string>
441
+        </custom-config-file>`
442
+
443
+    - will appear in the plist file as:
444
+
445
+        `<key>NSLocationAlwaysUsageDescription</key>
446
+        <string>This app requires constant access to your location in order to track your position, even when the screen is off.</string>`
447
+- if the .plist value type is an array, by default the values in the `<custom-config-file>` block will be merged with any existing values.
448
+    - For example, if the plist already contains:
449
+
450
+        `<key>LSApplicationQueriesSchemes</key>
451
+        <array>
452
+            <string>fbapi</string>
453
+            <string>fb-messenger-api</string>
454
+        </array>`
455
+
456
+    - Then adding the `<custom-config-file>` block:
457
+
458
+        `<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist">
459
+             <array>
460
+                 <string>myapp</string>
461
+                 <string>myapp2</string>
462
+             </array>
463
+        </custom-config-file>`
464
+
465
+    - will result in the plist file as:
466
+
467
+        `<key>LSApplicationQueriesSchemes</key>
468
+        <array>
469
+            <string>fbapi</string>
470
+            <string>fb-messenger-api</string>
471
+            <string>myapp</string>
472
+             <string>myapp2</string>
473
+        </array>`
474
+
475
+- this behaviour can also be explicitly specified by adding `mode="merge"` to the `<custom-config-file>` block:
476
+  - For example, the `<custom-config-file>` block:
477
+
478
+          `<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist" mode="replace">
479
+               <array>
480
+                   <string>myapp</string>
481
+                   <string>myapp2</string>
482
+               </array>
483
+          </custom-config-file>`
484
+
485
+      - will also result in the plist file as:
486
+
487
+          `<key>LSApplicationQueriesSchemes</key>
488
+          <array>
489
+              <string>fbapi</string>
490
+              <string>fb-messenger-api</string>
491
+              <string>myapp</string>
492
+               <string>myapp2</string>
493
+          </array>`
494
+
495
+- to replace existing values with those in the `<custom-config-file>` block, use the attribute `mode="replace"`:
496
+     - For example, if the plist already contains:
497
+
498
+        `<key>LSApplicationQueriesSchemes</key>
499
+        <array>
500
+            <string>fbapi</string>
501
+            <string>fb-messenger-api</string>
502
+        </array>`
503
+
504
+    - Then adding the `<custom-config-file>` block:
505
+
506
+        `<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist" mode="replace">
507
+             <array>
508
+                 <string>myapp</string>
509
+                 <string>myapp2</string>
510
+             </array>
511
+        </custom-config-file>`
512
+
513
+      - will result in the plist file as:
514
+
515
+          `<key>LSApplicationQueriesSchemes</key>
516
+          <array>
517
+              <string>myapp</string>
518
+               <string>myapp2</string>
519
+          </array>`
520
+          
521
+- to delete existing values in the plist, specify the key to delete as the parent and use the attribute `mode="delete"`:
522
+     - For example, if the plist already contains:
523
+
524
+        `<key>LSApplicationQueriesSchemes</key>
525
+        <array>
526
+            <string>fbapi</string>
527
+            <string>fb-messenger-api</string>
528
+        </array>`
529
+
530
+    - Then adding the `<custom-config-file>` block:
531
+
532
+        `<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist" mode="delete"/>`
533
+
534
+      - will result in the existing block being removed from the plist
535
+  
536
+
537
+#### iOS Precompile Header config blocks
538
+
539
+- the `target` attribute of the `<custom-config-file>` should be set to `*-Prefix.pch`: `<custom-config-file platform="ios" target="*-Prefix.pch">`
540
+
541
+### iOS image resources
542
+
543
+Purpose:
544
+- Sometimes it can be necessary to create custom iOS image asset catalogs in Cordova-based iOS apps.
545
+    - For example, some plugins require that custom images be present in a custom asset catalog in order to make use of them:
546
+        - [cordova-plugin-themeablebrowser](https://github.com/initialxy/cordova-plugin-themeablebrowser)
547
+        - [cordova-plugin-3dtouch](https://github.com/EddyVerbruggen/cordova-plugin-3dtouch)
548
+    - This could be done manually by editing the platform project in XCode, but this is fragile since platform projects are volatile. 
549
+        - i.e. can be removed when removing/updating the platform via Cordova CLI.
550
+    - So this plugin provides a mechanism to automate the generation custom asset catalogs.
551
+
552
+Usage:
553
+- Image [asset catalogs](https://developer.apple.com/library/content/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/) can be defined using `<custom-resource>` elements
554
+- The `<custom-resource>` elements must be places inside of the `<platform name="ios">` element
555
+- The `<custom-resource>` elements must have the attribute `type="image"`: `<custom-resource type="image" />`
556
+- The `src` attribute (required) should specify the relative local path to the image file. 
557
+    - The relative root is the Cordova project root
558
+    - e.g. `<custom-resource src="resources/custom-catalog/back@1x.png" />`
559
+        - where the image file is location in `/path/to/project/root/resources/custom-catalog/back@1x.png`
560
+- The `catalog` attribute (required) specifies the name of the catalog to add the image to
561
+    - e.g. `<custom-resource catalog="custom-catalog"/>`
562
+- The `scale` attribute (required) specifies the scale factor of the image
563
+    - Valid values are: `1x`, `2x`, `3x`
564
+    - e.g. `<custom-resource scale="1x"/>`
565
+- The `idiom` attribute (optional) specifies the target device family
566
+    - Valid values are: 
567
+        - `universal` - all devices
568
+        - `iphone` - iPhones only
569
+        - `ipad` - iPads only
570
+        - `watch` - Apple Watch only
571
+    - If not specified, defaults to `universal`
572
+    - e.g. `<custom-resource idiom="iphone"/>`
573
+- Full example: 
574
+
575
+    `<custom-resource type="image" src="resources/custom-catalog/back@1x.png" catalog="custom-catalog" scale="1x" idiom="iphone" />`
576
+
577
+### iOS example
578
+
579
+config.xml:
580
+
581
+    <platform name="ios">
582
+
583
+        <!-- Set ENABLE_BITCODE to YES in XCode project file override NO value in /ios/cordova/build.xcconfig -->
584
+        <custom-preference name="ios-XCBuildConfiguration-ENABLE_BITCODE" value="YES" />
585
+
586
+        <!-- Set deploy target SDKs for release and debug builds -->
587
+        <custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="9.1" buildType="debug" quote="none" />
588
+        <custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="7.0" buildType="release" />
589
+
590
+        <!-- Custom code signing profiles (overriding those in /ios/cordova/*.xcconfig -->
591
+        <custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Developer: Dave Alden (8VUQ6DYDLL)" buildType="debug" xcconfigEnforce="true" />
592
+        <custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY[sdk=iphoneos*]" value="iPhone Developer: Dave Alden (8VUQ6DYDLL)" buildType="debug" />
593
+        <custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY[sdk=iphoneos9.1]" value="iPhone Developer: Dave Alden (8VUQ6DYDLL)" buildType="debug" />
594
+        <custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: Working Edge Ltd (556F3DRHUD)" buildType="release" xcconfigEnforce="false" />
595
+        <custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY[sdk=iphoneos*]" value="iPhone Distribution: Working Edge Ltd (556F3DRHUD)" buildType="release" />
596
+        <custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY[sdk=iphoneos9.1]" value="iPhone Distribution: Working Edge Ltd (556F3DRHUD)" buildType="release" />
597
+
598
+        <!-- Add resource file by relative path -->
599
+        <custom-preference name="ios-xcodefunc" func="addResourceFile">
600
+            <arg type="String" value="src/content/image.png" flag="path" />
601
+        </custom-preference>
602
+
603
+       <!-- By default, merge with existing array values -->
604
+       <custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist">
605
+           <array>
606
+               <string>myapp</string>
607
+               <string>myapp2</string>
608
+               <string>myapp3</string>
609
+           </array>
610
+       </custom-config-file>
611
+
612
+       <!-- Explicitly merge with existing array values -->
613
+       <custom-config-file platform="ios" target="*-Info.plist" parent="UISupportedInterfaceOrientations" mode="merge" >
614
+           <array>
615
+               <string>UIInterfaceOrientationPortrait</string>
616
+               <string>UIInterfaceOrientationPortraitUpsideDown</string>
617
+           </array>
618
+       </custom-config-file>
619
+
620
+       <!-- Replace existing values -->
621
+       <custom-config-file platform="ios" target="*-Info.plist" parent="UISupportedInterfaceOrientations~ipad" mode="replace">
622
+           <array>
623
+               <string>UIInterfaceOrientationPortrait</string>
624
+               <string>UIInterfaceOrientationPortraitUpsideDown</string>
625
+           </array>
626
+       </custom-config-file>
627
+
628
+        <!-- Set background location mode -->
629
+        <custom-config-file platform="ios" target="*-Info.plist" parent="UIBackgroundModes">
630
+            <array>
631
+                <string>location</string>
632
+            </array>
633
+        </custom-config-file>
634
+
635
+        <!-- Set message displayed when app requests constant location updates -->
636
+        <custom-config-file platform="ios" target="*-Info.plist" parent="NSLocationAlwaysUsageDescription">
637
+            <string>This app requires constant access to your location in order to track your position, even when the screen is off.</string>
638
+        </custom-config-file>
639
+
640
+        <!-- Set message displayed when app requests foreground location updates -->
641
+        <custom-config-file platform="ios" target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
642
+            <string>This app will now only track your location when the screen is on and the app is displayed.</string>
643
+        </custom-config-file>
644
+
645
+        <!-- Allow arbitrary loading of resources over HTTP on iOS9 -->
646
+        <custom-config-file platform="ios" target="*-Info.plist" parent="NSAppTransportSecurity">
647
+            <dict>
648
+                <key>NSAllowsArbitraryLoads</key>
649
+                <true/>
650
+            </dict>
651
+        </custom-config-file>
652
+        
653
+        <!-- Custom image asset catalog -->
654
+        <custom-resource type="image" catalog="custom" src="resources/ios/custom-icons/back@1x.png" scale="1x" idiom="universal" />
655
+        <custom-resource type="image" catalog="custom" src="resources/ios/custom-icons/back@2x.png" scale="2x" idiom="universal" />
656
+        <custom-resource type="image" catalog="custom" src="resources/ios/custom-icons/back@3x.png" scale="3x" idiom="universal" />
657
+    </platform>
658
+
659
+## Plugin preferences
660
+
661
+The plugin supports some preferences which are used to customise the behaviour of the plugin.
662
+These preferences should be placed at the top level (inside `<widget>`) rather than inside individual `<platform>` elements.
663
+Each preference name is prefixed with `cordova-custom-config` to avoid name clashes, for example:
664
+
665
+    <preference name="cordova-custom-config-autorestore" value="true" />
666
+
667
+The following preferences are currently supported:
668
+
669
+- `cordova-custom-config-autorestore` - if true, the plugin will restore a backup of platform configuration files taken at plugin installation time.
670
+See the [Removable preferences](#removable-preferences-via-backuprestore) section for details. Defaults to `false`.
671
+- `cordova-custom-config-stoponerror` - if true and an error occurs while updating config for a given platform during a `prepare` operation, the error will cause the `prepare` operation to fail.
672
+If false, the plugin will log the error but will proceed and attempt to update any other platforms, before allowing the `prepare` operation to continue.
673
+ Defaults to `false`.
674
+- `cordova-custom-config-hook` - determines which Cordova hook operation to use to run the plugin and apply custom config.
675
+Defaults to `after_prepare` if not specified.
676
+You may wish to change this to apply custom config earlier or later, for example if config applied by this plugin is clashing with other plugins.
677
+Possible values are: `before_prepare`, `after_prepare`, `before_compile`.
678
+See the [Cordova hooks documentation](https://cordova.apache.org/docs/en/latest/guide/appdev/hooks/) for more on the Cordova build process.
679
+- `cordova-custom-config-parse_unprefixed` - determines whether the plugin should attempt to parse unprefixed custom config elements in `config.xml`
680
+    - If not explicitly specified, the plugin will set a default by detecting whether the platform project is `cordova-android@7` or `cordova-android@6` (or below)
681
+        - If `cordova-android@6`, defaults to `true`
682
+        - If `cordova-android@7`, defaults to `false`
683
+
684
+## Log output
685
+
686
+If you run the prepare operation with the `--verbose` command-line option, the plugin will output detail about the operations it's performing. Console messages are prefixed with `cordova-custom-config: `. For example:
687
+
688
+    cordova prepare ios --verbose
689
+
690
+# Example project
691
+
692
+An example project illustrating use of this plugin can be found here: [https://github.com/dpa99c/cordova-custom-config-example](https://github.com/dpa99c/cordova-custom-config-example)
693
+
694
+# TODO
695
+
696
+See the [TODO list](https://github.com/dpa99c/cordova-custom-config/wiki/TODO) for planned features/improvements.
697
+
698
+
699
+# Credits
700
+
701
+Config update hook based on [this hook](https://github.com/diegonetto/generator-ionic/blob/master/templates/hooks/after_prepare/update_platform_config.js) by [Diego Netto](https://github.com/diegonetto)
702
+
703
+# License
704
+================
705
+
706
+The MIT License
707
+
708
+Copyright (c) 2016 Working Edge Ltd.
709
+
710
+Permission is hereby granted, free of charge, to any person obtaining a copy
711
+of this software and associated documentation files (the "Software"), to deal
712
+in the Software without restriction, including without limitation the rights
713
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
714
+copies of the Software, and to permit persons to whom the Software is
715
+furnished to do so, subject to the following conditions:
716
+
717
+The above copyright notice and this permission notice shall be included in
718
+all copies or substantial portions of the Software.
719
+
720
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
721
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
722
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
723
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
724
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
725
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
726
+THE SOFTWARE.

+ 1186
- 0
plugins/cordova-custom-config/hooks/applyCustomConfig.js
File diff suppressed because it is too large
View File


+ 114
- 0
plugins/cordova-custom-config/hooks/fileUtils.js View File

@@ -0,0 +1,114 @@
1
+#!/usr/bin/env node
2
+
3
+/**********
4
+ * Globals
5
+ **********/
6
+var fs,
7
+    path,
8
+    _,
9
+    et,
10
+    tostr;
11
+
12
+/**
13
+ * Provides files utilities
14
+ */
15
+var fileUtils = (function(){
16
+
17
+    /**********************
18
+     * Internal properties
19
+     *********************/
20
+    var fileUtils = {}, context, configXmlData, settings;
21
+
22
+    /************
23
+     * Public API
24
+     ************/
25
+
26
+        // Parses a given file into an elementtree object
27
+    fileUtils.parseElementtreeSync =  function(filename) {
28
+        var contents = fs.readFileSync(filename, 'utf-8');
29
+        if(contents) {
30
+            //Windows is the BOM. Skip the Byte Order Mark.
31
+            contents = contents.substring(contents.indexOf('<'));
32
+        }
33
+        return new et.ElementTree(et.XML(contents));
34
+    };
35
+
36
+    // Parses the config.xml into an elementtree object and stores in the config object
37
+    fileUtils.getConfigXml = function() {
38
+        if(!configXmlData) {
39
+            configXmlData = fileUtils.parseElementtreeSync(path.join(context.opts.projectRoot, 'config.xml'));
40
+        }
41
+        return configXmlData;
42
+    };
43
+
44
+    // Returns plugin settings from config.xml
45
+    fileUtils.getSettings = function (){
46
+        if(!settings){
47
+            settings = {};
48
+            var name, preferences = fileUtils.getConfigXml().findall("preference");
49
+            _.each(preferences, function (preference) {
50
+                name = preference.attrib.name;
51
+                if(name.match("cordova-custom-config")){
52
+                    settings[name.split('-').pop()] = preference.attrib.value;
53
+                }
54
+            });
55
+        }
56
+        return settings;
57
+    };
58
+
59
+    // Returns project name from config.xml
60
+    fileUtils.getProjectName = function(){
61
+        if(!configXmlData) {
62
+            fileUtils.getConfigXml();
63
+        }
64
+        return configXmlData.findtext('name');
65
+    };
66
+
67
+    fileUtils.fileExists = function(filePath){
68
+        try {
69
+            return fs.statSync(filePath).isFile();
70
+        }
71
+        catch (err) {
72
+            return false;
73
+        }
74
+    };
75
+
76
+    fileUtils.directoryExists = function(dirPath){
77
+        try {
78
+            return fs.statSync(dirPath).isDirectory();
79
+        }
80
+        catch (err) {
81
+            return false;
82
+        }
83
+    };
84
+
85
+    fileUtils.createDirectory = function (dirPath){
86
+        return fs.mkdirSync(dirPath);
87
+    };
88
+
89
+    fileUtils.copySync = function (sourcePath, targetPath){
90
+        var contents = fs.readFileSync(sourcePath);
91
+        fs.writeFileSync(targetPath, contents);
92
+    };
93
+
94
+    fileUtils.copySyncRelative = function (sourcePath, targetPath){
95
+        fileUtils.copySync(path.resolve(sourcePath), path.resolve(targetPath));
96
+    };
97
+
98
+    fileUtils.init = function(ctx){
99
+        context = ctx;
100
+
101
+        // Load modules
102
+        fs = require('fs');
103
+        path = require('path');
104
+        _ = require('lodash');
105
+        et = require('elementtree');
106
+        tostr = require('tostr');
107
+    };
108
+    return fileUtils;
109
+})();
110
+
111
+module.exports = function(ctx){
112
+    fileUtils.init(ctx);
113
+    return fileUtils;
114
+};

+ 87
- 0
plugins/cordova-custom-config/hooks/logger.js View File

@@ -0,0 +1,87 @@
1
+#!/usr/bin/env node
2
+
3
+var logger = (function(){
4
+
5
+    /**********************
6
+     * Internal properties
7
+     *********************/
8
+    var logger, context, hasColors = true;
9
+
10
+    try{
11
+        require('colors');
12
+    }catch(e){
13
+        hasColors = false;
14
+    }
15
+
16
+    function prefixMsg(msg){
17
+        return context.opts.plugin.id+": "+msg;
18
+    }
19
+
20
+    /************
21
+     * Public API
22
+     ************/
23
+    logger = {
24
+        init: function(ctx){
25
+            context = ctx;
26
+        },
27
+        dump: function (obj){
28
+            if(context.cmdLine.match("--debug") || context.cmdLine.match("--dump")) {
29
+                console.log("DUMP: "+require('util').inspect(obj));
30
+            }
31
+        },
32
+        debug: function(msg){
33
+            if(context.cmdLine.match("--debug")){
34
+                msg = "DEBUG: " + msg;
35
+                console.log(msg);
36
+            }
37
+        },
38
+        verbose: function(msg){
39
+            if(context.opts.verbose || context.cmdLine.match("--verbose") || context.cmdLine.match("--debug")){
40
+                msg = prefixMsg(msg);
41
+                if(hasColors){
42
+                    console.log(msg.green);
43
+                }else{
44
+                    console.log(msg);
45
+                }
46
+            }
47
+        },
48
+        log: function(msg){
49
+            msg = prefixMsg(msg);
50
+            if(hasColors){
51
+                console.log(msg.white);
52
+            }else{
53
+                console.log(msg);
54
+            }
55
+        },
56
+        info: function(msg){
57
+            msg = prefixMsg(msg);
58
+            if(hasColors){
59
+                console.log(msg.blue);
60
+            }else{
61
+                console.info(msg);
62
+            }
63
+        },
64
+        warn: function(msg){
65
+            msg = prefixMsg(msg);
66
+            if(hasColors){
67
+                console.log(msg.yellow);
68
+            }else{
69
+                console.warn(msg);
70
+            }
71
+        },
72
+        error: function(msg){
73
+            msg = prefixMsg(msg);
74
+            if(hasColors){
75
+                console.log(msg.red);
76
+            }else{
77
+                console.error(msg);
78
+            }
79
+        }
80
+    };
81
+    return logger;
82
+})();
83
+
84
+module.exports = function(ctx){
85
+    logger.init(ctx);
86
+    return logger;
87
+};

+ 150
- 0
plugins/cordova-custom-config/hooks/restoreBackups.js View File

@@ -0,0 +1,150 @@
1
+#!/usr/bin/env node
2
+
3
+/**********
4
+ * Globals
5
+ **********/
6
+var TAG = "cordova-custom-config";
7
+var SCRIPT_NAME = "restoreBackups.js";
8
+
9
+// Pre-existing Cordova npm modules
10
+var deferral, path, cwd;
11
+
12
+// Npm dependencies
13
+var logger,
14
+    fs,
15
+    _,
16
+    fileUtils;
17
+
18
+// Other globals
19
+var hooksPath;
20
+
21
+var restoreBackups = (function(){
22
+
23
+    /**********************
24
+     * Internal properties
25
+     *********************/
26
+    var restoreBackups = {}, context, projectName, logFn, settings;
27
+
28
+    var PLATFORM_CONFIG_FILES = {
29
+        "ios":{
30
+            "{projectName}-Info.plist": "{projectName}/{projectName}-Info.plist",
31
+            "project.pbxproj": "{projectName}.xcodeproj/project.pbxproj",
32
+            "build.xcconfig": "cordova/build.xcconfig",
33
+            "build-extras.xcconfig": "cordova/build-extras.xcconfig",
34
+            "build-debug.xcconfig": "cordova/build-debug.xcconfig",
35
+            "build-release.xcconfig": "cordova/build-release.xcconfig",
36
+            "Entitlements-Release.plist": "{projectName}/Entitlements-Release.plist",
37
+            "Entitlements-Debug.plist": "{projectName}/Entitlements-Debug.plist"
38
+        },
39
+        "android":{
40
+            "AndroidManifest.xml": "AndroidManifest.xml"
41
+        }
42
+    };
43
+
44
+    /*********************
45
+     * Internal functions
46
+     *********************/
47
+
48
+    function restorePlatformBackups(platform){
49
+        var configFiles = PLATFORM_CONFIG_FILES[platform],
50
+            backupFile, backupFileName, backupFilePath, backupFileExists, targetFilePath;
51
+
52
+        logger.verbose("Checking to see if there are backups to restore...");
53
+        for(backupFile in configFiles){
54
+            backupFileName = parseProjectName(backupFile);
55
+            backupFilePath = path.join(cwd, 'plugins', context.opts.plugin.id, "backup", platform, backupFileName);
56
+            backupFileExists = fileUtils.fileExists(backupFilePath);
57
+            if(backupFileExists){
58
+                targetFilePath = path.join(cwd, 'platforms', platform, parseProjectName(configFiles[backupFile]));
59
+                fileUtils.copySync(backupFilePath, targetFilePath);
60
+                logFn("Restored backup of '"+backupFileName+"' to :"+targetFilePath);
61
+            }
62
+        }
63
+    }
64
+
65
+    function parseProjectName(fileName){
66
+        return fileName.replace(/{(projectName)}/g, projectName);
67
+    }
68
+
69
+    // Script operations are complete, so resolve deferred promises
70
+    function complete(){
71
+        deferral.resolve();
72
+    }
73
+
74
+    /*************
75
+     * Public API
76
+     *************/
77
+    restoreBackups.loadDependencies = function(ctx){
78
+        fs = require('fs'),
79
+        _ = require('lodash'),
80
+        fileUtils = require(path.resolve(hooksPath, "fileUtils.js"))(ctx);
81
+        logger.verbose("Loaded module dependencies");
82
+    };
83
+
84
+    restoreBackups.init = function(ctx){
85
+        context = ctx;
86
+
87
+        projectName = fileUtils.getProjectName();
88
+        logFn = context.hook === "before_plugin_uninstall" ? logger.log : logger.verbose;
89
+
90
+        settings = fileUtils.getSettings();
91
+        if(typeof(settings.autorestore) === "undefined" || settings.autorestore === "false"){
92
+            logger.log("Skipping auto-restore of config file backup(s)");
93
+            complete();
94
+            return;
95
+        }
96
+
97
+        // go through each of the platform directories
98
+        var platforms = _.filter(fs.readdirSync('platforms'), function (file) {
99
+            return fs.statSync(path.resolve('platforms', file)).isDirectory();
100
+        });
101
+        _.each(platforms, function (platform, index) {
102
+            platform = platform.trim().toLowerCase();
103
+            try{
104
+                restorePlatformBackups(platform);
105
+                if(index === platforms.length - 1){
106
+                    logger.verbose("Finished restoring backups");
107
+                    complete();
108
+                }
109
+            }catch(e){
110
+                var msg = "Error restoring backups for platform '"+platform+"': "+ e.message;
111
+                logger.error(msg);
112
+                if(settings.stoponerror){
113
+                    deferral.reject(TAG + ": " +msg);
114
+                }
115
+            }
116
+        });
117
+    };
118
+
119
+    return restoreBackups;
120
+})();
121
+
122
+module.exports = function(ctx) {
123
+    try{
124
+        deferral = require('q').defer();
125
+        path = require('path');
126
+        cwd = path.resolve();
127
+
128
+        hooksPath = path.resolve(ctx.opts.projectRoot, "plugins", ctx.opts.plugin.id, "hooks");
129
+        logger = require(path.resolve(hooksPath, "logger.js"))(ctx);
130
+
131
+        restoreBackups.loadDependencies(ctx);
132
+    }catch(e){
133
+        e.message = TAG + ": Error loading dependencies for "+SCRIPT_NAME+" - ensure the plugin has been installed via cordova-fetch or run 'npm install cordova-custom-config': "+e.message;
134
+        if(typeof deferral !== "undefined"){
135
+            deferral.reject(e.message);
136
+            return deferral.promise;
137
+        }
138
+        throw e;
139
+    }
140
+
141
+    try{
142
+        logger.verbose("Running " + SCRIPT_NAME);
143
+        restoreBackups.init(ctx);
144
+    }catch(e){
145
+        var msg = TAG + ": Error running "+SCRIPT_NAME+": "+e.message;
146
+        deferral.reject(msg);
147
+    }
148
+
149
+    return deferral.promise;
150
+};

+ 49
- 0
plugins/cordova-custom-config/hooks/triggerExampleProjBuild.js View File

@@ -0,0 +1,49 @@
1
+#!/usr/bin/env node
2
+"use strict";
3
+
4
+var shell = require('shelljs');
5
+var path = require('path');
6
+var got = require('got');
7
+
8
+var targetRepo = 'dpa99c/cordova-custom-config-example';
9
+
10
+console.log("Fetching Git commit hash...");
11
+
12
+var gitCommitRet = shell.exec('git rev-parse HEAD', {
13
+  cwd: path.join(__dirname, '..')
14
+});
15
+
16
+if (0 !== gitCommitRet.code) {
17
+  console.error('Error getting git commit hash');
18
+
19
+  process.exit(-1);
20
+}
21
+
22
+var gitCommitHash = gitCommitRet.stdout.trim();
23
+
24
+console.log("Git commit: "+gitCommitHash);
25
+
26
+console.log('Calling Travis...');
27
+
28
+got.post("https://api.travis-ci.org/repo/"+encodeURIComponent(targetRepo)+"/requests", {
29
+  headers: {
30
+    "Content-Type": "application/json",
31
+    "Accept": "application/json",
32
+    "Travis-API-Version": "3",
33
+    "Authorization": "token "+process.env.TRAVIS_API_TOKEN
34
+  },
35
+  body: JSON.stringify({
36
+    request: {
37
+      message: "Trigger build at "+targetRepo+" commit: "+gitCommitHash,
38
+      branch: 'master'
39
+    }
40
+  })
41
+})
42
+.then(function(){
43
+  console.log("Triggered build of "+targetRepo);
44
+})
45
+.catch(function(err){
46
+  console.error(err);
47
+  process.exit(-1);
48
+});
49
+

+ 82
- 0
plugins/cordova-custom-config/package.json View File

@@ -0,0 +1,82 @@
1
+{
2
+  "_args": [
3
+    [
4
+      "cordova-custom-config@5.1.0",
5
+      "/home/rarce/Downloads/tutorial"
6
+    ]
7
+  ],
8
+  "_from": "cordova-custom-config@5.1.0",
9
+  "_id": "cordova-custom-config@5.1.0",
10
+  "_inBundle": false,
11
+  "_integrity": "sha512-qPk1l1Rayu0PAp5kXqyvzMprc0oCqRa5pcs7iRrb0daq0I/RWIw7BngCyOJuv/wnJrTBbwxVued1+lVLe0/MeA==",
12
+  "_location": "/cordova-custom-config",
13
+  "_phantomChildren": {},
14
+  "_requested": {
15
+    "type": "version",
16
+    "registry": true,
17
+    "raw": "cordova-custom-config@5.1.0",
18
+    "name": "cordova-custom-config",
19
+    "escapedName": "cordova-custom-config",
20
+    "rawSpec": "5.1.0",
21
+    "saveSpec": null,
22
+    "fetchSpec": "5.1.0"
23
+  },
24
+  "_requiredBy": [
25
+    "/"
26
+  ],
27
+  "_resolved": "https://registry.npmjs.org/cordova-custom-config/-/cordova-custom-config-5.1.0.tgz",
28
+  "_spec": "5.1.0",
29
+  "_where": "/home/rarce/Downloads/tutorial",
30
+  "author": {
31
+    "name": "Dave Alden"
32
+  },
33
+  "bugs": {
34
+    "url": "https://github.com/dpa99c/cordova-custom-config/issues"
35
+  },
36
+  "cordova": {
37
+    "id": "cordova-custom-config",
38
+    "platforms": [
39
+      "android",
40
+      "ios"
41
+    ]
42
+  },
43
+  "cordova_name": "cordova-custom-config",
44
+  "dependencies": {
45
+    "colors": "^1.1.2",
46
+    "elementtree": "^0.1.6",
47
+    "lodash": "^4.17.11",
48
+    "plist": "^3.0.1",
49
+    "q": "^1.4.1",
50
+    "shelljs": "^0.7.0",
51
+    "tostr": "^0.1.0",
52
+    "xcode": "^1.0.0"
53
+  },
54
+  "description": "Cordova/Phonegap plugin to update platform configuration files based on preferences and config-file data defined in config.xml.",
55
+  "devDependencies": {
56
+    "got": "^6.5.0",
57
+    "jshint": "^2.6.0"
58
+  },
59
+  "homepage": "https://github.com/dpa99c/cordova-custom-config#readme",
60
+  "issue": "https://github.com/dpa99c/cordova-custom-config/issues",
61
+  "keywords": [
62
+    "ecosystem:cordova",
63
+    "cordova",
64
+    "cordova-android",
65
+    "cordova-ios",
66
+    "phonegap",
67
+    "config",
68
+    "configuration",
69
+    "plist",
70
+    "manifest"
71
+  ],
72
+  "license": "MIT",
73
+  "name": "cordova-custom-config",
74
+  "repository": {
75
+    "type": "git",
76
+    "url": "git+https://github.com/dpa99c/cordova-custom-config.git"
77
+  },
78
+  "scripts": {
79
+    "test": "jshint hooks"
80
+  },
81
+  "version": "5.1.0"
82
+}

+ 27
- 0
plugins/cordova-custom-config/plugin.xml View File

@@ -0,0 +1,27 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
3
+    xmlns:android="http://schemas.android.com/apk/res/android"
4
+    id="cordova-custom-config"
5
+    version="5.1.0">
6
+
7
+    <name>cordova-custom-config</name>
8
+    <description>Cordova/Phonegap plugin to update platform configuration files based on preferences and config-file data defined in config.xml</description>
9
+    <author>Dave Alden</author>
10
+
11
+    <engines>
12
+        <engine name="cordova" version=">=3.0.0" />
13
+    </engines>
14
+
15
+    <repo>https://github.com/dpa99c/cordova-custom-config.git</repo>
16
+    <issue>https://github.com/dpa99c/cordova-custom-config/issues</issue>
17
+
18
+    <keywords>ecosystem:cordova,cordova,phonegap,ios,android,config,configuration,plist,manifest</keywords>
19
+
20
+    <license>MIT</license>
21
+
22
+    <hook src="hooks/restoreBackups.js" type="before_prepare" />
23
+    <hook src="hooks/restoreBackups.js" type="before_plugin_uninstall" />
24
+    <hook src="hooks/applyCustomConfig.js" type="after_prepare" />
25
+    <hook src="hooks/applyCustomConfig.js" type="before_prepare" />
26
+    <hook src="hooks/applyCustomConfig.js" type="before_compile" />
27
+</plugin>

+ 7
- 0
plugins/cordova-custom-config/templates/ios/Contents.json View File

@@ -0,0 +1,7 @@
1
+{
2
+    "images" : [],
3
+    "info" : {
4
+        "version" : 1,
5
+        "author" : "xcode"
6
+    }
7
+}

+ 17
- 0
plugins/cordova-plugin-email/.npmignore View File

@@ -0,0 +1,17 @@
1
+.idea/*
2
+*.iml
3
+*.log
4
+dist/
5
+etc/
6
+node_modules/
7
+tests/
8
+tmp/
9
+platforms/
10
+out/
11
+.travis.yml
12
+appveyor.yml
13
+Jenkinsfile
14
+*.md
15
+jsdoc.json
16
+karma.conf.js
17
+sample.png

+ 135
- 0
plugins/cordova-plugin-email/CHANGELOG.md View File

@@ -0,0 +1,135 @@
1
+<a name="1.2.7"></a>
2
+## [1.2.7](https://github.com/hypery2k/cordova-email-plugin/compare/v1.2.6...v1.2.7) (2018-02-16)
3
+
4
+
5
+
6
+<a name="1.2.6"></a>
7
+## [1.2.6](https://github.com/hypery2k/cordova-email-plugin/compare/v1.2.5...v1.2.6) (2017-01-03)
8
+
9
+
10
+### Bug Fixes
11
+
12
+* **isAvailable-error:** fix callback error on newer android ([171b99b](https://github.com/hypery2k/cordova-email-plugin/commit/171b99b)), closes [#39](https://github.com/hypery2k/cordova-email-plugin/issues/39)
13
+
14
+
15
+
16
+<a name="1.2.5"></a>
17
+## [1.2.5](https://github.com/hypery2k/cordova-email-plugin/compare/v1.2.4...v1.2.5) (2016-12-24)
18
+
19
+
20
+### Bug Fixes
21
+
22
+* **package:** fix version error ([#38](https://github.com/hypery2k/cordova-email-plugin/issues/38)) ([1992e95](https://github.com/hypery2k/cordova-email-plugin/commit/1992e95))
23
+
24
+
25
+
26
+<a name="1.2.4"></a>
27
+## [1.2.4](https://github.com/hypery2k/cordova-email-plugin/compare/v1.2.3...v1.2.4) (2016-12-18)
28
+
29
+
30
+
31
+<a name="1.2.3"></a>
32
+## [1.2.3](https://github.com/hypery2k/cordova-email-plugin/compare/v1.2.1...v1.2.3) (2016-12-17)
33
+
34
+
35
+
36
+<a name="1.2.1"></a>
37
+## [1.2.1](https://github.com/hypery2k/cordova-email-plugin/compare/v1.2.0...v1.2.1) (2016-11-06)
38
+
39
+
40
+### Bug Fixes
41
+
42
+* **iOS:** Fix isAvailable null error ([#23](https://github.com/hypery2k/cordova-email-plugin/issues/23)) ([78ec84c](https://github.com/hypery2k/cordova-email-plugin/commit/78ec84c))
43
+
44
+
45
+
46
+<a name="1.2.0"></a>
47
+# [1.2.0](https://github.com/hypery2k/cordova-email-plugin/compare/v1.1.1...v1.2.0) (2016-11-03)
48
+
49
+
50
+
51
+<a name="1.1.1"></a>
52
+## [1.1.1](https://github.com/hypery2k/cordova-email-plugin/compare/0.8.3...v1.1.1) (2016-06-27)
53
+
54
+
55
+
56
+<a name="0.8.3"></a>
57
+## [0.8.3](https://github.com/hypery2k/cordova-email-plugin/compare/v1.1.0...0.8.3) (2016-03-01)
58
+
59
+
60
+
61
+<a name="1.1.0"></a>
62
+# [1.1.0](https://github.com/hypery2k/cordova-email-plugin/compare/v1.0.0...v1.1.0) (2015-12-16)
63
+
64
+
65
+
66
+<a name="1.0.0"></a>
67
+# [1.0.0](https://github.com/hypery2k/cordova-email-plugin/compare/0.8.2...v1.0.0) (2015-10-29)
68
+
69
+
70
+
71
+<a name="0.8.2"></a>
72
+## [0.8.2](https://github.com/hypery2k/cordova-email-plugin/compare/v0.8.2...0.8.2) (2015-03-01)
73
+
74
+
75
+
76
+<a name="0.8.2"></a>
77
+## [0.8.2](https://github.com/hypery2k/cordova-email-plugin/compare/0.8.1...v0.8.2) (2015-02-28)
78
+
79
+
80
+
81
+<a name="0.8.1"></a>
82
+## [0.8.1](https://github.com/hypery2k/cordova-email-plugin/compare/0.8.0...0.8.1) (2014-04-06)
83
+
84
+
85
+
86
+<a name="0.8.0"></a>
87
+# [0.8.0](https://github.com/hypery2k/cordova-email-plugin/compare/0.7.2...0.8.0) (2014-03-02)
88
+
89
+
90
+
91
+<a name="0.7.2"></a>
92
+## [0.7.2](https://github.com/hypery2k/cordova-email-plugin/compare/0.7.1...0.7.2) (2014-03-01)
93
+
94
+
95
+
96
+<a name="0.7.1"></a>
97
+## [0.7.1](https://github.com/hypery2k/cordova-email-plugin/compare/0.7.0...0.7.1) (2013-12-19)
98
+
99
+
100
+
101
+<a name="0.7.0"></a>
102
+# [0.7.0](https://github.com/hypery2k/cordova-email-plugin/compare/0.6.0...0.7.0) (2013-12-05)
103
+
104
+
105
+
106
+<a name="0.6.0"></a>
107
+# [0.6.0](https://github.com/hypery2k/cordova-email-plugin/compare/0.4.2...0.6.0) (2013-11-17)
108
+
109
+
110
+
111
+<a name="0.4.2"></a>
112
+## [0.4.2](https://github.com/hypery2k/cordova-email-plugin/compare/0.4.1...0.4.2) (2013-11-17)
113
+
114
+
115
+
116
+<a name="0.4.1"></a>
117
+## [0.4.1](https://github.com/hypery2k/cordova-email-plugin/compare/0.4.0...0.4.1) (2013-11-03)
118
+
119
+
120
+
121
+<a name="0.4.0"></a>
122
+# [0.4.0](https://github.com/hypery2k/cordova-email-plugin/compare/0.2.1...0.4.0) (2013-08-20)
123
+
124
+
125
+
126
+<a name="0.2.1"></a>
127
+## [0.2.1](https://github.com/hypery2k/cordova-email-plugin/compare/0.2.0...0.2.1) (2013-08-15)
128
+
129
+
130
+
131
+<a name="0.2.0"></a>
132
+# 0.2.0 (2013-08-12)
133
+
134
+
135
+

+ 13
- 0
plugins/cordova-plugin-email/CONTRIBUTING.MD View File

@@ -0,0 +1,13 @@
1
+First search if the issue is already described!
2
+
3
+If not create a new issue:
4
+
5
+* Tell about your environment:
6
+  * operating system and version
7
+  * cordova version
8
+  * Android/iOS version
9
+  * java version
10
+* Describe your issue
11
+  * describe your steps leading to the issue
12
+  * attach error logs or screenshots
13
+  * if possible provide test case or screenshots

+ 202
- 0
plugins/cordova-plugin-email/LICENSE View File

@@ -0,0 +1,202 @@
1
+
2
+                                 Apache License
3
+                           Version 2.0, January 2004
4
+                        http://www.apache.org/licenses/
5
+
6
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+   1. Definitions.
9
+
10
+      "License" shall mean the terms and conditions for use, reproduction,
11
+      and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+      "Licensor" shall mean the copyright owner or entity authorized by
14
+      the copyright owner that is granting the License.
15
+
16
+      "Legal Entity" shall mean the union of the acting entity and all
17
+      other entities that control, are controlled by, or are under common
18
+      control with that entity. For the purposes of this definition,
19
+      "control" means (i) the power, direct or indirect, to cause the
20
+      direction or management of such entity, whether by contract or
21
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+      outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+      "You" (or "Your") shall mean an individual or Legal Entity
25
+      exercising permissions granted by this License.
26
+
27
+      "Source" form shall mean the preferred form for making modifications,
28
+      including but not limited to software source code, documentation
29
+      source, and configuration files.
30
+
31
+      "Object" form shall mean any form resulting from mechanical
32
+      transformation or translation of a Source form, including but
33
+      not limited to compiled object code, generated documentation,
34
+      and conversions to other media types.
35
+
36
+      "Work" shall mean the work of authorship, whether in Source or
37
+      Object form, made available under the License, as indicated by a
38
+      copyright notice that is included in or attached to the work
39
+      (an example is provided in the Appendix below).
40
+
41
+      "Derivative Works" shall mean any work, whether in Source or Object
42
+      form, that is based on (or derived from) the Work and for which the
43
+      editorial revisions, annotations, elaborations, or other modifications
44
+      represent, as a whole, an original work of authorship. For the purposes
45
+      of this License, Derivative Works shall not include works that remain
46
+      separable from, or merely link (or bind by name) to the interfaces of,
47
+      the Work and Derivative Works thereof.
48
+
49
+      "Contribution" shall mean any work of authorship, including
50
+      the original version of the Work and any modifications or additions
51
+      to that Work or Derivative Works thereof, that is intentionally
52
+      submitted to Licensor for inclusion in the Work by the copyright owner
53
+      or by an individual or Legal Entity authorized to submit on behalf of
54
+      the copyright owner. For the purposes of this definition, "submitted"
55
+      means any form of electronic, verbal, or written communication sent
56
+      to the Licensor or its representatives, including but not limited to
57
+      communication on electronic mailing lists, source code control systems,
58
+      and issue tracking systems that are managed by, or on behalf of, the
59
+      Licensor for the purpose of discussing and improving the Work, but
60
+      excluding communication that is conspicuously marked or otherwise
61
+      designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+      "Contributor" shall mean Licensor and any individual or Legal Entity
64
+      on behalf of whom a Contribution has been received by Licensor and
65
+      subsequently incorporated within the Work.
66
+
67
+   2. Grant of Copyright License. Subject to the terms and conditions of
68
+      this License, each Contributor hereby grants to You a perpetual,
69
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+      copyright license to reproduce, prepare Derivative Works of,
71
+      publicly display, publicly perform, sublicense, and distribute the
72
+      Work and such Derivative Works in Source or Object form.
73
+
74
+   3. Grant of Patent License. Subject to the terms and conditions of
75
+      this License, each Contributor hereby grants to You a perpetual,
76
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+      (except as stated in this section) patent license to make, have made,
78
+      use, offer to sell, sell, import, and otherwise transfer the Work,
79
+      where such license applies only to those patent claims licensable
80
+      by such Contributor that are necessarily infringed by their
81
+      Contribution(s) alone or by combination of their Contribution(s)
82
+      with the Work to which such Contribution(s) was submitted. If You
83
+      institute patent litigation against any entity (including a
84
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+      or a Contribution incorporated within the Work constitutes direct
86
+      or contributory patent infringement, then any patent licenses
87
+      granted to You under this License for that Work shall terminate
88
+      as of the date such litigation is filed.
89
+
90
+   4. Redistribution. You may reproduce and distribute copies of the
91
+      Work or Derivative Works thereof in any medium, with or without
92
+      modifications, and in Source or Object form, provided that You
93
+      meet the following conditions:
94
+
95
+      (a) You must give any other recipients of the Work or
96
+          Derivative Works a copy of this License; and
97
+
98
+      (b) You must cause any modified files to carry prominent notices
99
+          stating that You changed the files; and
100
+
101
+      (c) You must retain, in the Source form of any Derivative Works
102
+          that You distribute, all copyright, patent, trademark, and
103
+          attribution notices from the Source form of the Work,
104
+          excluding those notices that do not pertain to any part of
105
+          the Derivative Works; and
106
+
107
+      (d) If the Work includes a "NOTICE" text file as part of its
108
+          distribution, then any Derivative Works that You distribute must
109
+          include a readable copy of the attribution notices contained
110
+          within such NOTICE file, excluding those notices that do not
111
+          pertain to any part of the Derivative Works, in at least one
112
+          of the following places: within a NOTICE text file distributed
113
+          as part of the Derivative Works; within the Source form or
114
+          documentation, if provided along with the Derivative Works; or,
115
+          within a display generated by the Derivative Works, if and
116
+          wherever such third-party notices normally appear. The contents
117
+          of the NOTICE file are for informational purposes only and
118
+          do not modify the License. You may add Your own attribution
119
+          notices within Derivative Works that You distribute, alongside
120
+          or as an addendum to the NOTICE text from the Work, provided
121
+          that such additional attribution notices cannot be construed
122
+          as modifying the License.
123
+
124
+      You may add Your own copyright statement to Your modifications and
125
+      may provide additional or different license terms and conditions
126
+      for use, reproduction, or distribution of Your modifications, or
127
+      for any such Derivative Works as a whole, provided Your use,
128
+      reproduction, and distribution of the Work otherwise complies with
129
+      the conditions stated in this License.
130
+
131
+   5. Submission of Contributions. Unless You explicitly state otherwise,
132
+      any Contribution intentionally submitted for inclusion in the Work
133
+      by You to the Licensor shall be under the terms and conditions of
134
+      this License, without any additional terms or conditions.
135
+      Notwithstanding the above, nothing herein shall supersede or modify
136
+      the terms of any separate license agreement you may have executed
137
+      with Licensor regarding such Contributions.
138
+
139
+   6. Trademarks. This License does not grant permission to use the trade
140
+      names, trademarks, service marks, or product names of the Licensor,
141
+      except as required for reasonable and customary use in describing the
142
+      origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+   7. Disclaimer of Warranty. Unless required by applicable law or
145
+      agreed to in writing, Licensor provides the Work (and each
146
+      Contributor provides its Contributions) on an "AS IS" BASIS,
147
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+      implied, including, without limitation, any warranties or conditions
149
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+      PARTICULAR PURPOSE. You are solely responsible for determining the
151
+      appropriateness of using or redistributing the Work and assume any
152
+      risks associated with Your exercise of permissions under this License.
153
+
154
+   8. Limitation of Liability. In no event and under no legal theory,
155
+      whether in tort (including negligence), contract, or otherwise,
156
+      unless required by applicable law (such as deliberate and grossly
157
+      negligent acts) or agreed to in writing, shall any Contributor be
158
+      liable to You for damages, including any direct, indirect, special,
159
+      incidental, or consequential damages of any character arising as a
160
+      result of this License or out of the use or inability to use the
161
+      Work (including but not limited to damages for loss of goodwill,
162
+      work stoppage, computer failure or malfunction, or any and all
163
+      other commercial damages or losses), even if such Contributor
164
+      has been advised of the possibility of such damages.
165
+
166
+   9. Accepting Warranty or Additional Liability. While redistributing
167
+      the Work or Derivative Works thereof, You may choose to offer,
168
+      and charge a fee for, acceptance of support, warranty, indemnity,
169
+      or other liability obligations and/or rights consistent with this
170
+      License. However, in accepting such obligations, You may act only
171
+      on Your own behalf and on Your sole responsibility, not on behalf
172
+      of any other Contributor, and only if You agree to indemnify,
173
+      defend, and hold each Contributor harmless for any liability
174
+      incurred by, or claims asserted against, such Contributor by reason
175
+      of your accepting any such warranty or additional liability.
176
+
177
+   END OF TERMS AND CONDITIONS
178
+
179
+   APPENDIX: How to apply the Apache License to your work.
180
+
181
+      To apply the Apache License to your work, attach the following
182
+      boilerplate notice, with the fields enclosed by brackets "[]"
183
+      replaced with your own identifying information. (Don't include
184
+      the brackets!)  The text should be enclosed in the appropriate
185
+      comment syntax for the file format. We also recommend that a
186
+      file or class name and description of purpose be included on the
187
+      same "printed page" as the copyright notice for easier
188
+      identification within third-party archives.
189
+
190
+   Copyright 2013-2016 appPlant UG
191
+
192
+   Licensed under the Apache License, Version 2.0 (the "License");
193
+   you may not use this file except in compliance with the License.
194
+   You may obtain a copy of the License at
195
+
196
+       http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+   Unless required by applicable law or agreed to in writing, software
199
+   distributed under the License is distributed on an "AS IS" BASIS,
200
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+   See the License for the specific language governing permissions and
202
+   limitations under the License.

+ 74
- 0
plugins/cordova-plugin-email/PLUGIN_USAGE.MD View File

@@ -0,0 +1,74 @@
1
+<a name="cordova.plugins.email"></a>
2
+
3
+## cordova.plugins.email : <code>object</code>
4
+**Kind**: global namespace  
5
+
6
+* [cordova.plugins.email](#cordova.plugins.email) : <code>object</code>
7
+    * [.aliases](#cordova.plugins.email+aliases)
8
+    * [.getDefaults()](#cordova.plugins.email+getDefaults) ⇒ <code>Object</code>
9
+    * [.isAvailable(callback, scope)](#cordova.plugins.email+isAvailable)
10
+    * [.open(options, callback, scope)](#cordova.plugins.email+open)
11
+    * [.addAlias(alias, packageName)](#cordova.plugins.email+addAlias)
12
+    * [.isServiceAvailable()](#cordova.plugins.email+isServiceAvailable)
13
+    * [.openDraft()](#cordova.plugins.email+openDraft)
14
+
15
+<a name="cordova.plugins.email+aliases"></a>
16
+
17
+### cordova.plugins.email.aliases
18
+List of all registered mail app aliases.
19
+
20
+**Kind**: instance property of <code>[cordova.plugins.email](#cordova.plugins.email)</code>  
21
+<a name="cordova.plugins.email+getDefaults"></a>
22
+
23
+### cordova.plugins.email.getDefaults() ⇒ <code>Object</code>
24
+List of all available options with their default value.
25
+
26
+**Kind**: instance method of <code>[cordova.plugins.email](#cordova.plugins.email)</code>  
27
+<a name="cordova.plugins.email+isAvailable"></a>
28
+
29
+### cordova.plugins.email.isAvailable(callback, scope)
30
+Verifies if sending emails is supported on the device.
31
+
32
+**Kind**: instance method of <code>[cordova.plugins.email](#cordova.plugins.email)</code>  
33
+
34
+| Param | Type | Description |
35
+| --- | --- | --- |
36
+| callback | <code>function</code> | A callback function to be called with the result |
37
+| scope | <code>Object</code> | The scope of the callback |
38
+
39
+<a name="cordova.plugins.email+open"></a>
40
+
41
+### cordova.plugins.email.open(options, callback, scope)
42
+Displays the email composer pre-filled with data.
43
+
44
+**Kind**: instance method of <code>[cordova.plugins.email](#cordova.plugins.email)</code>  
45
+
46
+| Param | Type | Description |
47
+| --- | --- | --- |
48
+| options | <code>Object</code> | Different properties of the email like the body, subject |
49
+| callback | <code>function</code> | A callback function to be called with the result |
50
+| scope | <code>Object</code> | The scope of the callback |
51
+
52
+<a name="cordova.plugins.email+addAlias"></a>
53
+
54
+### cordova.plugins.email.addAlias(alias, packageName)
55
+Adds a new mail app alias.
56
+
57
+**Kind**: instance method of <code>[cordova.plugins.email](#cordova.plugins.email)</code>  
58
+
59
+| Param | Type | Description |
60
+| --- | --- | --- |
61
+| alias | <code>String</code> | The alias name |
62
+| packageName | <code>String</code> | The package name |
63
+
64
+<a name="cordova.plugins.email+isServiceAvailable"></a>
65
+
66
+### cordova.plugins.email.isServiceAvailable()
67
+**Kind**: instance method of <code>[cordova.plugins.email](#cordova.plugins.email)</code>  
68
+**Depreacted**:   
69
+<a name="cordova.plugins.email+openDraft"></a>
70
+
71
+### cordova.plugins.email.openDraft()
72
+Alias für `open()`.
73
+
74
+**Kind**: instance method of <code>[cordova.plugins.email](#cordova.plugins.email)</code>  

+ 332
- 0
plugins/cordova-plugin-email/README.md View File

@@ -0,0 +1,332 @@
1
+# Cordova Email Plugin
2
+
3
+[![Greenkeeper badge](https://badges.greenkeeper.io/hypery2k/cordova-email-plugin.svg)](https://greenkeeper.io/)
4
+
5
+
6
+[![Build Status](https://travis-ci.org/hypery2k/cordova-email-plugin.svg?branch=master)](https://travis-ci.org/hypery2k/cordova-email-plugin) [![Build status](https://ci.appveyor.com/api/projects/status/d1g8ygx20or6htpg?svg=true)](https://ci.appveyor.com/project/hypery2k/cordova-email-plugin) [![npm version](https://badge.fury.io/js/cordova-plugin-email.svg)](http://badge.fury.io/js/cordova-plugin-email) [![Dependency Status](https://david-dm.org/hypery2k/cordova-email-plugin.svg)](https://david-dm.org/hypery2k/cordova-email-plugin) [![devDependency Status](https://david-dm.org/hypery2k/cordova-email-plugin/dev-status.svg)](https://david-dm.org/hypery2k/cordova-email-plugin#info=devDependencies) 
7
+
8
+> The plugin provides access to the standard interface that manages the editing and sending an email message. You can use this view controller to display a standard email view inside your application and populate the fields of that view with initial values, such as the subject, email recipients, body text, and attachments. The user can edit the initial contents you specify and choose to send the email or cancel the operation.
9
+
10
+[![NPM](https://nodei.co/npm/cordova-plugin-email.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/cordova-plugin-email/)
11
+
12
+<a name="donation"></a>
13
+> Feel free to **donate**
14
+>
15
+> <a href='http://www.pledgie.com/campaigns/33053'><img alt='Click here to lend your support and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/33053.png?skin_name=chrome' border='0' /></a>
16
+> <a target="_blank" href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AGPGLZYNV6Y5S">
17
+> <img alt="" border="0" src="https://www.paypalobjects.com/de_DE/DE/i/btn/btn_donateCC_LG.gif"/>
18
+> </img></a>
19
+> Or donate Bitcoins: bitcoin:3NKtxw1SRYgess5ev4Ri54GekoAgkR213D
20
+>
21
+> [![Bitcoin](https://martinreinhardt-online.de/bitcoin.png)](bitcoin:3NKtxw1SRYgess5ev4Ri54GekoAgkR213D)
22
+>
23
+> Also via [greenaddress](https://greenaddress.it/pay/GA3ZPfh7As3Gc2oP6pQ1njxMij88u/)
24
+
25
+## Installation
26
+
27
+```bash
28
+$ cordova plugin add cordova-plugin-email
29
+```
30
+Or if you want to use the development version (nightly build), which maybe not stable!:
31
+
32
+```
33
+cordova plugin add cordova-plugin-email@next
34
+```
35
+
36
+<img width="260px" align="right" hspace="7" vspace="5" src="https://github.com/hypery2k/cordova-email-plugin/raw/master/sample.png">
37
+
38
+Using this interface does not guarantee immediate delivery of the corresponding email message. The user may cancel the creation of the message, and if the user does choose to send the message, the message is only queued in the Mail application outbox. This allows you to generate emails even in situations where the user does not have network access, such as in airplane mode. This interface does not provide a way for you to verify whether emails were actually sent.<br><br>
39
+
40
+
41
+## Overview
42
+1. [Supported Platforms](#supported-platforms)
43
+2. [Installation](#installation)
44
+3. [ChangeLog](#changelog)
45
+4. [Using the plugin](#using-the-plugin)
46
+5. [Examples](#examples)
47
+6. [Quirks](#quirks)
48
+
49
+
50
+## Supported Platforms
51
+- __iOS__
52
+- __Android__
53
+- __Amazon FireOS__
54
+- __Windows__
55
+- __Browser__
56
+
57
+
58
+### PhoneGap Build
59
+Add the following xml to your config.xml to always use the latest version of this plugin:
60
+```xml
61
+<gap:plugin name="cordova-plugin-email-composer" version="0.8.3" source="npm" />
62
+```
63
+
64
+
65
+## Changelog
66
+
67
+- See [CHANGELOG.md][changelog] to get the full changelog for the plugin.
68
+
69
+## Using the plugin
70
+The plugin creates the object ```cordova.plugins.email``` with following methods:
71
+
72
+1. [email.isAvailable][available]
73
+2. [email.open][open]
74
+
75
+### Plugin initialization
76
+The plugin and its methods are not available before the *deviceready* event has been fired.
77
+
78
+```javascript
79
+document.addEventListener('deviceready', function () {
80
+    // cordova.plugins.email is now available
81
+}, false);
82
+```
83
+
84
+### Determine if the device is capable to send emails
85
+The ability to send emails can be revised through the `email.isAvailable` interface. The method takes a callback function, passed to which is a boolean property. Optionally the callback scope can be assigned as a second parameter.
86
+
87
+The Email service is only available on devices capable which are able to send emails. E.g. which have configured an email account and have installed an email app. You can use this function to hide email functionality from users who will be unable to use it.
88
+
89
+```javascript
90
+cordova.plugins.email.isAvailable(
91
+    function (isAvailable) {
92
+        // alert('Service is not available') unless isAvailable;
93
+    }
94
+);
95
+```
96
+
97
+If you want to open a draft in a specific application, just pass its uri scheme on iOS, or its name on Android as first parameter, to check whether the application is installed or not. The callback function will return a second parameter of type boolean then.
98
+
99
+```javascript
100
+cordova.plugins.email.isAvailable(
101
+    urischeme, function (isAvailable, withScheme) {
102
+        // alert('Service is not available') unless isAvailable;
103
+    }
104
+);
105
+
106
+```
107
+
108
+>**Note**: If the user didn't have any email account configured on iOS this will also return false
109
+
110
+### Open a pre-filled email draft
111
+A pre-filled email draft can be opened through the `email.open` or `email.openDraft` interface. The method takes a hash as an argument to specify the email's properties. All properties are optional. Further more it accepts an callback function to be called after the email view has been dismissed.
112
+
113
+After opening the draft the user may have the possibilities to edit, delete or send the email.
114
+
115
+#### Further informations
116
+- An [configured email account][available] is required to send emails.
117
+- Attachments can be either base64 encoded datas, files from the the device storage or assets from within the *www* folder.
118
+- The default value for *isHTML* is *true*.
119
+- Its possible to [specify][email_app] the email app on Android and iOS.
120
+- See the [examples][examples] for how to create and show an email draft.
121
+
122
+```javascript
123
+cordova.plugins.email.open({
124
+    to:          Array, // email addresses for TO field
125
+    cc:          Array, // email addresses for CC field
126
+    bcc:         Array, // email addresses for BCC field
127
+    attachments: Array, // file paths or base64 data streams
128
+    subject:    String, // subject of the email
129
+    body:       String, // email body (for HTML, set isHtml to true)
130
+    isHtml:    Boolean, // indicats if the body is HTML or plain text
131
+}, callback, scope);
132
+```
133
+
134
+
135
+## Examples
136
+
137
+### Open an email draft
138
+The following example shows how to create and show an email draft pre-filled with different kind of properties.
139
+
140
+```javascript
141
+cordova.plugins.email.open({
142
+    to:      'max@mustermann.de',
143
+    cc:      'erika@mustermann.de',
144
+    bcc:     ['john@doe.com', 'jane@doe.com'],
145
+    subject: 'Greetings',
146
+    body:    'How are you? Nice greetings from Leipzig'
147
+});
148
+```
149
+
150
+Of course its also possible to open a blank draft.
151
+```javascript
152
+cordova.plugins.email.open();
153
+```
154
+
155
+### Send HTML encoded body
156
+Its possible to send the email body either as text or HTML. In the case of HTML the `isHTML` properties needs to be set.
157
+
158
+```javascript
159
+cordova.plugins.email.open({
160
+    to:      'max@mustermann.de',
161
+    subject: 'Greetings',
162
+    body:    '<h1>Nice greetings from Leipzig</h1>',
163
+    isHtml:  true
164
+});
165
+```
166
+
167
+When building for the browser, you *cannot* use HTML in the body content. Internally, this plugin generates a "mailto:"-style link to support browsers, and the mailto URI scheme only supports plain text body content. See [RFC6068](https://www.ietf.org/rfc/rfc6068.txt) for more details on mailto URIs.
168
+
169
+### Get informed when the view has been dismissed
170
+The `open` method supports additional callback to get informed when the view has been dismissed.
171
+
172
+```javascript
173
+cordova.plugins.email.open(properties, function () {
174
+    console.log('email view dismissed');
175
+}, this);
176
+```
177
+
178
+### Adding attachments
179
+Attachments can be either base64 encoded datas, files from the the device storage or assets from within the *www* folder.
180
+
181
+#### Attach Base64 encoded content
182
+The code below shows how to attach an base64 encoded image which will be added as a image with the name *icon.png*.
183
+
184
+```javascript
185
+cordova.plugins.email.open({
186
+    subject:     'Cordova Icon',
187
+    attachments: 'base64:icon.png//iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/...'
188
+});
189
+```
190
+
191
+#### Attach files from the device storage
192
+The path to the files must be defined absolute from the root of the file system.
193
+
194
+```javascript
195
+cordova.plugins.email.open({
196
+    attachments: 'file:///storage/sdcard/icon.png', //=> Android
197
+});
198
+```
199
+
200
+#### Attach native app resources
201
+Each app has a resource folder, e.g. the _res_ folder for Android apps or the _Resource_ folder for iOS apps. The following example shows how to attach the app icon from within the app's resource folder.
202
+
203
+```javascript
204
+cordova.plugins.email.open({
205
+    attachments: 'res://icon.png' //=> res/drawable/icon (Android)
206
+});
207
+```
208
+
209
+#### Attach assets from the www folder
210
+The path to the files must be defined relative from the root of the mobile web app folder, which is located under the _www_ folder.
211
+
212
+```javascript
213
+cordova.plugins.email.open({
214
+    attachments: [
215
+        'file://img/logo.png', //=> assets/www/img/logo.png (Android)
216
+        'file://css/index.css' //=> www/css/index.css (iOS)
217
+    ]
218
+});
219
+```
220
+
221
+### Specify email app
222
+Its possible to specify the email app which shall open the draft for further editing. Just pass its scheme name through the drafts app-attribute. If the phone isn´t able to handle the specified scheme it will fallback to standard.
223
+
224
+```javascript
225
+// Specify app by scheme name
226
+cordova.plugins.email.open({
227
+    app: 'mailto',
228
+    subject: 'Sent with mailto'
229
+})
230
+```
231
+
232
+On _Android_ the app can be specified by either an alias or its package name. The alias _gmail_ is available by default.
233
+
234
+```javascript
235
+// Add app alias
236
+cordova.plugins.email.addAlias('gmail', 'com.google.android.gm');
237
+
238
+// Specify app by name or alias
239
+cordova.plugins.email.open({
240
+    app: 'gmail',
241
+    subject: 'Sent from Gmail'
242
+})
243
+```
244
+
245
+
246
+## Quirks
247
+
248
+### HTML and CSS on Android
249
+Even Android is capable to render HTML formatted mails, most native Mail clients like the standard app or Gmail only support rich formatted text while writing mails. That means that __CSS cannot be used__ (no _class_ and _style_ support).
250
+
251
+The following table gives an overview which tags and attributes can be used:
252
+
253
+<table>
254
+<td width="60%">
255
+    <ul>
256
+        <li><code>&lt;a href="..."&gt;</code></li>
257
+        <li><code>&lt;b&gt;</code></li>
258
+        <li><code>&lt;big&gt;</code></li>
259
+        <li><code>&lt;blockquote&gt;</code></li>
260
+        <li><code>&lt;br&gt;</code></li>
261
+        <li><code>&lt;cite&gt;</code></li>
262
+        <li><code>&lt;dfn&gt;</code></li>
263
+        <li><code>&lt;div align="..."&gt;</code></li>
264
+        <li><code>&lt;em&gt;</code></li>
265
+        <li><code>&lt;font size="..." color="..." face="..."&gt;</code></li>
266
+        <li><code>&lt;h1&gt;</code></li>
267
+        <li><code>&lt;h2&gt;</code></li>
268
+        <li><code>&lt;h3&gt;</code></li>
269
+    </ul>
270
+</td>
271
+<td width="40%">
272
+    <ul>
273
+        <li><code>&lt;h4&gt;</code></li>
274
+        <li><code>&lt;h5&gt;</code></li>
275
+        <li><code>&lt;h6&gt;</code></li>
276
+        <li><code>&lt;i&gt;</code></li>
277
+        <li><code>&lt;img src="..."&gt;</code></li>
278
+        <li><code>&lt;p&gt;</code></li>
279
+        <li><code>&lt;small&gt;</code></li>
280
+        <li><code>&lt;strike&gt;</code></li>
281
+        <li><code>&lt;strong&gt;</code></li>
282
+        <li><code>&lt;sub&gt;</code></li>
283
+        <li><code>&lt;sup&gt;</code></li>
284
+        <li><code>&lt;tt&gt;</code></li>
285
+        <li><code>&lt;u&gt;</code></li>
286
+    </ul>
287
+</td>
288
+</table>
289
+
290
+### HTML and CSS on Windows
291
+HTML+CSS formatted body are not supported through the native API for Windows.
292
+
293
+
294
+## Contributing
295
+
296
+1. Fork it
297
+2. Create your feature branch (`git checkout -b my-new-feature`)
298
+3. Commit your changes (`git commit -am 'Add some feature'`)
299
+4. Push to the branch (`git push origin my-new-feature`)
300
+5. Create new Pull Request
301
+
302
+### Development
303
+
304
+### Testing
305
+
306
+Android and iOS Tooling setup, see 
307
+```
308
+export PLATFORM=android # or ios ..
309
+npm run clean && npm run setupDemoApp && npm run build
310
+```
311
+
312
+
313
+## License
314
+
315
+This software is released under the [Apache 2.0 License][apache2_license].
316
+
317
+© 2013-2016 appPlant UG, Inc. All rights reserved
318
+
319
+
320
+[cordova]: https://cordova.apache.org
321
+[ios_guide]: http://developer.apple.com/library/ios/documentation/MessageUI/Reference/MFMailComposeViewController_class/Reference/Reference.html
322
+[wp8_guide]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394003.aspx
323
+[CLI]: http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-line%20Interface
324
+[PGB]: http://docs.build.phonegap.com/en_US/index.html
325
+[npm]: https://www.npmjs.com/package/cordova-plugin-email-composer
326
+[messageui_framework]: #compile-error-on-ios
327
+[changelog]: https://github.com/katzer/cordova-plugin-email-composer/blob/master/CHANGELOG.md
328
+[available]: #determine-if-the-device-is-capable-to-send-emails
329
+[open]: #open-a-pre-filled-email-draft
330
+[email_app]: #specify-email-app
331
+[examples]: #examples
332
+[apache2_license]: http://opensource.org/licenses/Apache-2.0

+ 91
- 0
plugins/cordova-plugin-email/package.json View File

@@ -0,0 +1,91 @@
1
+{
2
+  "_from": "cordova-plugin-email",
3
+  "_id": "cordova-plugin-email@1.2.7",
4
+  "_inBundle": false,
5
+  "_integrity": "sha1-QZVfIGfxBLu6c2Fi5HbtfUFnBng=",
6
+  "_location": "/cordova-plugin-email",
7
+  "_phantomChildren": {},
8
+  "_requested": {
9
+    "type": "tag",
10
+    "registry": true,
11
+    "raw": "cordova-plugin-email",
12
+    "name": "cordova-plugin-email",
13
+    "escapedName": "cordova-plugin-email",
14
+    "rawSpec": "",
15
+    "saveSpec": null,
16
+    "fetchSpec": "latest"
17
+  },
18
+  "_requiredBy": [
19
+    "#USER",
20
+    "/"
21
+  ],
22
+  "_resolved": "https://registry.npmjs.org/cordova-plugin-email/-/cordova-plugin-email-1.2.7.tgz",
23
+  "_shasum": "41955f2067f104bbba736162e476ed7d41670678",
24
+  "_spec": "cordova-plugin-email",
25
+  "_where": "/home/rarce/Downloads/tutorial",
26
+  "author": {
27
+    "name": "Martin Reinhardt"
28
+  },
29
+  "bugs": {
30
+    "url": "https://github.com/hypery2k/cordova-email-plugin/issues"
31
+  },
32
+  "bundleDependencies": false,
33
+  "cordova": {
34
+    "id": "cordova-plugin-email",
35
+    "platforms": [
36
+      "android",
37
+      "amazon-fireos",
38
+      "ios",
39
+      "windows",
40
+      "wp8"
41
+    ]
42
+  },
43
+  "deprecated": false,
44
+  "description": "Cordova Email Plugin",
45
+  "devDependencies": {
46
+    "conventional-changelog-cli": "1.3.5",
47
+    "fs": "0.0.2",
48
+    "ink-docstrap": "1.3.2",
49
+    "jsdoc": "3.5.5",
50
+    "jsdoc-to-markdown": "3.0.3",
51
+    "karma": "2.0.0",
52
+    "karma-cordova-launcher": "0.0.10",
53
+    "xml2js": "0.4.19"
54
+  },
55
+  "homepage": "https://github.com/hypery2k/cordova-email-plugin#readme",
56
+  "keywords": [
57
+    "cordova",
58
+    "email",
59
+    "ecosystem:cordova",
60
+    "cordova-android",
61
+    "cordova-amazon-fireos",
62
+    "cordova-ios",
63
+    "cordova-wp8",
64
+    "cordova-windows"
65
+  ],
66
+  "license": "MIT",
67
+  "name": "cordova-plugin-email",
68
+  "repository": {
69
+    "type": "git",
70
+    "url": "git+https://github.com/hypery2k/cordova-email-plugin.git"
71
+  },
72
+  "scripts": {
73
+    "abc": "karma start karma.conf.js",
74
+    "build": "cd tmp/test-app && cordova platform add ${PLATFORM} && cordova plugin add ../.. && cordova build ${PLATFORM}",
75
+    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
76
+    "changelog:add": "git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md'",
77
+    "clean": "rm -rf tmp && mkdir -p tmp",
78
+    "e2e": "cd tmp/cordova-demo-app/ && cordova build ${PLATFORM} && cd ../..",
79
+    "posttest": "jsdoc -c jsdoc.json && jsdoc2md www/email_composer.js | sed s/emailComposerPlugin/cordova.plugins.email/g | sed s/EmailComposerPlugin/cordova.plugins.email/g > PLUGIN_USAGE.MD && cd tmp/test-app && cordova plugin rm cordova-plugin-email-tests",
80
+    "prepare": "npm run clean && npm run setupAndroidSDK && npm run setupDemoApp",
81
+    "release:major": "npm version major && npm run release:pre && git push origin && git push origin --tags && npm run release:post && npm publish",
82
+    "release:minor": "npm version minor && npm run release:pre && git push origin && git push origin --tags && npm run release:post && npm publish",
83
+    "release:patch": "npm version patch && npm run release:pre && git push origin && git push origin --tags && npm run release:post && npm publish",
84
+    "release:post": "node etc/release.js && git add plugin.xml && git commit -m 'Update plugin' && npm run changelog && npm run changelog:add",
85
+    "release:pre": "npm run clean",
86
+    "setupAndroidSDK": "cd tmp && git clone https://github.com/simpligility/maven-android-sdk-deployer.git && cd maven-android-sdk-deployer/ && mvn install -P 6.0 && ls ~/.m2/repository/android/android/",
87
+    "setupDemoApp": "cd tmp && git clone https://github.com/apache/cordova-app-hello-world.git && mkdir test-app && cp -rp cordova-app-hello-world/template_src/* test-app && cd test-app && cordova plugin add cordova-plugin-device && cordova plugin add cordova-plugin-compat",
88
+    "test": "cd tmp/test-app && node ../../etc/prepare_test_app.js config.xml && cordova platform add ${PLATFORM} && cordova plugin add http://git-wip-us.apache.org/repos/asf/cordova-plugin-test-framework.git && cordova plugin add ../../tests && cordova build ${PLATFORM}"
89
+  },
90
+  "version": "1.2.7"
91
+}

+ 83
- 0
plugins/cordova-plugin-email/plugin.xml View File

@@ -0,0 +1,83 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
+<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="cordova-plugin-email" version="1.2.7">
3
+  <name>EmailComposer</name>
4
+  <description>
5
+        Provides access to the standard interface that manages.
6
+        the editing and sending an email message
7
+    </description>
8
+  <author>Martin Reinhardt, Sebastián Katzer</author>
9
+  <license>Apache 2.0</license>
10
+  <keywords>cordova,email</keywords>
11
+  <repo>https://github.com/hypery2k/cordova-email-plugin.git</repo>
12
+  <issue>https://github.com/hypery2k/cordova-email-plugin/issues</issue>
13
+  <engines>
14
+    <engine name="cordova" version=">=4.0.0"/>
15
+  </engines>
16
+  <js-module src="www/email_composer.js" name="EmailComposer">
17
+    <clobbers target="cordova.plugins.email"/>
18
+    <clobbers target="plugin.email"/>
19
+  </js-module>
20
+  <platform name="ios">
21
+    <config-file target="config.xml" parent="/*">
22
+      <feature name="EmailComposer">
23
+        <param name="ios-package" value="APPEmailComposer"/>
24
+      </feature>
25
+    </config-file>
26
+    <config-file target="*-Info.plist" parent="LSApplicationQueriesSchemes">
27
+      <array>
28
+        <string>mailto</string>
29
+      </array>
30
+    </config-file>
31
+    <header-file src="src/ios/APPEmailComposer.h"/>
32
+    <header-file src="src/ios/APPEmailComposerImpl.h"/>
33
+    <source-file src="src/ios/APPEmailComposer.m"/>
34
+    <source-file src="src/ios/APPEmailComposerImpl.m"/>
35
+    <framework src="MessageUI.framework"/>
36
+    <framework src="MobileCoreServices.framework"/>
37
+  </platform>
38
+  <platform name="android">
39
+    <config-file target="res/xml/config.xml" parent="/*">
40
+      <feature name="EmailComposer">
41
+        <param name="android-package" value="de.martinreinhardt.cordova.plugins.email.EmailComposer"/>
42
+      </feature>
43
+    </config-file>
44
+    <source-file src="src/android/EmailComposer.java" target-dir="src/de/martinreinhardt/cordova/plugins/email"/>
45
+    <source-file src="src/android/EmailComposerImpl.java" target-dir="src/de/martinreinhardt/cordova/plugins/email"/>
46
+  </platform>
47
+  <platform name="amazon-fireos">
48
+    <config-file target="res/xml/config.xml" parent="/*">
49
+      <feature name="EmailComposer">
50
+        <param name="android-package" value="de.martinreinhardt.cordova.plugins.email.EmailComposer"/>
51
+      </feature>
52
+    </config-file>
53
+    <source-file src="src/android/EmailComposer.java" target-dir="src/de/martinreinhardt/cordova/plugins/email"/>
54
+    <source-file src="src/android/EmailComposerImpl.java" target-dir="src/de/martinreinhardt/cordova/plugins/email"/>
55
+  </platform>
56
+  <platform name="wp8">
57
+    <config-file target="config.xml" parent="/*">
58
+      <feature name="EmailComposer">
59
+        <param name="wp-package" value="EmailComposer"/>
60
+      </feature>
61
+    </config-file>
62
+    <source-file src="src/wp8/EmailComposer.cs"/>
63
+    <source-file src="src/wp8/Options.cs"/>
64
+  </platform>
65
+  <platform name="windows">
66
+    <js-module src="src/windows/EmailComposerProxy.js" name="EmailComposerProxy">
67
+      <runs/>
68
+    </js-module>
69
+    <js-module src="src/windows/EmailComposerProxyImpl.js" name="EmailComposerProxyImpl">
70
+      <runs/>
71
+    </js-module>
72
+  </platform>
73
+  <platform name="browser">
74
+    <config-file target="res/xml/config.xml" parent="/*">
75
+      <feature name="EmailComposer">
76
+        <param name="browser-package" value="de.martinreinhardt.cordova.plugins.email.EmailComposer"/>
77
+      </feature>
78
+    </config-file>
79
+    <js-module src="src/browser/EmailComposerProxy.js" name="EmailComposerProxy">
80
+      <runs/>
81
+    </js-module>
82
+  </platform>
83
+</plugin>

+ 177
- 0
plugins/cordova-plugin-email/src/android/EmailComposer.java View File

@@ -0,0 +1,177 @@
1
+/*
2
+    Copyright 2013-2016 appPlant UG
3
+
4
+    Licensed to the Apache Software Foundation (ASF) under one
5
+    or more contributor license agreements.  See the NOTICE file
6
+    distributed with this work for additional information
7
+    regarding copyright ownership.  The ASF licenses this file
8
+    to you under the Apache License, Version 2.0 (the
9
+    "License"); you may not use this file except in compliance
10
+    with the License.  You may obtain a copy of the License at
11
+
12
+     http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+    Unless required by applicable law or agreed to in writing,
15
+    software distributed under the License is distributed on an
16
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+    KIND, either express or implied.  See the License for the
18
+    specific language governing permissions and limitations
19
+    under the License.
20
+*/
21
+
22
+package de.martinreinhardt.cordova.plugins.email;
23
+
24
+import android.content.Context;
25
+import android.content.Intent;
26
+
27
+import org.apache.cordova.CallbackContext;
28
+import org.apache.cordova.CordovaInterface;
29
+import org.apache.cordova.CordovaPlugin;
30
+import org.apache.cordova.CordovaWebView;
31
+import org.apache.cordova.LOG;
32
+import org.apache.cordova.PluginResult;
33
+import org.json.JSONArray;
34
+import org.json.JSONException;
35
+import org.json.JSONObject;
36
+
37
+import java.util.ArrayList;
38
+import java.util.List;
39
+
40
+@SuppressWarnings("Convert2Diamond")
41
+public class EmailComposer extends CordovaPlugin {
42
+
43
+    /**
44
+     * The log tag for this plugin
45
+     */
46
+    static protected final String LOG_TAG = "EmailComposer";
47
+
48
+    // Implementation of the plugin.
49
+    private final EmailComposerImpl impl = new EmailComposerImpl();
50
+
51
+    // The callback context used when calling back into JavaScript
52
+    private CallbackContext command;
53
+
54
+    /**
55
+     * Delete externalCacheDirectory on appstart
56
+     *
57
+     * @param cordova Cordova-instance
58
+     * @param webView CordovaWebView-instance
59
+     */
60
+    @Override
61
+    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
62
+        super.initialize(cordova, webView);
63
+        impl.cleanupAttachmentFolder(getContext());
64
+    }
65
+
66
+    /**
67
+     * Executes the request.
68
+     * <p>
69
+     * This method is called from the WebView thread.
70
+     * To do a non-trivial amount of work, use:
71
+     * cordova.getThreadPool().execute(runnable);
72
+     * <p>
73
+     * To run on the UI thread, use:
74
+     * cordova.getActivity().runOnUiThread(runnable);
75
+     *
76
+     * @param action   The action to execute.
77
+     * @param args     The exec() arguments in JSON form.
78
+     * @param callback The callback context used when calling
79
+     *                 back into JavaScript.
80
+     * @return Whether the action was valid.
81
+     */
82
+    @Override
83
+    public boolean execute(String action, JSONArray args,
84
+                           CallbackContext callback) throws JSONException {
85
+
86
+        this.command = callback;
87
+
88
+        if ("open".equalsIgnoreCase(action)) {
89
+            open(args);
90
+            return true;
91
+        }
92
+
93
+        if ("isAvailable".equalsIgnoreCase(action)) {
94
+            isAvailable();
95
+            return true;
96
+        }
97
+
98
+        return false;
99
+    }
100
+
101
+    /**
102
+     * Returns the application context.
103
+     */
104
+    private Context getContext() {
105
+        return cordova.getActivity();
106
+    }
107
+
108
+    /**
109
+     * Tells if the device has the capability to send emails.
110
+     */
111
+    private void isAvailable() {
112
+        cordova.getThreadPool().execute(new Runnable() {
113
+            public void run() {
114
+                boolean[] available = impl.canSendMail(null, getContext());
115
+                List<PluginResult> messages = new ArrayList<PluginResult>();
116
+
117
+                messages.add(new PluginResult(PluginResult.Status.OK, available[0]));
118
+                messages.add(new PluginResult(PluginResult.Status.OK, available[1]));
119
+
120
+                PluginResult result = new PluginResult(
121
+                        PluginResult.Status.OK, messages);
122
+                if (available[0] && available[1]) {
123
+                    command.success("Can send emails");
124
+                } else {
125
+                    command.error("Can not send emails");
126
+                }
127
+            }
128
+        });
129
+    }
130
+
131
+    /**
132
+     * Sends an intent to the email app.
133
+     *
134
+     * @param args The email properties like subject or body
135
+     * @throws JSONException
136
+     */
137
+    private void open(JSONArray args) throws JSONException {
138
+        JSONObject props = args.getJSONObject(0);
139
+        String appId = props.optString("app");
140
+
141
+        if (!(impl.canSendMail(appId, getContext()))[0]) {
142
+            LOG.i(LOG_TAG, "No client or account found for.");
143
+            return;
144
+        }
145
+
146
+        Intent draft = impl.getDraftWithProperties(props, getContext());
147
+        String header = props.optString("chooserHeader", "Open with");
148
+
149
+        final Intent chooser = Intent.createChooser(draft, header);
150
+        final EmailComposer plugin = this;
151
+
152
+        cordova.getThreadPool().execute(new Runnable() {
153
+            public void run() {
154
+                cordova.startActivityForResult(plugin, chooser, 0);
155
+            }
156
+        });
157
+    }
158
+
159
+    /**
160
+     * Called when an activity you launched exits, giving you the reqCode you
161
+     * started it with, the resCode it returned, and any additional data from it.
162
+     *
163
+     * @param reqCode The request code originally supplied to startActivityForResult(),
164
+     *                allowing you to identify who this result came from.
165
+     * @param resCode The integer result code returned by the child activity
166
+     *                through its setResult().
167
+     * @param intent  An Intent, which can return result data to the caller
168
+     *                (various data can be attached to Intent "extras").
169
+     */
170
+    @Override
171
+    public void onActivityResult(int reqCode, int resCode, Intent intent) {
172
+        if (command != null) {
173
+            command.success();
174
+        }
175
+    }
176
+
177
+}

+ 681
- 0
plugins/cordova-plugin-email/src/android/EmailComposerImpl.java View File

@@ -0,0 +1,681 @@
1
+/*
2
+ * Copyright (c) 2014-2015 by appPlant UG. All rights reserved.
3
+ *
4
+ * @APPPLANT_LICENSE_HEADER_START@
5
+ *
6
+ * This file contains Original Code and/or Modifications of Original Code
7
+ * as defined in and that are subject to the Apache License
8
+ * Version 2.0 (the 'License'). You may not use this file except in
9
+ * compliance with the License. Please obtain a copy of the License at
10
+ * http://opensource.org/licenses/Apache-2.0/ and read it before using this
11
+ * file.
12
+ *
13
+ * The Original Code and all software distributed under the License are
14
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18
+ * Please see the License for the specific language governing rights and
19
+ * limitations under the License.
20
+ *
21
+ * @APPPLANT_LICENSE_HEADER_END@
22
+ */
23
+
24
+package de.martinreinhardt.cordova.plugins.email;
25
+
26
+import android.content.Context;
27
+import android.content.Intent;
28
+import android.content.pm.PackageManager;
29
+import android.content.pm.ResolveInfo;
30
+import android.content.res.AssetManager;
31
+import android.content.res.Resources;
32
+import android.media.MediaScannerConnection;
33
+import android.net.Uri;
34
+import android.text.Html;
35
+import android.util.Base64;
36
+import android.util.Log;
37
+
38
+import org.json.JSONArray;
39
+import org.json.JSONException;
40
+import org.json.JSONObject;
41
+
42
+import java.io.File;
43
+import java.io.FileOutputStream;
44
+import java.io.IOException;
45
+import java.io.InputStream;
46
+import java.io.OutputStream;
47
+import java.util.ArrayList;
48
+import java.util.List;
49
+import java.util.concurrent.atomic.AtomicReference;
50
+
51
+import static de.martinreinhardt.cordova.plugins.email.EmailComposer.LOG_TAG;
52
+
53
+/**
54
+ * Implements the interface methods of the plugin.
55
+ */
56
+public class EmailComposerImpl {
57
+
58
+    /**
59
+     * The default mailto: scheme.
60
+     */
61
+    static private final String MAILTO_SCHEME = "mailto:";
62
+
63
+    /**
64
+     * Path where to put tmp the attachments.
65
+     */
66
+    static private final String ATTACHMENT_FOLDER = "/email_composer";
67
+
68
+    /**
69
+     * Cleans the attachment folder.
70
+     *
71
+     * @param ctx
72
+     * The application context.
73
+     */
74
+    @SuppressWarnings("ResultOfMethodCallIgnored")
75
+    public void cleanupAttachmentFolder (Context ctx) {
76
+        try {
77
+            File dir = new File(ctx.getExternalCacheDir() + ATTACHMENT_FOLDER);
78
+
79
+            if (!dir.isDirectory())
80
+                return;
81
+
82
+            File[] files = dir.listFiles();
83
+
84
+            for (File file : files) { file.delete(); }
85
+        } catch (Exception npe){
86
+            Log.w(LOG_TAG, "Missing external cache dir");
87
+        }
88
+    }
89
+
90
+    /**
91
+     * Tells if the device has the capability to send emails.
92
+     *
93
+     * @param id
94
+     * The app id.
95
+     * @param ctx
96
+     * The application context.
97
+     */
98
+    public boolean[] canSendMail (String id, Context ctx) {
99
+        // is possible with specified app
100
+        boolean withScheme = isAppInstalled(id, ctx);
101
+        // is possible in general
102
+        boolean isPossible = isEmailClientExist(ctx);
103
+
104
+        return new boolean[] { isPossible, withScheme };
105
+    }
106
+
107
+    /**
108
+     * The intent with the containing email properties.
109
+     *
110
+     * @param params
111
+     * The email properties like subject or body
112
+     * @param ctx
113
+     * The context of the application.
114
+     * @return
115
+     * The resulting intent.
116
+     * @throws JSONException
117
+     */
118
+    public Intent getDraftWithProperties (JSONObject params, Context ctx)
119
+            throws JSONException {
120
+
121
+        Intent mail = getEmailIntent();
122
+        String app  = params.optString("app", MAILTO_SCHEME);
123
+
124
+        if (params.has("subject"))
125
+            setSubject(params.getString("subject"), mail);
126
+        if (params.has("body"))
127
+            setBody(params.getString("body"), params.optBoolean("isHtml"), mail);
128
+        if (params.has("to"))
129
+            setRecipients(params.getJSONArray("to"), mail);
130
+        if (params.has("cc"))
131
+            setCcRecipients(params.getJSONArray("cc"), mail);
132
+        if (params.has("bcc"))
133
+            setBccRecipients(params.getJSONArray("bcc"), mail);
134
+        if (params.has("attachments"))
135
+            setAttachments(params.getJSONArray("attachments"), mail, ctx);
136
+
137
+        if (!app.equals(MAILTO_SCHEME) && isAppInstalled(app, ctx)) {
138
+            mail.setPackage(app);
139
+        }
140
+
141
+        return mail;
142
+    }
143
+
144
+    /**
145
+     * Setter for the subject.
146
+     *
147
+     * @param subject
148
+     * The subject of the email.
149
+     * @param draft
150
+     * The intent to send.
151
+     */
152
+    private void setSubject (String subject, Intent draft) {
153
+        draft.putExtra(Intent.EXTRA_SUBJECT, subject);
154
+    }
155
+
156
+    /**
157
+     * Setter for the body.
158
+     *
159
+     * @param body
160
+     * The body of the email.
161
+     * @param isHTML
162
+     * Indicates the encoding (HTML or plain text).
163
+     * @param draft
164
+     * The intent to send.
165
+     */
166
+    private void setBody (String body, Boolean isHTML, Intent draft) {
167
+        CharSequence text = isHTML ? Html.fromHtml(body) : body;
168
+
169
+        draft.putExtra(Intent.EXTRA_TEXT, text);
170
+    }
171
+
172
+    /**
173
+     * Setter for the recipients.
174
+     *
175
+     * @param recipients
176
+     * List of email addresses.
177
+     * @param draft
178
+     * The intent to send.
179
+     * @throws JSONException
180
+     */
181
+    private void setRecipients (JSONArray recipients, Intent draft) throws JSONException {
182
+        String[] receivers = new String[recipients.length()];
183
+
184
+        for (int i = 0; i < recipients.length(); i++) {
185
+            receivers[i] = recipients.getString(i);
186
+        }
187
+
188
+        draft.putExtra(Intent.EXTRA_EMAIL, receivers);
189
+    }
190
+
191
+    /**
192
+     * Setter for the cc recipients.
193
+     *
194
+     * @param recipients
195
+     * List of email addresses.
196
+     * @param draft
197
+     * The intent to send.
198
+     * @throws JSONException
199
+     */
200
+    private void setCcRecipients (JSONArray recipients, Intent draft) throws JSONException {
201
+        String[] receivers = new String[recipients.length()];
202
+
203
+        for (int i = 0; i < recipients.length(); i++) {
204
+            receivers[i] = recipients.getString(i);
205
+        }
206
+
207
+        draft.putExtra(Intent.EXTRA_CC, receivers);
208
+    }
209
+
210
+    /**
211
+     * Setter for the bcc recipients.
212
+     *
213
+     * @param recipients
214
+     * List of email addresses.
215
+     * @param draft
216
+     * The intent to send.
217
+     * @throws JSONException
218
+     */
219
+    private void setBccRecipients (JSONArray recipients, Intent draft) throws JSONException {
220
+        String[] receivers = new String[recipients.length()];
221
+
222
+        for (int i = 0; i < recipients.length(); i++) {
223
+            receivers[i] = recipients.getString(i);
224
+        }
225
+
226
+        draft.putExtra(Intent.EXTRA_BCC, receivers);
227
+    }
228
+
229
+    /**
230
+     * Setter for the attachments.
231
+     *
232
+     * @param attachments
233
+     * List of URIs to attach.
234
+     * @param draft
235
+     * The intent to send.
236
+     * @param ctx
237
+     * The application context.
238
+     * @throws JSONException
239
+     */
240
+    private void setAttachments (JSONArray attachments, Intent draft,
241
+                                 Context ctx) throws JSONException {
242
+
243
+        ArrayList<Uri> uris = new ArrayList<Uri>();
244
+
245
+        for (int i = 0; i < attachments.length(); i++) {
246
+            Uri uri = getUriForPath(attachments.getString(i), ctx);
247
+
248
+            uris.add(uri);
249
+        }
250
+
251
+        if (uris.isEmpty())
252
+            return;
253
+
254
+        if (uris.size() == 1) {
255
+            draft.setAction(Intent.ACTION_SEND)
256
+                    .setType("message/rfc822")
257
+                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
258
+                    .putExtra(Intent.EXTRA_STREAM, uris.get(0));
259
+        } else {
260
+            draft.setAction(Intent.ACTION_SEND_MULTIPLE)
261
+                    .setType("message/rfc822")
262
+                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
263
+                    .putExtra(Intent.EXTRA_STREAM, uris);
264
+        }
265
+    }
266
+
267
+    /**
268
+     * The URI for an attachment path.
269
+     *
270
+     * @param path
271
+     * The given path to the attachment.
272
+     * @param ctx
273
+     * The application context.
274
+     * @return
275
+     * The URI pointing to the given path.
276
+     */
277
+    private Uri getUriForPath (String path, Context ctx) {
278
+        Uri result = null;
279
+        if (path.startsWith("res:")) {
280
+            result = getUriForResourcePath(path, ctx);
281
+        } else if (path.startsWith("file:///")) {
282
+            result = getUriForAbsolutePath(path);
283
+        } else if (path.startsWith("file://")) {
284
+            result = getUriForAssetPath(path, ctx);
285
+        } else if (path.startsWith("base64:")) {
286
+            result = getUriForBase64Content(path, ctx);
287
+        }
288
+
289
+        if (result == null) {
290
+            result = Uri.parse(path);
291
+        }
292
+
293
+        return getCorrespondingMediaFileUriIfPossible(result, ctx);
294
+    }
295
+
296
+    /**
297
+     * The URI for a file.
298
+     *
299
+     * @param path
300
+     * The given absolute path.
301
+     * @return
302
+     * The URI pointing to the given path.
303
+     */
304
+    private Uri getUriForAbsolutePath (String path) {
305
+        String absPath = path.replaceFirst("file://", "");
306
+        File file      = new File(absPath);
307
+
308
+        if (!file.exists()) {
309
+            Log.e(LOG_TAG, "File not found: " + file.getAbsolutePath());
310
+        }
311
+
312
+        return Uri.fromFile(file);
313
+    }
314
+
315
+    /**
316
+     * The URI for an asset.
317
+     *
318
+     * @param path
319
+     * The given asset path.
320
+     * @param ctx
321
+     * The application context.
322
+     * @return
323
+     * The URI pointing to the given path.
324
+     */
325
+    @SuppressWarnings("ResultOfMethodCallIgnored")
326
+    private Uri getUriForAssetPath (String path, Context ctx) {
327
+        String resPath  = path.replaceFirst("file:/", "www");
328
+        String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
329
+        File dir        = ctx.getExternalCacheDir();
330
+
331
+        if (dir == null) {
332
+            Log.e(LOG_TAG, "Missing external cache dir");
333
+            return Uri.EMPTY;
334
+        }
335
+
336
+        String storage  = dir.toString() + ATTACHMENT_FOLDER;
337
+        File file       = new File(storage, fileName);
338
+
339
+        new File(storage).mkdir();
340
+
341
+        FileOutputStream outStream = null;
342
+
343
+        try {
344
+            AssetManager assets = ctx.getAssets();
345
+
346
+            outStream = new FileOutputStream(file);
347
+            InputStream inputStream    = assets.open(resPath);
348
+
349
+            copyFile(inputStream, outStream);
350
+            outStream.flush();
351
+            outStream.close();
352
+        } catch (Exception e) {
353
+            Log.e(LOG_TAG, "File not found: assets/" + resPath);
354
+            e.printStackTrace();
355
+        } finally {
356
+            if (outStream != null) {
357
+                safeClose(outStream);
358
+            }
359
+        }
360
+
361
+        return Uri.fromFile(file);
362
+    }
363
+
364
+    /**
365
+     * The URI for a resource.
366
+     *
367
+     * @param path
368
+     * The given relative path.
369
+     * @param ctx
370
+     * The application context.
371
+     * @return
372
+     * The URI pointing to the given path
373
+     */
374
+    @SuppressWarnings("ResultOfMethodCallIgnored")
375
+    private Uri getUriForResourcePath (String path, Context ctx) {
376
+        String resPath   = path.replaceFirst("res://", "");
377
+        String fileName  = resPath.substring(resPath.lastIndexOf('/') + 1);
378
+        String resName   = fileName.substring(0, fileName.lastIndexOf('.'));
379
+        String extension = resPath.substring(resPath.lastIndexOf('.'));
380
+        File dir         = ctx.getExternalCacheDir();
381
+
382
+        if (dir == null) {
383
+            Log.e(LOG_TAG, "Missing external cache dir");
384
+            return Uri.EMPTY;
385
+        }
386
+
387
+        String storage   = dir.toString() + ATTACHMENT_FOLDER;
388
+        int resId        = getResId(resPath, ctx);
389
+        File file        = new File(storage, resName + extension);
390
+
391
+        if (resId == 0) {
392
+            Log.e(LOG_TAG, "File not found: " + resPath);
393
+        }
394
+
395
+        new File(storage).mkdir();
396
+
397
+        FileOutputStream outStream = null;
398
+
399
+        try {
400
+            Resources res = ctx.getResources();
401
+            outStream = new FileOutputStream(file);
402
+            InputStream inputStream    = res.openRawResource(resId);
403
+
404
+            copyFile(inputStream, outStream);
405
+            outStream.flush();
406
+            outStream.close();
407
+        } catch (Exception e) {
408
+            e.printStackTrace();
409
+        } finally {
410
+            if (outStream != null) {
411
+                safeClose(outStream);
412
+            }
413
+        }
414
+
415
+        return Uri.fromFile(file);
416
+    }
417
+
418
+    /**
419
+     * The URI for a base64 encoded content.
420
+     *
421
+     * @param content
422
+     * The given base64 encoded content.
423
+     * @param ctx
424
+     * The application context.
425
+     * @return
426
+     * The URI including the given content.
427
+     */
428
+    @SuppressWarnings("ResultOfMethodCallIgnored")
429
+    private Uri getUriForBase64Content (String content, Context ctx) {
430
+        String resName = content.substring(content.indexOf(":") + 1, content.indexOf("//"));
431
+        String resData = content.substring(content.indexOf("//") + 2);
432
+        File dir       = ctx.getExternalCacheDir();
433
+        byte[] bytes;
434
+
435
+        try {
436
+            bytes = Base64.decode(resData, 0);
437
+        } catch (Exception ignored) {
438
+            Log.e(LOG_TAG, "Invalid Base64 string");
439
+            return Uri.EMPTY;
440
+        }
441
+
442
+        if (dir == null) {
443
+            Log.e(LOG_TAG, "Missing external cache dir");
444
+            return Uri.EMPTY;
445
+        }
446
+
447
+        String storage = dir.toString() + ATTACHMENT_FOLDER;
448
+        File file      = new File(storage, resName);
449
+
450
+        new File(storage).mkdir();
451
+
452
+        FileOutputStream outStream = null;
453
+
454
+        try {
455
+            outStream = new FileOutputStream(file);
456
+
457
+            outStream.write(bytes);
458
+            outStream.flush();
459
+            outStream.close();
460
+        } catch (Exception e) {
461
+            e.printStackTrace();
462
+        } finally {
463
+            if (outStream != null) {
464
+                safeClose(outStream);
465
+            }
466
+        }
467
+
468
+        return Uri.fromFile(file);
469
+    }
470
+
471
+    /**
472
+     * Get corresponding Media File URI for a givin URI
473
+     * if available, otherwise it returns the same input URI.
474
+     *
475
+     * NOTE: Becuase MediaScannerConnection works in callback
476
+     * fashion, we instead wait for its result using a timing-out
477
+     * while loop, and we might further think for a better solution.
478
+     *
479
+     * @param uri
480
+     * The given uri.
481
+     * @param ctx
482
+     * The application context.
483
+     * @return
484
+     * The URI pointing to the corresponding Media File if available,
485
+     * otherwise it returns the same given uri.
486
+     */
487
+    private Uri getCorrespondingMediaFileUriIfPossible(Uri uri, Context ctx) {
488
+        return getCorrespondingMediaFileUriIfPossible(uri.toString(), ctx);
489
+    }
490
+
491
+    /**
492
+     * Get corresponding Media File URI for a givin path String
493
+     * if available, otherwise it returns the same input URI.
494
+     *
495
+     * NOTE: Becuase MediaScannerConnection works in callback
496
+     * fashion, we instead wait for its result using a timing-out
497
+     * while loop, and we might further think for a better solution.
498
+     *
499
+     * @param path
500
+     * The given path.
501
+     * @param ctx
502
+     * The application context.
503
+     * @return
504
+     * The URI pointing to the corresponding Media File if available,
505
+     * otherwise it returns the same given path.
506
+     */
507
+    private Uri getCorrespondingMediaFileUriIfPossible(String path, Context ctx) {
508
+        final AtomicReference<Uri> result = new AtomicReference<Uri>();
509
+        MediaScannerConnection.scanFile(ctx, new String[]{path}, null, new MediaScannerConnection.OnScanCompletedListener() {
510
+            @Override
511
+            public void onScanCompleted(String path, Uri uri) {
512
+                if (uri != null) {
513
+                    result.set(uri);
514
+                } else {
515
+                    result.set(Uri.parse(path));
516
+                }
517
+            }
518
+        });
519
+
520
+        // Wait until media scanner scans path and gets
521
+        // its corresponding content:// uri
522
+        long startTime = System.currentTimeMillis();
523
+        long maxWait = 5 * 1000; // 5 secs
524
+        while (result.get() == null && System.currentTimeMillis() - startTime < maxWait) {
525
+            try {
526
+                Thread.sleep(100);
527
+            } catch (Exception e) {
528
+                // ignore
529
+            }
530
+        }
531
+
532
+        return result.get() != null? result.get() : Uri.parse(path);
533
+    }
534
+
535
+    /**
536
+     * Writes an InputStream to an OutputStream
537
+     *
538
+     * @param in
539
+     * The input stream.
540
+     * @param out
541
+     * The output stream.
542
+     */
543
+    private void copyFile (InputStream in, OutputStream out) throws IOException {
544
+        byte[] buffer = new byte[1024];
545
+        int read;
546
+
547
+        while ((read = in.read(buffer)) != -1) {
548
+            out.write(buffer, 0, read);
549
+        }
550
+    }
551
+
552
+    /**
553
+     * Returns the resource ID for the given resource path.
554
+     *
555
+     * @param ctx
556
+     * The application context.
557
+     * @return
558
+     * The resource ID for the given resource.
559
+     */
560
+    private int getResId (String resPath, Context ctx) {
561
+        Resources res = ctx.getResources();
562
+        int resId;
563
+
564
+        String pkgName  = ctx.getPackageName();
565
+        String dirName  = "drawable";
566
+        String fileName = resPath;
567
+
568
+        if (resPath.contains("/")) {
569
+            dirName  = resPath.substring(0, resPath.lastIndexOf('/'));
570
+            fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
571
+        }
572
+
573
+        String resName = fileName.substring(0, fileName.lastIndexOf('.'));
574
+
575
+        resId = res.getIdentifier(resName, dirName, pkgName);
576
+
577
+        if (resId == 0) {
578
+            resId = res.getIdentifier(resName, "drawable", pkgName);
579
+        }
580
+
581
+        return resId;
582
+    }
583
+
584
+    /**
585
+     * If email apps are available.
586
+     *
587
+     * @param ctx
588
+     * The application context.
589
+     * @return
590
+     * true if available, otherwise false
591
+     */
592
+    private boolean isEmailClientExist (Context ctx) {
593
+        Log.i(LOG_TAG, "isEmailClientExist()");
594
+
595
+        return getAppsCountHandlesIntent(ctx, getEmailIntent()) > 0;
596
+    }
597
+
598
+    /**
599
+     * Get apps count which are able to handle specific Intent.
600
+     *
601
+     * @param ctx
602
+     * The application context.
603
+     * @param intent
604
+     * The intent to test against.
605
+     * @return
606
+     * The apps count
607
+     */
608
+    private int getAppsCountHandlesIntent(Context ctx, Intent intent) {
609
+        if (ctx == null || intent == null) return 0;
610
+
611
+        PackageManager manager = ctx.getPackageManager();
612
+        List<ResolveInfo> infos = manager.queryIntentActivities(intent, 0);
613
+
614
+        return infos.size();
615
+    }
616
+
617
+    /**
618
+     * Ask the package manager if the app is installed on the device.
619
+     *
620
+     * @param id
621
+     * The app id.
622
+     * @param ctx
623
+     * The application context.
624
+     * @return
625
+     * true if yes otherwise false.
626
+     */
627
+    private boolean isAppInstalled (String id, Context ctx) {
628
+
629
+        if (id == null || id.equalsIgnoreCase(MAILTO_SCHEME)) {
630
+            Intent intent     = getEmailIntent();
631
+            PackageManager pm = ctx.getPackageManager();
632
+            int apps          = pm.queryIntentActivities(intent, 0).size();
633
+
634
+            return (apps > 0);
635
+        }
636
+
637
+        try {
638
+            ctx.getPackageManager().getPackageInfo(id, 0);
639
+            return true;
640
+        } catch (PackageManager.NameNotFoundException e) {
641
+            return false;
642
+        }
643
+    }
644
+
645
+    /**
646
+     * Setup an intent to send to email apps only.
647
+     *
648
+     * @return intent
649
+     */
650
+    private static Intent getEmailIntent() {
651
+        Intent intent = new Intent(Intent.ACTION_SENDTO,
652
+                Uri.parse(MAILTO_SCHEME));
653
+
654
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
655
+
656
+        return intent;
657
+    }
658
+
659
+    /**
660
+     * Attempt to safely close the given stream.
661
+     *
662
+     * @param outStream
663
+     * The stream to close.
664
+     * @return
665
+     * true if successful, false otherwise
666
+     */
667
+    private static boolean safeClose (final FileOutputStream outStream) {
668
+
669
+        if (outStream != null) {
670
+            try {
671
+                outStream.close();
672
+                return true;
673
+            } catch (IOException e) {
674
+                Log.e(LOG_TAG, "Error attempting to safely close resource: " + e.getMessage());
675
+            }
676
+        }
677
+
678
+        return false;
679
+    }
680
+
681
+}

+ 76
- 0
plugins/cordova-plugin-email/src/browser/EmailComposerProxy.js View File

@@ -0,0 +1,76 @@
1
+/*
2
+    Copyright 2013-2016 appPlant UG
3
+
4
+    Licensed to the Apache Software Foundation (ASF) under one
5
+    or more contributor license agreements.  See the NOTICE file
6
+    distributed with this work for additional information
7
+    regarding copyright ownership.  The ASF licenses this file
8
+    to you under the Apache License, Version 2.0 (the
9
+    "License"); you may not use this file except in compliance
10
+    with the License.  You may obtain a copy of the License at
11
+
12
+     http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+    Unless required by applicable law or agreed to in writing,
15
+    software distributed under the License is distributed on an
16
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+    KIND, either express or implied.  See the License for the
18
+    specific language governing permissions and limitations
19
+    under the License.
20
+*/
21
+
22
+/**
23
+ * Verifies if sending emails is supported on the device.
24
+ *
25
+ * @param {Function} success
26
+ *      Success callback function
27
+ * @param {Function} error
28
+ *      Error callback function
29
+ * @param {Array} args
30
+ *      Interface arguments
31
+ */
32
+exports.isAvailable = function (success, error, args) {
33
+    success(true,false);
34
+};
35
+
36
+/**
37
+ * Displays the email composer pre-filled with data.
38
+ *
39
+ * @param {Function} success
40
+ *      Success callback function
41
+ * @param {Function} error
42
+ *      Error callback function
43
+ * @param {Array} args
44
+ *      Interface arguments
45
+ */
46
+exports.open = function (success, error, args) {
47
+    var props   = args[0],
48
+        mailto  = 'mailto:' + props.to,
49
+        options = '';
50
+
51
+    if (props.subject !== '') {
52
+        options = options + '&subject=' + props.subject;
53
+    }
54
+
55
+    if (props.body !== '') {
56
+        options = options + '&body=' + props.body;
57
+    }
58
+
59
+    if (props.cc !== '') {
60
+        options = options + '&cc=' + props.cc;
61
+    }
62
+
63
+    if (props.bcc !== '') {
64
+        options = options + '&bcc=' + props.bcc;
65
+    }
66
+
67
+    if (options !== '') {
68
+        mailto = mailto + '?' + options.substring(1);
69
+    }
70
+
71
+    window.location.href = mailto;
72
+
73
+    success();
74
+};
75
+
76
+require('cordova/exec/proxy').add('EmailComposer', exports);

+ 33
- 0
plugins/cordova-plugin-email/src/ios/APPEmailComposer.h View File

@@ -0,0 +1,33 @@
1
+/*
2
+ Copyright 2013-2016 appPlant UG
3
+
4
+ Licensed to the Apache Software Foundation (ASF) under one
5
+ or more contributor license agreements.  See the NOTICE file
6
+ distributed with this work for additional information
7
+ regarding copyright ownership.  The ASF licenses this file
8
+ to you under the Apache License, Version 2.0 (the
9
+ "License"); you may not use this file except in compliance
10
+ with the License.  You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ Unless required by applicable law or agreed to in writing,
15
+ software distributed under the License is distributed on an
16
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ KIND, either express or implied.  See the License for the
18
+ specific language governing permissions and limitations
19
+ under the License.
20
+ */
21
+
22
+#import <Foundation/Foundation.h>
23
+#import <MessageUI/MFMailComposeViewController.h>
24
+#import <Cordova/CDVPlugin.h>
25
+
26
+@interface APPEmailComposer : CDVPlugin <MFMailComposeViewControllerDelegate>
27
+
28
+// Shows the email composer view with pre-filled data
29
+- (void) open:(CDVInvokedUrlCommand*)command;
30
+// Checks if the mail composer is able to send mails
31
+- (void) isAvailable:(CDVInvokedUrlCommand*)command;
32
+
33
+@end

+ 205
- 0
plugins/cordova-plugin-email/src/ios/APPEmailComposer.m View File

@@ -0,0 +1,205 @@
1
+/*
2
+ Copyright 2013-2014 appPlant UG
3
+
4
+ Licensed to the Apache Software Foundation (ASF) under one
5
+ or more contributor license agreements.  See the NOTICE file
6
+ distributed with this work for additional information
7
+ regarding copyright ownership.  The ASF licenses this file
8
+ to you under the Apache License, Version 2.0 (the
9
+ "License"); you may not use this file except in compliance
10
+ with the License.  You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ Unless required by applicable law or agreed to in writing,
15
+ software distributed under the License is distributed on an
16
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ KIND, either express or implied.  See the License for the
18
+ specific language governing permissions and limitations
19
+ under the License.
20
+ */
21
+
22
+#import "APPEmailComposer.h"
23
+#import "APPEmailComposerImpl.h"
24
+#import <Cordova/CDVAvailability.h>
25
+#ifndef __CORDOVA_4_0_0
26
+    #import <Cordova/NSData+Base64.h>
27
+#endif
28
+#import <MessageUI/MFMailComposeViewController.h>
29
+#import <MobileCoreServices/MobileCoreServices.h>
30
+
31
+#include "TargetConditionals.h"
32
+
33
+@interface APPEmailComposer ()
34
+
35
+@property (nonatomic, retain) CDVInvokedUrlCommand* command;
36
+
37
+/**
38
+ * Implements the plugin functionality.
39
+ */
40
+@property (nonatomic, retain) APPEmailComposerImpl* impl;
41
+
42
+@end
43
+
44
+@implementation APPEmailComposer
45
+
46
+#pragma mark -
47
+#pragma mark Lifecycle
48
+
49
+- (void)pluginInitialize
50
+{
51
+    _impl = [[APPEmailComposerImpl alloc] init];
52
+}
53
+
54
+#pragma mark -
55
+#pragma mark Public
56
+
57
+/**
58
+ * Checks if the mail composer is able to send mails.
59
+ *
60
+ * @param callbackId
61
+ *      The ID of the JS function to be called with the result
62
+ */
63
+- (void) isAvailable:(CDVInvokedUrlCommand*)command
64
+{
65
+    [self.commandDelegate runInBackground:^{
66
+        NSString* scheme = @"mailto";
67
+        if (!command.arguments || command.arguments.count >= 1){
68
+          scheme =  command.arguments[0];
69
+        }
70
+        NSArray* boolArray = [_impl canSendMail:scheme];
71
+        CDVPluginResult* result;
72
+
73
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
74
+                                     messageAsMultipart:boolArray];
75
+
76
+        [self.commandDelegate sendPluginResult:result
77
+                                    callbackId:command.callbackId];
78
+    }];
79
+}
80
+
81
+/**
82
+ * Shows the email composer view with pre-filled data.
83
+ *
84
+ * @param properties
85
+ *      The email properties like subject, body, attachments
86
+ */
87
+- (void) open:(CDVInvokedUrlCommand*)command
88
+{
89
+    NSDictionary* props = command.arguments[0];
90
+
91
+    _command = command;
92
+
93
+    [self.commandDelegate runInBackground:^{
94
+        if (![props objectForKey:@"app"]) {
95
+            [props setValue:@"mailto" forKey:@"app"];
96
+        }
97
+        NSString* scheme = [props objectForKey:@"app"];
98
+
99
+        if (![self canUseAppleMail:scheme]) {
100
+            [self openURLFromProperties:props];
101
+            return;
102
+        }
103
+
104
+        if (TARGET_IPHONE_SIMULATOR) {
105
+            [self informAboutIssueWithSimulators];
106
+            [self execCallback];
107
+        }
108
+        else {
109
+            [self presentMailComposerFromProperties:props];
110
+        }
111
+    }];
112
+}
113
+
114
+#pragma mark -
115
+#pragma mark MFMailComposeViewControllerDelegate
116
+
117
+/**
118
+ * Delegate will be called after the mail composer did finish an action
119
+ * to dismiss the view.
120
+ */
121
+- (void) mailComposeController:(MFMailComposeViewController*)controller
122
+           didFinishWithResult:(MFMailComposeResult)result
123
+                         error:(NSError*)error
124
+{
125
+    [controller dismissViewControllerAnimated:YES completion:NULL];
126
+
127
+    [self execCallback];
128
+}
129
+
130
+#pragma mark -
131
+#pragma mark Private
132
+
133
+/**
134
+ * Displays the email draft.
135
+ *
136
+ * @param draft
137
+ *      The email composer view
138
+ */
139
+- (void) presentMailComposerFromProperties:(NSDictionary*)props
140
+{
141
+    dispatch_async(dispatch_get_main_queue(), ^{
142
+        MFMailComposeViewController* draft =
143
+        [_impl mailComposerFromProperties:props delegateTo:self];
144
+
145
+        [self.viewController presentViewController:draft
146
+                                          animated:YES
147
+                                        completion:NULL];
148
+    });
149
+
150
+}
151
+
152
+/**
153
+ * Instructs the application to open the specified URL.
154
+ *
155
+ * @param url
156
+ * A mailto: compatible URL.
157
+ */
158
+- (void) openURLFromProperties:(NSDictionary*)props
159
+{
160
+    NSURL* url = [_impl urlFromProperties:props];
161
+
162
+    [[UIApplication sharedApplication] openURL:url];
163
+}
164
+
165
+/**
166
+ * If the specified app if the buil-in iMail framework can be used.
167
+ *
168
+ * @param scheme
169
+ * An URL scheme.
170
+ * @return
171
+ * true if the scheme does refer to the email: scheme.
172
+ */
173
+- (BOOL) canUseAppleMail:(NSString*) scheme
174
+{
175
+    return [MFMailComposeViewController canSendMail] && [scheme hasPrefix:@"mailto"];
176
+}
177
+
178
+/**
179
+ * Presents a dialog to the user to inform him about an issue with the iOS8
180
+ * simulator in combination with the mail library.
181
+ */
182
+- (void) informAboutIssueWithSimulators
183
+{
184
+    dispatch_async(dispatch_get_main_queue(), ^{
185
+        [[[UIAlertView alloc] initWithTitle:@"Email-Composer"
186
+                                    message:@"Please use a physical device."
187
+                                   delegate:NULL
188
+                          cancelButtonTitle:@"OK"
189
+                          otherButtonTitles:NULL] show];
190
+    });
191
+}
192
+
193
+/**
194
+ * Invokes the callback without any parameter.
195
+ */
196
+- (void) execCallback
197
+{
198
+    CDVPluginResult *result = [CDVPluginResult
199
+                               resultWithStatus:CDVCommandStatus_OK];
200
+
201
+    [self.commandDelegate sendPluginResult:result
202
+                                callbackId:_command.callbackId];
203
+}
204
+
205
+@end

+ 58
- 0
plugins/cordova-plugin-email/src/ios/APPEmailComposerImpl.h View File

@@ -0,0 +1,58 @@
1
+/*
2
+ Copyright 2013-2016 appPlant UG
3
+
4
+ Licensed to the Apache Software Foundation (ASF) under one
5
+ or more contributor license agreements.  See the NOTICE file
6
+ distributed with this work for additional information
7
+ regarding copyright ownership.  The ASF licenses this file
8
+ to you under the Apache License, Version 2.0 (the
9
+ "License"); you may not use this file except in compliance
10
+ with the License.  You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ Unless required by applicable law or agreed to in writing,
15
+ software distributed under the License is distributed on an
16
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ KIND, either express or implied.  See the License for the
18
+ specific language governing permissions and limitations
19
+ under the License.
20
+ */
21
+
22
+#import <MessageUI/MFMailComposeViewController.h>
23
+
24
+@interface APPEmailComposerImpl : NSObject
25
+
26
+/**
27
+ * Checks if the mail composer is able to send mails and if an app is available
28
+ * to handle the specified scheme.
29
+ *
30
+ * @param scheme
31
+ * An URL scheme, that defaults to 'mailto:
32
+ */
33
+- (NSArray*) canSendMail:(NSString*)scheme;
34
+
35
+/**
36
+ * Instantiates an email composer view.
37
+ *
38
+ * @param properties
39
+ * The email properties like subject, body, attachments
40
+ * @param delegateTo
41
+ * The mail composition view controller’s delegate.
42
+ * @return
43
+ * The configured email composer view
44
+ */
45
+- (MFMailComposeViewController*) mailComposerFromProperties:(NSDictionary*)props
46
+                                                 delegateTo:(id)receiver;
47
+
48
+/**
49
+ * Creates an mailto-url-sheme.
50
+ *
51
+ * @param properties
52
+ * The email properties like subject, body, attachments
53
+ * @return
54
+ * The configured mailto-sheme
55
+ */
56
+- (NSURL*) urlFromProperties:(NSDictionary*)props;
57
+
58
+@end

+ 483
- 0
plugins/cordova-plugin-email/src/ios/APPEmailComposerImpl.m View File

@@ -0,0 +1,483 @@
1
+/*
2
+ Copyright 2013-2016 appPlant UG
3
+
4
+ Licensed to the Apache Software Foundation (ASF) under one
5
+ or more contributor license agreements.  See the NOTICE file
6
+ distributed with this work for additional information
7
+ regarding copyright ownership.  The ASF licenses this file
8
+ to you under the Apache License, Version 2.0 (the
9
+ "License"); you may not use this file except in compliance
10
+ with the License.  You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ Unless required by applicable law or agreed to in writing,
15
+ software distributed under the License is distributed on an
16
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ KIND, either express or implied.  See the License for the
18
+ specific language governing permissions and limitations
19
+ under the License.
20
+ */
21
+
22
+#import "APPEmailComposerImpl.h"
23
+#import <Cordova/CDVAvailability.h>
24
+#ifndef __CORDOVA_4_0_0
25
+    #import <Cordova/NSData+Base64.h>
26
+#endif
27
+#import <MessageUI/MFMailComposeViewController.h>
28
+#import <MobileCoreServices/MobileCoreServices.h>
29
+
30
+#include "TargetConditionals.h"
31
+
32
+/**
33
+ * Implements the interface methods of the plugin.
34
+ */
35
+@implementation APPEmailComposerImpl
36
+
37
+#pragma mark -
38
+#pragma mark Public
39
+
40
+/**
41
+ * Checks if the mail composer is able to send mails and if an app is available
42
+ * to handle the specified scheme.
43
+ *
44
+ * @param scheme
45
+ * An URL scheme, that defaults to 'mailto:
46
+ */
47
+- (NSArray*) canSendMail:(NSString*)scheme
48
+{
49
+    bool canSendMail = [MFMailComposeViewController canSendMail];
50
+    bool withScheme  = false;
51
+    
52
+    if (![scheme hasSuffix:@":"]) {
53
+        scheme = [scheme stringByAppendingString:@":"];
54
+    }
55
+    
56
+    scheme = [[scheme stringByAppendingString:@"test@test.de"]
57
+                stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
58
+
59
+    NSURL *url = [[NSURL URLWithString:scheme]
60
+                    absoluteURL];
61
+
62
+    withScheme = [[UIApplication sharedApplication]
63
+                   canOpenURL:url];
64
+
65
+    if (TARGET_IPHONE_SIMULATOR && [scheme hasPrefix:@"mailto:"]) {
66
+        canSendMail = withScheme = true;
67
+    }
68
+
69
+    NSArray* resultArray = [NSArray arrayWithObjects:@(canSendMail),@(withScheme), nil];
70
+
71
+    return resultArray;
72
+}
73
+
74
+/**
75
+ * Instantiates an email composer view.
76
+ *
77
+ * @param properties
78
+ * The email properties like subject, body, attachments
79
+ * @param delegateTo
80
+ * The mail composition view controller’s delegate.
81
+ * @return
82
+ * The configured email composer view
83
+ */
84
+- (MFMailComposeViewController*) mailComposerFromProperties:(NSDictionary*)props
85
+                                                 delegateTo:(id)receiver
86
+{
87
+    BOOL isHTML = [[props objectForKey:@"isHtml"] boolValue];
88
+
89
+    MFMailComposeViewController* draft;
90
+
91
+    draft = [[MFMailComposeViewController alloc] init];
92
+
93
+    // Subject
94
+    [self setSubject:[props objectForKey:@"subject"] ofDraft:draft];
95
+    // Body (as HTML)
96
+    [self setBody:[props objectForKey:@"body"] ofDraft:draft isHTML:isHTML];
97
+    // Recipients
98
+    [self setToRecipients:[props objectForKey:@"to"] ofDraft:draft];
99
+    // CC Recipients
100
+    [self setCcRecipients:[props objectForKey:@"cc"] ofDraft:draft];
101
+    // BCC Recipients
102
+    [self setBccRecipients:[props objectForKey:@"bcc"] ofDraft:draft];
103
+    // Attachments
104
+    [self setAttachments:[props objectForKey:@"attachments"] ofDraft:draft];
105
+
106
+    draft.mailComposeDelegate = receiver;
107
+
108
+    return draft;
109
+}
110
+
111
+/**
112
+ * Creates an mailto-url-sheme.
113
+ *
114
+ * @param properties
115
+ * The email properties like subject, body, attachments
116
+ * @return
117
+ * The configured mailto-sheme
118
+ */
119
+- (NSURL*) urlFromProperties:(NSDictionary*)props
120
+{
121
+    NSString* mailto     = [props objectForKey:@"app"];
122
+    NSString* query      = @"";
123
+    
124
+    BOOL isHTML = [[props objectForKey:@"isHtml"] boolValue];
125
+
126
+    NSString* subject    = [props objectForKey:@"subject"];
127
+    NSString* body       = [props objectForKey:@"body"];
128
+    NSArray* to          = [props objectForKey:@"to"];
129
+    NSArray* cc          = [props objectForKey:@"cc"];
130
+    NSArray* bcc         = [props objectForKey:@"bcc"];
131
+
132
+    NSArray* attachments = [props objectForKey:@"attachments"];
133
+
134
+    if (![mailto hasSuffix:@":"]) {
135
+        mailto = [mailto stringByAppendingString:@":"];
136
+    }
137
+
138
+    mailto = [mailto stringByAppendingString:
139
+              [to componentsJoinedByString:@","]];
140
+
141
+    if (body.length > 0) {
142
+        if (isHTML) {
143
+            body = [[NSAttributedString alloc] initWithData:[body dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} documentAttributes:nil error:nil].string;
144
+        }
145
+        query = [NSString stringWithFormat: @"%@%@body=%@",
146
+                   query, query.length > 0? @"&":@"", [body stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
147
+    }
148
+    if (subject.length > 0) {
149
+        query = [NSString stringWithFormat: @"%@%@subject=%@",
150
+                   query, query.length > 0? @"&":@"", [subject stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
151
+    }
152
+
153
+    if (cc.count > 0) {
154
+        query = [NSString stringWithFormat: @"%@%@cc=%@",
155
+                   query, query.length > 0? @"&":@"", [cc componentsJoinedByString:@","]];
156
+    }
157
+
158
+    if (bcc.count > 0) {
159
+        query = [NSString stringWithFormat: @"%@%@bcc=%@",
160
+                   query, query.length > 0? @"&":@"", [bcc componentsJoinedByString:@","]];
161
+    }
162
+
163
+    if (attachments.count > 0) {
164
+        NSLog(@"The 'mailto' URI Scheme (RFC 2368) does not support attachments.");
165
+    }
166
+
167
+    if (query.length > 0) {
168
+        query = [@"?" stringByAppendingString:query];
169
+    }
170
+
171
+    mailto = [mailto stringByAppendingString:query];
172
+
173
+    return [[NSURL URLWithString:mailto] absoluteURL];
174
+}
175
+
176
+#pragma mark -
177
+#pragma mark Private
178
+
179
+/**
180
+ * Sets the subject of the email draft.
181
+ *
182
+ * @param subject
183
+ * The subject of the email.
184
+ * @param draft
185
+ * The email composer view.
186
+ */
187
+- (void) setSubject:(NSString*)subject
188
+            ofDraft:(MFMailComposeViewController*)draft
189
+{
190
+    [draft setSubject:subject];
191
+}
192
+
193
+/**
194
+ * Sets the body of the email draft.
195
+ *
196
+ * @param body
197
+ * The body of the email.
198
+ * @param isHTML
199
+ * Indicates if the body is an HTML encoded string.
200
+ * @param draft
201
+ * The email composer view.
202
+ */
203
+- (void) setBody:(NSString*)body ofDraft:(MFMailComposeViewController*)draft
204
+          isHTML:(BOOL)isHTML
205
+{
206
+    [draft setMessageBody:body isHTML:isHTML];
207
+}
208
+
209
+/**
210
+ * Sets the recipients of the email draft.
211
+ *
212
+ * @param recipients
213
+ * The recipients of the email.
214
+ * @param draft
215
+ * The email composer view.
216
+ */
217
+- (void) setToRecipients:(NSArray*)recipients
218
+                 ofDraft:(MFMailComposeViewController*)draft
219
+{
220
+    [draft setToRecipients:recipients];
221
+}
222
+
223
+/**
224
+ * Sets the CC recipients of the email draft.
225
+ *
226
+ * @param ccRecipients
227
+ * The CC recipients of the email.
228
+ * @param draft
229
+ * The email composer view.
230
+ */
231
+- (void) setCcRecipients:(NSArray*)ccRecipients
232
+                 ofDraft:(MFMailComposeViewController*)draft
233
+{
234
+    [draft setCcRecipients:ccRecipients];
235
+}
236
+
237
+/**
238
+ * Sets the BCC recipients of the email draft.
239
+ *
240
+ * @param bccRecipients
241
+ * The BCC recipients of the email.
242
+ * @param draft
243
+ * The email composer view.
244
+ */
245
+- (void) setBccRecipients:(NSArray*)bccRecipients
246
+                  ofDraft:(MFMailComposeViewController*)draft
247
+{
248
+    [draft setBccRecipients:bccRecipients];
249
+}
250
+
251
+/**
252
+ * Sets the attachments of the email draft.
253
+ *
254
+ * @param attachments
255
+ * The attachments of the email.
256
+ * @param draft
257
+ * The email composer view.
258
+ */
259
+- (void) setAttachments:(NSArray*)attatchments
260
+                ofDraft:(MFMailComposeViewController*)draft
261
+{
262
+    if (attatchments)
263
+    {
264
+        for (NSString* path in attatchments)
265
+        {
266
+            NSData* data = [self getDataForAttachmentPath:path];
267
+
268
+            NSString* basename = [self getBasenameFromAttachmentPath:path];
269
+            NSString* pathExt  = [basename pathExtension];
270
+            NSString* fileName = [basename pathComponents].lastObject;
271
+            NSString* mimeType = [self getMimeTypeFromFileExtension:pathExt];
272
+
273
+            // Couldn't find mimeType, must be some type of binary data
274
+            if (mimeType == nil) mimeType = @"application/octet-stream";
275
+
276
+            [draft addAttachmentData:data mimeType:mimeType fileName:fileName];
277
+        }
278
+    }
279
+}
280
+
281
+/**
282
+ * Returns the data for a given (relative) attachment path.
283
+ *
284
+ * @param path
285
+ * An absolute/relative path or the base64 data.
286
+ * @return
287
+ * The data for the attachment.
288
+ */
289
+- (NSData*) getDataForAttachmentPath:(NSString*)path
290
+{
291
+    if ([path hasPrefix:@"file:///"])
292
+    {
293
+        return [self dataForAbsolutePath:path];
294
+    }
295
+    else if ([path hasPrefix:@"res:"])
296
+    {
297
+        return [self dataForResource:path];
298
+    }
299
+    else if ([path hasPrefix:@"file://"])
300
+    {
301
+        return [self dataForAsset:path];
302
+    }
303
+    else if ([path hasPrefix:@"base64:"])
304
+    {
305
+        return [self dataFromBase64:path];
306
+    }
307
+
308
+    NSFileManager* fileManager = [NSFileManager defaultManager];
309
+
310
+    if (![fileManager fileExistsAtPath:path]){
311
+        NSLog(@"File not found: %@", path);
312
+    }
313
+
314
+    return [fileManager contentsAtPath:path];
315
+}
316
+
317
+/**
318
+ * Retrieves the data for an absolute attachment path.
319
+ *
320
+ * @param path
321
+ * An absolute file path.
322
+ * @return
323
+ * The data for the attachment.
324
+ */
325
+- (NSData*) dataForAbsolutePath:(NSString*)path
326
+{
327
+    NSFileManager* fileManager = [NSFileManager defaultManager];
328
+    NSString* absPath;
329
+
330
+    absPath = [path stringByReplacingOccurrencesOfString:@"file://"
331
+                                              withString:@""];
332
+
333
+    if (![fileManager fileExistsAtPath:absPath]) {
334
+        NSLog(@"File not found: %@", absPath);
335
+    }
336
+
337
+    NSData* data = [fileManager contentsAtPath:absPath];
338
+
339
+    return data;
340
+}
341
+
342
+/**
343
+ * Retrieves the data for a resource path.
344
+ *
345
+ * @param path
346
+ * A relative file path.
347
+ * @return
348
+ * The data for the attachment.
349
+ */
350
+- (NSData*) dataForResource:(NSString*)path
351
+{
352
+    NSString* imgName = [[path pathComponents].lastObject
353
+                         stringByDeletingPathExtension];
354
+
355
+#ifdef __CORDOVA_4_0_0
356
+    if ([imgName isEqualToString:@"icon"]) {
357
+        imgName = @"AppIcon60x60@3x";
358
+    }
359
+#endif
360
+
361
+    UIImage* img = [UIImage imageNamed:imgName];
362
+
363
+    if (img == NULL) {
364
+        NSLog(@"File not found: %@", path);
365
+    }
366
+
367
+    NSData* data = UIImagePNGRepresentation(img);
368
+
369
+    return data;
370
+}
371
+
372
+/**
373
+ * Retrieves the data for a asset path.
374
+ *
375
+ * @param path
376
+ * A relative www file path.
377
+ * @return
378
+ * The data for the attachment.
379
+ */
380
+- (NSData*) dataForAsset:(NSString*)path
381
+{
382
+    NSFileManager* fileManager = [NSFileManager defaultManager];
383
+    NSString* absPath;
384
+
385
+    NSBundle* mainBundle = [NSBundle mainBundle];
386
+    NSString* bundlePath = [[mainBundle bundlePath]
387
+                            stringByAppendingString:@"/"];
388
+
389
+    absPath = [path stringByReplacingOccurrencesOfString:@"file:/"
390
+                                              withString:@"www"];
391
+
392
+    absPath = [bundlePath stringByAppendingString:absPath];
393
+
394
+    if (![fileManager fileExistsAtPath:absPath]) {
395
+        NSLog(@"File not found: %@", absPath);
396
+    }
397
+
398
+    NSData* data = [fileManager contentsAtPath:absPath];
399
+
400
+    return data;
401
+}
402
+
403
+/**
404
+ * Retrieves the data for a base64 encoded string.
405
+ *
406
+ * @param base64String
407
+ * Base64 encoded string.
408
+ * @return
409
+ * The data for the attachment.
410
+ */
411
+- (NSData*) dataFromBase64:(NSString*)base64String
412
+{
413
+    NSUInteger length = [base64String length];
414
+    NSRegularExpression *regex;
415
+    NSString *dataString;
416
+
417
+    regex = [NSRegularExpression regularExpressionWithPattern:@"^base64:[^/]+.."
418
+                                                      options:NSRegularExpressionCaseInsensitive
419
+                                                        error:Nil];
420
+
421
+    dataString = [regex stringByReplacingMatchesInString:base64String
422
+                                                 options:0
423
+                                                   range:NSMakeRange(0, length)
424
+                                            withTemplate:@""];
425
+
426
+#ifndef __CORDOVA_3_8_0
427
+    NSData* data = [NSData dataFromBase64String:dataString];
428
+#else
429
+    NSData* data = [[NSData alloc] initWithBase64EncodedString:dataString options:0];
430
+#endif
431
+
432
+    return data;
433
+}
434
+
435
+/**
436
+ * Retrieves the mime type from the file extension.
437
+ *
438
+ * @param extension
439
+ * The file's extension.
440
+ * @return
441
+ * The coresponding MIME type.
442
+ */
443
+- (NSString*) getMimeTypeFromFileExtension:(NSString*)extension
444
+{
445
+    if (!extension) {
446
+        return nil;
447
+    }
448
+
449
+    // Get the UTI from the file's extension
450
+    CFStringRef ext = (CFStringRef)CFBridgingRetain(extension);
451
+    CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL);
452
+
453
+    ext = NULL;
454
+
455
+    // Converting UTI to a mime type
456
+    return (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType));
457
+}
458
+
459
+/**
460
+ * Retrieves the attachments basename.
461
+ *
462
+ * @param path
463
+ * The file path or bas64 data of the attachment.
464
+ * @return
465
+ * The attachments basename.
466
+ */
467
+- (NSString*) getBasenameFromAttachmentPath:(NSString*)path
468
+{
469
+    if ([path hasPrefix:@"base64:"])
470
+    {
471
+        NSString* pathWithoutPrefix;
472
+
473
+        pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:"
474
+                                                            withString:@""];
475
+
476
+        return [pathWithoutPrefix substringToIndex:
477
+                [pathWithoutPrefix rangeOfString:@"//"].location];
478
+    }
479
+
480
+    return path;
481
+}
482
+
483
+@end

+ 68
- 0
plugins/cordova-plugin-email/src/windows/EmailComposerProxy.js View File

@@ -0,0 +1,68 @@
1
+/* globals Windows: true */
2
+
3
+/*
4
+    Copyright 2013-2016 appPlant UG
5
+
6
+    Licensed to the Apache Software Foundation (ASF) under one
7
+    or more contributor license agreements.  See the NOTICE file
8
+    distributed with this work for additional information
9
+    regarding copyright ownership.  The ASF licenses this file
10
+    to you under the Apache License, Version 2.0 (the
11
+    "License"); you may not use this file except in compliance
12
+    with the License.  You may obtain a copy of the License at
13
+
14
+     http://www.apache.org/licenses/LICENSE-2.0
15
+
16
+    Unless required by applicable law or agreed to in writing,
17
+    software distributed under the License is distributed on an
18
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19
+    KIND, either express or implied.  See the License for the
20
+    specific language governing permissions and limitations
21
+    under the License.
22
+*/
23
+
24
+var WinLauncher = Windows.System.Launcher,
25
+    WinMail     = Windows.ApplicationModel.Email;
26
+
27
+/**
28
+ * Verifies if sending emails is supported on the device.
29
+ *
30
+ * @param {Function} success
31
+ *      Success callback function
32
+ * @param {Function} error
33
+ *      Error callback function
34
+ * @param {Array} args
35
+ *      Interface arguments
36
+ */
37
+exports.isAvailable = function (success, error, args) {
38
+    success(true);
39
+};
40
+
41
+/**
42
+ * Displays the email composer pre-filled with data.
43
+ *
44
+ * @param {Function} success
45
+ *      Success callback function
46
+ * @param {Function} error
47
+ *      Error callback function
48
+ * @param {Array} args
49
+ *      Interface arguments
50
+ */
51
+exports.open = function (success, error, args) {
52
+    var props = args[0],
53
+        impl  = exports.impl;
54
+
55
+    if (WinMail) {
56
+        impl.getDraftWithProperties(props)
57
+            .then(WinMail.EmailManager.showComposeNewEmailAsync)
58
+            .done(success, error);
59
+    } else {
60
+        var mailTo = impl.getMailTo(props);
61
+
62
+        WinLauncher
63
+            .launchUriAsync(mailTo)
64
+            .done(success, error);
65
+    }
66
+};
67
+
68
+require('cordova/exec/proxy').add('EmailComposer', exports);

+ 259
- 0
plugins/cordova-plugin-email/src/windows/EmailComposerProxyImpl.js View File

@@ -0,0 +1,259 @@
1
+/*
2
+Copyright 2013-2016 appPlant UG
3
+
4
+Licensed to the Apache Software Foundation (ASF) under one
5
+or more contributor license agreements.  See the NOTICE file
6
+distributed with this work for additional information
7
+regarding copyright ownership.  The ASF licenses this file
8
+to you under the Apache License, Version 2.0 (the
9
+"License"); you may not use this file except in compliance
10
+with the License.  You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+Unless required by applicable law or agreed to in writing,
15
+software distributed under the License is distributed on an
16
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+KIND, either express or implied.  See the License for the
18
+specific language governing permissions and limitations
19
+under the License.
20
+*/
21
+
22
+var proxy = require('de.appplant.cordova.plugin.email-composer.EmailComposerProxy'),
23
+    impl  = proxy.impl = {},
24
+    WinMail = Windows.ApplicationModel.Email;
25
+
26
+/**
27
+ * The Email with the containing properties.
28
+ *
29
+ * @param {Object} props
30
+ *      The email properties like subject or body
31
+ * @return {Windows.ApplicationModel.Email.EmailMessage}
32
+ *      The resulting email draft
33
+ */
34
+impl.getDraftWithProperties = function (props) {
35
+    var me = this;
36
+
37
+    return new WinJS.Promise(function (complete) {
38
+        var mail = new WinMail.EmailMessage();
39
+
40
+        // subject
41
+        me.setSubject(props.subject, mail);
42
+        // body
43
+        me.setBody(props.body, props.isHtml, mail);
44
+        // To recipients
45
+        me.setRecipients(props.to, mail.to);
46
+        // CC recipients
47
+        me.setRecipients(props.cc, mail.cc);
48
+        // BCC recipients
49
+        me.setRecipients(props.bcc, mail.bcc);
50
+        // attachments
51
+        me.setAttachments(props.attachments, mail)
52
+
53
+        .then(function () {
54
+            complete(mail);
55
+        });
56
+    });
57
+};
58
+
59
+impl.getMailTo = function (props) {
60
+    // The URI to launch
61
+    var uriToLaunch = "mailto:" + props.to;
62
+
63
+    var options = '';
64
+    if (props.subject !== '') {
65
+        options = options + '&subject=' + props.subject;
66
+    }
67
+    if (props.body !== '') {
68
+        options = options + '&body=' + props.body;
69
+    }
70
+    if (props.cc !== '') {
71
+        options = options + '&cc=' + props.cc;
72
+    }
73
+    if (props.bcc !== '') {
74
+        options = options + '&bcc=' + props.bcc;
75
+    }
76
+    if (options !== '') {
77
+        options = '?' + options.substring(1);
78
+        uriToLaunch = uriToLaunch + options;
79
+    }
80
+
81
+    // Create a Uri object from a URI string
82
+    var uri = new Windows.Foundation.Uri(uriToLaunch);
83
+
84
+    return uri;
85
+};
86
+
87
+/**
88
+ * Setter for the subject.
89
+ *
90
+ * @param {String} subject
91
+ *      The subject
92
+ * @param {Windows.ApplicationModel.Email.EmailMessage} draft
93
+ *      The draft
94
+ */
95
+impl.setSubject = function (subject, draft) {
96
+    draft.subject = subject;
97
+};
98
+
99
+/**
100
+ * Setter for the body.
101
+ *
102
+ * @param {String} body
103
+ *      The body
104
+ * @param isHTML
105
+ *      Indicates the encoding
106
+ *      (HTML or plain text)
107
+ * @param {Windows.ApplicationModel.Email.EmailMessage} draft
108
+ *      The draft
109
+ */
110
+impl.setBody = function (body, isHTML, draft) {
111
+    draft.body = body;
112
+};
113
+
114
+/**
115
+ * Setter for the recipients.
116
+ *
117
+ * @param {String[]} recipients
118
+ *      List of mail addresses
119
+ * @param {Windows.ApplicationModel.Email.EmailMessage} draft
120
+ *      The draft.to / *.cc / *.bcc
121
+ */
122
+impl.setRecipients = function (recipients, draft) {
123
+    recipients.forEach(function (address) {
124
+        draft.push(new WinMail.EmailRecipient(address));
125
+    });
126
+};
127
+
128
+/**
129
+ * Setter for the attachments.
130
+ *
131
+ * @param {String[]} attachments
132
+ *      List of URIs
133
+ * @param {Windows.ApplicationModel.Email.EmailMessage} draft
134
+ *      The draft
135
+ */
136
+impl.setAttachments = function (attachments, draft) {
137
+    var promises = [], me = this;
138
+
139
+    return new WinJS.Promise(function (complete) {
140
+        attachments.forEach(function (path) {
141
+            promises.push(me.getUriForPath(path));
142
+        });
143
+
144
+        WinJS.Promise.thenEach(promises, function (uri) {
145
+            draft.attachments.push(
146
+                new WinMail.EmailAttachment(
147
+                    uri.path.split('/').reverse()[0],
148
+                    Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(uri)
149
+                )
150
+            );
151
+        }).done(complete);
152
+    });
153
+};
154
+
155
+/**
156
+ * The URI for an attachment path.
157
+ *
158
+ * @param {String} path
159
+ *      The given path to the attachment
160
+ * @return
161
+ *      The URI pointing to the given path
162
+ */
163
+impl.getUriForPath = function (path) {
164
+    var me = this;
165
+
166
+    return new WinJS.Promise(function (complete) {
167
+        if (path.match(/^res:/)) {
168
+            complete(me.getUriForResourcePath(path));
169
+        } else if (path.match(/^file:\/{3}/)) {
170
+            complete(me.getUriForAbsolutePath(path));
171
+        } else if (path.match(/^file:/)) {
172
+            complete(me.getUriForAssetPath(path));
173
+        } else if (path.match(/^base64:/)) {
174
+            me.getUriFromBase64(path).then(complete);
175
+        } else {
176
+            complete(new Windows.Foundation.Uri(path));
177
+        }
178
+    });
179
+};
180
+
181
+/**
182
+ * The URI for a file.
183
+ *
184
+ * @param {String} path
185
+ *      The given absolute path
186
+ * @return
187
+ *      The URI pointing to the given path
188
+ */
189
+impl.getUriForAbsolutePath = function (path) {
190
+    return new Windows.Foundation.Uri(path);
191
+};
192
+
193
+/**
194
+ * The URI for an asset.
195
+ *
196
+ * @param {String} path
197
+ *      The given asset path
198
+ * @return
199
+ *      The URI pointing to the given path
200
+ */
201
+impl.getUriForAssetPath = function (path) {
202
+    var resPath = path.replace('file:/', '/www');
203
+
204
+    return this.getUriForPathUtil(resPath);
205
+};
206
+
207
+/**
208
+ * The URI for a resource.
209
+ *
210
+ * @param {String} path
211
+ *      The given relative path
212
+ * @return
213
+ *      The URI pointing to the given path
214
+ */
215
+impl.getUriForResourcePath = function (path) {
216
+    var resPath = path.replace('res:/', '/images');
217
+
218
+    return this.getUriForPathUtil(resPath);
219
+};
220
+
221
+/**
222
+ * The URI for a path.
223
+ *
224
+ * @param {String} resPath
225
+ *      The given relative path
226
+ * @return
227
+ *      The URI pointing to the given path
228
+ */
229
+impl.getUriForPathUtil = function (resPath) {
230
+    var rawUri = 'ms-appx:' + '//' + resPath;
231
+
232
+    return new Windows.Foundation.Uri(rawUri);
233
+};
234
+
235
+/**
236
+ * The URI for a base64 encoded content.
237
+ *
238
+ * @param {String} content
239
+ *      The given base64 encoded content
240
+ * @return
241
+ *      The URI including the given content
242
+ */
243
+impl.getUriFromBase64 = function (content) {
244
+    return new WinJS.Promise(function (complete) {
245
+        var match  = content.match(/^base64:([^\/]+)\/\/(.*)/),
246
+            base64 = match[2],
247
+            name   = match[1],
248
+            buffer = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(base64),
249
+            rwplus = Windows.Storage.CreationCollisionOption.openIfExists,
250
+            folder = Windows.Storage.ApplicationData.current.temporaryFolder,
251
+            uri    = new Windows.Foundation.Uri('ms-appdata:///temp/' + name);
252
+
253
+        folder.createFileAsync(name, rwplus).done(function (file) {
254
+            Windows.Storage.FileIO.writeBufferAsync(file, buffer).then(function () {
255
+                complete(uri);
256
+            });
257
+        });
258
+    });
259
+};

+ 133
- 0
plugins/cordova-plugin-email/src/wp8/EmailComposer.cs View File

@@ -0,0 +1,133 @@
1
+/*
2
+    Copyright 2013-2014 appPlant UG
3
+
4
+    Licensed to the Apache Software Foundation (ASF) under one
5
+    or more contributor license agreements.  See the NOTICE file
6
+    distributed with this work for additional information
7
+    regarding copyright ownership.  The ASF licenses this file
8
+    to you under the Apache License, Version 2.0 (the
9
+    "License"); you may not use this file except in compliance
10
+    with the License.  You may obtain a copy of the License at
11
+
12
+     http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+    Unless required by applicable law or agreed to in writing,
15
+    software distributed under the License is distributed on an
16
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+    KIND, either express or implied.  See the License for the
18
+    specific language governing permissions and limitations
19
+    under the License.
20
+*/
21
+
22
+using De.Martinreinhardt.Cordova.Plugins.Email;
23
+using Microsoft.Phone.Tasks;
24
+using System;
25
+using System.Linq;
26
+using WPCordovaClassLib.Cordova;
27
+using WPCordovaClassLib.Cordova.Commands;
28
+using WPCordovaClassLib.Cordova.JSON;
29
+
30
+namespace Cordova.Extension.Commands
31
+{
32
+    /// <summary>
33
+    /// Implementes access to email composer task
34
+    /// http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394003(v=vs.105).aspx
35
+    /// </summary>
36
+    public class EmailComposer : BaseCommand
37
+    {
38
+        /// <summary>
39
+        /// Überprüft, ob Emails versendet werden können.
40
+        /// </summary>
41
+        public void isAvailable(string jsonArgs)
42
+        {
43
+            DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true));
44
+        }
45
+
46
+        /// <summary>
47
+        /// Öffnet den Email-Kontroller mit vorausgefüllten Daten.
48
+        /// </summary>
49
+        public void open(string jsonArgs)
50
+        {
51
+            string[] args          = JsonHelper.Deserialize<string[]>(jsonArgs);
52
+            Options options        = JsonHelper.Deserialize<Options>(args[0]);
53
+            EmailComposeTask draft = GetDraftWithProperties(options);
54
+
55
+            DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true));
56
+
57
+            OpenDraft(draft);
58
+        }
59
+
60
+        /// </summary>
61
+        /// Erstellt den Email-Composer und fügt die übergebenen Eigenschaften ein.
62
+        /// </summary>
63
+        private EmailComposeTask GetDraftWithProperties(Options options)
64
+        {
65
+            EmailComposeTask draft = new EmailComposeTask();
66
+
67
+            SetSubject(options.Subject, draft);
68
+            SetBody(options.Body, options.IsHtml, draft);
69
+            SetTo(options.To, draft);
70
+            SetCc(options.Cc, draft);
71
+            SetBcc(options.Bcc, draft);
72
+            SetAttachments(options.Attachments, draft);
73
+
74
+            return draft;
75
+        }
76
+
77
+        /// </summary>
78
+        /// Zeigt den ViewController zum Versenden/Bearbeiten der Mail an.
79
+        /// </summary>
80
+        private void OpenDraft(EmailComposeTask draft)
81
+        {
82
+            draft.Show();
83
+        }
84
+
85
+        /// </summary>
86
+        /// Setzt den Subject der Mail.
87
+        /// </summary>
88
+        private void SetSubject(string subject, EmailComposeTask draft)
89
+        {
90
+            draft.Subject = subject;
91
+        }
92
+
93
+        /// </summary>
94
+        /// Setzt den Body der Mail.
95
+        /// </summary>
96
+        private void SetBody(string body, Boolean isHTML, EmailComposeTask draft)
97
+        {
98
+            draft.Body = body;
99
+        }
100
+
101
+        /// </summary>
102
+        /// Setzt die Empfänger der Mail.
103
+        /// </summary>
104
+        private void SetTo(string[] recipients, EmailComposeTask draft)
105
+        {
106
+            draft.To = string.Join(",", recipients);
107
+        }
108
+
109
+        /// </summary>
110
+        /// Setzt die CC-Empfänger der Mail.
111
+        /// </summary>
112
+        private void SetCc(string[] recipients, EmailComposeTask draft)
113
+        {
114
+            draft.Cc = string.Join(",", recipients);
115
+        }
116
+
117
+        /// </summary>
118
+        /// Setzt die BCC-Empfänger der Mail.
119
+        /// </summary>
120
+        private void SetBcc(string[] recipients, EmailComposeTask draft)
121
+        {
122
+            draft.Bcc = string.Join(",", recipients);
123
+        }
124
+
125
+        /// </summary>
126
+        /// Fügt die Anhände zur Mail hinzu.
127
+        /// </summary>
128
+        private void SetAttachments(string[] attachments, EmailComposeTask draft)
129
+        {
130
+            // Not supported on WP8.0 and WP8.1 Silverlight
131
+        }
132
+    }
133
+}

+ 77
- 0
plugins/cordova-plugin-email/src/wp8/Options.cs View File

@@ -0,0 +1,77 @@
1
+/*
2
+    Copyright 2013-2014 appPlant UG
3
+
4
+    Licensed to the Apache Software Foundation (ASF) under one
5
+    or more contributor license agreements.  See the NOTICE file
6
+    distributed with this work for additional information
7
+    regarding copyright ownership.  The ASF licenses this file
8
+    to you under the Apache License, Version 2.0 (the
9
+    "License"); you may not use this file except in compliance
10
+    with the License.  You may obtain a copy of the License at
11
+
12
+     http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+    Unless required by applicable law or agreed to in writing,
15
+    software distributed under the License is distributed on an
16
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+    KIND, either express or implied.  See the License for the
18
+    specific language governing permissions and limitations
19
+    under the License.
20
+*/
21
+
22
+using System;
23
+using System.Linq;
24
+using System.Runtime.Serialization;
25
+
26
+
27
+namespace De.Martinreinhardt.Cordova.Plugins.Email
28
+{
29
+    /// <summary>
30
+    /// Represents email composer task options
31
+    /// </summary>
32
+    [DataContract]
33
+    class Options
34
+    {
35
+        /// <summary>
36
+        /// Represents the subject of the email
37
+        /// </summary>
38
+        [DataMember(IsRequired = false, Name = "subject")]
39
+        public string Subject { get; set; }
40
+
41
+        /// <summary>
42
+        /// Represents the email body (could be HTML code, in this case set isHtml to true)
43
+        /// </summary>
44
+        [DataMember(IsRequired = false, Name = "body")]
45
+        public string Body { get; set; }
46
+
47
+        /// <summary>
48
+        /// Indicats if the body is HTML or plain text
49
+        /// </summary>
50
+        [DataMember(IsRequired = false, Name = "isHtml")]
51
+        public bool IsHtml { get; set; }
52
+
53
+        /// <summary>
54
+        /// Contains all the email addresses for TO field
55
+        /// </summary>
56
+        [DataMember(IsRequired = false, Name = "to")]
57
+        public string[] To { get; set; }
58
+
59
+        /// <summary>
60
+        /// Contains all the email addresses for CC field
61
+        /// </summary>
62
+        [DataMember(IsRequired = false, Name = "cc")]
63
+        public string[] Cc { get; set; }
64
+
65
+        /// <summary>
66
+        /// Contains all the email addresses for BCC field
67
+        /// </summary>
68
+        [DataMember(IsRequired = false, Name = "bcc")]
69
+        public string[] Bcc { get; set; }
70
+
71
+        /// <summary>
72
+        /// Contains all full paths to the files you want to attach
73
+        /// </summary>
74
+        [DataMember(IsRequired = false, Name = "attachments")]
75
+        public string[] Attachments { get; set; }
76
+    }
77
+}

+ 204
- 0
plugins/cordova-plugin-email/www/email_composer.js View File

@@ -0,0 +1,204 @@
1
+/*
2
+ Copyright (c) 2016 Martin Reinhardt
3
+ Copyright 2013-2014 appPlant UG
4
+
5
+ Licensed to the Apache Software Foundation (ASF) under one
6
+ or more contributor license agreements.  See the NOTICE file
7
+ distributed with this work for additional information
8
+ regarding copyright ownership.  The ASF licenses this file
9
+ to you under the Apache License, Version 2.0 (the
10
+ "License"); you may not use this file except in compliance
11
+ with the License.  You may obtain a copy of the License at
12
+
13
+ http://www.apache.org/licenses/LICENSE-2.0
14
+
15
+ Unless required by applicable law or agreed to in writing,
16
+ software distributed under the License is distributed on an
17
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ KIND, either express or implied.  See the License for the
19
+ specific language governing permissions and limitations
20
+ under the License.
21
+ */
22
+
23
+/**
24
+ * @namespace
25
+ */
26
+var EmailComposerPlugin = function () {
27
+
28
+};
29
+
30
+var exec = require('cordova/exec');
31
+
32
+EmailComposerPlugin.prototype = {
33
+    /**
34
+     * List of all registered mail app aliases.
35
+     */
36
+    aliases: {
37
+        gmail: 'com.google.android.gm'
38
+    },
39
+
40
+    /**
41
+     * List of all available options with their default value.
42
+     *
43
+     * @return {Object}
44
+     */
45
+    getDefaults: function () {
46
+        return {
47
+            app: undefined,
48
+            subject: '',
49
+            body: '',
50
+            to: [],
51
+            cc: [],
52
+            bcc: [],
53
+            attachments: [],
54
+            isHtml: true
55
+        };
56
+    },
57
+
58
+    /**
59
+     * Verifies if sending emails is supported on the device.
60
+     *
61
+     * @param {Function} callback
62
+     *      A callback function to be called with the result
63
+     * @param {Object} scope
64
+     *      The scope of the callback
65
+     */
66
+    isAvailable: function (callback, scope) {
67
+        var fn = this.createCallbackFn(callback, scope);
68
+        exec(function () {
69
+            fn(true);
70
+        }, function () {
71
+            fn(false);
72
+        }, 'EmailComposer', 'isAvailable', []);
73
+    },
74
+
75
+    /**
76
+     * Displays the email composer pre-filled with data.
77
+     *
78
+     * @param {Object} options
79
+     *      Different properties of the email like the body, subject
80
+     * @param {Function} callback
81
+     *      A callback function to be called with the result
82
+     * @param {Object?} scope
83
+     *      The scope of the callback
84
+     */
85
+    open: function (options, callback, scope) {
86
+        var fn = this.createCallbackFn(callback, scope);
87
+
88
+        options = this.mergeWithDefaults(options || {});
89
+
90
+        exec(fn, null, 'EmailComposer', 'open', [options]);
91
+    },
92
+
93
+    /**
94
+     * Adds a new mail app alias.
95
+     *
96
+     * @param {String} alias
97
+     *      The alias name
98
+     * @param {String} packageName
99
+     *      The package name
100
+     */
101
+    addAlias: function (alias, packageName) {
102
+        this.aliases[alias] = packageName;
103
+    },
104
+
105
+    /**
106
+     * @depreacted
107
+     */
108
+    isServiceAvailable: function () {
109
+        console.log('`email.isServiceAvailable` is deprecated.' +
110
+            ' Please use `email.isAvailable` instead.');
111
+
112
+        this.isAvailable.apply(this, arguments);
113
+    },
114
+
115
+    /**
116
+     * Alias für `open()`.
117
+     */
118
+    openDraft: function () {
119
+        this.open.apply(this, arguments);
120
+    },
121
+
122
+    /**
123
+     * @private
124
+     *
125
+     * Merge settings with default values.
126
+     *
127
+     * @param {Object} options
128
+     *      The custom options
129
+     *
130
+     * @retrun {Object}
131
+     *      Default values merged
132
+     *      with custom values
133
+     */
134
+    mergeWithDefaults: function (options) {
135
+        var defaults = this.getDefaults();
136
+
137
+        if (options.hasOwnProperty('isHTML')) {
138
+            options.isHtml = options.isHTML;
139
+        }
140
+
141
+        if (options.hasOwnProperty('app')) {
142
+            var packageName = this.aliases[options.app];
143
+
144
+            options.app = packageName || options.app;
145
+        }
146
+
147
+        for (var key in defaults) {
148
+
149
+            if (!options.hasOwnProperty(key)) {
150
+                options[key] = defaults[key];
151
+                continue;
152
+            }
153
+
154
+            var custom_ = options[key],
155
+                default_ = defaults[key];
156
+
157
+            if (custom_ === null || custom_ === undefined) {
158
+                options[key] = default_;
159
+                continue;
160
+            }
161
+
162
+            if (typeof default_ != typeof custom_) {
163
+
164
+                if (typeof default_ == 'string') {
165
+                    options[key] = custom_.join('');
166
+                }
167
+
168
+                else if (typeof default_ == 'object') {
169
+                    options[key] = [custom_.toString()];
170
+                }
171
+            }
172
+        }
173
+
174
+        return options;
175
+    },
176
+
177
+    /**
178
+     * @private
179
+     *
180
+     * Creates a callback, which will be executed
181
+     * within a specific scope.
182
+     *
183
+     * @param {Function} callbackFn
184
+     *      The callback function
185
+     * @param {Object} scope
186
+     *      The scope for the function
187
+     *
188
+     * @return {Function}
189
+     *      The new callback function
190
+     */
191
+    createCallbackFn: function (callbackFn, scope) {
192
+        if (typeof callbackFn != 'function') {
193
+            return function () {
194
+            };
195
+        } else {
196
+            return function () {
197
+                callbackFn.apply(scope || this, arguments);
198
+            };
199
+        }
200
+    }
201
+
202
+};
203
+
204
+module.exports = new EmailComposerPlugin();

+ 30
- 0
plugins/cordova-plugin-file/.jshintrc View File

@@ -0,0 +1,30 @@
1
+{
2
+    "browser": true
3
+  , "devel": true
4
+  , "bitwise": true
5
+  , "undef": true
6
+  , "trailing": true
7
+  , "quotmark": false
8
+  , "indent": 4
9
+  , "unused": "vars"
10
+  , "latedef": "nofunc"
11
+  , "globals": {
12
+        "module": false,
13
+        "exports": false,
14
+        "require": false,
15
+        "cordova": false,
16
+        "File": true,
17
+        "FileSystem": true,
18
+        "FileReader": true,
19
+        "FileWriter": true,
20
+        "FileError": true,
21
+        "LocalFileSystem": true,
22
+        "Metadata": true,
23
+        "Flags": true,
24
+        "DirectoryEntry": true,
25
+        "resolveLocalFileSystemURL": false,
26
+        "requestFileSystem": true,
27
+        "FILESYSTEM_PREFIX": true,
28
+        "FILESYSTEM_PROTOCOL": true
29
+    }
30
+}

+ 1
- 0
plugins/cordova-plugin-file/.ratignore View File

@@ -0,0 +1 @@
1
+asset-test.txt

+ 37
- 0
plugins/cordova-plugin-file/CONTRIBUTING.md View File

@@ -0,0 +1,37 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+
22
+# Contributing to Apache Cordova
23
+
24
+Anyone can contribute to Cordova. And we need your contributions.
25
+
26
+There are multiple ways to contribute: report bugs, improve the docs, and
27
+contribute code.
28
+
29
+For instructions on this, start with the 
30
+[contribution overview](http://cordova.apache.org/contribute/).
31
+
32
+The details are explained there, but the important items are:
33
+ - Sign and submit an Apache ICLA (Contributor License Agreement).
34
+ - Have a Jira issue open that corresponds to your contribution.
35
+ - Run the tests so your patch doesn't break existing functionality.
36
+
37
+We look forward to your contributions!

+ 202
- 0
plugins/cordova-plugin-file/LICENSE View File

@@ -0,0 +1,202 @@
1
+
2
+                                 Apache License
3
+                           Version 2.0, January 2004
4
+                        http://www.apache.org/licenses/
5
+
6
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+   1. Definitions.
9
+
10
+      "License" shall mean the terms and conditions for use, reproduction,
11
+      and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+      "Licensor" shall mean the copyright owner or entity authorized by
14
+      the copyright owner that is granting the License.
15
+
16
+      "Legal Entity" shall mean the union of the acting entity and all
17
+      other entities that control, are controlled by, or are under common
18
+      control with that entity. For the purposes of this definition,
19
+      "control" means (i) the power, direct or indirect, to cause the
20
+      direction or management of such entity, whether by contract or
21
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+      outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+      "You" (or "Your") shall mean an individual or Legal Entity
25
+      exercising permissions granted by this License.
26
+
27
+      "Source" form shall mean the preferred form for making modifications,
28
+      including but not limited to software source code, documentation
29
+      source, and configuration files.
30
+
31
+      "Object" form shall mean any form resulting from mechanical
32
+      transformation or translation of a Source form, including but
33
+      not limited to compiled object code, generated documentation,
34
+      and conversions to other media types.
35
+
36
+      "Work" shall mean the work of authorship, whether in Source or
37
+      Object form, made available under the License, as indicated by a
38
+      copyright notice that is included in or attached to the work
39
+      (an example is provided in the Appendix below).
40
+
41
+      "Derivative Works" shall mean any work, whether in Source or Object
42
+      form, that is based on (or derived from) the Work and for which the
43
+      editorial revisions, annotations, elaborations, or other modifications
44
+      represent, as a whole, an original work of authorship. For the purposes
45
+      of this License, Derivative Works shall not include works that remain
46
+      separable from, or merely link (or bind by name) to the interfaces of,
47
+      the Work and Derivative Works thereof.
48
+
49
+      "Contribution" shall mean any work of authorship, including
50
+      the original version of the Work and any modifications or additions
51
+      to that Work or Derivative Works thereof, that is intentionally
52
+      submitted to Licensor for inclusion in the Work by the copyright owner
53
+      or by an individual or Legal Entity authorized to submit on behalf of
54
+      the copyright owner. For the purposes of this definition, "submitted"
55
+      means any form of electronic, verbal, or written communication sent
56
+      to the Licensor or its representatives, including but not limited to
57
+      communication on electronic mailing lists, source code control systems,
58
+      and issue tracking systems that are managed by, or on behalf of, the
59
+      Licensor for the purpose of discussing and improving the Work, but
60
+      excluding communication that is conspicuously marked or otherwise
61
+      designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+      "Contributor" shall mean Licensor and any individual or Legal Entity
64
+      on behalf of whom a Contribution has been received by Licensor and
65
+      subsequently incorporated within the Work.
66
+
67
+   2. Grant of Copyright License. Subject to the terms and conditions of
68
+      this License, each Contributor hereby grants to You a perpetual,
69
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+      copyright license to reproduce, prepare Derivative Works of,
71
+      publicly display, publicly perform, sublicense, and distribute the
72
+      Work and such Derivative Works in Source or Object form.
73
+
74
+   3. Grant of Patent License. Subject to the terms and conditions of
75
+      this License, each Contributor hereby grants to You a perpetual,
76
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+      (except as stated in this section) patent license to make, have made,
78
+      use, offer to sell, sell, import, and otherwise transfer the Work,
79
+      where such license applies only to those patent claims licensable
80
+      by such Contributor that are necessarily infringed by their
81
+      Contribution(s) alone or by combination of their Contribution(s)
82
+      with the Work to which such Contribution(s) was submitted. If You
83
+      institute patent litigation against any entity (including a
84
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+      or a Contribution incorporated within the Work constitutes direct
86
+      or contributory patent infringement, then any patent licenses
87
+      granted to You under this License for that Work shall terminate
88
+      as of the date such litigation is filed.
89
+
90
+   4. Redistribution. You may reproduce and distribute copies of the
91
+      Work or Derivative Works thereof in any medium, with or without
92
+      modifications, and in Source or Object form, provided that You
93
+      meet the following conditions:
94
+
95
+      (a) You must give any other recipients of the Work or
96
+          Derivative Works a copy of this License; and
97
+
98
+      (b) You must cause any modified files to carry prominent notices
99
+          stating that You changed the files; and
100
+
101
+      (c) You must retain, in the Source form of any Derivative Works
102
+          that You distribute, all copyright, patent, trademark, and
103
+          attribution notices from the Source form of the Work,
104
+          excluding those notices that do not pertain to any part of
105
+          the Derivative Works; and
106
+
107
+      (d) If the Work includes a "NOTICE" text file as part of its
108
+          distribution, then any Derivative Works that You distribute must
109
+          include a readable copy of the attribution notices contained
110
+          within such NOTICE file, excluding those notices that do not
111
+          pertain to any part of the Derivative Works, in at least one
112
+          of the following places: within a NOTICE text file distributed
113
+          as part of the Derivative Works; within the Source form or
114
+          documentation, if provided along with the Derivative Works; or,
115
+          within a display generated by the Derivative Works, if and
116
+          wherever such third-party notices normally appear. The contents
117
+          of the NOTICE file are for informational purposes only and
118
+          do not modify the License. You may add Your own attribution
119
+          notices within Derivative Works that You distribute, alongside
120
+          or as an addendum to the NOTICE text from the Work, provided
121
+          that such additional attribution notices cannot be construed
122
+          as modifying the License.
123
+
124
+      You may add Your own copyright statement to Your modifications and
125
+      may provide additional or different license terms and conditions
126
+      for use, reproduction, or distribution of Your modifications, or
127
+      for any such Derivative Works as a whole, provided Your use,
128
+      reproduction, and distribution of the Work otherwise complies with
129
+      the conditions stated in this License.
130
+
131
+   5. Submission of Contributions. Unless You explicitly state otherwise,
132
+      any Contribution intentionally submitted for inclusion in the Work
133
+      by You to the Licensor shall be under the terms and conditions of
134
+      this License, without any additional terms or conditions.
135
+      Notwithstanding the above, nothing herein shall supersede or modify
136
+      the terms of any separate license agreement you may have executed
137
+      with Licensor regarding such Contributions.
138
+
139
+   6. Trademarks. This License does not grant permission to use the trade
140
+      names, trademarks, service marks, or product names of the Licensor,
141
+      except as required for reasonable and customary use in describing the
142
+      origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+   7. Disclaimer of Warranty. Unless required by applicable law or
145
+      agreed to in writing, Licensor provides the Work (and each
146
+      Contributor provides its Contributions) on an "AS IS" BASIS,
147
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+      implied, including, without limitation, any warranties or conditions
149
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+      PARTICULAR PURPOSE. You are solely responsible for determining the
151
+      appropriateness of using or redistributing the Work and assume any
152
+      risks associated with Your exercise of permissions under this License.
153
+
154
+   8. Limitation of Liability. In no event and under no legal theory,
155
+      whether in tort (including negligence), contract, or otherwise,
156
+      unless required by applicable law (such as deliberate and grossly
157
+      negligent acts) or agreed to in writing, shall any Contributor be
158
+      liable to You for damages, including any direct, indirect, special,
159
+      incidental, or consequential damages of any character arising as a
160
+      result of this License or out of the use or inability to use the
161
+      Work (including but not limited to damages for loss of goodwill,
162
+      work stoppage, computer failure or malfunction, or any and all
163
+      other commercial damages or losses), even if such Contributor
164
+      has been advised of the possibility of such damages.
165
+
166
+   9. Accepting Warranty or Additional Liability. While redistributing
167
+      the Work or Derivative Works thereof, You may choose to offer,
168
+      and charge a fee for, acceptance of support, warranty, indemnity,
169
+      or other liability obligations and/or rights consistent with this
170
+      License. However, in accepting such obligations, You may act only
171
+      on Your own behalf and on Your sole responsibility, not on behalf
172
+      of any other Contributor, and only if You agree to indemnify,
173
+      defend, and hold each Contributor harmless for any liability
174
+      incurred by, or claims asserted against, such Contributor by reason
175
+      of your accepting any such warranty or additional liability.
176
+
177
+   END OF TERMS AND CONDITIONS
178
+
179
+   APPENDIX: How to apply the Apache License to your work.
180
+
181
+      To apply the Apache License to your work, attach the following
182
+      boilerplate notice, with the fields enclosed by brackets "[]"
183
+      replaced with your own identifying information. (Don't include
184
+      the brackets!)  The text should be enclosed in the appropriate
185
+      comment syntax for the file format. We also recommend that a
186
+      file or class name and description of purpose be included on the
187
+      same "printed page" as the copyright notice for easier
188
+      identification within third-party archives.
189
+
190
+   Copyright [yyyy] [name of copyright owner]
191
+
192
+   Licensed under the Apache License, Version 2.0 (the "License");
193
+   you may not use this file except in compliance with the License.
194
+   You may obtain a copy of the License at
195
+
196
+       http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+   Unless required by applicable law or agreed to in writing, software
199
+   distributed under the License is distributed on an "AS IS" BASIS,
200
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+   See the License for the specific language governing permissions and
202
+   limitations under the License.

+ 5
- 0
plugins/cordova-plugin-file/NOTICE View File

@@ -0,0 +1,5 @@
1
+Apache Cordova
2
+Copyright 2012 The Apache Software Foundation
3
+
4
+This product includes software developed at
5
+The Apache Software Foundation (http://www.apache.org/).

+ 833
- 0
plugins/cordova-plugin-file/README.md View File

@@ -0,0 +1,833 @@
1
+---
2
+title: File
3
+description: Read/write files on the device.
4
+---
5
+<!--
6
+# license: Licensed to the Apache Software Foundation (ASF) under one
7
+#         or more contributor license agreements.  See the NOTICE file
8
+#         distributed with this work for additional information
9
+#         regarding copyright ownership.  The ASF licenses this file
10
+#         to you under the Apache License, Version 2.0 (the
11
+#         "License"); you may not use this file except in compliance
12
+#         with the License.  You may obtain a copy of the License at
13
+#
14
+#           http://www.apache.org/licenses/LICENSE-2.0
15
+#
16
+#         Unless required by applicable law or agreed to in writing,
17
+#         software distributed under the License is distributed on an
18
+#         "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19
+#         KIND, either express or implied.  See the License for the
20
+#         specific language governing permissions and limitations
21
+#         under the License.
22
+-->
23
+
24
+|AppVeyor|Travis CI|
25
+|:-:|:-:|
26
+|[![Build status](https://ci.appveyor.com/api/projects/status/github/apache/cordova-plugin-file?branch=master)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/cordova-plugin-file)|[![Build Status](https://travis-ci.org/apache/cordova-plugin-file.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-file)|
27
+
28
+# cordova-plugin-file
29
+
30
+This plugin implements a File API allowing read/write access to files residing on the device.
31
+
32
+This plugin is based on several specs, including :
33
+The HTML5 File API
34
+[http://www.w3.org/TR/FileAPI/](http://www.w3.org/TR/FileAPI/)
35
+
36
+The Directories and System extensions
37
+Latest:
38
+[http://www.w3.org/TR/2012/WD-file-system-api-20120417/](http://www.w3.org/TR/2012/WD-file-system-api-20120417/)
39
+Although most of the plugin code was written when an earlier spec was current:
40
+[http://www.w3.org/TR/2011/WD-file-system-api-20110419/](http://www.w3.org/TR/2011/WD-file-system-api-20110419/)
41
+
42
+It also implements the FileWriter spec :
43
+[http://dev.w3.org/2009/dap/file-system/file-writer.html](http://dev.w3.org/2009/dap/file-system/file-writer.html)
44
+
45
+>*Note* While the W3C FileSystem spec is deprecated for web browsers, the FileSystem APIs are supported in Cordova applications with this plugin for the platforms listed in the _Supported Platforms_ list, with the exception of the Browser platform.
46
+
47
+To get a few ideas how to use the plugin, check out the [sample](#sample) at the bottom of this page. For additional examples (browser focused), see the HTML5 Rocks' [FileSystem article.](http://www.html5rocks.com/en/tutorials/file/filesystem/)
48
+
49
+For an overview of other storage options, refer to Cordova's
50
+[storage guide](http://cordova.apache.org/docs/en/latest/cordova/storage/storage.html).
51
+
52
+This plugin defines global `cordova.file` object.
53
+
54
+Although in the global scope, it is not available until after the `deviceready` event.
55
+
56
+    document.addEventListener("deviceready", onDeviceReady, false);
57
+    function onDeviceReady() {
58
+        console.log(cordova.file);
59
+    }
60
+
61
+## Installation
62
+
63
+    cordova plugin add cordova-plugin-file
64
+
65
+## Supported Platforms
66
+
67
+- Android
68
+- iOS
69
+- OS X
70
+- Windows*
71
+- Browser
72
+
73
+\* _These platforms do not support `FileReader.readAsArrayBuffer` nor `FileWriter.write(blob)`._
74
+
75
+## Where to Store Files
76
+
77
+As of v1.2.0, URLs to important file-system directories are provided.
78
+Each URL is in the form _file:///path/to/spot/_, and can be converted to a
79
+`DirectoryEntry` using `window.resolveLocalFileSystemURL()`.
80
+
81
+* `cordova.file.applicationDirectory` - Read-only directory where the application
82
+  is installed. (_iOS_, _Android_, _BlackBerry 10_, _OSX_, _windows_)
83
+
84
+* `cordova.file.applicationStorageDirectory` - Root directory of the application's
85
+  sandbox; on iOS & windows this location is read-only (but specific subdirectories [like
86
+  `/Documents` on iOS or `/localState` on windows] are read-write). All data contained within
87
+  is private to the app. (_iOS_, _Android_, _BlackBerry 10_, _OSX_)
88
+
89
+* `cordova.file.dataDirectory` - Persistent and private data storage within the
90
+  application's sandbox using internal memory (on Android, if you need to use
91
+  external memory, use `.externalDataDirectory`). On iOS, this directory is not
92
+  synced with iCloud (use `.syncedDataDirectory`). (_iOS_, _Android_, _BlackBerry 10_, _windows_)
93
+
94
+* `cordova.file.cacheDirectory` -  Directory for cached data files or any files
95
+  that your app can re-create easily. The OS may delete these files when the device
96
+  runs low on storage, nevertheless, apps should not rely on the OS to delete files
97
+  in here. (_iOS_, _Android_, _BlackBerry 10_, _OSX_, _windows_)
98
+
99
+* `cordova.file.externalApplicationStorageDirectory` - Application space on
100
+  external storage. (_Android_)
101
+
102
+* `cordova.file.externalDataDirectory` - Where to put app-specific data files on
103
+  external storage. (_Android_)
104
+
105
+* `cordova.file.externalCacheDirectory` - Application cache on external storage.
106
+  (_Android_)
107
+
108
+* `cordova.file.externalRootDirectory` - External storage (SD card) root. (_Android_, _BlackBerry 10_)
109
+
110
+* `cordova.file.tempDirectory` - Temp directory that the OS can clear at will. Do not
111
+  rely on the OS to clear this directory; your app should always remove files as
112
+  applicable. (_iOS_, _OSX_, _windows_)
113
+
114
+* `cordova.file.syncedDataDirectory` - Holds app-specific files that should be synced
115
+  (e.g. to iCloud). (_iOS_, _windows_)
116
+
117
+* `cordova.file.documentsDirectory` - Files private to the app, but that are meaningful
118
+  to other application (e.g. Office files). Note that for _OSX_ this is the user's `~/Documents` directory. (_iOS_, _OSX_)
119
+
120
+* `cordova.file.sharedDirectory` - Files globally available to all applications (_BlackBerry 10_)
121
+
122
+## File System Layouts
123
+
124
+Although technically an implementation detail, it can be very useful to know how
125
+the `cordova.file.*` properties map to physical paths on a real device.
126
+
127
+### iOS File System Layout
128
+
129
+| Device Path                                    | `cordova.file.*`            | `iosExtraFileSystems` | r/w? | persistent? | OS clears | sync | private |
130
+|:-----------------------------------------------|:----------------------------|:----------------------|:----:|:-----------:|:---------:|:----:|:-------:|
131
+| `/var/mobile/Applications/<UUID>/`             | applicationStorageDirectory | -                     | r    |     N/A     |     N/A   | N/A  |   Yes   |
132
+| &nbsp;&nbsp;&nbsp;`appname.app/`               | applicationDirectory        | bundle                | r    |     N/A     |     N/A   | N/A  |   Yes   |
133
+| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`www/`     | -                           | -                     | r    |     N/A     |     N/A   | N/A  |   Yes   |
134
+| &nbsp;&nbsp;&nbsp;`Documents/`                 | documentsDirectory          | documents             | r/w  |     Yes     |     No    | Yes  |   Yes   |
135
+| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`NoCloud/` | -                           | documents-nosync      | r/w  |     Yes     |     No    | No   |   Yes   |
136
+| &nbsp;&nbsp;&nbsp;`Library`                    | -                           | library               | r/w  |     Yes     |     No    | Yes? |   Yes   |
137
+| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`NoCloud/` | dataDirectory               | library-nosync        | r/w  |     Yes     |     No    | No   |   Yes   |
138
+| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`Cloud/`   | syncedDataDirectory         | -                     | r/w  |     Yes     |     No    | Yes  |   Yes   |
139
+| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`Caches/`  | cacheDirectory              | cache                 | r/w  |     Yes*    |  Yes\*\*\*| No   |   Yes   |
140
+| &nbsp;&nbsp;&nbsp;`tmp/`                       | tempDirectory               | -                     | r/w  |     No\*\*  |  Yes\*\*\*| No   |   Yes   |
141
+
142
+
143
+  \* Files persist across app restarts and upgrades, but this directory can
144
+     be cleared whenever the OS desires. Your app should be able to recreate any
145
+     content that might be deleted.
146
+
147
+\*\* Files may persist across app restarts, but do not rely on this behavior. Files
148
+     are not guaranteed to persist across updates. Your app should remove files from
149
+     this directory when it is applicable, as the OS does not guarantee when (or even
150
+     if) these files are removed.
151
+
152
+\*\*\* The OS may clear the contents of this directory whenever it feels it is
153
+     necessary, but do not rely on this. You should clear this directory as
154
+     appropriate for your application.
155
+
156
+### Android File System Layout
157
+
158
+| Device Path                                     | `cordova.file.*`            | `AndroidExtraFileSystems` | r/w? | persistent? | OS clears | private |
159
+|:------------------------------------------------|:----------------------------|:--------------------------|:----:|:-----------:|:---------:|:-------:|
160
+| `file:///android_asset/`                        | applicationDirectory        | assets                    | r    |     N/A     |     N/A   |   Yes   |
161
+| `/data/data/<app-id>/`                          | applicationStorageDirectory | -                         | r/w  |     N/A     |     N/A   |   Yes   |
162
+| &nbsp;&nbsp;&nbsp;`cache`                       | cacheDirectory              | cache                     | r/w  |     Yes     |     Yes\* |   Yes   |
163
+| &nbsp;&nbsp;&nbsp;`files`                       | dataDirectory               | files                     | r/w  |     Yes     |     No    |   Yes   |
164
+| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`Documents` |                             | documents                 | r/w  |     Yes     |     No    |   Yes   |
165
+| `<sdcard>/`                                     | externalRootDirectory       | sdcard                    | r/w  |     Yes     |     No    |   No    |
166
+| &nbsp;&nbsp;&nbsp;`Android/data/<app-id>/`      | externalApplicationStorageDirectory | -                 | r/w  |     Yes     |     No    |   No    |
167
+| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`cache`     | externalCacheDirectory       | cache-external            | r/w  |     Yes     |     No\*\*|   No    |
168
+| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`files`     | externalDataDirectory       | files-external            | r/w  |     Yes     |     No    |   No    |
169
+
170
+\* The OS may periodically clear this directory, but do not rely on this behavior. Clear
171
+   the contents of this directory as appropriate for your application. Should a user
172
+   purge the cache manually, the contents of this directory are removed.
173
+
174
+\*\* The OS does not clear this directory automatically; you are responsible for managing
175
+     the contents yourself. Should the user purge the cache manually, the contents of the
176
+     directory are removed.
177
+
178
+**Note**: If external storage can't be mounted, the `cordova.file.external*`
179
+properties are `null`.
180
+
181
+### OS X File System Layout
182
+
183
+| Device Path                                      | `cordova.file.*`            | `iosExtraFileSystems` | r/w? |  OS clears | private |
184
+|:-------------------------------------------------|:----------------------------|:----------------------|:----:|:---------:|:-------:|
185
+| `/Applications/<appname>.app/`                   | -                           | bundle                | r    |     N/A   |   Yes   |
186
+| &nbsp;&nbsp;&nbsp;&nbsp;`Content/Resources/`     | applicationDirectory        | -                     | r    |     N/A   |   Yes   |
187
+| `~/Library/Application Support/<bundle-id>/`     | applicationStorageDirectory | -                     | r/w  |     No    |   Yes   |
188
+| &nbsp;&nbsp;&nbsp;&nbsp;`files/`                 | dataDirectory               | -                     | r/w  |     No    |   Yes   |
189
+| `~/Documents/`                                   | documentsDirectory          | documents             | r/w  |     No    |    No   |
190
+| `~/Library/Caches/<bundle-id>/`                  | cacheDirectory              | cache                 | r/w  |     No    |   Yes   |
191
+| `/tmp/`                                          | tempDirectory               | -                     | r/w  |    Yes\*  |   Yes   |
192
+| `/`                                              | rootDirectory               | root                  | r/w  |    No\*\* |    No   |
193
+
194
+**Note**: This is the layout for non sandboxed applications. I you enable sandboxing, the `applicationStorageDirectory` will be below ` ~/Library/Containers/<bundle-id>/Data/Library/Application Support`.
195
+
196
+\* Files persist across app restarts and upgrades, but this directory can
197
+     be cleared whenever the OS desires. Your app should be able to recreate any
198
+     content that might be deleted. You should clear this directory as
199
+     appropriate for your application.
200
+
201
+\*\* Allows access to the entire file system. This is only available for non sandboxed apps.
202
+
203
+### Windows File System Layout
204
+
205
+| Device Path                                           | `cordova.file.*`            | r/w? | persistent? | OS clears | private |
206
+|:------------------------------------------------------|:----------------------------|:----:|:-----------:|:---------:|:-------:|
207
+| `ms-appdata:///`                                      | applicationDirectory        | r    |     N/A     |     N/A   |   Yes   |
208
+| &nbsp;&nbsp;&nbsp;`local/`                            | dataDirectory               | r/w  |     Yes     |     No    |   Yes   |
209
+| &nbsp;&nbsp;&nbsp;`temp/`                             | cacheDirectory              | r/w  |     No      |     Yes\* |   Yes   |
210
+| &nbsp;&nbsp;&nbsp;`temp/`                             | tempDirectory               | r/w  |     No      |     Yes\* |   Yes   |
211
+| &nbsp;&nbsp;&nbsp;`roaming/`                          | syncedDataDirectory         | r/w  |     Yes     |     No    |   Yes   |
212
+
213
+\* The OS may periodically clear this directory
214
+
215
+
216
+## Android Quirks
217
+
218
+### Android Persistent storage location
219
+
220
+There are multiple valid locations to store persistent files on an Android
221
+device. See [this page](http://developer.android.com/guide/topics/data/data-storage.html)
222
+for an extensive discussion of the various possibilities.
223
+
224
+Previous versions of the plugin would choose the location of the temporary and
225
+persistent files on startup, based on whether the device claimed that the SD
226
+Card (or equivalent storage partition) was mounted. If the SD Card was mounted,
227
+or if a large internal storage partition was available (such as on Nexus
228
+devices,) then the persistent files would be stored in the root of that space.
229
+This meant that all Cordova apps could see all of the files available on the
230
+card.
231
+
232
+If the SD card was not available, then previous versions would store data under
233
+`/data/data/<packageId>`, which isolates apps from each other, but may still
234
+cause data to be shared between users.
235
+
236
+It is now possible to choose whether to store files in the internal file
237
+storage location, or using the previous logic, with a preference in your
238
+application's `config.xml` file. To do this, add one of these two lines to
239
+`config.xml`:
240
+
241
+    <preference name="AndroidPersistentFileLocation" value="Internal" />
242
+
243
+    <preference name="AndroidPersistentFileLocation" value="Compatibility" />
244
+
245
+Without this line, the File plugin will use `Internal` as the default. If
246
+a preference tag is present, and is not one of these values, the application
247
+will not start.
248
+
249
+If your application has previously been shipped to users, using an older (pre-
250
+3.0.0) version of this plugin, and has stored files in the persistent filesystem,
251
+then you should set the preference to `Compatibility` if your config.xml does not specify a location for the persistent filesystem. Switching the location to
252
+"Internal" would mean that existing users who upgrade their application may be
253
+unable to access their previously-stored files, depending on their device.
254
+
255
+If your application is new, or has never previously stored files in the
256
+persistent filesystem, then the `Internal` setting is generally recommended.
257
+
258
+### Slow recursive operations for /android_asset
259
+
260
+Listing asset directories is really slow on Android. You can speed it up though, by
261
+adding `src/android/build-extras.gradle` to the root of your android project (also
262
+requires cordova-android@4.0.0 or greater).
263
+
264
+### Permisson to write to external storage when it's not mounted on Marshmallow
265
+
266
+Marshmallow requires the apps to ask for permissions when reading/writing to external locations. By
267
+[default](http://developer.android.com/guide/topics/data/data-storage.html#filesExternal), your app has permission to write to
268
+`cordova.file.applicationStorageDirectory` and `cordova.file.externalApplicationStorageDirectory`, and the plugin doesn't request permission
269
+for these two directories unless external storage is not mounted. However due to a limitation, when external storage is not mounted, it would ask for
270
+permission to write to `cordova.file.externalApplicationStorageDirectory`.
271
+
272
+## iOS Quirks
273
+
274
+- `cordova.file.applicationStorageDirectory` is read-only; attempting to store
275
+  files within the root directory will fail. Use one of the other `cordova.file.*`
276
+  properties defined for iOS (only `applicationDirectory` and `applicationStorageDirectory` are
277
+  read-only).
278
+- `FileReader.readAsText(blob, encoding)`
279
+  - The `encoding` parameter is not supported, and UTF-8 encoding is always in effect.
280
+
281
+### iOS Persistent storage location
282
+
283
+There are two valid locations to store persistent files on an iOS device: the
284
+Documents directory and the Library directory. Previous versions of the plugin
285
+only ever stored persistent files in the Documents directory. This had the
286
+side-effect of making all of an application's files visible in iTunes, which
287
+was often unintended, especially for applications which handle lots of small
288
+files, rather than producing complete documents for export, which is the
289
+intended purpose of the directory.
290
+
291
+It is now possible to choose whether to store files in the documents or library
292
+directory, with a preference in your application's `config.xml` file. To do this,
293
+add one of these two lines to `config.xml`:
294
+
295
+    <preference name="iosPersistentFileLocation" value="Library" />
296
+
297
+    <preference name="iosPersistentFileLocation" value="Compatibility" />
298
+
299
+Without this line, the File plugin will use `Compatibility` as the default. If
300
+a preference tag is present, and is not one of these values, the application
301
+will not start.
302
+
303
+If your application has previously been shipped to users, using an older (pre-
304
+1.0) version of this plugin, and has stored files in the persistent filesystem,
305
+then you should set the preference to `Compatibility`. Switching the location to
306
+`Library` would mean that existing users who upgrade their application would be
307
+unable to access their previously-stored files.
308
+
309
+If your application is new, or has never previously stored files in the
310
+persistent filesystem, then the `Library` setting is generally recommended.
311
+
312
+## Browser Quirks
313
+
314
+### Common quirks and remarks
315
+- Each browser uses its own sandboxed filesystem. IE and Firefox use IndexedDB as a base.
316
+All browsers use forward slash as directory separator in a path.
317
+- Directory entries have to be created successively.
318
+For example, the call `fs.root.getDirectory('dir1/dir2', {create:true}, successCallback, errorCallback)`
319
+will fail if dir1 did not exist.
320
+- The plugin requests user permission to use persistent storage at the application first start.
321
+- Plugin supports `cdvfile://localhost` (local resources) only. I.e. external resources are not supported via `cdvfile`.
322
+- The plugin does not follow ["File System API 8.3 Naming restrictions"](http://www.w3.org/TR/2011/WD-file-system-api-20110419/#naming-restrictions).
323
+- Blob and File' `close` function is not supported.
324
+- `FileSaver` and `BlobBuilder` are not supported by this plugin and don't have stubs.
325
+- The plugin does not support `requestAllFileSystems`. This function is also missing in the specifications.
326
+- Entries in directory will not be removed if you use `create: true` flag for existing directory.
327
+- Files created via constructor are not supported. You should use entry.file method instead.
328
+- Each browser uses its own form for blob URL references.
329
+- `readAsDataURL` function is supported, but the mediatype in Chrome depends on entry name extension,
330
+mediatype in IE is always empty (which is the same as `text-plain` according the specification),
331
+the mediatype in Firefox is always `application/octet-stream`.
332
+For example, if the content is `abcdefg` then Firefox returns `data:application/octet-stream;base64,YWJjZGVmZw==`,
333
+IE returns `data:;base64,YWJjZGVmZw==`, Chrome returns `data:<mediatype depending on extension of entry name>;base64,YWJjZGVmZw==`.
334
+- `toInternalURL` returns the path in the form `file:///persistent/path/to/entry` (Firefox, IE).
335
+Chrome returns the path in the form `cdvfile://localhost/persistent/file`.
336
+
337
+### Chrome quirks
338
+- Chrome filesystem is not immediately ready after device ready event. As a workaround you can subscribe to `filePluginIsReady` event.
339
+Example:
340
+```javascript
341
+window.addEventListener('filePluginIsReady', function(){ console.log('File plugin is ready');}, false);
342
+```
343
+You can use `window.isFilePluginReadyRaised` function to check whether event was already raised.
344
+- window.requestFileSystem TEMPORARY and PERSISTENT filesystem quotas are not limited in Chrome.
345
+- To increase persistent storage in Chrome you need to call `window.initPersistentFileSystem` method. Persistent storage quota is 5 MB by default.
346
+- Chrome requires `--allow-file-access-from-files` run argument to support API via `file:///` protocol.
347
+- `File` object will be not changed if you use flag `{create:true}` when getting an existing `Entry`.
348
+- events `cancelable` property is set to true in Chrome. This is contrary to the [specification](http://dev.w3.org/2009/dap/file-system/file-writer.html).
349
+- `toURL` function in Chrome returns `filesystem:`-prefixed path depending on application host.
350
+For example, `filesystem:file:///persistent/somefile.txt`, `filesystem:http://localhost:8080/persistent/somefile.txt`.
351
+- `toURL` function result does not contain trailing slash in case of directory entry.
352
+Chrome resolves directories with slash-trailed urls correctly though.
353
+- `resolveLocalFileSystemURL` method requires the inbound `url` to have `filesystem` prefix. For example, `url` parameter for `resolveLocalFileSystemURL`
354
+should be in the form `filesystem:file:///persistent/somefile.txt` as opposed to the form `file:///persistent/somefile.txt` in Android.
355
+- Deprecated `toNativeURL` function is not supported and does not have a stub.
356
+- `setMetadata` function is not stated in the specifications and not supported.
357
+- INVALID_MODIFICATION_ERR (code: 9) is thrown instead of SYNTAX_ERR(code: 8) on requesting of a non-existant filesystem.
358
+- INVALID_MODIFICATION_ERR (code: 9) is thrown instead of PATH_EXISTS_ERR(code: 12) on trying to exclusively create a file or directory, which already exists.
359
+- INVALID_MODIFICATION_ERR (code: 9) is thrown instead of  NO_MODIFICATION_ALLOWED_ERR(code: 6) on trying to call removeRecursively on the root file system.
360
+- INVALID_MODIFICATION_ERR (code: 9) is thrown instead of NOT_FOUND_ERR(code: 1) on trying to moveTo directory that does not exist.
361
+
362
+### IndexedDB-based impl quirks (Firefox and IE)
363
+- `.` and `..` are not supported.
364
+- IE does not support `file:///`-mode; only hosted mode is supported (http://localhost:xxxx).
365
+- Firefox filesystem size is not limited but each 50MB extension will request a user permission.
366
+IE10 allows up to 10mb of combined AppCache and IndexedDB used in implementation of filesystem without prompting,
367
+once you hit that level you will be asked if you want to allow it to be increased up to a max of 250mb per site.
368
+So `size` parameter for `requestFileSystem` function does not affect filesystem in Firefox and IE.
369
+- `readAsBinaryString` function is not stated in the Specs and not supported in IE and does not have a stub.
370
+- `file.type` is always null.
371
+- You should not create entry using DirectoryEntry instance callback result which was deleted.
372
+Otherwise, you will get a 'hanging entry'.
373
+- Before you can read a file, which was just written you need to get a new instance of this file.
374
+- `setMetadata` function, which is not stated in the Specs supports `modificationTime` field change only.
375
+- `copyTo` and `moveTo` functions do not support directories.
376
+- Directories metadata is not supported.
377
+- Both Entry.remove and directoryEntry.removeRecursively don't fail when removing
378
+non-empty directories - directories being removed are cleaned along with contents instead.
379
+- `abort` and `truncate` functions are not supported.
380
+- progress events are not fired. For example, this handler will be not executed:
381
+```javascript
382
+writer.onprogress = function() { /*commands*/ };
383
+```
384
+
385
+## Upgrading Notes
386
+
387
+In v1.0.0 of this plugin, the `FileEntry` and `DirectoryEntry` structures have changed,
388
+to be more in line with the published specification.
389
+
390
+Previous (pre-1.0.0) versions of the plugin stored the device-absolute-file-location
391
+in the `fullPath` property of `Entry` objects. These paths would typically look like
392
+
393
+    /var/mobile/Applications/<application UUID>/Documents/path/to/file  (iOS)
394
+    /storage/emulated/0/path/to/file                                    (Android)
395
+
396
+These paths were also returned by the `toURL()` method of the `Entry` objects.
397
+
398
+With v1.0.0, the `fullPath` attribute is the path to the file, _relative to the root of
399
+the HTML filesystem_. So, the above paths would now both be represented by a `FileEntry`
400
+object with a `fullPath` of
401
+
402
+    /path/to/file
403
+
404
+If your application works with device-absolute-paths, and you previously retrieved those
405
+paths through the `fullPath` property of `Entry` objects, then you should update your code
406
+to use `entry.toURL()` instead.
407
+
408
+For backwards compatibility, the `resolveLocalFileSystemURL()` method will accept a
409
+device-absolute-path, and will return an `Entry` object corresponding to it, as long as that
410
+file exists within either the `TEMPORARY` or `PERSISTENT` filesystems.
411
+
412
+This has particularly been an issue with the File-Transfer plugin, which previously used
413
+device-absolute-paths (and can still accept them). It has been updated to work correctly
414
+with FileSystem URLs, so replacing `entry.fullPath` with `entry.toURL()` should resolve any
415
+issues getting that plugin to work with files on the device.
416
+
417
+In v1.1.0 the return value of `toURL()` was changed (see [CB-6394](https://issues.apache.org/jira/browse/CB-6394))
418
+to return an absolute 'file://' URL. wherever possible. To ensure a 'cdvfile:'-URL you can use `toInternalURL()` now.
419
+This method will now return filesystem URLs of the form
420
+
421
+    cdvfile://localhost/persistent/path/to/file
422
+
423
+which can be used to identify the file uniquely.
424
+
425
+## cdvfile protocol
426
+**Purpose**
427
+
428
+`cdvfile://localhost/persistent|temporary|another-fs-root*/path/to/file` can be used for platform-independent file paths.
429
+cdvfile paths are supported by core plugins - for example you can download an mp3 file to cdvfile-path via `cordova-plugin-file-transfer` and play it via `cordova-plugin-media`.
430
+
431
+__*Note__: See [Where to Store Files](#where-to-store-files), [File System Layouts](#file-system-layouts) and [Configuring the Plugin](#configuring-the-plugin-optional) for more details about available fs roots.
432
+
433
+To use `cdvfile` as a tag' `src` you can convert it to native path via `toURL()` method of the resolved fileEntry, which you can get via `resolveLocalFileSystemURL` - see examples below.
434
+
435
+You can also use `cdvfile://` paths directly in the DOM, for example:
436
+```HTML
437
+<img src="cdvfile://localhost/persistent/img/logo.png" />
438
+```
439
+
440
+__Note__: This method requires following Content Security rules updates:
441
+* Add `cdvfile:` scheme to `Content-Security-Policy` meta tag of the index page, e.g.:
442
+  - `<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: `**cdvfile:**` https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">`
443
+* Add `<access origin="cdvfile://*" />` to `config.xml`.
444
+
445
+**Converting cdvfile:// to native path**
446
+
447
+```javascript
448
+resolveLocalFileSystemURL('cdvfile://localhost/temporary/path/to/file.mp4', function(entry) {
449
+    var nativePath = entry.toURL();
450
+    console.log('Native URI: ' + nativePath);
451
+    document.getElementById('video').src = nativePath;
452
+```
453
+
454
+**Converting native path to cdvfile://**
455
+
456
+```javascript
457
+resolveLocalFileSystemURL(nativePath, function(entry) {
458
+    console.log('cdvfile URI: ' + entry.toInternalURL());
459
+```
460
+
461
+**Using cdvfile in core plugins**
462
+
463
+```javascript
464
+fileTransfer.download(uri, 'cdvfile://localhost/temporary/path/to/file.mp3', function (entry) { ...
465
+```
466
+```javascript
467
+var my_media = new Media('cdvfile://localhost/temporary/path/to/file.mp3', ...);
468
+my_media.play();
469
+```
470
+
471
+#### cdvfile quirks
472
+- Using `cdvfile://` paths in the DOM is not supported on Windows platform (a path can be converted to native instead).
473
+
474
+
475
+## List of Error Codes and Meanings
476
+When an error is thrown, one of the following codes will be used.
477
+
478
+| Code | Constant                      |
479
+|-----:|:------------------------------|
480
+|    1 | `NOT_FOUND_ERR`               |
481
+|    2 | `SECURITY_ERR`                |
482
+|    3 | `ABORT_ERR`                   |
483
+|    4 | `NOT_READABLE_ERR`            |
484
+|    5 | `ENCODING_ERR`                |
485
+|    6 | `NO_MODIFICATION_ALLOWED_ERR` |
486
+|    7 | `INVALID_STATE_ERR`           |
487
+|    8 | `SYNTAX_ERR`                  |
488
+|    9 | `INVALID_MODIFICATION_ERR`    |
489
+|   10 | `QUOTA_EXCEEDED_ERR`          |
490
+|   11 | `TYPE_MISMATCH_ERR`           |
491
+|   12 | `PATH_EXISTS_ERR`             |
492
+
493
+## Configuring the Plugin (Optional)
494
+
495
+The set of available filesystems can be configured per-platform. Both iOS and
496
+Android recognize a <preference> tag in `config.xml` which names the
497
+filesystems to be installed. By default, all file-system roots are enabled.
498
+
499
+    <preference name="iosExtraFilesystems" value="library,library-nosync,documents,documents-nosync,cache,bundle,root" />
500
+    <preference name="AndroidExtraFilesystems" value="files,files-external,documents,sdcard,cache,cache-external,assets,root" />
501
+
502
+### Android
503
+
504
+* `files`: The application's internal file storage directory
505
+* `files-external`: The application's external file storage directory
506
+* `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.
507
+* `cache`: The application's internal cache directory
508
+* `cache-external`: The application's external cache directory
509
+* `assets`: The application's bundle (read-only)
510
+* `root`: The entire device filesystem
511
+* `applicationDirectory`: ReadOnly with restricted access. Copying files in this directory is possible, but reading it directly results in 'file not found'.
512
+Android also supports a special filesystem named "documents", which represents a "/Documents/" subdirectory within the "files" filesystem.
513
+
514
+### iOS
515
+
516
+* `library`: The application's Library directory
517
+* `documents`: The application's Documents directory
518
+* `cache`: The application's Cache directory
519
+* `bundle`: The application's bundle; the location of the app itself on disk (read-only)
520
+* `root`: The entire device filesystem
521
+
522
+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.
523
+
524
+## Sample: Create Files and Directories, Write, Read, and Append files <a name="sample"></a>
525
+
526
+The File plugin allows you to do things like store files in a temporary or persistent storage location for your app (sandboxed storage) and to store files in other platform-dependent locations. The code snippets in this section demonstrate different tasks including:
527
+* [Accessing the file system](#persistent)
528
+* Using cross-platform Cordova file URLs to [store your files](#appendFile) (see _Where to Store Files_ for more info)
529
+* Creating [files](#persistent) and [directories](#createDir)
530
+* [Writing to files](#writeFile)
531
+* [Reading files](#readFile)
532
+* [Appending files](#appendFile)
533
+* [Display an image file](#displayImage)
534
+
535
+## Create a persistent file <a name="persistent"></a>
536
+
537
+Before you use the File plugin APIs, you can get access to the file system using `requestFileSystem`. When you do this, you can request either persistent or temporary storage. Persistent storage will not be removed unless permission is granted by the user.
538
+
539
+When you get file system access using `requestFileSystem`, access is granted for the sandboxed file system only (the sandbox limits access to the app itself), not for general access to any file system location on the device. (To access file system locations outside the sandboxed storage, use other methods such as window.resolveLocalFileSystemURL, which support platform-specific locations. For one example of this, see _Append a File_.)
540
+
541
+Here is a request for persistent storage.
542
+
543
+>*Note* When targeting WebView clients (instead of a browser) or native apps (Windows), you dont need to use `requestQuota` before using persistent storage.
544
+
545
+```js
546
+window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fs) {
547
+
548
+    console.log('file system open: ' + fs.name);
549
+    fs.root.getFile("newPersistentFile.txt", { create: true, exclusive: false }, function (fileEntry) {
550
+
551
+        console.log("fileEntry is file?" + fileEntry.isFile.toString());
552
+        // fileEntry.name == 'someFile.txt'
553
+        // fileEntry.fullPath == '/someFile.txt'
554
+        writeFile(fileEntry, null);
555
+
556
+    }, onErrorCreateFile);
557
+
558
+}, onErrorLoadFs);
559
+```
560
+
561
+The success callback receives FileSystem object (fs). Use `fs.root` to return a DirectoryEntry object, which you can use to create or get a file (by calling `getFile`). In this example, `fs.root` is a DirectoryEntry object that represents the persistent storage in the sandboxed file system.
562
+
563
+The success callback for `getFile` receives a FileEntry object. You can use this to perform file write and file read operations.
564
+
565
+## Create a temporary file
566
+
567
+Here is an example of a request for temporary storage. Temporary storage may be deleted by the operating system if the device runs low on memory.
568
+
569
+```js
570
+window.requestFileSystem(window.TEMPORARY, 5 * 1024 * 1024, function (fs) {
571
+
572
+    console.log('file system open: ' + fs.name);
573
+    createFile(fs.root, "newTempFile.txt", false);
574
+
575
+}, onErrorLoadFs);
576
+```
577
+When you are using temporary storage, you can create or get the file by calling `getFile`. As in the persistent storage example, this will give you a FileEntry object that you can use for read or write operations.
578
+
579
+```js
580
+function createFile(dirEntry, fileName, isAppend) {
581
+    // Creates a new file or returns the file if it already exists.
582
+    dirEntry.getFile(fileName, {create: true, exclusive: false}, function(fileEntry) {
583
+
584
+        writeFile(fileEntry, null, isAppend);
585
+
586
+    }, onErrorCreateFile);
587
+
588
+}
589
+```
590
+
591
+## Write to a file <a name="writeFile"></a>
592
+
593
+Once you have a FileEntry object, you can write to the file by calling `createWriter`, which returns a FileWriter object in the success callback. Call the `write` method of FileWriter to write to the file.
594
+
595
+```js
596
+function writeFile(fileEntry, dataObj) {
597
+    // Create a FileWriter object for our FileEntry (log.txt).
598
+    fileEntry.createWriter(function (fileWriter) {
599
+
600
+        fileWriter.onwriteend = function() {
601
+            console.log("Successful file write...");
602
+            readFile(fileEntry);
603
+        };
604
+
605
+        fileWriter.onerror = function (e) {
606
+            console.log("Failed file write: " + e.toString());
607
+        };
608
+
609
+        // If data object is not passed in,
610
+        // create a new Blob instead.
611
+        if (!dataObj) {
612
+            dataObj = new Blob(['some file data'], { type: 'text/plain' });
613
+        }
614
+
615
+        fileWriter.write(dataObj);
616
+    });
617
+}
618
+```
619
+
620
+## Read a file <a name="readFile"></a>
621
+
622
+You also need a FileEntry object to read an existing file. Use the file property of FileEntry to get the file reference, and then create a new FileReader object. You can use methods like `readAsText` to start the read operation. When the read operation is complete, `this.result` stores the result of the read operation.
623
+
624
+```js
625
+function readFile(fileEntry) {
626
+
627
+    fileEntry.file(function (file) {
628
+        var reader = new FileReader();
629
+
630
+        reader.onloadend = function() {
631
+            console.log("Successful file read: " + this.result);
632
+            displayFileData(fileEntry.fullPath + ": " + this.result);
633
+        };
634
+
635
+        reader.readAsText(file);
636
+
637
+    }, onErrorReadFile);
638
+}
639
+```
640
+
641
+## Append a file using alternative methods <a name="appendFile"></a>
642
+
643
+Of course, you will often want to append existing files instead of creating new ones. Here is an example of that. This example shows another way that you can access the file system using window.resolveLocalFileSystemURL. In this example, pass the cross-platform Cordova file URL, cordova.file.dataDirectory, to the function. The success callback receives a DirectoryEntry object, which you can use to do things like create a file.
644
+
645
+```js
646
+window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function (dirEntry) {
647
+    console.log('file system open: ' + dirEntry.name);
648
+    var isAppend = true;
649
+    createFile(dirEntry, "fileToAppend.txt", isAppend);
650
+}, onErrorLoadFs);
651
+```
652
+
653
+In addition to this usage, you can use `resolveLocalFileSystemURL` to get access to some file system locations that are not part of the sandboxed storage system. See _Where to store Files_ for more information; many of these storage locations are platform-specific. You can also pass cross-platform file system locations to `resolveLocalFileSystemURL` using the _cdvfile protocol_.
654
+
655
+For the append operation, there is nothing new in the `createFile` function that is called in the preceding code (see the preceding examples for the actual code). `createFile` calls `writeFile`. In `writeFile`, you check whether an append operation is requested.
656
+
657
+Once you have a FileWriter object, call the `seek` method, and pass in the index value for the position where you want to write. In this example, you also test whether the file exists. After calling seek, then call the write method of FileWriter.
658
+
659
+```js
660
+function writeFile(fileEntry, dataObj, isAppend) {
661
+    // Create a FileWriter object for our FileEntry (log.txt).
662
+    fileEntry.createWriter(function (fileWriter) {
663
+
664
+        fileWriter.onwriteend = function() {
665
+            console.log("Successful file read...");
666
+            readFile(fileEntry);
667
+        };
668
+
669
+        fileWriter.onerror = function (e) {
670
+            console.log("Failed file read: " + e.toString());
671
+        };
672
+
673
+        // If we are appending data to file, go to the end of the file.
674
+        if (isAppend) {
675
+            try {
676
+                fileWriter.seek(fileWriter.length);
677
+            }
678
+            catch (e) {
679
+                console.log("file doesn't exist!");
680
+            }
681
+        }
682
+        fileWriter.write(dataObj);
683
+    });
684
+}
685
+```
686
+
687
+## Store an existing binary file <a name="binaryFile"></a>
688
+
689
+We already showed how to write to a file that you just created in the sandboxed file system. What if you need to get access to an existing file and convert that to something you can store on your device? In this example, you obtain a file using an xhr request, and then save it to the cache in the sandboxed file system.
690
+
691
+Before you get the file, get a FileSystem reference using `requestFileSystem`. By passing window.TEMPORARY in the method call (same as before), the returned FileSystem object (fs) represents the cache in the sandboxed file system. Use `fs.root` to get the DirectoryEntry object that you need.
692
+
693
+```js
694
+window.requestFileSystem(window.TEMPORARY, 5 * 1024 * 1024, function (fs) {
695
+
696
+    console.log('file system open: ' + fs.name);
697
+    getSampleFile(fs.root);
698
+
699
+}, onErrorLoadFs);
700
+```
701
+
702
+For completeness, here is the xhr request to get a Blob image. There is nothing Cordova-specific in this code, except that you forward the DirectoryEntry reference that you already obtained as an argument to the saveFile function. You will save the blob image and display it later after reading the file (to validate the operation).
703
+
704
+```js
705
+function getSampleFile(dirEntry) {
706
+
707
+    var xhr = new XMLHttpRequest();
708
+    xhr.open('GET', 'http://cordova.apache.org/static/img/cordova_bot.png', true);
709
+    xhr.responseType = 'blob';
710
+
711
+    xhr.onload = function() {
712
+        if (this.status == 200) {
713
+
714
+            var blob = new Blob([this.response], { type: 'image/png' });
715
+            saveFile(dirEntry, blob, "downloadedImage.png");
716
+        }
717
+    };
718
+    xhr.send();
719
+}
720
+```
721
+>*Note* For Cordova 5 security, the preceding code requires that you add the domain name, http://cordova.apache.org, to the Content-Security-Policy <meta> element in index.html.
722
+
723
+After getting the file, copy the contents to a new file. The current DirectoryEntry object is already associated with the app cache.
724
+
725
+```js
726
+function saveFile(dirEntry, fileData, fileName) {
727
+
728
+    dirEntry.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) {
729
+
730
+        writeFile(fileEntry, fileData);
731
+
732
+    }, onErrorCreateFile);
733
+}
734
+```
735
+
736
+In writeFile, you pass in the Blob object as the dataObj and you will save that in the new file.
737
+
738
+```js
739
+function writeFile(fileEntry, dataObj, isAppend) {
740
+
741
+    // Create a FileWriter object for our FileEntry (log.txt).
742
+    fileEntry.createWriter(function (fileWriter) {
743
+
744
+        fileWriter.onwriteend = function() {
745
+            console.log("Successful file write...");
746
+            if (dataObj.type == "image/png") {
747
+                readBinaryFile(fileEntry);
748
+            }
749
+            else {
750
+                readFile(fileEntry);
751
+            }
752
+        };
753
+
754
+        fileWriter.onerror = function(e) {
755
+            console.log("Failed file write: " + e.toString());
756
+        };
757
+
758
+        fileWriter.write(dataObj);
759
+    });
760
+}
761
+```
762
+
763
+After writing to the file, read it and display it. You saved the image as binary data, so you can read it using FileReader.readAsArrayBuffer.
764
+
765
+```js
766
+function readBinaryFile(fileEntry) {
767
+
768
+    fileEntry.file(function (file) {
769
+        var reader = new FileReader();
770
+
771
+        reader.onloadend = function() {
772
+
773
+            console.log("Successful file write: " + this.result);
774
+            displayFileData(fileEntry.fullPath + ": " + this.result);
775
+
776
+            var blob = new Blob([new Uint8Array(this.result)], { type: "image/png" });
777
+            displayImage(blob);
778
+        };
779
+
780
+        reader.readAsArrayBuffer(file);
781
+
782
+    }, onErrorReadFile);
783
+}
784
+```
785
+
786
+After reading the data, you can display the image using code like this. Use window.URL.createObjectURL to get a DOM string for the Blob image.
787
+
788
+```js
789
+function displayImage(blob) {
790
+
791
+    // Displays image if result is a valid DOM string for an image.
792
+    var elem = document.getElementById('imageFile');
793
+    // Note: Use window.URL.revokeObjectURL when finished with image.
794
+    elem.src = window.URL.createObjectURL(blob);
795
+}
796
+```
797
+
798
+## Display an image file <a name="displayImage"></a>
799
+
800
+To display an image using a FileEntry, you can call the `toURL` method.
801
+
802
+```js
803
+function displayImageByFileURL(fileEntry) {
804
+    var elem = document.getElementById('imageFile');
805
+    elem.src = fileEntry.toURL();
806
+}
807
+```
808
+
809
+If you are using some platform-specific URIs instead of a FileEntry and you want to display an image, you may need to include the main part of the URI in the Content-Security-Policy <meta> element in index.html. For example, on Windows 10, you can include `ms-appdata:` in your <meta> element. Here is an example.
810
+
811
+```html
812
+<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: ms-appdata: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
813
+```
814
+
815
+## Create Directories <a name="createDir"></a>
816
+
817
+In the code here, you create directories in the root of the app storage location. You could use this code with any writable storage location (that is, any DirectoryEntry). Here, you write to the application cache (assuming that you used window.TEMPORARY to get your FileSystem object) by passing fs.root into this function.
818
+
819
+This code creates the /NewDirInRoot/images folder in the application cache. For platform-specific values, look at _File System Layouts_.
820
+
821
+```js
822
+function createDirectory(rootDirEntry) {
823
+    rootDirEntry.getDirectory('NewDirInRoot', { create: true }, function (dirEntry) {
824
+        dirEntry.getDirectory('images', { create: true }, function (subDirEntry) {
825
+
826
+            createFile(subDirEntry, "fileInNewSubDir.txt");
827
+
828
+        }, onErrorGetDir);
829
+    }, onErrorGetDir);
830
+}
831
+```
832
+
833
+When creating subfolders, you need to create each folder separately as shown in the preceding code.

+ 496
- 0
plugins/cordova-plugin-file/RELEASENOTES.md View File

@@ -0,0 +1,496 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Release Notes
22
+
23
+### 6.0.2 (Jun 27, 2019)
24
+
25
+-   fix: manually fix line endings of some files that were using CRLF ([`e8e06c3`](https://github.com/apache/cordova-plugin-file/commit/e8e06c3))
26
+-   chore: fix repo and issue urls and license in package.json and plugin.xml ([`1bca166`](https://github.com/apache/cordova-plugin-file/commit/1bca166))
27
+-   docs: remove outdated translations ([`a898b85`](https://github.com/apache/cordova-plugin-file/commit/a898b85))
28
+-   build: add `.gitattributes` to force LF (instead of possible CRLF on Windows) ([`d691a04`](https://github.com/apache/cordova-plugin-file/commit/d691a04))
29
+-   build: add `.npmignore` to remove unneeded files from npm package ([`a255202`](https://github.com/apache/cordova-plugin-file/commit/a255202))
30
+-   ci(travis): Update Travis CI configuration for new paramedic ([#314](https://github.com/apache/cordova-plugin-file/issues/314)) ([`2b93a67`](https://github.com/apache/cordova-plugin-file/commit/2b93a67))
31
+-   fix(browser): support safari private browsing mode by using base64 text instead of blob ([#301](https://github.com/apache/cordova-plugin-file/issues/301)) ([`c609ff6`](https://github.com/apache/cordova-plugin-file/commit/c609ff6))
32
+-   chore(github): Add or update GitHub pull request and issue template ([`b762743`](https://github.com/apache/cordova-plugin-file/commit/b762743))
33
+-   fix(types): CB-13569: fix inverted LocalFileSystem enum ([#274](https://github.com/apache/cordova-plugin-file/issues/274)) ([`d135cd0`](https://github.com/apache/cordova-plugin-file/commit/d135cd0))
34
+-   docs: remove JIRA link ([`341fa9c`](https://github.com/apache/cordova-plugin-file/commit/341fa9c))
35
+-   fix(types): CB-13850: Fix spelling in typed-file: property name filesystem (wrong… ([#229](https://github.com/apache/cordova-plugin-file/issues/229)) ([`4642fde`](https://github.com/apache/cordova-plugin-file/commit/4642fde))
36
+-   fix(types): CB-13960: fix FileWriter.write argument type definition for typescript ([#231](https://github.com/apache/cordova-plugin-file/issues/231)) ([`5353b84`](https://github.com/apache/cordova-plugin-file/commit/5353b84))
37
+-   ci(travis): also accept terms for android sdk `android-27` ([`6e7871b`](https://github.com/apache/cordova-plugin-file/commit/6e7871b))
38
+-   docs: add details for `applicationDirectory` on android ([#234](https://github.com/apache/cordova-plugin-file/issues/234)) ([`c2f5216`](https://github.com/apache/cordova-plugin-file/commit/c2f5216))
39
+-   fix: require FileReader in FileWriter to fix 'write' exec not being called ([#237](https://github.com/apache/cordova-plugin-file/issues/237)) ([`4a92bbb`](https://github.com/apache/cordova-plugin-file/commit/4a92bbb))
40
+-   fix(android): CB-14181: (android) Fix bug - Cannot read property 'filesystemName' of null ([#235](https://github.com/apache/cordova-plugin-file/issues/235)) ([`cc3aedb`](https://github.com/apache/cordova-plugin-file/commit/cc3aedb))
41
+-   ci(travis): CB-13753: Add build-tools-26.0.2 to travis ([#228](https://github.com/apache/cordova-plugin-file/issues/228)) ([`d8cc0fd`](https://github.com/apache/cordova-plugin-file/commit/d8cc0fd), [`5e12b5e`](https://github.com/apache/cordova-plugin-file/commit/5e12b5e))
42
+-   chore: Fix release notes ([#227](https://github.com/apache/cordova-plugin-file/issues/227)) ([`c248827`](https://github.com/apache/cordova-plugin-file/commit/c248827))
43
+
44
+
45
+### 6.0.1 (Dec 27, 2017)
46
+* [CB-13704](https://issues.apache.org/jira/browse/CB-13704) Fix to allow 6.0.0 version install
47
+
48
+### 6.0.0 (Dec 15, 2017)
49
+* [CB-13668](https://issues.apache.org/jira/browse/CB-13668) Remove deprecated platforms
50
+
51
+### 5.0.0 (Nov 06, 2017)
52
+* [CB-13481](https://issues.apache.org/jira/browse/CB-13481) (android) Don't ask for permission to read `file:///android_asset/`
53
+* [CB-13518](https://issues.apache.org/jira/browse/CB-13518) Add 'protective' entry to `cordovaDependencies`
54
+* [CB-13472](https://issues.apache.org/jira/browse/CB-13472) (CI) Fixed Travis **Android** builds again
55
+* [CB-13294](https://issues.apache.org/jira/browse/CB-13294) Remove `cordova-plugin-compat`
56
+* fixing `README` in use of `window.resolveLocalFileSystemURL`
57
+* [CB-12895](https://issues.apache.org/jira/browse/CB-12895) setup `eslint` and took out `jshint`
58
+* [CB-13028](https://issues.apache.org/jira/browse/CB-13028) (CI) **Browser** builds on Travis
59
+* [CB-13000](https://issues.apache.org/jira/browse/CB-13000) (CI) Speed up **Android** builds
60
+* [CB-12355](https://issues.apache.org/jira/browse/CB-12355) (iOS) add desciption about the `mimeTypeForFileAtPath` method
61
+* [CB-12355](https://issues.apache.org/jira/browse/CB-12355) (iOS) fix `FileEntry.file.type`
62
+* [CB-12847](https://issues.apache.org/jira/browse/CB-12847) added `bugs` entry to `package.json`.
63
+
64
+### 4.3.3 (Apr 27, 2017)
65
+* [CB-12622](https://issues.apache.org/jira/browse/CB-12622) Added **Android 6.0** build badge to `README`
66
+* [CB-12685](https://issues.apache.org/jira/browse/CB-12685) added `package.json` to tests folder
67
+
68
+### 4.3.2 (Feb 28, 2017)
69
+* [CB-12353](https://issues.apache.org/jira/browse/CB-12353) Corrected merges usage in `plugin.xml`
70
+* [CB-12369](https://issues.apache.org/jira/browse/CB-12369) Add plugin typings from `DefinitelyTyped`
71
+* [CB-12363](https://issues.apache.org/jira/browse/CB-12363) Added build badges for **iOS 9.3** and **iOS 10.0**
72
+* [CB-12230](https://issues.apache.org/jira/browse/CB-12230) Removed **Windows 8.1** build badges
73
+
74
+### 4.3.1 (Dec 07, 2016)
75
+* [CB-12224](https://issues.apache.org/jira/browse/CB-12224) Updated version and RELEASENOTES.md for release 4.3.1
76
+* [CB-12112](https://issues.apache.org/jira/browse/CB-12112) windows: Make available to move folder trees
77
+* fix ENCODING_ERR for applicationDirectory
78
+* [CB-11848](https://issues.apache.org/jira/browse/CB-11848) windows: Remove duplicate slash after file system path
79
+* [CB-11917](https://issues.apache.org/jira/browse/CB-11917) - Remove pull request template checklist item: "iCLA has been submitted…"
80
+* [CB-11947](https://issues.apache.org/jira/browse/CB-11947) fixed typo that occurs when adding file-transfer plugin
81
+* [CB-11832](https://issues.apache.org/jira/browse/CB-11832) Incremented plugin version.
82
+
83
+### 4.3.0 (Sep 08, 2016)
84
+* [CB-11795](https://issues.apache.org/jira/browse/CB-11795) Add 'protective' entry to cordovaDependencies
85
+* Add handling for `SecurityException`
86
+* [CB-11368](https://issues.apache.org/jira/browse/CB-11368) **android**: Resolve content `URLs` produced by contacts plugin
87
+* Plugin uses `Android Log class` and not `Cordova LOG class`
88
+* [CB-11693](https://issues.apache.org/jira/browse/CB-11693) **ios**: Run copy and move operations in the background thread
89
+* [CB-11699](https://issues.apache.org/jira/browse/CB-11699) Read files as Data URLs properly
90
+* [CB-11305](https://issues.apache.org/jira/browse/CB-11305) Enable `cdvfile: assets fs root` for `DOM` requests
91
+* [CB-11385](https://issues.apache.org/jira/browse/CB-11385) android: Import java.nio.charset.Charset in LocalFileSystem class
92
+* Add badges for paramedic builds on Jenkins
93
+* [CB-11407](https://issues.apache.org/jira/browse/CB-11407) ios: added extern keyword to constants to fix phonegap-webview-ios template issue.
94
+* [CB-11385](https://issues.apache.org/jira/browse/CB-11385) **android**: Does not pass sonarqube scan
95
+* Add pull request template.
96
+* Minor edits to the `README.md`
97
+* [CB-11142](https://issues.apache.org/jira/browse/CB-11142) Fix the `NeedPermission` code for the case when external media is not mounted in Android
98
+* [CB-11003](https://issues.apache.org/jira/browse/CB-11003) Adding samples to Readme.
99
+* [CB-10996](https://issues.apache.org/jira/browse/CB-10996) Adding front matter to README.md
100
+* [CB-11115](https://issues.apache.org/jira/browse/CB-11115) **android**: Removing dependency on FileDescriptor toString in content provider tests
101
+
102
+### 4.2.0 (Apr 15, 2016)
103
+* [CB-10960](https://issues.apache.org/jira/browse/CB-10960) Uncaught `#<FileError>` in `write()` when `readyState != WRITING ?`
104
+* Replace `PermissionHelper.java` with `cordova-plugin-compat`
105
+* [CB-10977](https://issues.apache.org/jira/browse/CB-10977) **Android** Removing global state used for permission requests
106
+* CB-10798, [CB-10384](https://issues.apache.org/jira/browse/CB-10384) Fixing permissions for **Marshmallow**.
107
+* Fix test failure on **WP 8.1**
108
+* [CB-10577](https://issues.apache.org/jira/browse/CB-10577) **Windows** `resolveLocalFileSystemURL` should omit trailing slash for file
109
+* [CB-7862](https://issues.apache.org/jira/browse/CB-7862) `FileReader` reads large files in chunks with progress.
110
+* [CB-10577](https://issues.apache.org/jira/browse/CB-10577) **Android** `resolveLocalFileSystemURL` should detect directory vs file.
111
+* [CB-9753](https://issues.apache.org/jira/browse/CB-9753) index out of bounds on `requestFileSystem`.
112
+* Remove `warning` emoji, as it doesn't correctly display in the docs website: cordova.apache.org/docs/en/dev/cordova-plugin-file/index.html. This closes #166
113
+* [CB-10636](https://issues.apache.org/jira/browse/CB-10636) Add `JSHint` for plugins
114
+* [CB-10411](https://issues.apache.org/jira/browse/CB-10411) Error in `file.spec.129` of `cordova-plugin-file`
115
+
116
+### 4.1.1 (Feb 09, 2016)
117
+* Edit package.json license to match SPDX id
118
+* [CB-10419](https://issues.apache.org/jira/browse/CB-10419) cordova-plugin-file 4.0.0 error with browserify workflow
119
+
120
+### 4.1.0 (Jan 15, 2016)
121
+* added `.ratignore` file
122
+* [CB-10319](https://issues.apache.org/jira/browse/CB-10319) **android** Adding reflective helper methods for permission requests
123
+* [CB-10023](https://issues.apache.org/jira/browse/CB-10023) Fix `proxy not found error` on Chrome.
124
+* [CB-8863](https://issues.apache.org/jira/browse/CB-8863) **ios** Fix block usage of self
125
+
126
+### 4.0.0 (Nov 18, 2015)
127
+* [CB-10035](https://issues.apache.org/jira/browse/CB-10035) Updated `RELEASENOTES` to be newest to oldest
128
+* [CB-8497](https://issues.apache.org/jira/browse/CB-8497) Fix handling of file paths with `#` character
129
+* Do not inject default `AndroidPersistentFileLocation` into `config.xml`
130
+* [CB-9891](https://issues.apache.org/jira/browse/CB-9891): Fix permission errors due to `URI encoding` inconsistency on **Android**
131
+* Fixed `NullPointer Exception` in **Android 5** and above due to invalid column name on cursor
132
+* Fix default persistent file location
133
+* fix `applicationDirectory` to use `ms-appx:///`
134
+* Add **Windows** paths to `cordova.file` object
135
+* [CB-9851](https://issues.apache.org/jira/browse/CB-9851) Document `cdvfile` protocol quirk - using `cdvfile://` in the `DOM` is not supported on **Windows**
136
+* [CB-9752](https://issues.apache.org/jira/browse/CB-9752) `getDirectory` fails on valid directory with assets filesystem
137
+* [CB-7253](https://issues.apache.org/jira/browse/CB-7253) `requestFileSystem` fails when no external storage is present
138
+* Adding permissions for **Marshmallow**. Now supports **Anrdoid 6.0**
139
+* Fixing contribute link.
140
+* always use setters to fix memory issues without `ARC` for **iOS**
141
+* [CB-9331](https://issues.apache.org/jira/browse/CB-9331) `getFreeDiskSpace` **iOS**.
142
+* override `resolveLocalFileSystemURL` by `webkitResolveLocalFileSystemURL` for **browser** platform add `.project` into git ignore list
143
+* Fail with `FileError.ENCODING_ERR` on encoding exception.
144
+* [CB-9544](https://issues.apache.org/jira/browse/CB-9544) Add file plugin for **OSX**
145
+* [CB-9539](https://issues.apache.org/jira/browse/CB-9539) Fixed test failure on **Android** emulator
146
+* Added docs on `CSP` rules needed for using `cdvfile` in DOM src. This closes #120
147
+* Added `cdvfile` protocol purpose description and examples
148
+
149
+### 3.0.0 (Aug 18, 2015)
150
+* Make Android default persistent file location internal
151
+* Fixed issue with file paths not existing when using browserify
152
+* [CB-9251](https://issues.apache.org/jira/browse/CB-9251): Changed from Intents to Preferences object as per the issue
153
+* [CB-9215](https://issues.apache.org/jira/browse/CB-9215) Add cordova-plugin-file manual test for windows platform
154
+
155
+### 2.1.0 (Jun 17, 2015)
156
+* added missing license header
157
+* [CB-9128](https://issues.apache.org/jira/browse/CB-9128) cordova-plugin-file documentation translation: cordova-plugin-file
158
+* fix npm md
159
+* [CB-8844](https://issues.apache.org/jira/browse/CB-8844) Increased timeout for asset tests
160
+* Updated resolveFileSystem.js so it can be parsed by uglifyJS
161
+* [CB-8860](https://issues.apache.org/jira/browse/CB-8860) cordova-plugin-file documentation translation: cordova-plugin-file
162
+* [CB-8792](https://issues.apache.org/jira/browse/CB-8792) Fixes reading of json files using readAsText
163
+
164
+### 2.0.0 (Apr 15, 2015)
165
+* [CB-8849](https://issues.apache.org/jira/browse/CB-8849) Fixed ReadAsArrayBuffer to return ArrayBuffer and not Array on WP8
166
+* [CB-8819](https://issues.apache.org/jira/browse/CB-8819) Fixed FileReader's readAsBinaryString on wp8
167
+* [CB-8746](https://issues.apache.org/jira/browse/CB-8746) gave plugin major version bump
168
+* [CB-8683](https://issues.apache.org/jira/browse/CB-8683) android: Fix broken unit tests from plugin rename
169
+* [CB-8683](https://issues.apache.org/jira/browse/CB-8683) changed plugin-id to pacakge-name
170
+* [CB-8653](https://issues.apache.org/jira/browse/CB-8653) properly updated translated docs to use new id
171
+* [CB-8653](https://issues.apache.org/jira/browse/CB-8653) updated translated docs to use new id
172
+* Use TRAVIS_BUILD_DIR, install paramedic by npm
173
+* docs: added Windows to supported platforms
174
+* [CB-8699](https://issues.apache.org/jira/browse/CB-8699) [CB-6428](https://issues.apache.org/jira/browse/CB-6428) Fix uncompressed assets being copied as zero length files
175
+* [CB-6428](https://issues.apache.org/jira/browse/CB-6428) android: Fix assets FileEntry having size of -1
176
+* android: Move URLforFullPath into base class (and rename to localUrlforFullPath)
177
+* [CB-6428](https://issues.apache.org/jira/browse/CB-6428) Mention build-extras.gradle in README
178
+* [CB-7109](https://issues.apache.org/jira/browse/CB-7109) android: Parse arguments off of the main thread (close #97)
179
+* [CB-8695](https://issues.apache.org/jira/browse/CB-8695) ios: Fix `blob.slice()` for `asset-library` URLs (close #105)
180
+* Tweak build-extras.gradle to just read/write to main `assets/` instead of `build/`
181
+* [CB-8689](https://issues.apache.org/jira/browse/CB-8689) Fix NPE in makeEntryForNativeUri (was affecting file-transfer)
182
+* [CB-8675](https://issues.apache.org/jira/browse/CB-8675) Revert "CB-8351 ios: Use base64EncodedStringWithOptions instead of CordovaLib's class extension"
183
+* [CB-8653](https://issues.apache.org/jira/browse/CB-8653) Updated Readme
184
+* [CB-8659](https://issues.apache.org/jira/browse/CB-8659): ios: 4.0.x Compatibility: Remove use of initWebView method
185
+* Add a cache to speed up AssetFilesystem directory listings
186
+* [CB-8663](https://issues.apache.org/jira/browse/CB-8663) android: Don't notify MediaScanner of private files
187
+* Don't log stacktrace for normal exceptions (e.g. file not found)
188
+* android: Don't use LimitedInputStream when reading entire file (optimization)
189
+* [CB-6428](https://issues.apache.org/jira/browse/CB-6428) android: Add support for directory copies from assets -> filesystem
190
+* android: Add `listChildren()`: Java-consumable version of `readEntriesAtLocalURL()`
191
+* [CB-6428](https://issues.apache.org/jira/browse/CB-6428) android: Add support for file:///android_asset URLs
192
+* [CB-8642](https://issues.apache.org/jira/browse/CB-8642) android: Fix content URIs not working with resolve / copy
193
+* Tweak tests to fail if deleteEntry fails (rather than time out)
194
+* android: Ensure LocalFilesystemURL can only be created with "cdvfile" URLs
195
+* android: Move CordovaResourceApi into Filesystem base class
196
+* android: Use `CordovaResourceApi.mapUriToFile()` rather than own custom logic in ContentFilesystem
197
+* android: Use Uri.parse rather than manual parsing in resolveLocalFileSystemURI
198
+* Tweak test case that failed twice on error rather than just once
199
+* android: Delete invalid JavaDoc (lint errors)
200
+* android: Use CordovaResourceApi rather than FileHelper
201
+* [CB-8032](https://issues.apache.org/jira/browse/CB-8032) - File Plugin - Add nativeURL external method support for CDVFileSystem->makeEntryForPath:isDirectory: (closes #96)
202
+* [CB-8567](https://issues.apache.org/jira/browse/CB-8567) Integrate TravisCI
203
+* [CB-8438](https://issues.apache.org/jira/browse/CB-8438) cordova-plugin-file documentation translation: cordova-plugin-file
204
+* [CB-8538](https://issues.apache.org/jira/browse/CB-8538) Added package.json file
205
+* [CB-7956](https://issues.apache.org/jira/browse/CB-7956) Add cordova-plugin-file support for browser platform
206
+* [CB-8423](https://issues.apache.org/jira/browse/CB-8423) Corrected usage of done() in async tests
207
+* [CB-8459](https://issues.apache.org/jira/browse/CB-8459) Fixes spec 111 failure due to incorrect relative paths handling
208
+* Code cleanup, whitespace
209
+* Added nativeURL property to FileEntry, implemented readAsArrayBuffer and readAsBinaryString
210
+
211
+### 1.3.3 (Feb 04, 2015)
212
+* [CB-7927](https://issues.apache.org/jira/browse/CB-7927) Encoding data to bytes instead of chars when writing a file.
213
+* ios: Fix compile warning about implicit int conversion
214
+* [CB-8351](https://issues.apache.org/jira/browse/CB-8351) ios: Use base64EncodedStringWithOptions instead of CordovaLib's class extension
215
+* [CB-8351](https://issues.apache.org/jira/browse/CB-8351) ios: Use argumentForIndex rather than NSArray extension
216
+* [CB-8351](https://issues.apache.org/jira/browse/CB-8351) ios: Use a local copy of valueForKeyIsNumber rather than CordovaLib's version
217
+* windows: Handle url's containing absolute windows path starting with drive letter and colon (encoded as %3A) through root FS
218
+* windows: Rework to use normal url form
219
+* android: refactor: Make Filesystem base class store its own name, rootUri, and rootEntry
220
+* android: Simplify code a bit by making makeEntryForPath not throw JSONException
221
+* [CB-6431](https://issues.apache.org/jira/browse/CB-6431) android: Fix plugin breaking content: URLs
222
+* [CB-7375](https://issues.apache.org/jira/browse/CB-7375) Never create new FileSystem instances (except on windows since they don't implement requestAllFileSystems())
223
+
224
+### 1.3.2 (Dec 02, 2014)
225
+* Gets rid of thread block error in File plugin
226
+* [CB-7917](https://issues.apache.org/jira/browse/CB-7917) Made tests file.spec.114 - 116 pass for **Windows** platform
227
+* [CB-7977](https://issues.apache.org/jira/browse/CB-7977) Mention `deviceready` in plugin docs
228
+* [CB-7602](https://issues.apache.org/jira/browse/CB-7602): Fix `isCopyOnItself` logic
229
+* [CB-7700](https://issues.apache.org/jira/browse/CB-7700) cordova-plugin-file documentation translation: cordova-plugin-file
230
+* Use one proxy for both **Windows** and **Windows8** platforms
231
+* [CB-6994](https://issues.apache.org/jira/browse/CB-6994) Fixes result, returned by proxy's write method
232
+* [fxos] update `__format__` to match `pathsPrefix`
233
+* [CB-6994](https://issues.apache.org/jira/browse/CB-6994) Improves merged code to be able to write a File
234
+* Optimize `FileProxy` for **Windows** platforms
235
+* Synchronize changes with **Windows** platform
236
+* Fix function write for big files on **Windows 8**
237
+* Write file in background
238
+* [CB-7487](https://issues.apache.org/jira/browse/CB-7487) **Android** Broadcast file write This allows MTP USB shares to show the file immediately without reboot/manual refresh using 3rd party app.
239
+* [CB-7700](https://issues.apache.org/jira/browse/CB-7700) cordova-plugin-file documentation translation: cordova-plugin-file
240
+* [CB-7571](https://issues.apache.org/jira/browse/CB-7571) Bump version of nested plugin to match parent plugin
241
+
242
+### 1.3.1 (Sep 17, 2014)
243
+* [CB-7471](https://issues.apache.org/jira/browse/CB-7471) cordova-plugin-file documentation translation
244
+* [CB-7272](https://issues.apache.org/jira/browse/CB-7272) Replace confusing "r/o" abbreviation with just "r"
245
+* [CB-7423](https://issues.apache.org/jira/browse/CB-7423) encode path before attempting to resolve
246
+* [CB-7375](https://issues.apache.org/jira/browse/CB-7375) Fix the filesystem name in resolveLocalFileSystemUri
247
+* [CB-7445](https://issues.apache.org/jira/browse/CB-7445) [BlackBerry10] resolveLocalFileSystemURI - change DEFAULT_SIZE to MAX_SIZE
248
+* [CB-7458](https://issues.apache.org/jira/browse/CB-7458) [BlackBerry10] resolveLocalFileSystemURL - add filesystem property
249
+* [CB-7445](https://issues.apache.org/jira/browse/CB-7445) [BlackBerry10] Add default file system size to prevent quota exceeded error on initial install
250
+* [CB-7431](https://issues.apache.org/jira/browse/CB-7431) Avoid calling done() twice in file.spec.109 test
251
+* [CB-7413](https://issues.apache.org/jira/browse/CB-7413) Adds support of 'ms-appdata://' URIs
252
+* [CB-7422](https://issues.apache.org/jira/browse/CB-7422) [File Tests] Use proper fileSystem to create fullPath
253
+* [CB-7375](https://issues.apache.org/jira/browse/CB-7375) [Entry] get proper filesystem in Entry
254
+* Amazon related changes.
255
+* [CB-7375](https://issues.apache.org/jira/browse/CB-7375) Remove leading slash statement from condition
256
+* Refactored much of the logic in FileMetadata constructor.  Directory.size will return 0
257
+* [CB-7419](https://issues.apache.org/jira/browse/CB-7419) [WP8] Added support to get metada from dir
258
+* [CB-7418](https://issues.apache.org/jira/browse/CB-7418) [DirectoryEntry] Added fullPath variable as part of condition
259
+* [CB-7417](https://issues.apache.org/jira/browse/CB-7417) [File tests] added proper matcher to compare fullPath property
260
+* [CB-7375](https://issues.apache.org/jira/browse/CB-7375) Partial revert to resolve WP8 failures
261
+* Overwrite existing file on getFile when create is true
262
+* [CB-7375](https://issues.apache.org/jira/browse/CB-7375) [CB-6148](https://issues.apache.org/jira/browse/CB-6148): Ensure that return values from copy and move operations reference the correct filesystem
263
+* [CB-6724](https://issues.apache.org/jira/browse/CB-6724) changed style detail on documentation
264
+* Added new js files to amazon-fireos platform.
265
+* Adds Windows platform
266
+* Fixes multiple mobilespec tests errors
267
+* Removed test/tests.js module from main plugin.xml
268
+* [CB-7094](https://issues.apache.org/jira/browse/CB-7094) renamed folder to tests + added nested plugin.xml
269
+* added documentation for manual tests
270
+* [CB-6923](https://issues.apache.org/jira/browse/CB-6923) Adding support to handle relative paths
271
+* Style improvements on Manual tests
272
+* [CB-7094](https://issues.apache.org/jira/browse/CB-7094) Ported File manual tests
273
+
274
+### 1.3.0 (Aug 06, 2014)
275
+* **FFOS** Remove unsupported paths from requestAllPaths
276
+* **FFOS** Support for resolve URI, request all paths and local app directory.
277
+* [CB-4263](https://issues.apache.org/jira/browse/CB-4263) set ready state to done before onload
278
+* [CB-7167](https://issues.apache.org/jira/browse/CB-7167) [BlackBerry10] copyTo - return wrapped entry rather than native
279
+* [CB-7167](https://issues.apache.org/jira/browse/CB-7167) [BlackBerry10] Add directory support to getFileMetadata
280
+* [CB-7167](https://issues.apache.org/jira/browse/CB-7167) [BlackBerry10] Fix tests detection of blob support (window.Blob is BlobConstructor object)
281
+* [CB-7161](https://issues.apache.org/jira/browse/CB-7161) [BlackBerry10] Add file system directory paths
282
+* [CB-7093](https://issues.apache.org/jira/browse/CB-7093) Create separate plugin.xml for new-style tests
283
+* [CB-7057](https://issues.apache.org/jira/browse/CB-7057) Docs update: elaborate on what directories are for
284
+* [CB-7093](https://issues.apache.org/jira/browse/CB-7093): Undo the effects of an old bad S&R command
285
+* [CB-7093](https://issues.apache.org/jira/browse/CB-7093): Remove a bunch of unneeded log messages
286
+* [CB-7093](https://issues.apache.org/jira/browse/CB-7093): Add JS module to plugin.xml file for auto-tests
287
+* [CB-7093](https://issues.apache.org/jira/browse/CB-7093) Ported automated file tests
288
+* **WINDOWS** remove extra function closure, not    needed
289
+* **WINDOWS** remove check for undefined fail(), it is defined by the proxy and always exists
290
+* **WINDOWS** re-apply readAsBinaryString and readAsArrayBuffer
291
+* **WINDOWS** Moved similar calls to be the same calls, aliased long namespaced functions
292
+* [CB-6127](https://issues.apache.org/jira/browse/CB-6127) Updated translations for docs.
293
+* [CB-6571](https://issues.apache.org/jira/browse/CB-6571) Fix getParentForLocalURL to work correctly with directories with trailing '/' (This closes #58)
294
+* UTTypeCopyPreferredTagWithClass returns nil mimetype for css when there is no network
295
+* updated spec links in docs ( en only )
296
+* [CB-6571](https://issues.apache.org/jira/browse/CB-6571) add trailing space it is missing in DirectoryEnty constructor.
297
+* [CB-6980](https://issues.apache.org/jira/browse/CB-6980) Fixing filesystem:null property in Entry
298
+* Add win8 support for readAsBinaryString and readAsArrayBuffer
299
+* [FFOS] Update FileProxy.js
300
+* [CB-6940](https://issues.apache.org/jira/browse/CB-6940): Fixing up commit from dzeims
301
+* [CB-6940](https://issues.apache.org/jira/browse/CB-6940): Android: cleanup try/catch exception handling
302
+* [CB-6940](https://issues.apache.org/jira/browse/CB-6940): `context.getExternal*` methods return null if sdcard isn't in mounted state, causing exceptions that prevent startup from reaching readystate
303
+* Fix mis-handling of filesystem reference in Entry.moveTo ('this' used in closure).
304
+* [CB-6902](https://issues.apache.org/jira/browse/CB-6902): Use File.lastModified rather than .lastModifiedDate
305
+* [CB-6922](https://issues.apache.org/jira/browse/CB-6922): Remove unused getMetadata native code
306
+* [CB-6922](https://issues.apache.org/jira/browse/CB-6922): Use getFileMetadata consistently to get metadata
307
+* changed fullPath to self.rootDocsPath
308
+* [CB-6890](https://issues.apache.org/jira/browse/CB-6890): Fix pluginManager access for 4.0.x branch
309
+
310
+### 1.2.1
311
+* [CB-6922](https://issues.apache.org/jira/browse/CB-6922) Fix inconsistent handling of lastModifiedDate and modificationTime
312
+* [CB-285](https://issues.apache.org/jira/browse/CB-285): Document filesystem root properties
313
+
314
+### 1.2.0 (Jun 05, 2014)
315
+* [CB-6127](https://issues.apache.org/jira/browse/CB-6127) Spanish and French Translations added. Github close #31
316
+* updated this reference to window
317
+* Add missing semicolon (copy & paste error)
318
+* Fix compiler warning about symbol in interface not matching implementation
319
+* Fix sorting order in supported platforms
320
+* ubuntu: increase quota value
321
+* ubuntu: Change FS URL scheme to 'cdvfile'
322
+* ubuntu: Return size with Entry.getMetadata() method
323
+* [CB-6803](https://issues.apache.org/jira/browse/CB-6803) Add license
324
+* Initial implementation for Firefox OS
325
+* Small wording tweaks
326
+* Fixed toURL() toInternalURL() information in the doku
327
+* ios: Don't fail a write of zero-length payload.
328
+* [CB-285](https://issues.apache.org/jira/browse/CB-285) Docs for cordova.file.\*Directory properties
329
+* [CB-285](https://issues.apache.org/jira/browse/CB-285) Add cordova.file.\*Directory properties for iOS & Android
330
+* [CB-3440](https://issues.apache.org/jira/browse/CB-3440) [BlackBerry10] Proxy based implementation
331
+* Fix typo in docs "app-bundle" -> "bundle"
332
+* [CB-6583](https://issues.apache.org/jira/browse/CB-6583) ios: Fix failing to create entry when space in parent path
333
+* [CB-6571](https://issues.apache.org/jira/browse/CB-6571) android: Make DirectoryEntry.toURL() have a trailing /
334
+* [CB-6491](https://issues.apache.org/jira/browse/CB-6491) add CONTRIBUTING.md
335
+* [CB-6525](https://issues.apache.org/jira/browse/CB-6525) android, ios: Allow file: URLs in all APIs. Fixes FileTransfer.download not being called.
336
+* fix the Windows 8  implementation of the getFile method
337
+* Update File.js for typo: lastModifiedData --> lastModifiedDate (closes #38)
338
+* Add error codes.
339
+* [CB-5980](https://issues.apache.org/jira/browse/CB-5980) Updated version and RELEASENOTES.md for release 1.0.0
340
+* Add NOTICE file
341
+* [CB-6114](https://issues.apache.org/jira/browse/CB-6114) Updated version and RELEASENOTES.md for release 1.0.1
342
+* [CB-5980](https://issues.apache.org/jira/browse/CB-5980) Updated version and RELEASENOTES.md for release 1.0.0
343
+
344
+### 1.1.0 (Apr 17, 2014)
345
+* [CB-4965](https://issues.apache.org/jira/browse/CB-4965): Remove tests from file plugin
346
+* Android: Allow file:/ URLs
347
+* [CB-6422](https://issues.apache.org/jira/browse/CB-6422): [windows8] use cordova/exec/proxy
348
+* [CB-6249](https://issues.apache.org/jira/browse/CB-6249): [android] Opportunistically resolve content urls to file
349
+* [CB-6394](https://issues.apache.org/jira/browse/CB-6394): [ios, android] Add extra filesystem roots
350
+* [CB-6394](https://issues.apache.org/jira/browse/CB-6394): [ios, android] Fix file resolution for the device root case
351
+* [CB-6394](https://issues.apache.org/jira/browse/CB-6394): [ios] Return ENCODING_ERR when fs name is not valid
352
+* [CB-6393](https://issues.apache.org/jira/browse/CB-6393): Change behaviour of toURL and toNativeURL
353
+* ios: Style: plugin initialization
354
+* ios: Fix handling of file URLs with encoded spaces
355
+* Always use Android's recommended temp file location for temporary file system
356
+* [CB-6352](https://issues.apache.org/jira/browse/CB-6352): Allow FileSystem objects to be serialized to JSON
357
+* [CB-5959](https://issues.apache.org/jira/browse/CB-5959): size is explicitly 0 if not set, file.spec.46&47 are testing the type of size
358
+* [CB-6242](https://issues.apache.org/jira/browse/CB-6242): [BlackBerry10] Add deprecated version of resolveLocalFileSystemURI
359
+* [CB-6242](https://issues.apache.org/jira/browse/CB-6242): [BlackBerry10] add file:/// prefix for toURI / toURL
360
+* [CB-6242](https://issues.apache.org/jira/browse/CB-6242): [BlackBerry10] Polyfill window.requestAnimationFrame for OS < 10.2
361
+* [CB-6242](https://issues.apache.org/jira/browse/CB-6242): [BlackBerry10] Override window.resolveLocalFileSystemURL
362
+* [CB-6212](https://issues.apache.org/jira/browse/CB-6212): [iOS] fix warnings compiled under arm64 64-bit
363
+* ios: Don't cache responses from CDVFile's URLProtocol
364
+* [CB-6199](https://issues.apache.org/jira/browse/CB-6199): [iOS] Fix toNativeURL() not escaping characters properly
365
+* [CB-6148](https://issues.apache.org/jira/browse/CB-6148): Fix cross-filesystem copy and move
366
+* fixed setMetadata() to use the formatted fullPath
367
+* corrected typo which leads to a "comma expression"
368
+* [CB-4952](https://issues.apache.org/jira/browse/CB-4952): ios: Resolve symlinks in file:// URLs
369
+* Add docs about the extraFileSystems preference
370
+* [CB-6460](https://issues.apache.org/jira/browse/CB-6460): Update license headers
371
+
372
+### 1.0.1 (Feb 28, 2014)
373
+* [CB-6116](https://issues.apache.org/jira/browse/CB-6116) Fix error where resolveLocalFileSystemURL would fail
374
+* [CB-6106](https://issues.apache.org/jira/browse/CB-6106) Add support for nativeURL attribute on Entry objects
375
+* [CB-6110](https://issues.apache.org/jira/browse/CB-6110) iOS: Fix typo in filesystemPathForURL: method
376
+* Android: Use most specific FS match when resolving file: URIs
377
+* iOS: Update fileSystemURLforLocalPath: to return the most match url.
378
+* Allow third-party plugin registration, and the total count of fs type is not limited to just 4.
379
+* [CB-6097](https://issues.apache.org/jira/browse/CB-6097) Added missing files for amazon-fireos platform. Added onLoad flag to true.
380
+* [CB-6087](https://issues.apache.org/jira/browse/CB-6087) Android, iOS: Load file plugin on startup
381
+* [CB-6013](https://issues.apache.org/jira/browse/CB-6013) BlackBerry10: wrap webkit prefixed called in requestAnimationFrame
382
+* Update plugin writers' documentation
383
+* [CB-6080](https://issues.apache.org/jira/browse/CB-6080) Fix file copy when src and dst are on different local file systems
384
+* [CB-6057](https://issues.apache.org/jira/browse/CB-6057) Add methods for plugins to convert between URLs and paths
385
+* [CB-6050](https://issues.apache.org/jira/browse/CB-6050) Public method for returning a FileEntry from a device file path
386
+* [CB-2432](https://issues.apache.org/jira/browse/CB-2432) [CB-3185](https://issues.apache.org/jira/browse/CB-3185), [CB-5975](https://issues.apache.org/jira/browse/CB-5975): Fix Android handling of content:// URLs
387
+* [CB-6022](https://issues.apache.org/jira/browse/CB-6022) Add upgrade notes to doc
388
+* [CB-5233](https://issues.apache.org/jira/browse/CB-5233) Make asset-library urls work properly on iOS
389
+* [CB-6012](https://issues.apache.org/jira/browse/CB-6012) Preserve query strings on cdvfile:// URLs where necessary
390
+* [CB-6010](https://issues.apache.org/jira/browse/CB-6010) Test properly for presence of URLforFilesystemPath method
391
+* [CB-5959](https://issues.apache.org/jira/browse/CB-5959) Entry.getMetadata should return size attribute
392
+
393
+### 1.0.0 (Feb 05, 2014)
394
+* [CB-5974](https://issues.apache.org/jira/browse/CB-5974): Use safe 'Compatibilty' mode by default
395
+* [CB-5915](https://issues.apache.org/jira/browse/CB-5915): [CB-5916](https://issues.apache.org/jira/browse/CB-5916): Reorganize preference code to make defaults possible
396
+* [CB-5974](https://issues.apache.org/jira/browse/CB-5974): Android: Don't allow File operations to continue when not configured
397
+* [CB-5960](https://issues.apache.org/jira/browse/CB-5960): ios: android: Properly handle parent references in getFile/getDirectory
398
+* [ubuntu] adopt to recent changes
399
+* Add default FS root to new FS objects
400
+* [CB-5899](https://issues.apache.org/jira/browse/CB-5899): Make DirectoryReader.readEntries return properly formatted Entry objects
401
+* Add constuctor params to FileUploadResult related to [CB-2421](https://issues.apache.org/jira/browse/CB-2421)
402
+* Fill out filesystem attribute of entities returned from resolveLocalFileSystemURL
403
+* [CB-5916](https://issues.apache.org/jira/browse/CB-5916): Create documents directories if they don't exist
404
+* [CB-5915](https://issues.apache.org/jira/browse/CB-5915): Create documents directories if they don't exist
405
+* [CB-5916](https://issues.apache.org/jira/browse/CB-5916): Android: Fix unfortunate NPE in config check
406
+* [CB-5916](https://issues.apache.org/jira/browse/CB-5916): Android: Add "/files/" to persistent files path
407
+* [CB-5915](https://issues.apache.org/jira/browse/CB-5915): ios: Update config preference (and docs) to match issue
408
+* [CB-5916](https://issues.apache.org/jira/browse/CB-5916): Android: Add config preference for Android persistent storage location
409
+* iOS: Add config preference for iOS persistent storage location
410
+* iOS: Android: Allow third-party plugin registration
411
+* Android: Expose filePlugin getter so that other plugins can register filesystems
412
+* Fix typos in deprecation message
413
+* Add backwards-compatibility shim for file-transfer
414
+* Android: Allow third-party plugin registration
415
+* [CB-5810](https://issues.apache.org/jira/browse/CB-5810) [BlackBerry10] resolve local:/// paths (application assets)
416
+* [CB-5774](https://issues.apache.org/jira/browse/CB-5774): create DirectoryEntry instead of FileEntry
417
+* Initial fix for [CB-5747](https://issues.apache.org/jira/browse/CB-5747)
418
+* Change default FS URL scheme to "cdvfile"
419
+* Android: Properly format content urls
420
+* Android, iOS: Replace "filesystem" protocol string with constant
421
+* Android: Allow absolute paths on Entry.getFile / Entry.getDirectory
422
+* Android: Make clear that getFile takes a path, not just a filename
423
+* [CB-5008](https://issues.apache.org/jira/browse/CB-5008): Rename resolveLocalFileSystemURI to resolveLocalFileSystemURL; deprecate original
424
+* Remove old file reference from plugin.xml
425
+* Android: Refactor File API
426
+* [CB-4899](https://issues.apache.org/jira/browse/CB-4899) [BlackBerry10] Fix resolve directories
427
+* [CB-5602](https://issues.apache.org/jira/browse/CB-5602) Windows8. Fix File Api mobile spec tests
428
+* Android: Better support for content urls and cross-filesystem copy/move ops
429
+* [CB-5699](https://issues.apache.org/jira/browse/CB-5699) [BlackBerry10] Update resolveLocalFileSystemURI implementation
430
+* [CB-5658](https://issues.apache.org/jira/browse/CB-5658) Update license comment formatting of doc/index.md
431
+* [CB-5658](https://issues.apache.org/jira/browse/CB-5658) Add doc.index.md for File plugin.
432
+* [CB-5658](https://issues.apache.org/jira/browse/CB-5658) Delete stale snapshot of plugin docs
433
+* [CB-5403](https://issues.apache.org/jira/browse/CB-5403): Backwards-compatibility with file:// urls where possible
434
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Fixes for ContentFilesystem
435
+* Android: Add method for testing backwards-compatibility of filetransfer plugin
436
+* iOS: Add method for testing backwards-compatiblity of filetransfer plugin
437
+* Android: Updates to allow FileTransfer to continue to work
438
+* Android: Clean up unclosed file objects
439
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Cleanup
440
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Add new Android source files to plugin.xml
441
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Move read, write and truncate methods into modules
442
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Move copy/move methods into FS modules
443
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Move getParent into FS modules
444
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Move getmetadata methods into FS modules
445
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Move readdir methods into FS modules
446
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Move remove methods into FS modules
447
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Move getFile into FS modules
448
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Start refactoring android code: Modular filesystems, rfs, rlfsurl
449
+* [CB-5407](https://issues.apache.org/jira/browse/CB-5407): Update android JS to use FS urls
450
+* [CB-5405](https://issues.apache.org/jira/browse/CB-5405): Use URL formatting for Entry.toURL
451
+* [CB-5532](https://issues.apache.org/jira/browse/CB-5532) Fix
452
+* Log file path for File exceptions.
453
+* Partial fix for iOS File compatibility with previous fileTransfer plugin
454
+* [CB-5532](https://issues.apache.org/jira/browse/CB-5532) WP8. Add binary data support to FileWriter
455
+* [CB-5531](https://issues.apache.org/jira/browse/CB-5531) WP8. File Api readAsText incorrectly handles position args
456
+* Added ubuntu platform support
457
+* Added amazon-fireos platform support
458
+* [CB-5118](https://issues.apache.org/jira/browse/CB-5118) [BlackBerry10] Add check for undefined error handler
459
+* [CB-5406](https://issues.apache.org/jira/browse/CB-5406): Extend public API for dependent plugins
460
+* [CB-5403](https://issues.apache.org/jira/browse/CB-5403): Bump File plugin major version
461
+* [CB-5406](https://issues.apache.org/jira/browse/CB-5406): Split iOS file plugin into modules
462
+* [CB-5406](https://issues.apache.org/jira/browse/CB-5406): Factor out filesystem providers in iOS
463
+* [CB-5408](https://issues.apache.org/jira/browse/CB-5408): Add handler for filesystem:// urls
464
+* [CB-5406](https://issues.apache.org/jira/browse/CB-5406): Update iOS native code to use filesystem URLs internally
465
+* [CB-5405](https://issues.apache.org/jira/browse/CB-5405): Update JS code to use URLs exclusively
466
+* [CB-4816](https://issues.apache.org/jira/browse/CB-4816) Fix file creation outside sandbox for BB10
467
+
468
+### 0.2.5 (Oct 28, 2013)
469
+* [CB-5129](https://issues.apache.org/jira/browse/CB-5129): Add a consistent filesystem attribute to FileEntry and DirectoryEntry objects
470
+* [CB-5128](https://issues.apache.org/jira/browse/CB-5128): added repo + issue tag to plugin.xml for file plugin
471
+* [CB-5015](https://issues.apache.org/jira/browse/CB-5015) [BlackBerry10] Add missing dependency for File.slice
472
+* [CB-5010](https://issues.apache.org/jira/browse/CB-5010) Incremented plugin version on dev branch.
473
+
474
+### 0.2.4 (Oct 9, 2013)
475
+* [CB-5020](https://issues.apache.org/jira/browse/CB-5020) - File plugin should execute on a separate thread
476
+* [CB-4915](https://issues.apache.org/jira/browse/CB-4915) Incremented plugin version on dev branch.
477
+* [CB-4504](https://issues.apache.org/jira/browse/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
478
+
479
+### 0.2.3 (Sept 25, 2013)
480
+* [CB-4889](https://issues.apache.org/jira/browse/CB-4889) bumping&resetting version
481
+* [CB-4903](https://issues.apache.org/jira/browse/CB-4903) File Plugin not loading Windows8
482
+* [CB-4903](https://issues.apache.org/jira/browse/CB-4903) File Plugin not loading Windows8
483
+* [CB-4889](https://issues.apache.org/jira/browse/CB-4889) renaming references
484
+* [CB-4889](https://issues.apache.org/jira/browse/CB-4889) renaming org.apache.cordova.core.file to org.apache.cordova.file
485
+* Rename CHANGELOG.md -> RELEASENOTES.md
486
+* [CB-4771](https://issues.apache.org/jira/browse/CB-4771) Expose TEMPORARY and PERSISTENT constants on window.
487
+* Fix compiler/lint warnings
488
+* [CB-4764](https://issues.apache.org/jira/browse/CB-4764) Move DirectoryManager.java into file plugin
489
+* [CB-4763](https://issues.apache.org/jira/browse/CB-4763) Copy FileHelper.java into the plugin.
490
+* [CB-2901](https://issues.apache.org/jira/browse/CB-2901) [BlackBerry10] Automatically unsandbox filesystem if path is not in app sandbox
491
+* [CB-4752](https://issues.apache.org/jira/browse/CB-4752) Incremented plugin version on dev branch.
492
+
493
+### 0.2.1 (Sept 5, 2013)
494
+* [CB-4656](https://issues.apache.org/jira/browse/CB-4656) Don't add newlines in data urls within readAsDataUrl.
495
+* [CB-4514](https://issues.apache.org/jira/browse/CB-4514) Making DirectoryCopy Recursive
496
+* [iOS] Simplify the code in resolveLocalFileSystemURI

+ 120
- 0
plugins/cordova-plugin-file/doc/plugins.md View File

@@ -0,0 +1,120 @@
1
+<!---
2
+    Licensed to the Apache Software Foundation (ASF) under one
3
+    or more contributor license agreements.  See the NOTICE file
4
+    distributed with this work for additional information
5
+    regarding copyright ownership.  The ASF licenses this file
6
+    to you under the Apache License, Version 2.0 (the
7
+    "License"); you may not use this file except in compliance
8
+    with the License.  You may obtain a copy of the License at
9
+
10
+      http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+    Unless required by applicable law or agreed to in writing,
13
+    software distributed under the License is distributed on an
14
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+    KIND, either express or implied.  See the License for the
16
+    specific language governing permissions and limitations
17
+    under the License.
18
+-->
19
+
20
+Notes for plugin developers
21
+===========================
22
+
23
+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.
24
+
25
+Working with Cordova file system URLs
26
+-------------------------------------
27
+
28
+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. 
29
+
30
+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.
31
+
32
+### Converting cdvfile:// URLs to fileystem paths
33
+
34
+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.
35
+
36
+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.
37
+
38
+#### Android
39
+
40
+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:
41
+
42
+    // webView is a member of the Plugin class
43
+    CordovaResourceApi resourceApi = webView.getResourceApi();
44
+
45
+    // Obtain a file:/// URL representing this file on the device,
46
+    // or the same URL unchanged if it cannot be mapped to a file
47
+    Uri fileURL = resourceApi.remapUri(Uri.parse(cdvfileURL));
48
+
49
+It is also possible to use the File plugin directly:
50
+
51
+    import org.apache.cordova.file.FileUtils;
52
+    import org.apache.cordova.file.FileSystem;
53
+    import java.net.MalformedURLException;
54
+
55
+    // Get the File plugin from the plugin manager
56
+    FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
57
+
58
+    // Given a URL, get a path for it
59
+    try {
60
+        String path = filePlugin.filesystemPathForURL(cdvfileURL);
61
+    } catch (MalformedURLException e) {
62
+        // The filesystem url wasn't recognized
63
+    }
64
+
65
+To convert from a path to a `cdvfile://` URL:
66
+
67
+    import org.apache.cordova.file.LocalFilesystemURL;
68
+
69
+    // Get a LocalFilesystemURL object for a device path,
70
+    // or null if it cannot be represented as a cdvfile URL.
71
+    LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(path);
72
+    // Get the string representation of the URL object
73
+    String cdvfileURL = url.toString();
74
+
75
+If your plugin creates a file, and you want to return a FileEntry object for it, use the File plugin:
76
+
77
+    // Return a JSON structure suitable for returning to JavaScript,
78
+    // or null if this file is not representable as a cdvfile URL.
79
+    JSONObject entry = filePlugin.getEntryForFile(file);
80
+
81
+#### iOS
82
+
83
+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.
84
+
85
+    // Get a CDVFilesystem URL object from a URL string
86
+    CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithString:cdvfileURL];
87
+    // Get a path for the URL object, or nil if it cannot be mapped to a file
88
+    NSString* path = [filePlugin filesystemPathForURL:url];
89
+    
90
+
91
+    // Get a CDVFilesystem URL object for a device path, or
92
+    // nil if it cannot be represented as a cdvfile URL.
93
+    CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
94
+    // Get the string representation of the URL object
95
+    NSString* cdvfileURL = [url absoluteString];
96
+
97
+If your plugin creates a file, and you want to return a FileEntry object for it, use the File plugin:
98
+
99
+    // Get a CDVFilesystem URL object for a device path, or
100
+    // nil if it cannot be represented as a cdvfile URL.
101
+    CDVFilesystemURL* url = [filePlugin fileSystemURLforLocalPath:path];
102
+    // Get a structure to return to JavaScript
103
+    NSDictionary* entry = [filePlugin makeEntryForLocalURL:url]
104
+
105
+#### JavaScript
106
+
107
+In JavaScript, to get a `cdvfile://` URL from a FileEntry or DirectoryEntry object, simply call `.toURL()` on it:
108
+
109
+    var cdvfileURL = entry.toURL();
110
+
111
+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:
112
+
113
+    // create appropriate Entry object
114
+    var entry;
115
+    if (entryStruct.isDirectory) {
116
+        entry = new DirectoryEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
117
+    } else {
118
+        entry = new FileEntry(entryStruct.name, entryStruct.fullPath, new FileSystem(entryStruct.filesystemName));
119
+    }
120
+

+ 87
- 0
plugins/cordova-plugin-file/package.json View File

@@ -0,0 +1,87 @@
1
+{
2
+  "_from": "cordova-plugin-file@6.0.2",
3
+  "_id": "cordova-plugin-file@6.0.2",
4
+  "_inBundle": false,
5
+  "_integrity": "sha512-m7cughw327CjONN/qjzsTpSesLaeybksQh420/gRuSXJX5Zt9NfgsSbqqKDon6jnQ9Mm7h7imgyO2uJ34XMBtA==",
6
+  "_location": "/cordova-plugin-file",
7
+  "_phantomChildren": {},
8
+  "_requested": {
9
+    "type": "version",
10
+    "registry": true,
11
+    "raw": "cordova-plugin-file@6.0.2",
12
+    "name": "cordova-plugin-file",
13
+    "escapedName": "cordova-plugin-file",
14
+    "rawSpec": "6.0.2",
15
+    "saveSpec": null,
16
+    "fetchSpec": "6.0.2"
17
+  },
18
+  "_requiredBy": [
19
+    "#USER",
20
+    "/"
21
+  ],
22
+  "_resolved": "https://registry.npmjs.org/cordova-plugin-file/-/cordova-plugin-file-6.0.2.tgz",
23
+  "_shasum": "f3911479f8357e9aacb5576674f8d95b31a1fb20",
24
+  "_spec": "cordova-plugin-file@6.0.2",
25
+  "_where": "/home/rarce/Downloads/tutorial",
26
+  "author": {
27
+    "name": "Apache Software Foundation"
28
+  },
29
+  "bugs": {
30
+    "url": "https://github.com/apache/cordova-plugin-file/issues"
31
+  },
32
+  "bundleDependencies": false,
33
+  "cordova": {
34
+    "id": "cordova-plugin-file",
35
+    "platforms": [
36
+      "android",
37
+      "browser",
38
+      "ios",
39
+      "osx",
40
+      "windows"
41
+    ]
42
+  },
43
+  "deprecated": false,
44
+  "description": "Cordova File Plugin",
45
+  "devDependencies": {
46
+    "eslint": "^3.19.0",
47
+    "eslint-config-semistandard": "^11.0.0",
48
+    "eslint-config-standard": "^10.2.1",
49
+    "eslint-plugin-import": "^2.3.0",
50
+    "eslint-plugin-node": "^5.0.0",
51
+    "eslint-plugin-promise": "^3.5.0",
52
+    "eslint-plugin-standard": "^3.0.1"
53
+  },
54
+  "engines": {
55
+    "cordovaDependencies": {
56
+      "5.0.0": {
57
+        "cordova-android": ">=6.3.0"
58
+      },
59
+      "7.0.0": {
60
+        "cordova": ">100"
61
+      }
62
+    }
63
+  },
64
+  "homepage": "https://github.com/apache/cordova-plugin-file#readme",
65
+  "keywords": [
66
+    "cordova",
67
+    "file",
68
+    "ecosystem:cordova",
69
+    "cordova-android",
70
+    "cordova-browser",
71
+    "cordova-ios",
72
+    "cordova-osx",
73
+    "cordova-windows"
74
+  ],
75
+  "license": "Apache-2.0",
76
+  "name": "cordova-plugin-file",
77
+  "repository": {
78
+    "type": "git",
79
+    "url": "git+https://github.com/apache/cordova-plugin-file.git"
80
+  },
81
+  "scripts": {
82
+    "eslint": "node node_modules/eslint/bin/eslint www && node node_modules/eslint/bin/eslint src && node node_modules/eslint/bin/eslint tests",
83
+    "test": "npm run eslint"
84
+  },
85
+  "types": "./types/index.d.ts",
86
+  "version": "6.0.2"
87
+}

+ 260
- 0
plugins/cordova-plugin-file/plugin.xml View File

@@ -0,0 +1,260 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!--
3
+  Licensed to the Apache Software Foundation (ASF) under one
4
+  or more contributor license agreements.  See the NOTICE file
5
+  distributed with this work for additional information
6
+  regarding copyright ownership.  The ASF licenses this file
7
+  to you under the Apache License, Version 2.0 (the
8
+  "License"); you may not use this file except in compliance
9
+  with the License.  You may obtain a copy of the License at
10
+
11
+    http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+  Unless required by applicable law or agreed to in writing,
14
+  software distributed under the License is distributed on an
15
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+  KIND, either express or implied.  See the License for the
17
+  specific language governing permissions and limitations
18
+  under the License.
19
+-->
20
+
21
+<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
22
+xmlns:android="http://schemas.android.com/apk/res/android"
23
+           id="cordova-plugin-file"
24
+      version="6.0.2">
25
+    <name>File</name>
26
+    <description>Cordova File Plugin</description>
27
+    <license>Apache 2.0</license>
28
+    <keywords>cordova,file</keywords>
29
+    <repo>https://github.com/apache/cordova-plugin-file</repo>
30
+    <issue>https://github.com/apache/cordova-plugin-file/issues</issue>
31
+
32
+    <engines>
33
+        <engine name="cordova-android" version=">=6.3.0" />
34
+    </engines>
35
+
36
+    <js-module src="www/DirectoryEntry.js" name="DirectoryEntry">
37
+        <clobbers target="window.DirectoryEntry" />
38
+    </js-module>
39
+
40
+    <js-module src="www/DirectoryReader.js" name="DirectoryReader">
41
+        <clobbers target="window.DirectoryReader" />
42
+    </js-module>
43
+
44
+    <js-module src="www/Entry.js" name="Entry">
45
+        <clobbers target="window.Entry" />
46
+    </js-module>
47
+
48
+    <js-module src="www/File.js" name="File">
49
+        <clobbers target="window.File" />
50
+    </js-module>
51
+
52
+    <js-module src="www/FileEntry.js" name="FileEntry">
53
+        <clobbers target="window.FileEntry" />
54
+    </js-module>
55
+
56
+    <js-module src="www/FileError.js" name="FileError">
57
+        <clobbers target="window.FileError" />
58
+    </js-module>
59
+
60
+    <js-module src="www/FileReader.js" name="FileReader">
61
+        <clobbers target="window.FileReader" />
62
+    </js-module>
63
+
64
+    <js-module src="www/FileSystem.js" name="FileSystem">
65
+        <clobbers target="window.FileSystem" />
66
+    </js-module>
67
+
68
+    <js-module src="www/FileUploadOptions.js" name="FileUploadOptions">
69
+        <clobbers target="window.FileUploadOptions" />
70
+    </js-module>
71
+
72
+    <js-module src="www/FileUploadResult.js" name="FileUploadResult">
73
+        <clobbers target="window.FileUploadResult" />
74
+    </js-module>
75
+
76
+    <js-module src="www/FileWriter.js" name="FileWriter">
77
+        <clobbers target="window.FileWriter" />
78
+    </js-module>
79
+
80
+    <js-module src="www/Flags.js" name="Flags">
81
+        <clobbers target="window.Flags" />
82
+    </js-module>
83
+
84
+    <js-module src="www/LocalFileSystem.js" name="LocalFileSystem">
85
+        <!-- Non-standards way -->
86
+        <clobbers target="window.LocalFileSystem" />
87
+        <!-- Standards-compliant way -->
88
+        <merges target="window" />
89
+    </js-module>
90
+
91
+    <js-module src="www/Metadata.js" name="Metadata">
92
+        <clobbers target="window.Metadata" />
93
+    </js-module>
94
+
95
+    <js-module src="www/ProgressEvent.js" name="ProgressEvent">
96
+        <clobbers target="window.ProgressEvent" />
97
+    </js-module>
98
+
99
+    <js-module src="www/fileSystems.js" name="fileSystems" />
100
+
101
+    <js-module src="www/requestFileSystem.js" name="requestFileSystem">
102
+        <clobbers target="window.requestFileSystem" />
103
+    </js-module>
104
+
105
+    <js-module src="www/resolveLocalFileSystemURI.js" name="resolveLocalFileSystemURI">
106
+        <merges target="window" />
107
+    </js-module>
108
+
109
+    <!-- Required for browserify: we always link module below as there is conditional reference
110
+    to this module from requestFileSystem and resolveLocalFileSystemURI modules. -->
111
+    <js-module src="www/browser/isChrome.js" name="isChrome">
112
+        <runs />
113
+    </js-module>
114
+
115
+    <!-- android -->
116
+    <platform name="android">
117
+        <info>
118
+The Android Persistent storage location now defaults to "Internal". Please check this plugin's README to see if your application needs any changes in its config.xml.
119
+
120
+If this is a new application no changes are required.
121
+
122
+If this is an update to an existing application that did not specify an "AndroidPersistentFileLocation" you may need to add:
123
+
124
+      "&lt;preference name="AndroidPersistentFileLocation" value="Compatibility" /&gt;"
125
+
126
+to config.xml in order for the application to find previously stored files.
127
+        </info>
128
+        <config-file target="res/xml/config.xml" parent="/*">
129
+            <feature name="File" >
130
+                <param name="android-package" value="org.apache.cordova.file.FileUtils"/>
131
+                <param name="onload" value="true" />
132
+            </feature>
133
+            <allow-navigation href="cdvfile:*" />
134
+        </config-file>
135
+
136
+        <config-file target="AndroidManifest.xml" parent="/*">
137
+            <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
138
+        </config-file>
139
+
140
+        <source-file src="src/android/EncodingException.java" target-dir="src/org/apache/cordova/file" />
141
+        <source-file src="src/android/FileExistsException.java" target-dir="src/org/apache/cordova/file" />
142
+        <source-file src="src/android/InvalidModificationException.java" target-dir="src/org/apache/cordova/file" />
143
+        <source-file src="src/android/NoModificationAllowedException.java" target-dir="src/org/apache/cordova/file" />
144
+        <source-file src="src/android/TypeMismatchException.java" target-dir="src/org/apache/cordova/file" />
145
+        <source-file src="src/android/FileUtils.java" target-dir="src/org/apache/cordova/file" />
146
+        <source-file src="src/android/DirectoryManager.java" target-dir="src/org/apache/cordova/file" />
147
+        <source-file src="src/android/LocalFilesystemURL.java" target-dir="src/org/apache/cordova/file" />
148
+        <source-file src="src/android/Filesystem.java" target-dir="src/org/apache/cordova/file" />
149
+        <source-file src="src/android/LocalFilesystem.java" target-dir="src/org/apache/cordova/file" />
150
+        <source-file src="src/android/ContentFilesystem.java" target-dir="src/org/apache/cordova/file" />
151
+        <source-file src="src/android/AssetFilesystem.java" target-dir="src/org/apache/cordova/file" />
152
+        <source-file src="src/android/PendingRequests.java" target-dir="src/org/apache/cordova/file" />
153
+
154
+        <!-- android specific file apis -->
155
+        <js-module src="www/android/FileSystem.js" name="androidFileSystem">
156
+            <merges target="FileSystem" />
157
+        </js-module>
158
+        <js-module src="www/fileSystems-roots.js" name="fileSystems-roots">
159
+            <runs/>
160
+        </js-module>
161
+        <js-module src="www/fileSystemPaths.js" name="fileSystemPaths">
162
+            <merges target="cordova" />
163
+            <runs/>
164
+        </js-module>
165
+    </platform>
166
+
167
+    <!-- ios -->
168
+    <platform name="ios">
169
+        <config-file target="config.xml" parent="/*">
170
+            <feature name="File">
171
+                <param name="ios-package" value="CDVFile" />
172
+                <param name="onload" value="true" />
173
+            </feature>
174
+        </config-file>
175
+        <header-file src="src/ios/CDVFile.h" />
176
+        <source-file src="src/ios/CDVFile.m" />
177
+        <header-file src="src/ios/CDVLocalFilesystem.h" />
178
+        <source-file src="src/ios/CDVLocalFilesystem.m" />
179
+        <header-file src="src/ios/CDVAssetLibraryFilesystem.h" />
180
+        <source-file src="src/ios/CDVAssetLibraryFilesystem.m" />
181
+
182
+        <!-- ios specific file apis -->
183
+        <js-module src="www/ios/FileSystem.js" name="iosFileSystem">
184
+            <merges target="FileSystem" />
185
+        </js-module>
186
+
187
+        <js-module src="www/fileSystems-roots.js" name="fileSystems-roots">
188
+            <runs/>
189
+        </js-module>
190
+
191
+        <js-module src="www/fileSystemPaths.js" name="fileSystemPaths">
192
+            <merges target="cordova" />
193
+            <runs/>
194
+        </js-module>
195
+
196
+        <framework src="AssetsLibrary.framework" />
197
+        <framework src="MobileCoreServices.framework" />
198
+    </platform>
199
+
200
+    <!-- osx -->
201
+    <platform name="osx">
202
+        <config-file target="config.xml" parent="/*">
203
+            <feature name="File">
204
+                <param name="ios-package" value="CDVFile" />
205
+                <param name="onload" value="true" />
206
+            </feature>
207
+        </config-file>
208
+        <header-file src="src/osx/CDVFile.h" />
209
+        <source-file src="src/osx/CDVFile.m" />
210
+        <header-file src="src/osx/CDVLocalFilesystem.h" />
211
+        <source-file src="src/osx/CDVLocalFilesystem.m" />
212
+
213
+        <!-- osx specific file apis -->
214
+        <js-module src="www/osx/FileSystem.js" name="osxFileSystem">
215
+            <merges target="FileSystem" />
216
+        </js-module>
217
+
218
+        <js-module src="www/fileSystems-roots.js" name="fileSystems-roots">
219
+            <runs/>
220
+        </js-module>
221
+
222
+        <js-module src="www/fileSystemPaths.js" name="fileSystemPaths">
223
+            <merges target="cordova" />
224
+            <runs/>
225
+        </js-module>
226
+    </platform>
227
+
228
+    <!-- windows -->
229
+    <platform name="windows">
230
+        <js-module src="src/windows/FileProxy.js" name="FileProxy">
231
+            <runs />
232
+        </js-module>
233
+
234
+        <js-module src="www/fileSystemPaths.js" name="fileSystemPaths">
235
+            <merges target="cordova" />
236
+            <runs/>
237
+        </js-module>
238
+    </platform>
239
+
240
+    <platform name="browser">
241
+        <!-- File for Chrome -->
242
+        <js-module src="www/browser/Preparing.js" name="Preparing">
243
+            <runs />
244
+        </js-module>
245
+
246
+        <js-module src="src/browser/FileProxy.js" name="browserFileProxy">
247
+            <runs />
248
+        </js-module>
249
+
250
+        <js-module src="www/fileSystemPaths.js" name="fileSystemPaths">
251
+            <merges target="cordova" />
252
+            <runs />
253
+        </js-module>
254
+
255
+        <js-module src="www/browser/FileSystem.js" name="firefoxFileSystem">
256
+            <merges target="window.FileSystem" />
257
+        </js-module>
258
+    </platform>
259
+
260
+</plugin>

+ 294
- 0
plugins/cordova-plugin-file/src/android/AssetFilesystem.java View File

@@ -0,0 +1,294 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+ */
19
+package org.apache.cordova.file;
20
+
21
+import android.content.res.AssetManager;
22
+import android.net.Uri;
23
+
24
+import org.apache.cordova.CordovaResourceApi;
25
+import org.apache.cordova.LOG;
26
+import org.json.JSONArray;
27
+import org.json.JSONException;
28
+import org.json.JSONObject;
29
+
30
+import java.io.File;
31
+import java.io.FileNotFoundException;
32
+import java.io.IOException;
33
+import java.io.ObjectInputStream;
34
+import java.util.HashMap;
35
+import java.util.Map;
36
+
37
+public class AssetFilesystem extends Filesystem {
38
+
39
+    private final AssetManager assetManager;
40
+
41
+    // A custom gradle hook creates the cdvasset.manifest file, which speeds up asset listing a tonne.
42
+    // See: http://stackoverflow.com/questions/16911558/android-assetmanager-list-incredibly-slow
43
+    private static Object listCacheLock = new Object();
44
+    private static boolean listCacheFromFile;
45
+    private static Map<String, String[]> listCache;
46
+    private static Map<String, Long> lengthCache;
47
+
48
+    private static final String LOG_TAG = "AssetFilesystem";
49
+
50
+    private void lazyInitCaches() {
51
+        synchronized (listCacheLock) {
52
+            if (listCache == null) {
53
+                ObjectInputStream ois = null;
54
+                try {
55
+                    ois = new ObjectInputStream(assetManager.open("cdvasset.manifest"));
56
+                    listCache = (Map<String, String[]>) ois.readObject();
57
+                    lengthCache = (Map<String, Long>) ois.readObject();
58
+                    listCacheFromFile = true;
59
+                } catch (ClassNotFoundException e) {
60
+                    e.printStackTrace();
61
+                } catch (IOException e) {
62
+                    // Asset manifest won't exist if the gradle hook isn't set up correctly.
63
+                } finally {
64
+                    if (ois != null) {
65
+                        try {
66
+                            ois.close();
67
+                        } catch (IOException e) {
68
+                            LOG.d(LOG_TAG, e.getLocalizedMessage());
69
+                        }
70
+                    }
71
+                }
72
+                if (listCache == null) {
73
+                    LOG.w("AssetFilesystem", "Asset manifest not found. Recursive copies and directory listing will be slow.");
74
+                    listCache = new HashMap<String, String[]>();
75
+                }
76
+            }
77
+        }
78
+    }
79
+
80
+    private String[] listAssets(String assetPath) throws IOException {
81
+        if (assetPath.startsWith("/")) {
82
+            assetPath = assetPath.substring(1);
83
+        }
84
+        if (assetPath.endsWith("/")) {
85
+            assetPath = assetPath.substring(0, assetPath.length() - 1);
86
+        }
87
+        lazyInitCaches();
88
+        String[] ret = listCache.get(assetPath);
89
+        if (ret == null) {
90
+            if (listCacheFromFile) {
91
+                ret = new String[0];
92
+            } else {
93
+                ret = assetManager.list(assetPath);
94
+                listCache.put(assetPath, ret);
95
+            }
96
+        }
97
+        return ret;
98
+    }
99
+
100
+    private long getAssetSize(String assetPath) throws FileNotFoundException {
101
+        if (assetPath.startsWith("/")) {
102
+            assetPath = assetPath.substring(1);
103
+        }
104
+        lazyInitCaches();
105
+        if (lengthCache != null) {
106
+            Long ret = lengthCache.get(assetPath);
107
+            if (ret == null) {
108
+                throw new FileNotFoundException("Asset not found: " + assetPath);
109
+            }
110
+            return ret;
111
+        }
112
+        CordovaResourceApi.OpenForReadResult offr = null;
113
+        try {
114
+            offr = resourceApi.openForRead(nativeUriForFullPath(assetPath));
115
+            long length = offr.length;
116
+            if (length < 0) {
117
+                // available() doesn't always yield the file size, but for assets it does.
118
+                length = offr.inputStream.available();
119
+            }
120
+            return length;
121
+        } catch (IOException e) {
122
+            FileNotFoundException fnfe = new FileNotFoundException("File not found: " + assetPath);
123
+            fnfe.initCause(e);
124
+            throw fnfe;
125
+        } finally {
126
+            if (offr != null) {
127
+                try {
128
+                    offr.inputStream.close();
129
+                } catch (IOException e) {
130
+                    LOG.d(LOG_TAG, e.getLocalizedMessage());
131
+                }
132
+            }
133
+        }
134
+    }
135
+
136
+    public AssetFilesystem(AssetManager assetManager, CordovaResourceApi resourceApi) {
137
+        super(Uri.parse("file:///android_asset/"), "assets", resourceApi);
138
+        this.assetManager = assetManager;
139
+	}
140
+
141
+    @Override
142
+    public Uri toNativeUri(LocalFilesystemURL inputURL) {
143
+        return nativeUriForFullPath(inputURL.path);
144
+    }
145
+
146
+    @Override
147
+    public LocalFilesystemURL toLocalUri(Uri inputURL) {
148
+        if (!"file".equals(inputURL.getScheme())) {
149
+            return null;
150
+        }
151
+        File f = new File(inputURL.getPath());
152
+        // Removes and duplicate /s (e.g. file:///a//b/c)
153
+        Uri resolvedUri = Uri.fromFile(f);
154
+        String rootUriNoTrailingSlash = rootUri.getEncodedPath();
155
+        rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
156
+        if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
157
+            return null;
158
+        }
159
+        String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
160
+        // Strip leading slash
161
+        if (!subPath.isEmpty()) {
162
+            subPath = subPath.substring(1);
163
+        }
164
+        Uri.Builder b = new Uri.Builder()
165
+            .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
166
+            .authority("localhost")
167
+            .path(name);
168
+        if (!subPath.isEmpty()) {
169
+            b.appendEncodedPath(subPath);
170
+        }
171
+        if (isDirectory(subPath) || inputURL.getPath().endsWith("/")) {
172
+            // Add trailing / for directories.
173
+            b.appendEncodedPath("");
174
+        }
175
+        return LocalFilesystemURL.parse(b.build());
176
+    }
177
+
178
+    private boolean isDirectory(String assetPath) {
179
+        try {
180
+            return listAssets(assetPath).length != 0;
181
+        } catch (IOException e) {
182
+            return false;
183
+        }
184
+    }
185
+
186
+    @Override
187
+    public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
188
+        String pathNoSlashes = inputURL.path.substring(1);
189
+        if (pathNoSlashes.endsWith("/")) {
190
+            pathNoSlashes = pathNoSlashes.substring(0, pathNoSlashes.length() - 1);
191
+        }
192
+
193
+        String[] files;
194
+        try {
195
+            files = listAssets(pathNoSlashes);
196
+        } catch (IOException e) {
197
+            FileNotFoundException fnfe = new FileNotFoundException();
198
+            fnfe.initCause(e);
199
+            throw fnfe;
200
+        }
201
+
202
+        LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
203
+        for (int i = 0; i < files.length; ++i) {
204
+            entries[i] = localUrlforFullPath(new File(inputURL.path, files[i]).getPath());
205
+        }
206
+        return entries;
207
+	}
208
+
209
+    @Override
210
+    public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
211
+                                         String path, JSONObject options, boolean directory)
212
+            throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
213
+        if (options != null && options.optBoolean("create")) {
214
+            throw new UnsupportedOperationException("Assets are read-only");
215
+        }
216
+
217
+        // Check whether the supplied path is absolute or relative
218
+        if (directory && !path.endsWith("/")) {
219
+            path += "/";
220
+        }
221
+
222
+        LocalFilesystemURL requestedURL;
223
+        if (path.startsWith("/")) {
224
+            requestedURL = localUrlforFullPath(normalizePath(path));
225
+        } else {
226
+            requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
227
+        }
228
+
229
+        // Throws a FileNotFoundException if it doesn't exist.
230
+        getFileMetadataForLocalURL(requestedURL);
231
+
232
+        boolean isDir = isDirectory(requestedURL.path);
233
+        if (directory && !isDir) {
234
+            throw new TypeMismatchException("path doesn't exist or is file");
235
+        } else if (!directory && isDir) {
236
+            throw new TypeMismatchException("path doesn't exist or is directory");
237
+        }
238
+
239
+        // Return the directory
240
+        return makeEntryForURL(requestedURL);
241
+    }
242
+
243
+    @Override
244
+	public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
245
+        JSONObject metadata = new JSONObject();
246
+        long size = inputURL.isDirectory ? 0 : getAssetSize(inputURL.path);
247
+        try {
248
+        	metadata.put("size", size);
249
+        	metadata.put("type", inputURL.isDirectory ? "text/directory" : resourceApi.getMimeType(toNativeUri(inputURL)));
250
+        	metadata.put("name", new File(inputURL.path).getName());
251
+        	metadata.put("fullPath", inputURL.path);
252
+        	metadata.put("lastModifiedDate", 0);
253
+        } catch (JSONException e) {
254
+            return null;
255
+        }
256
+        return metadata;
257
+	}
258
+
259
+	@Override
260
+	public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
261
+		return false;
262
+	}
263
+
264
+    @Override
265
+    long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset, boolean isBinary) throws NoModificationAllowedException, IOException {
266
+        throw new NoModificationAllowedException("Assets are read-only");
267
+    }
268
+
269
+    @Override
270
+    long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException, NoModificationAllowedException {
271
+        throw new NoModificationAllowedException("Assets are read-only");
272
+    }
273
+
274
+    @Override
275
+    String filesystemPathForURL(LocalFilesystemURL url) {
276
+        return new File(rootUri.getPath(), url.path).toString();
277
+    }
278
+
279
+    @Override
280
+    LocalFilesystemURL URLforFilesystemPath(String path) {
281
+        return null;
282
+    }
283
+
284
+    @Override
285
+    boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException {
286
+        throw new NoModificationAllowedException("Assets are read-only");
287
+    }
288
+
289
+    @Override
290
+    boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws NoModificationAllowedException {
291
+        throw new NoModificationAllowedException("Assets are read-only");
292
+    }
293
+
294
+}

+ 223
- 0
plugins/cordova-plugin-file/src/android/ContentFilesystem.java View File

@@ -0,0 +1,223 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+ */
19
+package org.apache.cordova.file;
20
+
21
+import android.content.ContentResolver;
22
+import android.content.Context;
23
+import android.database.Cursor;
24
+import android.net.Uri;
25
+import android.provider.DocumentsContract;
26
+import android.provider.MediaStore;
27
+import android.provider.OpenableColumns;
28
+import java.io.File;
29
+import java.io.FileNotFoundException;
30
+import java.io.IOException;
31
+import org.apache.cordova.CordovaResourceApi;
32
+import org.json.JSONException;
33
+import org.json.JSONObject;
34
+
35
+public class ContentFilesystem extends Filesystem {
36
+
37
+    private final Context context;
38
+
39
+	public ContentFilesystem(Context context, CordovaResourceApi resourceApi) {
40
+		super(Uri.parse("content://"), "content", resourceApi);
41
+        this.context = context;
42
+	}
43
+
44
+    @Override
45
+    public Uri toNativeUri(LocalFilesystemURL inputURL) {
46
+        String authorityAndPath = inputURL.uri.getEncodedPath().substring(this.name.length() + 2);
47
+        if (authorityAndPath.length() < 2) {
48
+            return null;
49
+        }
50
+        String ret = "content://" + authorityAndPath;
51
+        String query = inputURL.uri.getEncodedQuery();
52
+        if (query != null) {
53
+            ret += '?' + query;
54
+        }
55
+        String frag = inputURL.uri.getEncodedFragment();
56
+        if (frag != null) {
57
+            ret += '#' + frag;
58
+        }
59
+        return Uri.parse(ret);
60
+    }
61
+
62
+    @Override
63
+    public LocalFilesystemURL toLocalUri(Uri inputURL) {
64
+        if (!"content".equals(inputURL.getScheme())) {
65
+            return null;
66
+        }
67
+        String subPath = inputURL.getEncodedPath();
68
+        if (subPath.length() > 0) {
69
+            subPath = subPath.substring(1);
70
+        }
71
+        Uri.Builder b = new Uri.Builder()
72
+            .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
73
+            .authority("localhost")
74
+            .path(name)
75
+            .appendPath(inputURL.getAuthority());
76
+        if (subPath.length() > 0) {
77
+            b.appendEncodedPath(subPath);
78
+        }
79
+        Uri localUri = b.encodedQuery(inputURL.getEncodedQuery())
80
+            .encodedFragment(inputURL.getEncodedFragment())
81
+            .build();
82
+        return LocalFilesystemURL.parse(localUri);
83
+    }
84
+
85
+    @Override
86
+	public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
87
+			String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
88
+        throw new UnsupportedOperationException("getFile() not supported for content:. Use resolveLocalFileSystemURL instead.");
89
+	}
90
+
91
+	@Override
92
+	public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL)
93
+			throws NoModificationAllowedException {
94
+        Uri contentUri = toNativeUri(inputURL);
95
+		try {
96
+            context.getContentResolver().delete(contentUri, null, null);
97
+		} catch (UnsupportedOperationException t) {
98
+			// Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
99
+			// The ContentResolver applies only when the file was registered in the
100
+			// first case, which is generally only the case with images.
101
+            NoModificationAllowedException nmae = new NoModificationAllowedException("Deleting not supported for content uri: " + contentUri);
102
+            nmae.initCause(t);
103
+            throw nmae;
104
+		}
105
+        return true;
106
+	}
107
+
108
+	@Override
109
+	public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL)
110
+			throws NoModificationAllowedException {
111
+		throw new NoModificationAllowedException("Cannot remove content url");
112
+	}
113
+
114
+    @Override
115
+    public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
116
+        throw new UnsupportedOperationException("readEntriesAtLocalURL() not supported for content:. Use resolveLocalFileSystemURL instead.");
117
+    }
118
+
119
+	@Override
120
+	public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
121
+        long size = -1;
122
+        long lastModified = 0;
123
+        Uri nativeUri = toNativeUri(inputURL);
124
+        String mimeType = resourceApi.getMimeType(nativeUri);
125
+        Cursor cursor = openCursorForURL(nativeUri);
126
+        try {
127
+            if (cursor != null && cursor.moveToFirst()) {
128
+                Long sizeForCursor = resourceSizeForCursor(cursor);
129
+                if (sizeForCursor != null) {
130
+                    size = sizeForCursor.longValue();
131
+                }
132
+                Long modified = lastModifiedDateForCursor(cursor);
133
+                if (modified != null)
134
+                    lastModified = modified.longValue();
135
+            } else {
136
+                // Some content providers don't support cursors at all!
137
+                CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(nativeUri);
138
+                size = offr.length;
139
+            }
140
+        } catch (IOException e) {
141
+            FileNotFoundException fnfe = new FileNotFoundException();
142
+            fnfe.initCause(e);
143
+            throw fnfe;
144
+        } finally {
145
+        	if (cursor != null)
146
+        		cursor.close();
147
+        }
148
+
149
+        JSONObject metadata = new JSONObject();
150
+        try {
151
+        	metadata.put("size", size);
152
+        	metadata.put("type", mimeType);
153
+        	metadata.put("name", name);
154
+        	metadata.put("fullPath", inputURL.path);
155
+        	metadata.put("lastModifiedDate", lastModified);
156
+        } catch (JSONException e) {
157
+        	return null;
158
+        }
159
+        return metadata;
160
+	}
161
+
162
+	@Override
163
+	public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
164
+			int offset, boolean isBinary) throws NoModificationAllowedException {
165
+        throw new NoModificationAllowedException("Couldn't write to file given its content URI");
166
+    }
167
+	@Override
168
+	public long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
169
+			throws NoModificationAllowedException {
170
+        throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
171
+	}
172
+
173
+	protected Cursor openCursorForURL(Uri nativeUri) {
174
+        ContentResolver contentResolver = context.getContentResolver();
175
+        try {
176
+            return contentResolver.query(nativeUri, null, null, null, null);
177
+        } catch (UnsupportedOperationException e) {
178
+            return null;
179
+        }
180
+	}
181
+
182
+	private Long resourceSizeForCursor(Cursor cursor) {
183
+        int columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
184
+        if (columnIndex != -1) {
185
+            String sizeStr = cursor.getString(columnIndex);
186
+            if (sizeStr != null) {
187
+            	return Long.parseLong(sizeStr);
188
+            }
189
+        }
190
+        return null;
191
+	}
192
+	
193
+	protected Long lastModifiedDateForCursor(Cursor cursor) {
194
+        int columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
195
+        if (columnIndex == -1) {
196
+            columnIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED);
197
+        }
198
+        if (columnIndex != -1) {
199
+            String dateStr = cursor.getString(columnIndex);
200
+            if (dateStr != null) {
201
+                return Long.parseLong(dateStr);
202
+            }
203
+        }
204
+        return null;
205
+	}
206
+
207
+    @Override
208
+    public String filesystemPathForURL(LocalFilesystemURL url) {
209
+        File f = resourceApi.mapUriToFile(toNativeUri(url));
210
+        return f == null ? null : f.getAbsolutePath();
211
+    }
212
+
213
+	@Override
214
+	public LocalFilesystemURL URLforFilesystemPath(String path) {
215
+		// Returns null as we don't support reverse mapping back to content:// URLs
216
+		return null;
217
+	}
218
+
219
+	@Override
220
+	public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
221
+		return true;
222
+	}
223
+}

+ 134
- 0
plugins/cordova-plugin-file/src/android/DirectoryManager.java View File

@@ -0,0 +1,134 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+package org.apache.cordova.file;
20
+
21
+import android.os.Environment;
22
+import android.os.StatFs;
23
+
24
+import java.io.File;
25
+
26
+/**
27
+ * This class provides file directory utilities.
28
+ * All file operations are performed on the SD card.
29
+ *
30
+ * It is used by the FileUtils class.
31
+ */
32
+public class DirectoryManager {
33
+
34
+    @SuppressWarnings("unused")
35
+    private static final String LOG_TAG = "DirectoryManager";
36
+
37
+    /**
38
+     * Determine if a file or directory exists.
39
+     * @param name				The name of the file to check.
40
+     * @return					T=exists, F=not found
41
+     */
42
+    public static boolean testFileExists(String name) {
43
+        boolean status;
44
+
45
+        // If SD card exists
46
+        if ((testSaveLocationExists()) && (!name.equals(""))) {
47
+            File path = Environment.getExternalStorageDirectory();
48
+            File newPath = constructFilePaths(path.toString(), name);
49
+            status = newPath.exists();
50
+        }
51
+        // If no SD card
52
+        else {
53
+            status = false;
54
+        }
55
+        return status;
56
+    }
57
+
58
+    /**
59
+     * Get the free space in external storage
60
+     *
61
+     * @return 		Size in KB or -1 if not available
62
+     */
63
+    public static long getFreeExternalStorageSpace() {
64
+        String status = Environment.getExternalStorageState();
65
+        long freeSpaceInBytes = 0;
66
+
67
+        // Check if external storage exists
68
+        if (status.equals(Environment.MEDIA_MOUNTED)) {
69
+            freeSpaceInBytes = getFreeSpaceInBytes(Environment.getExternalStorageDirectory().getPath());
70
+        } else {
71
+            // If no external storage then return -1
72
+            return -1;
73
+        }
74
+
75
+        return freeSpaceInBytes / 1024;
76
+    }
77
+
78
+    /**
79
+     * Given a path return the number of free bytes in the filesystem containing the path.
80
+     *
81
+     * @param path to the file system
82
+     * @return free space in bytes
83
+     */
84
+    public static long getFreeSpaceInBytes(String path) {
85
+        try {
86
+            StatFs stat = new StatFs(path);
87
+            long blockSize = stat.getBlockSize();
88
+            long availableBlocks = stat.getAvailableBlocks();
89
+            return availableBlocks * blockSize;
90
+        } catch (IllegalArgumentException e) {
91
+            // The path was invalid. Just return 0 free bytes.
92
+            return 0;
93
+        }
94
+    }
95
+
96
+    /**
97
+     * Determine if SD card exists.
98
+     *
99
+     * @return				T=exists, F=not found
100
+     */
101
+    public static boolean testSaveLocationExists() {
102
+        String sDCardStatus = Environment.getExternalStorageState();
103
+        boolean status;
104
+
105
+        // If SD card is mounted
106
+        if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) {
107
+            status = true;
108
+        }
109
+
110
+        // If no SD card
111
+        else {
112
+            status = false;
113
+        }
114
+        return status;
115
+    }
116
+
117
+    /**
118
+     * Create a new file object from two file paths.
119
+     *
120
+     * @param file1			Base file path
121
+     * @param file2			Remaining file path
122
+     * @return				File object
123
+     */
124
+    private static File constructFilePaths (String file1, String file2) {
125
+        File newPath;
126
+        if (file2.startsWith(file1)) {
127
+            newPath = new File(file2);
128
+        }
129
+        else {
130
+            newPath = new File(file1 + "/" + file2);
131
+        }
132
+        return newPath;
133
+    }
134
+}

+ 29
- 0
plugins/cordova-plugin-file/src/android/EncodingException.java View File

@@ -0,0 +1,29 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+
20
+package org.apache.cordova.file;
21
+
22
+@SuppressWarnings("serial")
23
+public class EncodingException extends Exception {
24
+
25
+    public EncodingException(String message) {
26
+        super(message);
27
+    }
28
+
29
+}

+ 29
- 0
plugins/cordova-plugin-file/src/android/FileExistsException.java View File

@@ -0,0 +1,29 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+
20
+package org.apache.cordova.file;
21
+
22
+@SuppressWarnings("serial")
23
+public class FileExistsException extends Exception {
24
+
25
+    public FileExistsException(String msg) {
26
+        super(msg);
27
+    }
28
+
29
+}

+ 1225
- 0
plugins/cordova-plugin-file/src/android/FileUtils.java
File diff suppressed because it is too large
View File


+ 331
- 0
plugins/cordova-plugin-file/src/android/Filesystem.java View File

@@ -0,0 +1,331 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+ */
19
+package org.apache.cordova.file;
20
+
21
+import android.net.Uri;
22
+
23
+import java.io.File;
24
+import java.io.FileNotFoundException;
25
+import java.io.FilterInputStream;
26
+import java.io.IOException;
27
+import java.io.InputStream;
28
+import java.io.OutputStream;
29
+import java.util.ArrayList;
30
+import java.util.Arrays;
31
+
32
+import org.apache.cordova.CordovaResourceApi;
33
+import org.json.JSONArray;
34
+import org.json.JSONException;
35
+import org.json.JSONObject;
36
+
37
+public abstract class Filesystem {
38
+
39
+    protected final Uri rootUri;
40
+    protected final CordovaResourceApi resourceApi;
41
+    public final String name;
42
+    private JSONObject rootEntry;
43
+
44
+    public Filesystem(Uri rootUri, String name, CordovaResourceApi resourceApi) {
45
+        this.rootUri = rootUri;
46
+        this.name = name;
47
+        this.resourceApi = resourceApi;
48
+    }
49
+
50
+    public interface ReadFileCallback {
51
+		public void handleData(InputStream inputStream, String contentType) throws IOException;
52
+	}
53
+
54
+    public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Uri nativeURL) {
55
+        try {
56
+            String path = inputURL.path;
57
+            int end = path.endsWith("/") ? 1 : 0;
58
+            String[] parts = path.substring(0, path.length() - end).split("/+");
59
+            String fileName = parts[parts.length - 1];
60
+
61
+            JSONObject entry = new JSONObject();
62
+            entry.put("isFile", !inputURL.isDirectory);
63
+            entry.put("isDirectory", inputURL.isDirectory);
64
+            entry.put("name", fileName);
65
+            entry.put("fullPath", path);
66
+            // The file system can't be specified, as it would lead to an infinite loop,
67
+            // but the filesystem name can be.
68
+            entry.put("filesystemName", inputURL.fsName);
69
+            // Backwards compatibility
70
+            entry.put("filesystem", "temporary".equals(inputURL.fsName) ? 0 : 1);
71
+
72
+            String nativeUrlStr = nativeURL.toString();
73
+            if (inputURL.isDirectory && !nativeUrlStr.endsWith("/")) {
74
+                nativeUrlStr += "/";
75
+            }
76
+            entry.put("nativeURL", nativeUrlStr);
77
+            return entry;
78
+        } catch (JSONException e) {
79
+            e.printStackTrace();
80
+            throw new RuntimeException(e);
81
+        }
82
+    }
83
+
84
+    public JSONObject makeEntryForURL(LocalFilesystemURL inputURL) {
85
+        Uri nativeUri = toNativeUri(inputURL);
86
+        return nativeUri == null ? null : makeEntryForURL(inputURL, nativeUri);
87
+    }
88
+
89
+    public JSONObject makeEntryForNativeUri(Uri nativeUri) {
90
+        LocalFilesystemURL inputUrl = toLocalUri(nativeUri);
91
+        return inputUrl == null ? null : makeEntryForURL(inputUrl, nativeUri);
92
+    }
93
+
94
+    public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
95
+        return makeEntryForURL(inputURL);
96
+    }
97
+
98
+    public JSONObject makeEntryForFile(File file) {
99
+        return makeEntryForNativeUri(Uri.fromFile(file));
100
+    }
101
+
102
+    abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
103
+			JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
104
+
105
+	abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
106
+
107
+	abstract boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException, NoModificationAllowedException;
108
+
109
+	abstract LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException;
110
+
111
+    public final JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
112
+        LocalFilesystemURL[] children = listChildren(inputURL);
113
+        JSONArray entries = new JSONArray();
114
+        if (children != null) {
115
+            for (LocalFilesystemURL url : children) {
116
+                entries.put(makeEntryForURL(url));
117
+            }
118
+        }
119
+        return entries;
120
+    }
121
+
122
+	abstract JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
123
+
124
+    public Uri getRootUri() {
125
+        return rootUri;
126
+    }
127
+
128
+    public boolean exists(LocalFilesystemURL inputURL) {
129
+        try {
130
+            getFileMetadataForLocalURL(inputURL);
131
+        } catch (FileNotFoundException e) {
132
+            return false;
133
+        }
134
+        return true;
135
+    }
136
+
137
+    public Uri nativeUriForFullPath(String fullPath) {
138
+        Uri ret = null;
139
+        if (fullPath != null) {
140
+            String encodedPath = Uri.fromFile(new File(fullPath)).getEncodedPath();
141
+            if (encodedPath.startsWith("/")) {
142
+                encodedPath = encodedPath.substring(1);
143
+            }
144
+            ret = rootUri.buildUpon().appendEncodedPath(encodedPath).build();
145
+        }
146
+        return ret;
147
+    }
148
+
149
+    public LocalFilesystemURL localUrlforFullPath(String fullPath) {
150
+        Uri nativeUri = nativeUriForFullPath(fullPath);
151
+        if (nativeUri != null) {
152
+            return toLocalUri(nativeUri);
153
+        }
154
+        return null;
155
+    }
156
+
157
+    /**
158
+     * Removes multiple repeated //s, and collapses processes ../s.
159
+     */
160
+    protected static String normalizePath(String rawPath) {
161
+        // If this is an absolute path, trim the leading "/" and replace it later
162
+        boolean isAbsolutePath = rawPath.startsWith("/");
163
+        if (isAbsolutePath) {
164
+            rawPath = rawPath.replaceFirst("/+", "");
165
+        }
166
+        ArrayList<String> components = new ArrayList<String>(Arrays.asList(rawPath.split("/+")));
167
+        for (int index = 0; index < components.size(); ++index) {
168
+            if (components.get(index).equals("..")) {
169
+                components.remove(index);
170
+                if (index > 0) {
171
+                    components.remove(index-1);
172
+                    --index;
173
+                }
174
+            }
175
+        }
176
+        StringBuilder normalizedPath = new StringBuilder();
177
+        for(String component: components) {
178
+            normalizedPath.append("/");
179
+            normalizedPath.append(component);
180
+        }
181
+        if (isAbsolutePath) {
182
+            return normalizedPath.toString();
183
+        } else {
184
+            return normalizedPath.toString().substring(1);
185
+        }
186
+    }
187
+
188
+    /**
189
+     * Gets the free space in bytes available on this filesystem.
190
+     * Subclasses may override this method to return nonzero free space.
191
+     */
192
+    public long getFreeSpaceInBytes() {
193
+        return 0;
194
+    }
195
+
196
+    public abstract Uri toNativeUri(LocalFilesystemURL inputURL);
197
+    public abstract LocalFilesystemURL toLocalUri(Uri inputURL);
198
+
199
+    public JSONObject getRootEntry() {
200
+        if (rootEntry == null) {
201
+            rootEntry = makeEntryForNativeUri(rootUri);
202
+        }
203
+        return rootEntry;
204
+    }
205
+
206
+	public JSONObject getParentForLocalURL(LocalFilesystemURL inputURL) throws IOException {
207
+        Uri parentUri = inputURL.uri;
208
+        String parentPath = new File(inputURL.uri.getPath()).getParent();
209
+        if (!"/".equals(parentPath)) {
210
+            parentUri = inputURL.uri.buildUpon().path(parentPath + '/').build();
211
+		}
212
+		return getEntryForLocalURL(LocalFilesystemURL.parse(parentUri));
213
+	}
214
+
215
+    protected LocalFilesystemURL makeDestinationURL(String newName, LocalFilesystemURL srcURL, LocalFilesystemURL destURL, boolean isDirectory) {
216
+        // I know this looks weird but it is to work around a JSON bug.
217
+        if ("null".equals(newName) || "".equals(newName)) {
218
+            newName = srcURL.uri.getLastPathSegment();;
219
+        }
220
+
221
+        String newDest = destURL.uri.toString();
222
+        if (newDest.endsWith("/")) {
223
+            newDest = newDest + newName;
224
+        } else {
225
+            newDest = newDest + "/" + newName;
226
+        }
227
+        if (isDirectory) {
228
+            newDest += '/';
229
+        }
230
+        return LocalFilesystemURL.parse(newDest);
231
+    }
232
+
233
+	/* Read a source URL (possibly from a different filesystem, srcFs,) and copy it to
234
+	 * the destination URL on this filesystem, optionally with a new filename.
235
+	 * If move is true, then this method should either perform an atomic move operation
236
+	 * or remove the source file when finished.
237
+	 */
238
+    public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
239
+            Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
240
+        // First, check to see that we can do it
241
+        if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
242
+            throw new NoModificationAllowedException("Cannot move file at source URL");
243
+        }
244
+        final LocalFilesystemURL destination = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
245
+
246
+        Uri srcNativeUri = srcFs.toNativeUri(srcURL);
247
+
248
+        CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcNativeUri);
249
+        OutputStream os = null;
250
+        try {
251
+            os = getOutputStreamForURL(destination);
252
+        } catch (IOException e) {
253
+            ofrr.inputStream.close();
254
+            throw e;
255
+        }
256
+        // Closes streams.
257
+        resourceApi.copyResource(ofrr, os);
258
+
259
+        if (move) {
260
+            srcFs.removeFileAtLocalURL(srcURL);
261
+        }
262
+        return getEntryForLocalURL(destination);
263
+    }
264
+
265
+    public OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException {
266
+        return resourceApi.openOutputStream(toNativeUri(inputURL));
267
+    }
268
+
269
+    public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
270
+                              ReadFileCallback readFileCallback) throws IOException {
271
+        CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(toNativeUri(inputURL));
272
+        if (end < 0) {
273
+            end = ofrr.length;
274
+        }
275
+        long numBytesToRead = end - start;
276
+        try {
277
+            if (start > 0) {
278
+                ofrr.inputStream.skip(start);
279
+            }
280
+            InputStream inputStream = ofrr.inputStream;
281
+            if (end < ofrr.length) {
282
+                inputStream = new LimitedInputStream(inputStream, numBytesToRead);
283
+            }
284
+            readFileCallback.handleData(inputStream, ofrr.mimeType);
285
+        } finally {
286
+            ofrr.inputStream.close();
287
+        }
288
+    }
289
+
290
+	abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
291
+			boolean isBinary) throws NoModificationAllowedException, IOException;
292
+
293
+	abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
294
+			throws IOException, NoModificationAllowedException;
295
+
296
+	// This method should return null if filesystem urls cannot be mapped to paths
297
+	abstract String filesystemPathForURL(LocalFilesystemURL url);
298
+
299
+	abstract LocalFilesystemURL URLforFilesystemPath(String path);
300
+
301
+	abstract boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL);
302
+
303
+    protected class LimitedInputStream extends FilterInputStream {
304
+        long numBytesToRead;
305
+        public LimitedInputStream(InputStream in, long numBytesToRead) {
306
+            super(in);
307
+            this.numBytesToRead = numBytesToRead;
308
+        }
309
+        @Override
310
+        public int read() throws IOException {
311
+            if (numBytesToRead <= 0) {
312
+                return -1;
313
+            }
314
+            numBytesToRead--;
315
+            return in.read();
316
+        }
317
+        @Override
318
+        public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
319
+            if (numBytesToRead <= 0) {
320
+                return -1;
321
+            }
322
+            int bytesToRead = byteCount;
323
+            if (byteCount > numBytesToRead) {
324
+                bytesToRead = (int)numBytesToRead; // Cast okay; long is less than int here.
325
+            }
326
+            int numBytesRead = in.read(buffer, byteOffset, bytesToRead);
327
+            numBytesToRead -= numBytesRead;
328
+            return numBytesRead;
329
+        }
330
+    }
331
+}

+ 30
- 0
plugins/cordova-plugin-file/src/android/InvalidModificationException.java View File

@@ -0,0 +1,30 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+
20
+
21
+package org.apache.cordova.file;
22
+
23
+@SuppressWarnings("serial")
24
+public class InvalidModificationException extends Exception {
25
+
26
+    public InvalidModificationException(String message) {
27
+        super(message);
28
+    }
29
+
30
+}

+ 513
- 0
plugins/cordova-plugin-file/src/android/LocalFilesystem.java View File

@@ -0,0 +1,513 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+ */
19
+package org.apache.cordova.file;
20
+
21
+import java.io.ByteArrayInputStream;
22
+import java.io.File;
23
+import java.io.FileInputStream;
24
+import java.io.FileNotFoundException;
25
+import java.io.FileOutputStream;
26
+import java.io.IOException;
27
+import java.io.InputStream;
28
+import java.io.OutputStream;
29
+import java.io.RandomAccessFile;
30
+import java.nio.channels.FileChannel;
31
+import org.apache.cordova.CordovaResourceApi;
32
+import org.json.JSONException;
33
+import org.json.JSONObject;
34
+
35
+import android.os.Build;
36
+import android.os.Environment;
37
+import android.util.Base64;
38
+import android.net.Uri;
39
+import android.content.Context;
40
+import android.content.Intent;
41
+
42
+import java.nio.charset.Charset;
43
+
44
+public class LocalFilesystem extends Filesystem {
45
+    private final Context context;
46
+
47
+    public LocalFilesystem(String name, Context context, CordovaResourceApi resourceApi, File fsRoot) {
48
+        super(Uri.fromFile(fsRoot).buildUpon().appendEncodedPath("").build(), name, resourceApi);
49
+        this.context = context;
50
+    }
51
+
52
+    public String filesystemPathForFullPath(String fullPath) {
53
+	    return new File(rootUri.getPath(), fullPath).toString();
54
+	}
55
+
56
+	@Override
57
+	public String filesystemPathForURL(LocalFilesystemURL url) {
58
+		return filesystemPathForFullPath(url.path);
59
+	}
60
+
61
+	private String fullPathForFilesystemPath(String absolutePath) {
62
+		if (absolutePath != null && absolutePath.startsWith(rootUri.getPath())) {
63
+			return absolutePath.substring(rootUri.getPath().length() - 1);
64
+		}
65
+		return null;
66
+	}
67
+
68
+    @Override
69
+    public Uri toNativeUri(LocalFilesystemURL inputURL) {
70
+        return nativeUriForFullPath(inputURL.path);
71
+    }
72
+
73
+    @Override
74
+    public LocalFilesystemURL toLocalUri(Uri inputURL) {
75
+        if (!"file".equals(inputURL.getScheme())) {
76
+            return null;
77
+        }
78
+        File f = new File(inputURL.getPath());
79
+        // Removes and duplicate /s (e.g. file:///a//b/c)
80
+        Uri resolvedUri = Uri.fromFile(f);
81
+        String rootUriNoTrailingSlash = rootUri.getEncodedPath();
82
+        rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
83
+        if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
84
+            return null;
85
+        }
86
+        String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
87
+        // Strip leading slash
88
+        if (!subPath.isEmpty()) {
89
+            subPath = subPath.substring(1);
90
+        }
91
+        Uri.Builder b = new Uri.Builder()
92
+            .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
93
+            .authority("localhost")
94
+            .path(name);
95
+        if (!subPath.isEmpty()) {
96
+            b.appendEncodedPath(subPath);
97
+        }
98
+        if (f.isDirectory()) {
99
+            // Add trailing / for directories.
100
+            b.appendEncodedPath("");
101
+        }
102
+        return LocalFilesystemURL.parse(b.build());
103
+    }
104
+
105
+	@Override
106
+	public LocalFilesystemURL URLforFilesystemPath(String path) {
107
+	    return localUrlforFullPath(fullPathForFilesystemPath(path));
108
+	}
109
+
110
+	@Override
111
+	public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
112
+			String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
113
+        boolean create = false;
114
+        boolean exclusive = false;
115
+
116
+        if (options != null) {
117
+            create = options.optBoolean("create");
118
+            if (create) {
119
+                exclusive = options.optBoolean("exclusive");
120
+            }
121
+        }
122
+
123
+        // Check for a ":" character in the file to line up with BB and iOS
124
+        if (path.contains(":")) {
125
+            throw new EncodingException("This path has an invalid \":\" in it.");
126
+        }
127
+
128
+        LocalFilesystemURL requestedURL;
129
+
130
+        // Check whether the supplied path is absolute or relative
131
+        if (directory && !path.endsWith("/")) {
132
+            path += "/";
133
+        }
134
+        if (path.startsWith("/")) {
135
+        	requestedURL = localUrlforFullPath(normalizePath(path));
136
+        } else {
137
+        	requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
138
+        }
139
+
140
+        File fp = new File(this.filesystemPathForURL(requestedURL));
141
+
142
+        if (create) {
143
+            if (exclusive && fp.exists()) {
144
+                throw new FileExistsException("create/exclusive fails");
145
+            }
146
+            if (directory) {
147
+                fp.mkdir();
148
+            } else {
149
+                fp.createNewFile();
150
+            }
151
+            if (!fp.exists()) {
152
+                throw new FileExistsException("create fails");
153
+            }
154
+        }
155
+        else {
156
+            if (!fp.exists()) {
157
+                throw new FileNotFoundException("path does not exist");
158
+            }
159
+            if (directory) {
160
+                if (fp.isFile()) {
161
+                    throw new TypeMismatchException("path doesn't exist or is file");
162
+                }
163
+            } else {
164
+                if (fp.isDirectory()) {
165
+                    throw new TypeMismatchException("path doesn't exist or is directory");
166
+                }
167
+            }
168
+        }
169
+
170
+        // Return the directory
171
+        return makeEntryForURL(requestedURL);
172
+	}
173
+
174
+	@Override
175
+	public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException {
176
+
177
+        File fp = new File(filesystemPathForURL(inputURL));
178
+
179
+        // You can't delete a directory that is not empty
180
+        if (fp.isDirectory() && fp.list().length > 0) {
181
+            throw new InvalidModificationException("You can't delete a directory that is not empty.");
182
+        }
183
+
184
+        return fp.delete();
185
+	}
186
+
187
+    @Override
188
+    public boolean exists(LocalFilesystemURL inputURL) {
189
+        File fp = new File(filesystemPathForURL(inputURL));
190
+        return fp.exists();
191
+    }
192
+
193
+    @Override
194
+    public long getFreeSpaceInBytes() {
195
+        return DirectoryManager.getFreeSpaceInBytes(rootUri.getPath());
196
+    }
197
+
198
+    @Override
199
+	public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException {
200
+        File directory = new File(filesystemPathForURL(inputURL));
201
+    	return removeDirRecursively(directory);
202
+	}
203
+
204
+	protected boolean removeDirRecursively(File directory) throws FileExistsException {
205
+        if (directory.isDirectory()) {
206
+            for (File file : directory.listFiles()) {
207
+                removeDirRecursively(file);
208
+            }
209
+        }
210
+
211
+        if (!directory.delete()) {
212
+            throw new FileExistsException("could not delete: " + directory.getName());
213
+        } else {
214
+            return true;
215
+        }
216
+	}
217
+
218
+    @Override
219
+    public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
220
+        File fp = new File(filesystemPathForURL(inputURL));
221
+
222
+        if (!fp.exists()) {
223
+            // The directory we are listing doesn't exist so we should fail.
224
+            throw new FileNotFoundException();
225
+        }
226
+
227
+        File[] files = fp.listFiles();
228
+        if (files == null) {
229
+            // inputURL is a directory
230
+            return null;
231
+        }
232
+        LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
233
+        for (int i = 0; i < files.length; i++) {
234
+            entries[i] = URLforFilesystemPath(files[i].getPath());
235
+        }
236
+
237
+        return entries;
238
+	}
239
+
240
+	@Override
241
+	public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
242
+        File file = new File(filesystemPathForURL(inputURL));
243
+
244
+        if (!file.exists()) {
245
+            throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
246
+        }
247
+
248
+        JSONObject metadata = new JSONObject();
249
+        try {
250
+            // Ensure that directories report a size of 0
251
+        	metadata.put("size", file.isDirectory() ? 0 : file.length());
252
+        	metadata.put("type", resourceApi.getMimeType(Uri.fromFile(file)));
253
+        	metadata.put("name", file.getName());
254
+        	metadata.put("fullPath", inputURL.path);
255
+        	metadata.put("lastModifiedDate", file.lastModified());
256
+        } catch (JSONException e) {
257
+        	return null;
258
+        }
259
+        return metadata;
260
+	}
261
+
262
+    private void copyFile(Filesystem srcFs, LocalFilesystemURL srcURL, File destFile, boolean move) throws IOException, InvalidModificationException, NoModificationAllowedException {
263
+        if (move) {
264
+            String realSrcPath = srcFs.filesystemPathForURL(srcURL);
265
+            if (realSrcPath != null) {
266
+                File srcFile = new File(realSrcPath);
267
+                if (srcFile.renameTo(destFile)) {
268
+                    return;
269
+                }
270
+                // Trying to rename the file failed.  Possibly because we moved across file system on the device.
271
+            }
272
+        }
273
+
274
+        CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(srcFs.toNativeUri(srcURL));
275
+        copyResource(offr, new FileOutputStream(destFile));
276
+
277
+        if (move) {
278
+            srcFs.removeFileAtLocalURL(srcURL);
279
+        }
280
+    }
281
+
282
+    private void copyDirectory(Filesystem srcFs, LocalFilesystemURL srcURL, File dstDir, boolean move) throws IOException, NoModificationAllowedException, InvalidModificationException, FileExistsException {
283
+        if (move) {
284
+            String realSrcPath = srcFs.filesystemPathForURL(srcURL);
285
+            if (realSrcPath != null) {
286
+                File srcDir = new File(realSrcPath);
287
+                // If the destination directory already exists and is empty then delete it.  This is according to spec.
288
+                if (dstDir.exists()) {
289
+                    if (dstDir.list().length > 0) {
290
+                        throw new InvalidModificationException("directory is not empty");
291
+                    }
292
+                    dstDir.delete();
293
+                }
294
+                // Try to rename the directory
295
+                if (srcDir.renameTo(dstDir)) {
296
+                    return;
297
+                }
298
+                // Trying to rename the file failed.  Possibly because we moved across file system on the device.
299
+            }
300
+        }
301
+
302
+        if (dstDir.exists()) {
303
+            if (dstDir.list().length > 0) {
304
+                throw new InvalidModificationException("directory is not empty");
305
+            }
306
+        } else {
307
+            if (!dstDir.mkdir()) {
308
+                // If we can't create the directory then fail
309
+                throw new NoModificationAllowedException("Couldn't create the destination directory");
310
+            }
311
+        }
312
+
313
+        LocalFilesystemURL[] children = srcFs.listChildren(srcURL);
314
+        for (LocalFilesystemURL childLocalUrl : children) {
315
+            File target = new File(dstDir, new File(childLocalUrl.path).getName());
316
+            if (childLocalUrl.isDirectory) {
317
+                copyDirectory(srcFs, childLocalUrl, target, false);
318
+            } else {
319
+                copyFile(srcFs, childLocalUrl, target, false);
320
+            }
321
+        }
322
+
323
+        if (move) {
324
+            srcFs.recursiveRemoveFileAtLocalURL(srcURL);
325
+        }
326
+    }
327
+
328
+	@Override
329
+	public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
330
+			Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
331
+
332
+		// Check to see if the destination directory exists
333
+        String newParent = this.filesystemPathForURL(destURL);
334
+        File destinationDir = new File(newParent);
335
+        if (!destinationDir.exists()) {
336
+            // The destination does not exist so we should fail.
337
+            throw new FileNotFoundException("The source does not exist");
338
+        }
339
+
340
+        // Figure out where we should be copying to
341
+        final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
342
+
343
+        Uri dstNativeUri = toNativeUri(destinationURL);
344
+        Uri srcNativeUri = srcFs.toNativeUri(srcURL);
345
+        // Check to see if source and destination are the same file
346
+        if (dstNativeUri.equals(srcNativeUri)) {
347
+            throw new InvalidModificationException("Can't copy onto itself");
348
+        }
349
+
350
+        if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
351
+            throw new InvalidModificationException("Source URL is read-only (cannot move)");
352
+        }
353
+
354
+        File destFile = new File(dstNativeUri.getPath());
355
+        if (destFile.exists()) {
356
+            if (!srcURL.isDirectory && destFile.isDirectory()) {
357
+                throw new InvalidModificationException("Can't copy/move a file to an existing directory");
358
+            } else if (srcURL.isDirectory && destFile.isFile()) {
359
+                throw new InvalidModificationException("Can't copy/move a directory to an existing file");
360
+            }
361
+        }
362
+
363
+        if (srcURL.isDirectory) {
364
+            // E.g. Copy /sdcard/myDir to /sdcard/myDir/backup
365
+            if (dstNativeUri.toString().startsWith(srcNativeUri.toString() + '/')) {
366
+                throw new InvalidModificationException("Can't copy directory into itself");
367
+            }
368
+            copyDirectory(srcFs, srcURL, destFile, move);
369
+        } else {
370
+            copyFile(srcFs, srcURL, destFile, move);
371
+        }
372
+        return makeEntryForURL(destinationURL);
373
+	}
374
+
375
+	@Override
376
+	public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
377
+			int offset, boolean isBinary) throws IOException, NoModificationAllowedException {
378
+
379
+        boolean append = false;
380
+        if (offset > 0) {
381
+            this.truncateFileAtURL(inputURL, offset);
382
+            append = true;
383
+        }
384
+
385
+        byte[] rawData;
386
+        if (isBinary) {
387
+            rawData = Base64.decode(data, Base64.DEFAULT);
388
+        } else {
389
+            rawData = data.getBytes(Charset.defaultCharset());
390
+        }
391
+        ByteArrayInputStream in = new ByteArrayInputStream(rawData);
392
+        try
393
+        {
394
+        	byte buff[] = new byte[rawData.length];
395
+            String absolutePath = filesystemPathForURL(inputURL);
396
+            FileOutputStream out = new FileOutputStream(absolutePath, append);
397
+            try {
398
+            	in.read(buff, 0, buff.length);
399
+            	out.write(buff, 0, rawData.length);
400
+            	out.flush();
401
+            } finally {
402
+            	// Always close the output
403
+            	out.close();
404
+            }
405
+            if (isPublicDirectory(absolutePath)) {
406
+                broadcastNewFile(Uri.fromFile(new File(absolutePath)));
407
+            }
408
+        }
409
+        catch (NullPointerException e)
410
+        {
411
+            // This is a bug in the Android implementation of the Java Stack
412
+            NoModificationAllowedException realException = new NoModificationAllowedException(inputURL.toString());
413
+            realException.initCause(e);
414
+            throw realException;
415
+        }
416
+
417
+        return rawData.length;
418
+	}
419
+
420
+    private boolean isPublicDirectory(String absolutePath) {
421
+        // TODO: should expose a way to scan app's private files (maybe via a flag).
422
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
423
+            // Lollipop has a bug where SD cards are null.
424
+            for (File f : context.getExternalMediaDirs()) {
425
+                if(f != null && absolutePath.startsWith(f.getAbsolutePath())) {
426
+                    return true;
427
+                }
428
+            }
429
+        }
430
+
431
+        String extPath = Environment.getExternalStorageDirectory().getAbsolutePath();
432
+        return absolutePath.startsWith(extPath);
433
+    }
434
+
435
+     /**
436
+     * Send broadcast of new file so files appear over MTP
437
+     */
438
+    private void broadcastNewFile(Uri nativeUri) {
439
+        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, nativeUri);
440
+        context.sendBroadcast(intent);
441
+    }
442
+
443
+	@Override
444
+	public long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException {
445
+        File file = new File(filesystemPathForURL(inputURL));
446
+
447
+        if (!file.exists()) {
448
+            throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
449
+        }
450
+
451
+        RandomAccessFile raf = new RandomAccessFile(filesystemPathForURL(inputURL), "rw");
452
+        try {
453
+            if (raf.length() >= size) {
454
+                FileChannel channel = raf.getChannel();
455
+                channel.truncate(size);
456
+                return size;
457
+            }
458
+
459
+            return raf.length();
460
+        } finally {
461
+            raf.close();
462
+        }
463
+
464
+
465
+	}
466
+
467
+	@Override
468
+	public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
469
+		String path = filesystemPathForURL(inputURL);
470
+		File file = new File(path);
471
+		return file.exists();
472
+	}
473
+
474
+    // This is a copy & paste from CordovaResource API that is required since CordovaResourceApi
475
+    // has a bug pre-4.0.0.
476
+    // TODO: Once cordova-android@4.0.0 is released, delete this copy and make the plugin depend on
477
+    // 4.0.0 with an engine tag.
478
+    private static void copyResource(CordovaResourceApi.OpenForReadResult input, OutputStream outputStream) throws IOException {
479
+        try {
480
+            InputStream inputStream = input.inputStream;
481
+            if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
482
+                FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
483
+                FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
484
+                long offset = 0;
485
+                long length = input.length;
486
+                if (input.assetFd != null) {
487
+                    offset = input.assetFd.getStartOffset();
488
+                }
489
+                // transferFrom()'s 2nd arg is a relative position. Need to set the absolute
490
+                // position first.
491
+                inChannel.position(offset);
492
+                outChannel.transferFrom(inChannel, 0, length);
493
+            } else {
494
+                final int BUFFER_SIZE = 8192;
495
+                byte[] buffer = new byte[BUFFER_SIZE];
496
+
497
+                for (;;) {
498
+                    int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
499
+
500
+                    if (bytesRead <= 0) {
501
+                        break;
502
+                    }
503
+                    outputStream.write(buffer, 0, bytesRead);
504
+                }
505
+            }
506
+        } finally {
507
+            input.inputStream.close();
508
+            if (outputStream != null) {
509
+                outputStream.close();
510
+            }
511
+        }
512
+    }
513
+}

+ 64
- 0
plugins/cordova-plugin-file/src/android/LocalFilesystemURL.java View File

@@ -0,0 +1,64 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+ */
19
+package org.apache.cordova.file;
20
+
21
+import android.net.Uri;
22
+
23
+public class LocalFilesystemURL {
24
+	
25
+	public static final String FILESYSTEM_PROTOCOL = "cdvfile";
26
+
27
+    public final Uri uri;
28
+    public final String fsName;
29
+    public final String path;
30
+    public final boolean isDirectory;
31
+
32
+	private LocalFilesystemURL(Uri uri, String fsName, String fsPath, boolean isDirectory) {
33
+		this.uri = uri;
34
+        this.fsName = fsName;
35
+        this.path = fsPath;
36
+        this.isDirectory = isDirectory;
37
+	}
38
+
39
+    public static LocalFilesystemURL parse(Uri uri) {
40
+        if (!FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
41
+            return null;
42
+        }
43
+        String path = uri.getPath();
44
+        if (path.length() < 1) {
45
+            return null;
46
+        }
47
+        int firstSlashIdx = path.indexOf('/', 1);
48
+        if (firstSlashIdx < 0) {
49
+            return null;
50
+        }
51
+        String fsName = path.substring(1, firstSlashIdx);
52
+        path = path.substring(firstSlashIdx);
53
+        boolean isDirectory = path.charAt(path.length() - 1) == '/';
54
+        return new LocalFilesystemURL(uri, fsName, path, isDirectory);
55
+    }
56
+
57
+    public static LocalFilesystemURL parse(String uri) {
58
+        return parse(Uri.parse(uri));
59
+    }
60
+
61
+    public String toString() {
62
+        return uri.toString();
63
+    }
64
+}

+ 29
- 0
plugins/cordova-plugin-file/src/android/NoModificationAllowedException.java View File

@@ -0,0 +1,29 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+
20
+package org.apache.cordova.file;
21
+
22
+@SuppressWarnings("serial")
23
+public class NoModificationAllowedException extends Exception {
24
+
25
+    public NoModificationAllowedException(String message) {
26
+        super(message);
27
+    }
28
+
29
+}

+ 94
- 0
plugins/cordova-plugin-file/src/android/PendingRequests.java View File

@@ -0,0 +1,94 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+package org.apache.cordova.file;
20
+
21
+import android.util.SparseArray;
22
+
23
+import org.apache.cordova.CallbackContext;
24
+
25
+/**
26
+ * Holds pending runtime permission requests
27
+ */
28
+class PendingRequests {
29
+    private int currentReqId = 0;
30
+    private SparseArray<Request> requests = new SparseArray<Request>();
31
+
32
+    /**
33
+     * Creates a request and adds it to the array of pending requests. Each created request gets a
34
+     * unique result code for use with requestPermission()
35
+     * @param rawArgs           The raw arguments passed to the plugin
36
+     * @param action            The action this request corresponds to (get file, etc.)
37
+     * @param callbackContext   The CallbackContext for this plugin call
38
+     * @return                  The request code that can be used to retrieve the Request object
39
+     */
40
+    public synchronized int createRequest(String rawArgs, int action, CallbackContext callbackContext)  {
41
+        Request req = new Request(rawArgs, action, callbackContext);
42
+        requests.put(req.requestCode, req);
43
+        return req.requestCode;
44
+    }
45
+
46
+    /**
47
+     * Gets the request corresponding to this request code and removes it from the pending requests
48
+     * @param requestCode   The request code for the desired request
49
+     * @return              The request corresponding to the given request code or null if such a
50
+     *                      request is not found
51
+     */
52
+    public synchronized Request getAndRemove(int requestCode) {
53
+        Request result = requests.get(requestCode);
54
+        requests.remove(requestCode);
55
+        return result;
56
+    }
57
+
58
+    /**
59
+     * Holds the options and CallbackContext for a call made to the plugin.
60
+     */
61
+    public class Request {
62
+
63
+        // Unique int used to identify this request in any Android permission callback
64
+        private int requestCode;
65
+
66
+        // Action to be performed after permission request result
67
+        private int action;
68
+
69
+        // Raw arguments passed to plugin
70
+        private String rawArgs;
71
+
72
+        // The callback context for this plugin request
73
+        private CallbackContext callbackContext;
74
+
75
+        private Request(String rawArgs, int action, CallbackContext callbackContext) {
76
+            this.rawArgs = rawArgs;
77
+            this.action = action;
78
+            this.callbackContext = callbackContext;
79
+            this.requestCode = currentReqId ++;
80
+        }
81
+
82
+        public int getAction() {
83
+            return this.action;
84
+        }
85
+
86
+        public String getRawArgs() {
87
+            return rawArgs;
88
+        }
89
+
90
+        public CallbackContext getCallbackContext() {
91
+            return callbackContext;
92
+        }
93
+    }
94
+}

+ 30
- 0
plugins/cordova-plugin-file/src/android/TypeMismatchException.java View File

@@ -0,0 +1,30 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+
20
+
21
+package org.apache.cordova.file;
22
+
23
+@SuppressWarnings("serial")
24
+public class TypeMismatchException extends Exception {
25
+
26
+    public TypeMismatchException(String message) {
27
+        super(message);
28
+    }
29
+
30
+}

+ 47
- 0
plugins/cordova-plugin-file/src/android/build-extras.gradle View File

@@ -0,0 +1,47 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+ */
19
+ext.postBuildExtras = {
20
+    def inAssetsDir = file("assets")
21
+    def outAssetsDir = inAssetsDir
22
+    def outFile = new File(outAssetsDir, "cdvasset.manifest")
23
+
24
+    def newTask = task("cdvCreateAssetManifest") << {
25
+        def contents = new HashMap()
26
+        def sizes = new HashMap()
27
+        contents[""] = inAssetsDir.list()
28
+        def tree = fileTree(dir: inAssetsDir)
29
+        tree.visit { fileDetails ->
30
+            if (fileDetails.isDirectory()) {
31
+                contents[fileDetails.relativePath.toString()] = fileDetails.file.list()
32
+            } else {
33
+                sizes[fileDetails.relativePath.toString()] = fileDetails.file.length()
34
+            }
35
+        }
36
+
37
+        outAssetsDir.mkdirs()
38
+        outFile.withObjectOutputStream { oos ->
39
+            oos.writeObject(contents)
40
+            oos.writeObject(sizes)
41
+        }
42
+    }
43
+    newTask.inputs.dir inAssetsDir
44
+    newTask.outputs.file outFile
45
+    def preBuildTask = tasks["preBuild"]
46
+    preBuildTask.dependsOn(newTask)
47
+}

+ 1059
- 0
plugins/cordova-plugin-file/src/browser/FileProxy.js
File diff suppressed because it is too large
View File


+ 30
- 0
plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h View File

@@ -0,0 +1,30 @@
1
+/*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements.  See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership.  The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License.  You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied.  See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+ */
19
+
20
+#import "CDVFile.h"
21
+
22
+extern NSString* const kCDVAssetsLibraryPrefix;
23
+extern NSString* const kCDVAssetsLibraryScheme;
24
+
25
+@interface CDVAssetLibraryFilesystem : NSObject<CDVFileSystem> {
26
+}
27
+
28
+- (id) initWithName:(NSString *)name;
29
+
30
+@end

+ 253
- 0
plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m View File

@@ -0,0 +1,253 @@
1
+/*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements.  See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership.  The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License.  You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied.  See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+ */
19
+
20
+#import "CDVFile.h"
21
+#import "CDVAssetLibraryFilesystem.h"
22
+#import <Cordova/CDV.h>
23
+#import <AssetsLibrary/ALAsset.h>
24
+#import <AssetsLibrary/ALAssetRepresentation.h>
25
+#import <AssetsLibrary/ALAssetsLibrary.h>
26
+#import <MobileCoreServices/MobileCoreServices.h>
27
+
28
+NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
29
+NSString* const kCDVAssetsLibraryScheme = @"assets-library";
30
+
31
+@implementation CDVAssetLibraryFilesystem
32
+@synthesize name=_name, urlTransformer;
33
+
34
+
35
+/*
36
+ The CDVAssetLibraryFilesystem works with resources which are identified
37
+ by iOS as
38
+   asset-library://<path>
39
+ and represents them internally as URLs of the form
40
+   cdvfile://localhost/assets-library/<path>
41
+ */
42
+
43
+- (NSURL *)assetLibraryURLForLocalURL:(CDVFilesystemURL *)url
44
+{
45
+    if ([url.url.scheme isEqualToString:kCDVFilesystemURLPrefix]) {
46
+        NSString *path = [[url.url absoluteString] substringFromIndex:[@"cdvfile://localhost/assets-library" length]];
47
+        return [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@", path]];
48
+    }
49
+    return url.url;
50
+}
51
+
52
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
53
+{
54
+    NSDictionary* entry = [self makeEntryForLocalURL:url];
55
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
56
+}
57
+
58
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
59
+    return [self makeEntryForPath:url.fullPath isDirectory:NO];
60
+}
61
+
62
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
63
+{
64
+    NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
65
+    NSString* lastPart = [fullPath lastPathComponent];
66
+    if (isDir && ![fullPath hasSuffix:@"/"]) {
67
+        fullPath = [fullPath stringByAppendingString:@"/"];
68
+    }
69
+    [dirEntry setObject:[NSNumber numberWithBool:!isDir]  forKey:@"isFile"];
70
+    [dirEntry setObject:[NSNumber numberWithBool:isDir]  forKey:@"isDirectory"];
71
+    [dirEntry setObject:fullPath forKey:@"fullPath"];
72
+    [dirEntry setObject:lastPart forKey:@"name"];
73
+    [dirEntry setObject:self.name forKey: @"filesystemName"];
74
+
75
+    NSURL* nativeURL = [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@",fullPath]];
76
+    if (self.urlTransformer) {
77
+        nativeURL = self.urlTransformer(nativeURL);
78
+    }
79
+    dirEntry[@"nativeURL"] = [nativeURL absoluteString];
80
+
81
+    return dirEntry;
82
+}
83
+
84
+/* helper function to get the mimeType from the file extension
85
+ * IN:
86
+ *	NSString* fullPath - filename (may include path)
87
+ * OUT:
88
+ *	NSString* the mime type as type/subtype.  nil if not able to determine
89
+ */
90
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
91
+{
92
+    NSString* mimeType = nil;
93
+
94
+    if (fullPath) {
95
+        CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
96
+        if (typeId) {
97
+            mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
98
+            if (!mimeType) {
99
+                // special case for m4a
100
+                if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
101
+                    mimeType = @"audio/mp4";
102
+                } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
103
+                    mimeType = @"audio/wav";
104
+                } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
105
+                    mimeType = @"text/css";
106
+                }
107
+            }
108
+            CFRelease(typeId);
109
+        }
110
+    }
111
+    return mimeType;
112
+}
113
+
114
+- (id)initWithName:(NSString *)name
115
+{
116
+    if (self) {
117
+        self.name = name;
118
+    }
119
+    return self;
120
+}
121
+
122
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
123
+{
124
+    // return unsupported result for assets-library URLs
125
+   return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."];
126
+}
127
+
128
+- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
129
+{
130
+    // we don't (yet?) support getting the parent of an asset
131
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR];
132
+}
133
+
134
+- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
135
+{
136
+    // setMetadata doesn't make sense for asset library files
137
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
138
+}
139
+
140
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
141
+{
142
+    // return error for assets-library URLs
143
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
144
+}
145
+
146
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
147
+{
148
+    // return error for assets-library URLs
149
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."];
150
+}
151
+
152
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
153
+{
154
+    // return unsupported result for assets-library URLs
155
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."];
156
+}
157
+
158
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
159
+{
160
+    // assets-library files can't be truncated
161
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
162
+}
163
+
164
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
165
+{
166
+    // text can't be written into assets-library files
167
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
168
+}
169
+
170
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
171
+{
172
+    // Copying to an assets library file is not doable, since we can't write it.
173
+    CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
174
+    callback(result);
175
+}
176
+
177
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
178
+{
179
+    NSString *path = nil;
180
+    if ([[url.url scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
181
+        path = [url.url path];
182
+    } else {
183
+       path = url.fullPath;
184
+    }
185
+    if ([path hasSuffix:@"/"]) {
186
+      path = [path substringToIndex:([path length]-1)];
187
+    }
188
+    return path;
189
+}
190
+
191
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
192
+{
193
+    ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
194
+        if (asset) {
195
+            // We have the asset!  Get the data and send it off.
196
+            ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
197
+            NSUInteger size = (end > start) ? (end - start) : [assetRepresentation size];
198
+            Byte* buffer = (Byte*)malloc(size);
199
+            NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:start length:size error:nil];
200
+            NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES];
201
+            NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType);
202
+
203
+            callback(data, MIMEType, NO_ERROR);
204
+        } else {
205
+            callback(nil, nil, NOT_FOUND_ERR);
206
+        }
207
+    };
208
+
209
+    ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
210
+        // Retrieving the asset failed for some reason.  Send the appropriate error.
211
+        NSLog(@"Error: %@", error);
212
+        callback(nil, nil, SECURITY_ERR);
213
+    };
214
+
215
+    ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
216
+    [assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
217
+}
218
+
219
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
220
+{
221
+    // In this case, we need to use an asynchronous method to retrieve the file.
222
+    // Because of this, we can't just assign to `result` and send it at the end of the method.
223
+    // Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
224
+    ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
225
+        if (asset) {
226
+            // We have the asset!  Populate the dictionary and send it off.
227
+            NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
228
+            ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
229
+            [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[assetRepresentation size]] forKey:@"size"];
230
+            [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
231
+            NSString* filename = [assetRepresentation filename];
232
+            [fileInfo setObject:filename forKey:@"name"];
233
+            [fileInfo setObject:[CDVAssetLibraryFilesystem getMimeTypeFromPath:filename] forKey:@"type"];
234
+            NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate];
235
+            NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000];
236
+            [fileInfo setObject:msDate forKey:@"lastModifiedDate"];
237
+
238
+            callback([CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]);
239
+        } else {
240
+            // We couldn't find the asset.  Send the appropriate error.
241
+            callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]);
242
+        }
243
+    };
244
+    ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
245
+        // Retrieving the asset failed for some reason.  Send the appropriate error.
246
+        callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]);
247
+    };
248
+
249
+    ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
250
+    [assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
251
+    return;
252
+}
253
+@end

+ 157
- 0
plugins/cordova-plugin-file/src/ios/CDVFile.h View File

@@ -0,0 +1,157 @@
1
+/*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements.  See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership.  The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License.  You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied.  See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+ */
19
+
20
+#import <Foundation/Foundation.h>
21
+#import <Cordova/CDVPlugin.h>
22
+
23
+extern NSString* const kCDVAssetsLibraryPrefix;
24
+extern NSString* const kCDVFilesystemURLPrefix;
25
+
26
+enum CDVFileError {
27
+    NO_ERROR = 0,
28
+    NOT_FOUND_ERR = 1,
29
+    SECURITY_ERR = 2,
30
+    ABORT_ERR = 3,
31
+    NOT_READABLE_ERR = 4,
32
+    ENCODING_ERR = 5,
33
+    NO_MODIFICATION_ALLOWED_ERR = 6,
34
+    INVALID_STATE_ERR = 7,
35
+    SYNTAX_ERR = 8,
36
+    INVALID_MODIFICATION_ERR = 9,
37
+    QUOTA_EXCEEDED_ERR = 10,
38
+    TYPE_MISMATCH_ERR = 11,
39
+    PATH_EXISTS_ERR = 12
40
+};
41
+typedef int CDVFileError;
42
+
43
+@interface CDVFilesystemURL : NSObject  {
44
+    NSURL *_url;
45
+    NSString *_fileSystemName;
46
+    NSString *_fullPath;
47
+}
48
+
49
+- (id) initWithString:(NSString*)strURL;
50
+- (id) initWithURL:(NSURL*)URL;
51
++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL;
52
++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL;
53
+
54
+- (NSString *)absoluteURL;
55
+
56
+@property (atomic) NSURL *url;
57
+@property (atomic) NSString *fileSystemName;
58
+@property (atomic) NSString *fullPath;
59
+
60
+@end
61
+
62
+@interface CDVFilesystemURLProtocol : NSURLProtocol
63
+@end
64
+
65
+@protocol CDVFileSystem
66
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url;
67
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options;
68
+- (CDVPluginResult *)getParentForURL:(CDVFilesystemURL *)localURI;
69
+- (CDVPluginResult *)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options;
70
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI;
71
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI;
72
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI;
73
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos;
74
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend;
75
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback;
76
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback;
77
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback;
78
+
79
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url;
80
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir;
81
+
82
+@property (nonatomic,strong) NSString *name;
83
+@property (nonatomic, copy) NSURL*(^urlTransformer)(NSURL*);
84
+
85
+@optional
86
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURI;
87
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path;
88
+
89
+@end
90
+
91
+@interface CDVFile : CDVPlugin {
92
+    NSString* rootDocsPath;
93
+    NSString* appDocsPath;
94
+    NSString* appLibraryPath;
95
+    NSString* appTempPath;
96
+
97
+    NSMutableArray* fileSystems_;
98
+    BOOL userHasAllowed;
99
+}
100
+
101
+- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath;
102
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir;
103
+- (NSDictionary *)makeEntryForURL:(NSURL *)URL;
104
+- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath;
105
+
106
+- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL;
107
+
108
+/* Native Registration API */
109
+- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs;
110
+- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName;
111
+
112
+/* Exec API */
113
+- (void)requestFileSystem:(CDVInvokedUrlCommand*)command;
114
+- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command;
115
+- (void)getDirectory:(CDVInvokedUrlCommand*)command;
116
+- (void)getFile:(CDVInvokedUrlCommand*)command;
117
+- (void)getParent:(CDVInvokedUrlCommand*)command;
118
+- (void)removeRecursively:(CDVInvokedUrlCommand*)command;
119
+- (void)remove:(CDVInvokedUrlCommand*)command;
120
+- (void)copyTo:(CDVInvokedUrlCommand*)command;
121
+- (void)moveTo:(CDVInvokedUrlCommand*)command;
122
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command;
123
+- (void)readEntries:(CDVInvokedUrlCommand*)command;
124
+- (void)readAsText:(CDVInvokedUrlCommand*)command;
125
+- (void)readAsDataURL:(CDVInvokedUrlCommand*)command;
126
+- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command;
127
+- (void)write:(CDVInvokedUrlCommand*)command;
128
+- (void)testFileExists:(CDVInvokedUrlCommand*)command;
129
+- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command;
130
+- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command;
131
+- (void)truncate:(CDVInvokedUrlCommand*)command;
132
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy;
133
+
134
+/* Compatibilty with older File API */
135
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
136
+- (NSDictionary *)getDirectoryEntry:(NSString *)target isDirectory:(BOOL)bDirRequest;
137
+
138
+/* Conversion between filesystem paths and URLs */
139
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)URL;
140
+
141
+/* Internal methods for testing */
142
+- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command;
143
+
144
+@property (nonatomic, strong) NSString* rootDocsPath;
145
+@property (nonatomic, strong) NSString* appDocsPath;
146
+@property (nonatomic, strong) NSString* appLibraryPath;
147
+@property (nonatomic, strong) NSString* appTempPath;
148
+@property (nonatomic, strong) NSString* persistentPath;
149
+@property (nonatomic, strong) NSString* temporaryPath;
150
+@property (nonatomic, strong) NSMutableArray* fileSystems;
151
+
152
+@property BOOL userHasAllowed;
153
+
154
+@end
155
+
156
+#define kW3FileTemporary @"temporary"
157
+#define kW3FilePersistent @"persistent"

+ 1119
- 0
plugins/cordova-plugin-file/src/ios/CDVFile.m
File diff suppressed because it is too large
View File


+ 32
- 0
plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h View File

@@ -0,0 +1,32 @@
1
+/*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements.  See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership.  The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License.  You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied.  See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+ */
19
+
20
+#import "CDVFile.h"
21
+
22
+@interface CDVLocalFilesystem : NSObject<CDVFileSystem> {
23
+    NSString *_name;
24
+    NSString *_fsRoot;
25
+}
26
+
27
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot;
28
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
29
+
30
+@property (nonatomic,strong) NSString *fsRoot;
31
+
32
+@end

+ 750
- 0
plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m View File

@@ -0,0 +1,750 @@
1
+/*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements.  See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership.  The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License.  You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied.  See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+ */
19
+
20
+#import "CDVFile.h"
21
+#import "CDVLocalFilesystem.h"
22
+#import <Cordova/CDV.h>
23
+#import <MobileCoreServices/MobileCoreServices.h>
24
+#import <sys/xattr.h>
25
+
26
+@implementation CDVLocalFilesystem
27
+@synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
28
+
29
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
30
+{
31
+    if (self) {
32
+        self.name = name;
33
+        self.fsRoot = fsRoot;
34
+    }
35
+    return self;
36
+}
37
+
38
+/*
39
+ * IN
40
+ *  NSString localURI
41
+ * OUT
42
+ *  CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
43
+ *   URI represents a non-existent path, or is unrecognized or otherwise malformed.
44
+ */
45
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
46
+{
47
+    CDVPluginResult* result = nil;
48
+    NSDictionary* entry = [self makeEntryForLocalURL:url];
49
+    if (entry) {
50
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
51
+    } else {
52
+        // return NOT_FOUND_ERR
53
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
54
+    }
55
+    return result;
56
+}
57
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
58
+    NSString *path = [self filesystemPathForURL:url];
59
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
60
+    BOOL isDir = NO;
61
+    // see if exists and is file or dir
62
+    BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
63
+    if (bExists) {
64
+        return [self makeEntryForPath:url.fullPath isDirectory:isDir];
65
+    } else {
66
+        return nil;
67
+    }
68
+}
69
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
70
+{
71
+    NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
72
+    NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
73
+    if (isDir && ![fullPath hasSuffix:@"/"]) {
74
+        fullPath = [fullPath stringByAppendingString:@"/"];
75
+    }
76
+    [dirEntry setObject:[NSNumber numberWithBool:!isDir]  forKey:@"isFile"];
77
+    [dirEntry setObject:[NSNumber numberWithBool:isDir]  forKey:@"isDirectory"];
78
+    [dirEntry setObject:fullPath forKey:@"fullPath"];
79
+    [dirEntry setObject:lastPart forKey:@"name"];
80
+    [dirEntry setObject:self.name forKey: @"filesystemName"];
81
+
82
+    NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]];
83
+    if (self.urlTransformer) {
84
+        nativeURL = self.urlTransformer(nativeURL);
85
+    }
86
+
87
+    dirEntry[@"nativeURL"] = [nativeURL absoluteString];
88
+
89
+    return dirEntry;
90
+}
91
+
92
+- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
93
+{
94
+    NSRange questionMark = [fullPath rangeOfString:@"?"];
95
+    if (questionMark.location != NSNotFound) {
96
+        return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
97
+    }
98
+    return fullPath;
99
+}
100
+
101
+- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
102
+{
103
+    NSString *path = nil;
104
+    NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
105
+    path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
106
+    if ([path length] > 1 && [path hasSuffix:@"/"]) {
107
+      path = [path substringToIndex:([path length]-1)];
108
+    }
109
+    return path;
110
+}
111
+/*
112
+ * IN
113
+ *  NSString localURI
114
+ * OUT
115
+ *  NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
116
+ *  The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
117
+ *  or if the URL is malformed.
118
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
119
+ */
120
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
121
+{
122
+    return [self filesystemPathForFullPath:url.fullPath];
123
+}
124
+
125
+- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
126
+{
127
+    if (fullPath) {
128
+        NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
129
+        if ([fullPath hasPrefix:@"/"]) {
130
+            return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
131
+        }
132
+        return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
133
+    }
134
+    return nil;
135
+}
136
+
137
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
138
+{
139
+    return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
140
+
141
+}
142
+
143
+- (NSString *)normalizePath:(NSString *)rawPath
144
+{
145
+    // If this is an absolute path, the first path component will be '/'. Skip it if that's the case
146
+    BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
147
+    if (isAbsolutePath) {
148
+        rawPath = [rawPath substringFromIndex:1];
149
+    }
150
+    NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
151
+    for (int index = 0; index < [components count]; ++index) {
152
+        if ([[components objectAtIndex:index] isEqualToString:@".."]) {
153
+            [components removeObjectAtIndex:index];
154
+            if (index > 0) {
155
+                [components removeObjectAtIndex:index-1];
156
+                --index;
157
+            }
158
+        }
159
+    }
160
+
161
+    if (isAbsolutePath) {
162
+        return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
163
+    } else {
164
+        return [components componentsJoinedByString:@"/"];
165
+    }
166
+
167
+
168
+}
169
+
170
+- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
171
+{
172
+    BOOL bNumber = NO;
173
+    NSObject* value = dict[key];
174
+    if (value) {
175
+        bNumber = [value isKindOfClass:[NSNumber class]];
176
+    }
177
+    return bNumber;
178
+}
179
+
180
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
181
+{
182
+    CDVPluginResult* result = nil;
183
+    BOOL bDirRequest = NO;
184
+    BOOL create = NO;
185
+    BOOL exclusive = NO;
186
+    int errorCode = 0;  // !!! risky - no error code currently defined for 0
187
+
188
+    if ([self valueForKeyIsNumber:options key:@"create"]) {
189
+        create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
190
+    }
191
+    if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
192
+        exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
193
+    }
194
+    if ([self valueForKeyIsNumber:options key:@"getDir"]) {
195
+        // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method
196
+        bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue];
197
+    }
198
+    // see if the requested path has invalid characters - should we be checking for  more than just ":"?
199
+    if ([requestedPath rangeOfString:@":"].location != NSNotFound) {
200
+        errorCode = ENCODING_ERR;
201
+    } else {
202
+        // Build new fullPath for the requested resource.
203
+        // We concatenate the two paths together, and then scan the resulting string to remove
204
+        // parent ("..") references. Any parent references at the beginning of the string are
205
+        // silently removed.
206
+        NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
207
+        combinedPath = [self normalizePath:combinedPath];
208
+        CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
209
+
210
+        NSFileManager* fileMgr = [[NSFileManager alloc] init];
211
+        BOOL bIsDir;
212
+        BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
213
+        if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
214
+            // path exists and is not of requested type  - return TYPE_MISMATCH_ERR
215
+            errorCode = TYPE_MISMATCH_ERR;
216
+        } else if (!bExists && (create == NO)) {
217
+            // path does not exist and create is false - return NOT_FOUND_ERR
218
+            errorCode = NOT_FOUND_ERR;
219
+        } else if (bExists && (create == YES) && (exclusive == YES)) {
220
+            // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR
221
+            errorCode = PATH_EXISTS_ERR;
222
+        } else {
223
+            // if bExists and create == YES - just return data
224
+            // if bExists and create == NO  - just return data
225
+            // if !bExists and create == YES - create and return data
226
+            BOOL bSuccess = YES;
227
+            NSError __autoreleasing* pError = nil;
228
+            if (!bExists && (create == YES)) {
229
+                if (bDirRequest) {
230
+                    // create the dir
231
+                    bSuccess = [fileMgr createDirectoryAtPath:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
232
+                } else {
233
+                    // create the empty file
234
+                    bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] contents:nil attributes:nil];
235
+                }
236
+            }
237
+            if (!bSuccess) {
238
+                errorCode = ABORT_ERR;
239
+                if (pError) {
240
+                    NSLog(@"error creating directory: %@", [pError localizedDescription]);
241
+                }
242
+            } else {
243
+                // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]);
244
+                // file existed or was created
245
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
246
+            }
247
+        } // are all possible conditions met?
248
+    }
249
+
250
+    if (errorCode > 0) {
251
+        // create error callback
252
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
253
+    }
254
+    return result;
255
+
256
+}
257
+
258
+- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
259
+{
260
+    CDVPluginResult* result = nil;
261
+    CDVFilesystemURL *newURI = nil;
262
+    if ([localURI.fullPath isEqualToString:@""]) {
263
+        // return self
264
+        newURI = localURI;
265
+    } else {
266
+        newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
267
+    }
268
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
269
+    BOOL bIsDir;
270
+    BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
271
+    if (bExists) {
272
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
273
+    } else {
274
+        // invalid path or file does not exist
275
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
276
+    }
277
+    return result;
278
+}
279
+
280
+- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
281
+{
282
+    BOOL ok = NO;
283
+
284
+    NSString* filePath = [self filesystemPathForURL:localURI];
285
+    // we only care about this iCloud key for now.
286
+    // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute)
287
+    NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup";
288
+    id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey];
289
+
290
+    if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) {
291
+        if (IsAtLeastiOSVersion(@"5.1")) {
292
+            NSURL* url = [NSURL fileURLWithPath:filePath];
293
+            NSError* __autoreleasing error = nil;
294
+
295
+            ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error];
296
+        } else { // below 5.1 (deprecated - only really supported in 5.01)
297
+            u_int8_t value = [iCloudBackupExtendedAttributeValue intValue];
298
+            if (value == 0) { // remove the attribute (allow backup, the default)
299
+                ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0);
300
+            } else { // set the attribute (skip backup)
301
+                ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0);
302
+            }
303
+        }
304
+    }
305
+
306
+    if (ok) {
307
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
308
+    } else {
309
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
310
+    }
311
+}
312
+
313
+/* remove the file or directory (recursively)
314
+ * IN:
315
+ * NSString* fullPath - the full path to the file or directory to be removed
316
+ * NSString* callbackId
317
+ * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling
318
+ */
319
+
320
+- (CDVPluginResult*)doRemove:(NSString*)fullPath
321
+{
322
+    CDVPluginResult* result = nil;
323
+    BOOL bSuccess = NO;
324
+    NSError* __autoreleasing pError = nil;
325
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
326
+
327
+    @try {
328
+        bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError];
329
+        if (bSuccess) {
330
+            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
331
+        } else {
332
+            // see if we can give a useful error
333
+            CDVFileError errorCode = ABORT_ERR;
334
+            NSLog(@"error removing filesystem entry at %@: %@", fullPath, [pError localizedDescription]);
335
+            if ([pError code] == NSFileNoSuchFileError) {
336
+                errorCode = NOT_FOUND_ERR;
337
+            } else if ([pError code] == NSFileWriteNoPermissionError) {
338
+                errorCode = NO_MODIFICATION_ALLOWED_ERR;
339
+            }
340
+
341
+            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
342
+        }
343
+    } @catch(NSException* e) {  // NSInvalidArgumentException if path is . or ..
344
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR];
345
+    }
346
+
347
+    return result;
348
+}
349
+
350
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
351
+{
352
+    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
353
+
354
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
355
+    BOOL bIsDir = NO;
356
+    BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
357
+    if (!bExists) {
358
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
359
+    }
360
+    if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
361
+        // dir is not empty
362
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
363
+    }
364
+    return [self doRemove:fileSystemPath];
365
+}
366
+
367
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
368
+{
369
+    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
370
+    return [self doRemove:fileSystemPath];
371
+}
372
+
373
+/*
374
+ * IN
375
+ *  NSString localURI
376
+ * OUT
377
+ *  NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
378
+ *  The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
379
+ *  or if the URL is malformed.
380
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
381
+ */
382
+- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
383
+{
384
+    if ([fsPath hasPrefix:self.fsRoot]) {
385
+        return [fsPath substringFromIndex:[self.fsRoot length]];
386
+    }
387
+    return nil;
388
+}
389
+
390
+
391
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
392
+{
393
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
394
+    NSError* __autoreleasing error = nil;
395
+    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
396
+
397
+    NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath error:&error];
398
+
399
+    if (contents) {
400
+        NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1];
401
+        if ([contents count] > 0) {
402
+            // create an Entry (as JSON) for each file/dir
403
+            for (NSString* name in contents) {
404
+                // see if is dir or file
405
+                NSString* entryPath = [fileSystemPath stringByAppendingPathComponent:name];
406
+                BOOL bIsDir = NO;
407
+                [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
408
+                NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
409
+                [entries addObject:entryDict];
410
+            }
411
+        }
412
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
413
+    } else {
414
+        // assume not found but could check error for more specific error conditions
415
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
416
+    }
417
+}
418
+
419
+- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos
420
+{
421
+    unsigned long long newPos = 0UL;
422
+
423
+    NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath];
424
+
425
+    if (file) {
426
+        [file truncateFileAtOffset:(unsigned long long)pos];
427
+        newPos = [file offsetInFile];
428
+        [file synchronizeFile];
429
+        [file closeFile];
430
+    }
431
+    return newPos;
432
+}
433
+
434
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
435
+{
436
+    unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
437
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
438
+}
439
+
440
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
441
+{
442
+    NSString *filePath = [self filesystemPathForURL:localURL];
443
+
444
+    CDVPluginResult* result = nil;
445
+    CDVFileError errCode = INVALID_MODIFICATION_ERR;
446
+    int bytesWritten = 0;
447
+
448
+    if (filePath) {
449
+        NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
450
+        if (fileStream) {
451
+            NSUInteger len = [encData length];
452
+            if (len == 0) {
453
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
454
+            } else {
455
+                [fileStream open];
456
+
457
+                bytesWritten = (int)[fileStream write:[encData bytes] maxLength:len];
458
+
459
+                [fileStream close];
460
+                if (bytesWritten > 0) {
461
+                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten];
462
+                    // } else {
463
+                    // can probably get more detailed error info via [fileStream streamError]
464
+                    // errCode already set to INVALID_MODIFICATION_ERR;
465
+                    // bytesWritten = 0; // may be set to -1 on error
466
+                }
467
+            }
468
+        } // else fileStream not created return INVALID_MODIFICATION_ERR
469
+    } else {
470
+        // invalid filePath
471
+        errCode = NOT_FOUND_ERR;
472
+    }
473
+    if (!result) {
474
+        // was an error
475
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode];
476
+    }
477
+    return result;
478
+}
479
+
480
+/**
481
+ * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name,
482
+ * or attempted to copy a directory into a directory that it contains directly or indirectly.
483
+ *
484
+ * IN:
485
+ *  NSString* srcDir
486
+ *  NSString* destinationDir
487
+ * OUT:
488
+ *  YES copy/ move is allows
489
+ *  NO move is onto itself
490
+ */
491
+- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest
492
+{
493
+    // This weird test is to determine if we are copying or moving a directory into itself.
494
+    // Copy /Documents/myDir to /Documents/myDir-backup is okay but
495
+    // Copy /Documents/myDir to /Documents/myDir/backup not okay
496
+    BOOL copyOK = YES;
497
+    NSRange range = [dest rangeOfString:src];
498
+
499
+    if (range.location != NSNotFound) {
500
+        NSRange testRange = {range.length - 1, ([dest length] - range.length)};
501
+        NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange];
502
+        if (resultRange.location != NSNotFound) {
503
+            copyOK = NO;
504
+        }
505
+    }
506
+    return copyOK;
507
+}
508
+
509
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
510
+{
511
+    NSFileManager *fileMgr = [[NSFileManager alloc] init];
512
+    NSString *destRootPath = [self filesystemPathForURL:destURL];
513
+    BOOL bDestIsDir = NO;
514
+    BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
515
+
516
+    NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
517
+    NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
518
+
519
+    BOOL bNewIsDir = NO;
520
+    BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
521
+
522
+    CDVPluginResult *result = nil;
523
+    int errCode = 0;
524
+
525
+    if (!bDestExists) {
526
+        // the destination root does not exist
527
+        errCode = NOT_FOUND_ERR;
528
+    }
529
+
530
+    else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
531
+        /* Same FS, we can shortcut with NSFileManager operations */
532
+        NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
533
+
534
+        BOOL bSrcIsDir = NO;
535
+        BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
536
+
537
+        if (!bSrcExists) {
538
+            // the source does not exist
539
+            errCode = NOT_FOUND_ERR;
540
+        } else if ([newFileSystemPath isEqualToString:srcFullPath]) {
541
+            // source and destination can not be the same
542
+            errCode = INVALID_MODIFICATION_ERR;
543
+        } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) {
544
+            // can't copy/move dir to file
545
+            errCode = INVALID_MODIFICATION_ERR;
546
+        } else { // no errors yet
547
+            NSError* __autoreleasing error = nil;
548
+            BOOL bSuccess = NO;
549
+            if (bCopy) {
550
+                if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
551
+                    // can't copy dir into self
552
+                    errCode = INVALID_MODIFICATION_ERR;
553
+                } else if (bNewExists) {
554
+                    // the full destination should NOT already exist if a copy
555
+                    errCode = PATH_EXISTS_ERR;
556
+                } else {
557
+                    bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
558
+                }
559
+            } else { // move
560
+                // iOS requires that destination must not exist before calling moveTo
561
+                // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents
562
+                //
563
+                if (!bSrcIsDir && (bNewExists && bNewIsDir)) {
564
+                    // can't move a file to directory
565
+                    errCode = INVALID_MODIFICATION_ERR;
566
+                } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
567
+                    // can't move a dir into itself
568
+                    errCode = INVALID_MODIFICATION_ERR;
569
+                } else if (bNewExists) {
570
+                    if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
571
+                        // can't move dir to a dir that is not empty
572
+                        errCode = INVALID_MODIFICATION_ERR;
573
+                        newFileSystemPath = nil;  // so we won't try to move
574
+                    } else {
575
+                        // remove destination so can perform the moveItemAtPath
576
+                        bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
577
+                        if (!bSuccess) {
578
+                            errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
579
+                            newFileSystemPath = nil;
580
+                        }
581
+                    }
582
+                } else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
583
+                    // can't move a directory inside itself or to any child at any depth;
584
+                    errCode = INVALID_MODIFICATION_ERR;
585
+                    newFileSystemPath = nil;
586
+                }
587
+
588
+                if (newFileSystemPath != nil) {
589
+                    bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
590
+                }
591
+            }
592
+            if (bSuccess) {
593
+                // should verify it is there and of the correct type???
594
+                NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
595
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
596
+            } else {
597
+                if (error) {
598
+                    if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) {
599
+                        errCode = NOT_READABLE_ERR;
600
+                    } else if ([error code] == NSFileWriteOutOfSpaceError) {
601
+                        errCode = QUOTA_EXCEEDED_ERR;
602
+                    } else if ([error code] == NSFileWriteNoPermissionError) {
603
+                        errCode = NO_MODIFICATION_ALLOWED_ERR;
604
+                    }
605
+                }
606
+            }
607
+        }
608
+    } else {
609
+        // Need to copy the hard way
610
+        [srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
611
+            CDVPluginResult* result = nil;
612
+            if (data != nil) {
613
+                BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
614
+                if (bSuccess) {
615
+                    // should verify it is there and of the correct type???
616
+                    NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
617
+                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
618
+                } else {
619
+                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
620
+                }
621
+            } else {
622
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
623
+            }
624
+            callback(result);
625
+        }];
626
+        return; // Async IO; return without callback.
627
+    }
628
+    if (result == nil) {
629
+        if (!errCode) {
630
+            errCode = INVALID_MODIFICATION_ERR; // Catch-all default
631
+        }
632
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
633
+    }
634
+    callback(result);
635
+}
636
+
637
+/* helper function to get the mimeType from the file extension
638
+ * IN:
639
+ *	NSString* fullPath - filename (may include path)
640
+ * OUT:
641
+ *	NSString* the mime type as type/subtype.  nil if not able to determine
642
+ */
643
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
644
+{
645
+    NSString* mimeType = nil;
646
+
647
+    if (fullPath) {
648
+        CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
649
+        if (typeId) {
650
+            mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
651
+            if (!mimeType) {
652
+                // special case for m4a
653
+                if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
654
+                    mimeType = @"audio/mp4";
655
+                } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
656
+                    mimeType = @"audio/wav";
657
+                } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
658
+                    mimeType = @"text/css";
659
+                }
660
+            }
661
+            CFRelease(typeId);
662
+        }
663
+    }
664
+    return mimeType;
665
+}
666
+
667
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
668
+{
669
+    NSString *path = [self filesystemPathForURL:localURL];
670
+
671
+    NSString* mimeType = [CDVLocalFilesystem getMimeTypeFromPath:path];
672
+    if (mimeType == nil) {
673
+        mimeType = @"*/*";
674
+    }
675
+    NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
676
+    if (start > 0) {
677
+        [file seekToFileOffset:start];
678
+    }
679
+
680
+    NSData* readData;
681
+    if (end < 0) {
682
+        readData = [file readDataToEndOfFile];
683
+    } else {
684
+        readData = [file readDataOfLength:(end - start)];
685
+    }
686
+    [file closeFile];
687
+
688
+    callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR);
689
+}
690
+
691
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
692
+{
693
+    NSString *path = [self filesystemPathForURL:localURL];
694
+    CDVPluginResult *result;
695
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
696
+
697
+    NSError* __autoreleasing error = nil;
698
+    NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
699
+
700
+    if (fileAttrs) {
701
+
702
+        // create dictionary of file info
703
+        NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
704
+
705
+        [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
706
+        [fileInfo setObject:[self mimeTypeForFileAtPath: path] forKey:@"type"];
707
+        [fileInfo setObject:[path lastPathComponent] forKey:@"name"];
708
+
709
+        // Ensure that directories (and other non-regular files) report size of 0
710
+        unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
711
+        [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
712
+
713
+        NSDate* modDate = [fileAttrs fileModificationDate];
714
+        if (modDate) {
715
+            [fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
716
+        }
717
+
718
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
719
+
720
+    } else {
721
+        // didn't get fileAttribs
722
+        CDVFileError errorCode = ABORT_ERR;
723
+        NSLog(@"error getting metadata: %@", [error localizedDescription]);
724
+        if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
725
+            errorCode = NOT_FOUND_ERR;
726
+        }
727
+        // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
728
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
729
+    }
730
+
731
+    callback(result);
732
+}
733
+
734
+// fix errors that base on Alexsander Akers from http://stackoverflow.com/a/5998683/2613194
735
+- (NSString*) mimeTypeForFileAtPath: (NSString *) path {
736
+    if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
737
+        return nil;
738
+    }
739
+    
740
+    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
741
+    CFStringRef mimeType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
742
+    CFRelease(UTI);
743
+    
744
+    if (!mimeType) {
745
+        return @"application/octet-stream";
746
+    }
747
+    return (__bridge NSString *)mimeType;
748
+}
749
+
750
+@end

+ 189
- 0
plugins/cordova-plugin-file/src/osx/CDVFile.h View File

@@ -0,0 +1,189 @@
1
+/*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements.  See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership.  The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License.  You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied.  See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+ */
19
+
20
+#import <Foundation/Foundation.h>
21
+#import <Cordova/CDVPlugin.h>
22
+
23
+NSString* const kCDVFilesystemURLPrefix;
24
+
25
+/**
26
+* The default filesystems if non are specified.
27
+*/
28
+#define CDV_FILESYSTEMS_DEFAULT @"documents,cache,bundle,root"
29
+
30
+/**
31
+* Preference name of the extra filesystems to be "mounted". the following are supported:
32
+* 'bundle'    - mounts the application directory
33
+* 'documents' - mounts the users Documents directory (~/Documents)
34
+* 'root'      - mounts the root file system
35
+* 'cache'     - mounts the caches directory (~/Library/Caches/<bundle-id/)
36
+*/
37
+#define CDV_PREF_EXTRA_FILESYSTEM @"osxextrafilesystems"
38
+
39
+enum CDVFileError {
40
+    NO_ERROR = 0,
41
+    NOT_FOUND_ERR = 1,
42
+    SECURITY_ERR = 2,
43
+    ABORT_ERR = 3,
44
+    NOT_READABLE_ERR = 4,
45
+    ENCODING_ERR = 5,
46
+    NO_MODIFICATION_ALLOWED_ERR = 6,
47
+    INVALID_STATE_ERR = 7,
48
+    SYNTAX_ERR = 8,
49
+    INVALID_MODIFICATION_ERR = 9,
50
+    QUOTA_EXCEEDED_ERR = 10,
51
+    TYPE_MISMATCH_ERR = 11,
52
+    PATH_EXISTS_ERR = 12
53
+};
54
+typedef int CDVFileError;
55
+
56
+@interface CDVFilesystemURL : NSObject  {
57
+    NSURL *_url;
58
+    NSString *_fileSystemName;
59
+    NSString *_fullPath;
60
+}
61
+
62
+- (id) initWithString:(NSString*)strURL;
63
+- (id) initWithURL:(NSURL*)URL;
64
++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL;
65
++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL;
66
+
67
+- (NSString *)absoluteURL;
68
+
69
+@property (atomic) NSURL *url;
70
+@property (atomic) NSString *fileSystemName;
71
+@property (atomic) NSString *fullPath;
72
+
73
+@end
74
+
75
+@interface CDVFilesystemURLProtocol : NSURLProtocol
76
+@end
77
+
78
+@protocol CDVFileSystem
79
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url;
80
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options;
81
+- (CDVPluginResult *)getParentForURL:(CDVFilesystemURL *)localURI;
82
+- (CDVPluginResult *)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options;
83
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI;
84
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI;
85
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI;
86
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos;
87
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend;
88
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback;
89
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback;
90
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback;
91
+
92
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url;
93
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir;
94
+
95
+@property (nonatomic,strong) NSString *name;
96
+@property (nonatomic, copy) NSURL*(^urlTransformer)(NSURL*);
97
+
98
+@optional
99
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURI;
100
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path;
101
+
102
+@end
103
+
104
+@interface CDVFile : CDVPlugin {
105
+    NSString* rootDocsPath;
106
+    NSString* appDocsPath;
107
+    NSString* appLibraryPath;
108
+    NSString* appTempPath;
109
+
110
+    NSMutableArray* fileSystems_;
111
+    BOOL userHasAllowed;
112
+}
113
+
114
+- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath;
115
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir;
116
+- (NSDictionary *)makeEntryForURL:(NSURL *)URL;
117
+- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath;
118
+
119
+- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL;
120
+
121
+/* Native Registration API */
122
+- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs;
123
+- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName;
124
+
125
+/* Exec API */
126
+- (void)requestFileSystem:(CDVInvokedUrlCommand*)command;
127
+- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command;
128
+- (void)getDirectory:(CDVInvokedUrlCommand*)command;
129
+- (void)getFile:(CDVInvokedUrlCommand*)command;
130
+- (void)getParent:(CDVInvokedUrlCommand*)command;
131
+- (void)removeRecursively:(CDVInvokedUrlCommand*)command;
132
+- (void)remove:(CDVInvokedUrlCommand*)command;
133
+- (void)copyTo:(CDVInvokedUrlCommand*)command;
134
+- (void)moveTo:(CDVInvokedUrlCommand*)command;
135
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command;
136
+- (void)readEntries:(CDVInvokedUrlCommand*)command;
137
+- (void)readAsText:(CDVInvokedUrlCommand*)command;
138
+- (void)readAsDataURL:(CDVInvokedUrlCommand*)command;
139
+- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command;
140
+- (void)write:(CDVInvokedUrlCommand*)command;
141
+- (void)testFileExists:(CDVInvokedUrlCommand*)command;
142
+- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command;
143
+- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command;
144
+- (void)truncate:(CDVInvokedUrlCommand*)command;
145
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy;
146
+
147
+/* Compatibilty with older File API */
148
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
149
+- (NSDictionary *)getDirectoryEntry:(NSString *)target isDirectory:(BOOL)bDirRequest;
150
+
151
+/* Conversion between filesystem paths and URLs */
152
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)URL;
153
+
154
+/* Internal methods for testing */
155
+- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command;
156
+
157
+/**
158
+ * local path of the 'documents' file system (~/Documents)
159
+ */
160
+@property (nonatomic, strong) NSString* appDocsPath;
161
+
162
+/**
163
+* local path of the 'applicationStorageDirectory' file system (~/Library/Application Support/<bundle-id>)
164
+*/
165
+@property (nonatomic, strong) NSString* appSupportPath;
166
+
167
+/**
168
+* local path of the 'persistent' file system (~/Library/Application Support/<bundle-id>/files)
169
+*/
170
+@property (nonatomic, strong) NSString* appDataPath;
171
+
172
+/**
173
+* local path of the 'documents' file system (~/Documents)
174
+*/
175
+@property (nonatomic, strong) NSString* appTempPath;
176
+
177
+/**
178
+* local path of the 'cache' file system (~/Library/Caches/<bundle-id>)
179
+*/
180
+@property (nonatomic, strong) NSString* appCachePath;
181
+
182
+/**
183
+ * registered file systems
184
+ */
185
+@property (nonatomic, strong) NSMutableArray* fileSystems;
186
+
187
+@property BOOL userHasAllowed;
188
+
189
+@end

+ 1056
- 0
plugins/cordova-plugin-file/src/osx/CDVFile.m
File diff suppressed because it is too large
View File


+ 32
- 0
plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.h View File

@@ -0,0 +1,32 @@
1
+/*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements.  See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership.  The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License.  You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied.  See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+ */
19
+
20
+#import "CDVFile.h"
21
+
22
+@interface CDVLocalFilesystem : NSObject<CDVFileSystem> {
23
+    NSString *_name;
24
+    NSString *_fsRoot;
25
+}
26
+
27
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot;
28
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
29
+
30
+@property (nonatomic,strong) NSString *fsRoot;
31
+
32
+@end

+ 733
- 0
plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.m View File

@@ -0,0 +1,733 @@
1
+/*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements.  See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership.  The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License.  You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied.  See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+ */
19
+
20
+#import "CDVFile.h"
21
+#import "CDVLocalFilesystem.h"
22
+#import <sys/xattr.h>
23
+
24
+@implementation CDVLocalFilesystem
25
+@synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
26
+
27
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
28
+{
29
+    if (self) {
30
+        _name = name;
31
+        _fsRoot = fsRoot;
32
+    }
33
+    return self;
34
+}
35
+
36
+/*
37
+ * IN
38
+ *  NSString localURI
39
+ * OUT
40
+ *  CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
41
+ *   URI represents a non-existent path, or is unrecognized or otherwise malformed.
42
+ */
43
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
44
+{
45
+    CDVPluginResult* result = nil;
46
+    NSDictionary* entry = [self makeEntryForLocalURL:url];
47
+    if (entry) {
48
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
49
+    } else {
50
+        // return NOT_FOUND_ERR
51
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
52
+    }
53
+    return result;
54
+}
55
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
56
+    NSString *path = [self filesystemPathForURL:url];
57
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
58
+    BOOL isDir = NO;
59
+    // see if exists and is file or dir
60
+    BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
61
+    if (bExists) {
62
+        return [self makeEntryForPath:url.fullPath isDirectory:isDir];
63
+    } else {
64
+        return nil;
65
+    }
66
+}
67
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
68
+{
69
+    NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
70
+    NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
71
+    if (isDir && ![fullPath hasSuffix:@"/"]) {
72
+        fullPath = [fullPath stringByAppendingString:@"/"];
73
+    }
74
+    [dirEntry setObject:[NSNumber numberWithBool:!isDir]  forKey:@"isFile"];
75
+    [dirEntry setObject:[NSNumber numberWithBool:isDir]  forKey:@"isDirectory"];
76
+    [dirEntry setObject:fullPath forKey:@"fullPath"];
77
+    [dirEntry setObject:lastPart forKey:@"name"];
78
+    [dirEntry setObject:self.name forKey: @"filesystemName"];
79
+
80
+    NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]];
81
+    if (self.urlTransformer) {
82
+        nativeURL = self.urlTransformer(nativeURL);
83
+    }
84
+
85
+    dirEntry[@"nativeURL"] = [nativeURL absoluteString];
86
+
87
+    return dirEntry;
88
+}
89
+
90
+- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
91
+{
92
+    NSRange questionMark = [fullPath rangeOfString:@"?"];
93
+    if (questionMark.location != NSNotFound) {
94
+        return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
95
+    }
96
+    return fullPath;
97
+}
98
+
99
+- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
100
+{
101
+    NSString *path = nil;
102
+    NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
103
+    path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
104
+    if ([path length] > 1 && [path hasSuffix:@"/"]) {
105
+      path = [path substringToIndex:([path length]-1)];
106
+    }
107
+    return path;
108
+}
109
+/*
110
+ * IN
111
+ *  NSString localURI
112
+ * OUT
113
+ *  NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
114
+ *  The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
115
+ *  or if the URL is malformed.
116
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
117
+ */
118
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
119
+{
120
+    return [self filesystemPathForFullPath:url.fullPath];
121
+}
122
+
123
+- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
124
+{
125
+    if (fullPath) {
126
+        NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
127
+        if ([fullPath hasPrefix:@"/"]) {
128
+            return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
129
+        }
130
+        return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
131
+    }
132
+    return nil;
133
+}
134
+
135
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
136
+{
137
+    return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
138
+
139
+}
140
+
141
+- (NSString *)normalizePath:(NSString *)rawPath
142
+{
143
+    // If this is an absolute path, the first path component will be '/'. Skip it if that's the case
144
+    BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
145
+    if (isAbsolutePath) {
146
+        rawPath = [rawPath substringFromIndex:1];
147
+    }
148
+    NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
149
+    for (int index = 0; index < [components count]; ++index) {
150
+        if ([[components objectAtIndex:index] isEqualToString:@".."]) {
151
+            [components removeObjectAtIndex:index];
152
+            if (index > 0) {
153
+                [components removeObjectAtIndex:index-1];
154
+                --index;
155
+            }
156
+        }
157
+    }
158
+
159
+    if (isAbsolutePath) {
160
+        return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
161
+    } else {
162
+        return [components componentsJoinedByString:@"/"];
163
+    }
164
+
165
+
166
+}
167
+
168
+- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
169
+{
170
+    BOOL bNumber = NO;
171
+    NSObject* value = dict[key];
172
+    if (value) {
173
+        bNumber = [value isKindOfClass:[NSNumber class]];
174
+    }
175
+    return bNumber;
176
+}
177
+
178
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
179
+{
180
+    CDVPluginResult* result = nil;
181
+    BOOL bDirRequest = NO;
182
+    BOOL create = NO;
183
+    BOOL exclusive = NO;
184
+    int errorCode = 0;  // !!! risky - no error code currently defined for 0
185
+
186
+    if ([self valueForKeyIsNumber:options key:@"create"]) {
187
+        create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
188
+    }
189
+    if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
190
+        exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
191
+    }
192
+    if ([self valueForKeyIsNumber:options key:@"getDir"]) {
193
+        // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method
194
+        bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue];
195
+    }
196
+    // see if the requested path has invalid characters - should we be checking for  more than just ":"?
197
+    if ([requestedPath rangeOfString:@":"].location != NSNotFound) {
198
+        errorCode = ENCODING_ERR;
199
+    } else {
200
+        // Build new fullPath for the requested resource.
201
+        // We concatenate the two paths together, and then scan the resulting string to remove
202
+        // parent ("..") references. Any parent references at the beginning of the string are
203
+        // silently removed.
204
+        NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
205
+        combinedPath = [self normalizePath:combinedPath];
206
+        CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
207
+
208
+        NSFileManager* fileMgr = [[NSFileManager alloc] init];
209
+        BOOL bIsDir;
210
+        BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
211
+        if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
212
+            // path exists and is not of requested type  - return TYPE_MISMATCH_ERR
213
+            errorCode = TYPE_MISMATCH_ERR;
214
+        } else if (!bExists && (create == NO)) {
215
+            // path does not exist and create is false - return NOT_FOUND_ERR
216
+            errorCode = NOT_FOUND_ERR;
217
+        } else if (bExists && (create == YES) && (exclusive == YES)) {
218
+            // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR
219
+            errorCode = PATH_EXISTS_ERR;
220
+        } else {
221
+            // if bExists and create == YES - just return data
222
+            // if bExists and create == NO  - just return data
223
+            // if !bExists and create == YES - create and return data
224
+            BOOL bSuccess = YES;
225
+            NSError __autoreleasing* pError = nil;
226
+            if (!bExists && (create == YES)) {
227
+                if (bDirRequest) {
228
+                    // create the dir
229
+                    bSuccess = [fileMgr createDirectoryAtPath:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
230
+                } else {
231
+                    // create the empty file
232
+                    bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] contents:nil attributes:nil];
233
+                }
234
+            }
235
+            if (!bSuccess) {
236
+                errorCode = ABORT_ERR;
237
+                if (pError) {
238
+                    NSLog(@"error creating directory: %@", [pError localizedDescription]);
239
+                }
240
+            } else {
241
+                // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]);
242
+                // file existed or was created
243
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
244
+            }
245
+        } // are all possible conditions met?
246
+    }
247
+
248
+    if (errorCode > 0) {
249
+        // create error callback
250
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
251
+    }
252
+    return result;
253
+
254
+}
255
+
256
+- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
257
+{
258
+    CDVPluginResult* result = nil;
259
+    CDVFilesystemURL *newURI = nil;
260
+    if ([localURI.fullPath isEqualToString:@""]) {
261
+        // return self
262
+        newURI = localURI;
263
+    } else {
264
+        newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
265
+    }
266
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
267
+    BOOL bIsDir;
268
+    BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
269
+    if (bExists) {
270
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
271
+    } else {
272
+        // invalid path or file does not exist
273
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
274
+    }
275
+    return result;
276
+}
277
+
278
+- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
279
+{
280
+    BOOL ok = NO;
281
+
282
+    NSString* filePath = [self filesystemPathForURL:localURI];
283
+    // we only care about this iCloud key for now.
284
+    // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute)
285
+    NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup";
286
+    id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey];
287
+
288
+    if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) {
289
+// todo: fix me
290
+//        if (IsAtLeastiOSVersion(@"5.1")) {
291
+//            NSURL* url = [NSURL fileURLWithPath:filePath];
292
+//            NSError* __autoreleasing error = nil;
293
+//
294
+//            ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error];
295
+//        } else { // below 5.1 (deprecated - only really supported in 5.01)
296
+//            u_int8_t value = [iCloudBackupExtendedAttributeValue intValue];
297
+//            if (value == 0) { // remove the attribute (allow backup, the default)
298
+//                ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0);
299
+//            } else { // set the attribute (skip backup)
300
+//                ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0);
301
+//            }
302
+//        }
303
+    }
304
+
305
+    if (ok) {
306
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
307
+    } else {
308
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
309
+    }
310
+}
311
+
312
+/* remove the file or directory (recursively)
313
+ * IN:
314
+ * NSString* fullPath - the full path to the file or directory to be removed
315
+ * NSString* callbackId
316
+ * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling
317
+ */
318
+
319
+- (CDVPluginResult*)doRemove:(NSString*)fullPath
320
+{
321
+    CDVPluginResult* result = nil;
322
+    BOOL bSuccess = NO;
323
+    NSError* __autoreleasing pError = nil;
324
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
325
+
326
+    @try {
327
+        bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError];
328
+        if (bSuccess) {
329
+            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
330
+        } else {
331
+            // see if we can give a useful error
332
+            CDVFileError errorCode = ABORT_ERR;
333
+            NSLog(@"error removing filesystem entry at %@: %@", fullPath, [pError localizedDescription]);
334
+            if ([pError code] == NSFileNoSuchFileError) {
335
+                errorCode = NOT_FOUND_ERR;
336
+            } else if ([pError code] == NSFileWriteNoPermissionError) {
337
+                errorCode = NO_MODIFICATION_ALLOWED_ERR;
338
+            }
339
+
340
+            result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
341
+        }
342
+    } @catch(NSException* e) {  // NSInvalidArgumentException if path is . or ..
343
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR];
344
+    }
345
+
346
+    return result;
347
+}
348
+
349
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
350
+{
351
+    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
352
+
353
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
354
+    BOOL bIsDir = NO;
355
+    BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
356
+    if (!bExists) {
357
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
358
+    }
359
+    if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
360
+        // dir is not empty
361
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
362
+    }
363
+    return [self doRemove:fileSystemPath];
364
+}
365
+
366
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
367
+{
368
+    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
369
+    return [self doRemove:fileSystemPath];
370
+}
371
+
372
+/*
373
+ * IN
374
+ *  NSString localURI
375
+ * OUT
376
+ *  NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
377
+ *  The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
378
+ *  or if the URL is malformed.
379
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
380
+ */
381
+- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
382
+{
383
+    if ([fsPath hasPrefix:self.fsRoot]) {
384
+        return [fsPath substringFromIndex:[self.fsRoot length]];
385
+    }
386
+    return nil;
387
+}
388
+
389
+
390
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
391
+{
392
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
393
+    NSError* __autoreleasing error = nil;
394
+    NSString *fileSystemPath = [self filesystemPathForURL:localURI];
395
+
396
+    NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath error:&error];
397
+
398
+    if (contents) {
399
+        NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1];
400
+        if ([contents count] > 0) {
401
+            // create an Entry (as JSON) for each file/dir
402
+            for (NSString* name in contents) {
403
+                // see if is dir or file
404
+                NSString* entryPath = [fileSystemPath stringByAppendingPathComponent:name];
405
+                BOOL bIsDir = NO;
406
+                [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
407
+                NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
408
+                [entries addObject:entryDict];
409
+            }
410
+        }
411
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
412
+    } else {
413
+        // assume not found but could check error for more specific error conditions
414
+        return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
415
+    }
416
+}
417
+
418
+- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos
419
+{
420
+    unsigned long long newPos = 0UL;
421
+
422
+    NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath];
423
+
424
+    if (file) {
425
+        [file truncateFileAtOffset:(unsigned long long)pos];
426
+        newPos = [file offsetInFile];
427
+        [file synchronizeFile];
428
+        [file closeFile];
429
+    }
430
+    return newPos;
431
+}
432
+
433
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
434
+{
435
+    unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
436
+    return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
437
+}
438
+
439
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
440
+{
441
+    NSString *filePath = [self filesystemPathForURL:localURL];
442
+
443
+    CDVPluginResult* result = nil;
444
+    CDVFileError errCode = INVALID_MODIFICATION_ERR;
445
+    int bytesWritten = 0;
446
+
447
+    if (filePath) {
448
+        NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
449
+        if (fileStream) {
450
+            NSUInteger len = [encData length];
451
+            if (len == 0) {
452
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
453
+            } else {
454
+                [fileStream open];
455
+
456
+                bytesWritten = (int)[fileStream write:[encData bytes] maxLength:len];
457
+
458
+                [fileStream close];
459
+                if (bytesWritten > 0) {
460
+                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten];
461
+                    // } else {
462
+                    // can probably get more detailed error info via [fileStream streamError]
463
+                    // errCode already set to INVALID_MODIFICATION_ERR;
464
+                    // bytesWritten = 0; // may be set to -1 on error
465
+                }
466
+            }
467
+        } // else fileStream not created return INVALID_MODIFICATION_ERR
468
+    } else {
469
+        // invalid filePath
470
+        errCode = NOT_FOUND_ERR;
471
+    }
472
+    if (!result) {
473
+        // was an error
474
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode];
475
+    }
476
+    return result;
477
+}
478
+
479
+/**
480
+ * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name,
481
+ * or attempted to copy a directory into a directory that it contains directly or indirectly.
482
+ *
483
+ * IN:
484
+ *  NSString* srcDir
485
+ *  NSString* destinationDir
486
+ * OUT:
487
+ *  YES copy/ move is allows
488
+ *  NO move is onto itself
489
+ */
490
+- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest
491
+{
492
+    // This weird test is to determine if we are copying or moving a directory into itself.
493
+    // Copy /Documents/myDir to /Documents/myDir-backup is okay but
494
+    // Copy /Documents/myDir to /Documents/myDir/backup not okay
495
+    BOOL copyOK = YES;
496
+    NSRange range = [dest rangeOfString:src];
497
+
498
+    if (range.location != NSNotFound) {
499
+        NSRange testRange = {range.length - 1, ([dest length] - range.length)};
500
+        NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange];
501
+        if (resultRange.location != NSNotFound) {
502
+            copyOK = NO;
503
+        }
504
+    }
505
+    return copyOK;
506
+}
507
+
508
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
509
+{
510
+    NSFileManager *fileMgr = [[NSFileManager alloc] init];
511
+    NSString *destRootPath = [self filesystemPathForURL:destURL];
512
+    BOOL bDestIsDir = NO;
513
+    BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
514
+
515
+    NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
516
+    NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
517
+
518
+    BOOL bNewIsDir = NO;
519
+    BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
520
+
521
+    CDVPluginResult *result = nil;
522
+    int errCode = 0;
523
+
524
+    if (!bDestExists) {
525
+        // the destination root does not exist
526
+        errCode = NOT_FOUND_ERR;
527
+    }
528
+
529
+    else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
530
+        /* Same FS, we can shortcut with NSFileManager operations */
531
+        NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
532
+
533
+        BOOL bSrcIsDir = NO;
534
+        BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
535
+
536
+        if (!bSrcExists) {
537
+            // the source does not exist
538
+            errCode = NOT_FOUND_ERR;
539
+        } else if ([newFileSystemPath isEqualToString:srcFullPath]) {
540
+            // source and destination can not be the same
541
+            errCode = INVALID_MODIFICATION_ERR;
542
+        } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) {
543
+            // can't copy/move dir to file
544
+            errCode = INVALID_MODIFICATION_ERR;
545
+        } else { // no errors yet
546
+            NSError* __autoreleasing error = nil;
547
+            BOOL bSuccess = NO;
548
+            if (bCopy) {
549
+                if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
550
+                    // can't copy dir into self
551
+                    errCode = INVALID_MODIFICATION_ERR;
552
+                } else if (bNewExists) {
553
+                    // the full destination should NOT already exist if a copy
554
+                    errCode = PATH_EXISTS_ERR;
555
+                } else {
556
+                    bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
557
+                }
558
+            } else { // move
559
+                // iOS requires that destination must not exist before calling moveTo
560
+                // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents
561
+                //
562
+                if (!bSrcIsDir && (bNewExists && bNewIsDir)) {
563
+                    // can't move a file to directory
564
+                    errCode = INVALID_MODIFICATION_ERR;
565
+                } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
566
+                    // can't move a dir into itself
567
+                    errCode = INVALID_MODIFICATION_ERR;
568
+                } else if (bNewExists) {
569
+                    if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
570
+                        // can't move dir to a dir that is not empty
571
+                        errCode = INVALID_MODIFICATION_ERR;
572
+                        newFileSystemPath = nil;  // so we won't try to move
573
+                    } else {
574
+                        // remove destination so can perform the moveItemAtPath
575
+                        bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
576
+                        if (!bSuccess) {
577
+                            errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
578
+                            newFileSystemPath = nil;
579
+                        }
580
+                    }
581
+                } else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
582
+                    // can't move a directory inside itself or to any child at any depth;
583
+                    errCode = INVALID_MODIFICATION_ERR;
584
+                    newFileSystemPath = nil;
585
+                }
586
+
587
+                if (newFileSystemPath != nil) {
588
+                    bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
589
+                }
590
+            }
591
+            if (bSuccess) {
592
+                // should verify it is there and of the correct type???
593
+                NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
594
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
595
+            } else {
596
+                if (error) {
597
+                    if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) {
598
+                        errCode = NOT_READABLE_ERR;
599
+                    } else if ([error code] == NSFileWriteOutOfSpaceError) {
600
+                        errCode = QUOTA_EXCEEDED_ERR;
601
+                    } else if ([error code] == NSFileWriteNoPermissionError) {
602
+                        errCode = NO_MODIFICATION_ALLOWED_ERR;
603
+                    }
604
+                }
605
+            }
606
+        }
607
+    } else {
608
+        // Need to copy the hard way
609
+        [srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
610
+            CDVPluginResult* result = nil;
611
+            if (data != nil) {
612
+                BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
613
+                if (bSuccess) {
614
+                    // should verify it is there and of the correct type???
615
+                    NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
616
+                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
617
+                } else {
618
+                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
619
+                }
620
+            } else {
621
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
622
+            }
623
+            callback(result);
624
+        }];
625
+        return; // Async IO; return without callback.
626
+    }
627
+    if (result == nil) {
628
+        if (!errCode) {
629
+            errCode = INVALID_MODIFICATION_ERR; // Catch-all default
630
+        }
631
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
632
+    }
633
+    callback(result);
634
+}
635
+
636
+/* helper function to get the mimeType from the file extension
637
+ * IN:
638
+ *	NSString* fullPath - filename (may include path)
639
+ * OUT:
640
+ *	NSString* the mime type as type/subtype.  nil if not able to determine
641
+ */
642
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
643
+{
644
+    NSString* mimeType = nil;
645
+
646
+    if (fullPath) {
647
+        CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
648
+        if (typeId) {
649
+            mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
650
+            if (!mimeType) {
651
+                // special case for m4a
652
+                if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
653
+                    mimeType = @"audio/mp4";
654
+                } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
655
+                    mimeType = @"audio/wav";
656
+                } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
657
+                    mimeType = @"text/css";
658
+                }
659
+            }
660
+            CFRelease(typeId);
661
+        }
662
+    }
663
+    return mimeType;
664
+}
665
+
666
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
667
+{
668
+    NSString *path = [self filesystemPathForURL:localURL];
669
+
670
+    NSString* mimeType = [CDVLocalFilesystem getMimeTypeFromPath:path];
671
+    if (mimeType == nil) {
672
+        mimeType = @"*/*";
673
+    }
674
+    NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
675
+    if (start > 0) {
676
+        [file seekToFileOffset:start];
677
+    }
678
+
679
+    NSData* readData;
680
+    if (end < 0) {
681
+        readData = [file readDataToEndOfFile];
682
+    } else {
683
+        readData = [file readDataOfLength:(end - start)];
684
+    }
685
+    [file closeFile];
686
+
687
+    callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR);
688
+}
689
+
690
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
691
+{
692
+    NSString *path = [self filesystemPathForURL:localURL];
693
+    CDVPluginResult *result;
694
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
695
+
696
+    NSError* __autoreleasing error = nil;
697
+    NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
698
+
699
+    if (fileAttrs) {
700
+
701
+        // create dictionary of file info
702
+        NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
703
+
704
+        [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
705
+        [fileInfo setObject:@"" forKey:@"type"];  // can't easily get the mimetype unless create URL, send request and read response so skipping
706
+        [fileInfo setObject:[path lastPathComponent] forKey:@"name"];
707
+
708
+        // Ensure that directories (and other non-regular files) report size of 0
709
+        unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
710
+        [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
711
+
712
+        NSDate* modDate = [fileAttrs fileModificationDate];
713
+        if (modDate) {
714
+            [fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
715
+        }
716
+
717
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
718
+
719
+    } else {
720
+        // didn't get fileAttribs
721
+        CDVFileError errorCode = ABORT_ERR;
722
+        NSLog(@"error getting metadata: %@", [error localizedDescription]);
723
+        if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
724
+            errorCode = NOT_FOUND_ERR;
725
+        }
726
+        // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
727
+        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
728
+    }
729
+
730
+    callback(result);
731
+}
732
+
733
+@end

+ 1190
- 0
plugins/cordova-plugin-file/src/windows/FileProxy.js
File diff suppressed because it is too large
View File


+ 17
- 0
plugins/cordova-plugin-file/tests/package.json View File

@@ -0,0 +1,17 @@
1
+{
2
+  "name": "cordova-plugin-file-tests",
3
+  "version": "6.0.2",
4
+  "description": "",
5
+  "cordova": {
6
+    "id": "cordova-plugin-file-tests",
7
+    "platforms": [
8
+      "android"
9
+    ]
10
+  },
11
+  "keywords": [
12
+    "ecosystem:cordova",
13
+    "cordova-android"
14
+  ],
15
+  "author": "",
16
+  "license": "Apache-2.0"
17
+}

+ 43
- 0
plugins/cordova-plugin-file/tests/plugin.xml View File

@@ -0,0 +1,43 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!--
3
+  Licensed to the Apache Software Foundation (ASF) under one
4
+  or more contributor license agreements.  See the NOTICE file
5
+  distributed with this work for additional information
6
+  regarding copyright ownership.  The ASF licenses this file
7
+  to you under the Apache License, Version 2.0 (the
8
+  "License"); you may not use this file except in compliance
9
+  with the License.  You may obtain a copy of the License at
10
+
11
+    http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+  Unless required by applicable law or agreed to in writing,
14
+  software distributed under the License is distributed on an
15
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+  KIND, either express or implied.  See the License for the
17
+  specific language governing permissions and limitations
18
+  under the License.
19
+-->
20
+
21
+<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
22
+    xmlns:android="http://schemas.android.com/apk/res/android"
23
+    id="cordova-plugin-file-tests"
24
+    version="6.0.2">
25
+
26
+    <name>Cordova File Plugin Tests</name>
27
+    <license>Apache 2.0</license>
28
+
29
+    <js-module src="tests.js" name="tests">
30
+    </js-module>
31
+
32
+    <platform name="android">
33
+        <source-file src="src/android/TestContentProvider.java" target-dir="src/org/apache/cordova/file/test" />
34
+        <config-file target="AndroidManifest.xml" parent="/*/application">
35
+            <provider
36
+                android:name="org.apache.cordova.file.test.TestContentProvider"
37
+                android:authorities="org.apache.cordova.file.testprovider"
38
+                android:exported="false" />
39
+        </config-file>
40
+        <asset src="www/fixtures/asset-test" target="fixtures/asset-test" />
41
+        <dependency id="cordova-plugin-contacts" />
42
+    </platform>
43
+</plugin>

+ 93
- 0
plugins/cordova-plugin-file/tests/src/android/TestContentProvider.java View File

@@ -0,0 +1,93 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+ */
19
+package org.apache.cordova.file.test;
20
+
21
+import android.content.ContentProvider;
22
+import android.net.Uri;
23
+import android.content.res.AssetFileDescriptor;
24
+import android.content.res.AssetManager;
25
+
26
+import java.io.File;
27
+import java.io.FileInputStream;
28
+import java.io.FileNotFoundException;
29
+import android.content.ContentValues;
30
+import android.database.Cursor;
31
+import android.os.ParcelFileDescriptor;
32
+
33
+import org.apache.cordova.CordovaResourceApi;
34
+
35
+import java.io.IOException;
36
+import java.util.HashMap;
37
+
38
+public class TestContentProvider extends ContentProvider {
39
+
40
+    @Override
41
+    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
42
+        String fileName = uri.getQueryParameter("realPath");
43
+        if (fileName == null) {
44
+            fileName = uri.getPath();
45
+        }
46
+        if (fileName == null || fileName.length() < 1) {
47
+            throw new FileNotFoundException();
48
+        }
49
+        CordovaResourceApi resourceApi = new CordovaResourceApi(getContext(), null);
50
+        try {
51
+            File f = File.createTempFile("test-content-provider", ".tmp");
52
+            resourceApi.copyResource(Uri.parse("file:///android_asset" + fileName), Uri.fromFile(f));
53
+            return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
54
+        } catch (FileNotFoundException e) {
55
+            throw e;
56
+        } catch (IOException e) {
57
+            e.printStackTrace();
58
+            throw new FileNotFoundException("IO error: " + e.toString());
59
+        }
60
+    }
61
+
62
+    @Override
63
+    public boolean onCreate() {
64
+        return false;
65
+    }
66
+
67
+    @Override
68
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
69
+        throw new UnsupportedOperationException();
70
+    }
71
+
72
+    @Override
73
+    public String getType(Uri uri) {
74
+        return "text/html";
75
+    }
76
+
77
+    @Override
78
+    public Uri insert(Uri uri, ContentValues values) {
79
+        throw new UnsupportedOperationException();
80
+    }
81
+
82
+    @Override
83
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
84
+        throw new UnsupportedOperationException();
85
+    }
86
+
87
+    @Override
88
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
89
+        throw new UnsupportedOperationException();
90
+    }
91
+
92
+
93
+}

+ 4163
- 0
plugins/cordova-plugin-file/tests/tests.js
File diff suppressed because it is too large
View File


+ 1
- 0
plugins/cordova-plugin-file/tests/www/fixtures/asset-test/asset-test.txt View File

@@ -0,0 +1 @@
1
+This file is here for testing purposes

+ 378
- 0
plugins/cordova-plugin-file/types/index.d.ts View File

@@ -0,0 +1,378 @@
1
+// Type definitions for Apache Cordova File System plugin
2
+// Project: https://github.com/apache/cordova-plugin-file
3
+// Definitions by: Microsoft Open Technologies Inc <http://msopentech.com>
4
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
+//
6
+// Copyright (c) Microsoft Open Technologies, Inc.
7
+// Licensed under the MIT license.
8
+
9
+interface Window {
10
+    /**
11
+     * Requests a filesystem in which to store application data.
12
+     * @param type              Whether the filesystem requested should be persistent, as defined above. Use one of TEMPORARY or PERSISTENT.
13
+     * @param size              This is an indicator of how much storage space, in bytes, the application expects to need.
14
+     * @param successCallback   The callback that is called when the user agent provides a filesystem.
15
+     * @param errorCallback     A callback that is called when errors happen, or when the request to obtain the filesystem is denied.
16
+     */
17
+    requestFileSystem(
18
+        type: LocalFileSystem,
19
+        size: number,
20
+        successCallback: (fileSystem: FileSystem) => void,
21
+        errorCallback?: (fileError: FileError) => void): void;
22
+    /**
23
+     * Look up file system Entry referred to by local URL.
24
+     * @param string url       URL referring to a local file or directory
25
+     * @param successCallback  invoked with Entry object corresponding to URL
26
+     * @param errorCallback    invoked if error occurs retrieving file system entry
27
+     */
28
+    resolveLocalFileSystemURL(url: string,
29
+        successCallback: (entry: Entry) => void,
30
+        errorCallback?: (error: FileError) => void): void;
31
+    /**
32
+     * Look up file system Entry referred to by local URI.
33
+     * @param string uri       URI referring to a local file or directory
34
+     * @param successCallback  invoked with Entry object corresponding to URI
35
+     * @param errorCallback    invoked if error occurs retrieving file system entry
36
+     */
37
+    resolveLocalFileSystemURI(uri: string,
38
+        successCallback: (entry: Entry) => void,
39
+        errorCallback?: (error: FileError) => void): void;
40
+    TEMPORARY: number;
41
+    PERSISTENT: number;
42
+}
43
+
44
+/** This interface represents a file system. */
45
+interface FileSystem {
46
+    /* The name of the file system, unique across the list of exposed file systems. */
47
+    name: string;
48
+    /** The root directory of the file system. */
49
+    root: DirectoryEntry;
50
+}
51
+
52
+/**
53
+ * An abstract interface representing entries in a file system,
54
+ * each of which may be a File or DirectoryEntry.
55
+ */
56
+interface Entry {
57
+    /** Entry is a file. */
58
+    isFile: boolean;
59
+    /** Entry is a directory. */
60
+    isDirectory: boolean;
61
+    /** The name of the entry, excluding the path leading to it. */
62
+    name: string;
63
+    /** The full absolute path from the root to the entry. */
64
+    fullPath: string;
65
+    /** The file system on which the entry resides. */
66
+    filesystem: FileSystem;
67
+    nativeURL: string;
68
+    /**
69
+     * Look up metadata about this entry.
70
+     * @param successCallback A callback that is called with the time of the last modification.
71
+     * @param errorCallback   A callback that is called when errors happen.
72
+     */
73
+    getMetadata(
74
+        successCallback: (metadata: Metadata) => void,
75
+        errorCallback?: (error: FileError) => void): void;
76
+    /**
77
+     * Move an entry to a different location on the file system. It is an error to try to:
78
+     *     move a directory inside itself or to any child at any depth;move an entry into its parent if a name different from its current one isn't provided;
79
+     *     move a file to a path occupied by a directory;
80
+     *     move a directory to a path occupied by a file;
81
+     *     move any element to a path occupied by a directory which is not empty.
82
+     * A move of a file on top of an existing file must attempt to delete and replace that file.
83
+     * A move of a directory on top of an existing empty directory must attempt to delete and replace that directory.
84
+     * @param parent  The directory to which to move the entry.
85
+     * @param newName The new name of the entry. Defaults to the Entry's current name if unspecified.
86
+     * @param successCallback A callback that is called with the Entry for the new location.
87
+     * @param errorCallback   A callback that is called when errors happen.
88
+     */
89
+    moveTo(parent: DirectoryEntry,
90
+        newName?: string,
91
+        successCallback?: (entry: Entry) => void,
92
+        errorCallback?: (error: FileError) => void): void;
93
+    /**
94
+     * Copy an entry to a different location on the file system. It is an error to try to:
95
+     *     copy a directory inside itself or to any child at any depth;
96
+     *     copy an entry into its parent if a name different from its current one isn't provided;
97
+     *     copy a file to a path occupied by a directory;
98
+     *     copy a directory to a path occupied by a file;
99
+     *     copy any element to a path occupied by a directory which is not empty.
100
+     *     A copy of a file on top of an existing file must attempt to delete and replace that file.
101
+     *     A copy of a directory on top of an existing empty directory must attempt to delete and replace that directory.
102
+     * Directory copies are always recursive--that is, they copy all contents of the directory.
103
+     * @param parent The directory to which to move the entry.
104
+     * @param newName The new name of the entry. Defaults to the Entry's current name if unspecified.
105
+     * @param successCallback A callback that is called with the Entry for the new object.
106
+     * @param errorCallback A callback that is called when errors happen.
107
+     */
108
+    copyTo(parent: DirectoryEntry,
109
+        newName?: string,
110
+        successCallback?: (entry: Entry) => void,
111
+        errorCallback?: (error: FileError) => void): void;
112
+    /**
113
+     * Returns a URL that can be used as the src attribute of a <video> or <audio> tag.
114
+     * If that is not possible, construct a cdvfile:// URL.
115
+     * @return string URL
116
+     */
117
+    toURL(): string;
118
+    /**
119
+     * Return a URL that can be passed across the bridge to identify this entry.
120
+     * @return string URL that can be passed across the bridge to identify this entry
121
+     */
122
+    toInternalURL(): string;
123
+    /**
124
+     * 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.
125
+     * @param successCallback A callback that is called on success.
126
+     * @param errorCallback   A callback that is called when errors happen.
127
+     */
128
+    remove(successCallback: () => void,
129
+        errorCallback?: (error: FileError) => void): void;
130
+    /**
131
+     * Look up the parent DirectoryEntry containing this Entry. If this Entry is the root of its filesystem, its parent is itself.
132
+     * @param successCallback A callback that is called with the time of the last modification.
133
+     * @param errorCallback   A callback that is called when errors happen.
134
+     */
135
+    getParent(successCallback: (entry: Entry) => void,
136
+        errorCallback?: (error: FileError) => void): void;
137
+}
138
+
139
+/** This interface supplies information about the state of a file or directory. */
140
+interface Metadata {
141
+    /** This is the time at which the file or directory was last modified. */
142
+    modificationTime: Date;
143
+    /** The size of the file, in bytes. This must return 0 for directories. */
144
+    size: number;
145
+}
146
+
147
+/** This interface represents a directory on a file system. */
148
+interface DirectoryEntry extends Entry {
149
+    /**
150
+     * Creates a new DirectoryReader to read Entries from this Directory.
151
+     */
152
+    createReader(): DirectoryReader;
153
+    /**
154
+     * Creates or looks up a file.
155
+     * @param path    Either an absolute path or a relative path from this DirectoryEntry
156
+     *                to the file to be looked up or created.
157
+     *                It is an error to attempt to create a file whose immediate parent does not yet exist.
158
+     * @param options If create and exclusive are both true, and the path already exists, getFile must fail.
159
+     *                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.
160
+     *                If create is not true and the path doesn't exist, getFile must fail.
161
+     *                If create is not true and the path exists, but is a directory, getFile must fail.
162
+     *                Otherwise, if no other error occurs, getFile must return a FileEntry corresponding to path.
163
+     * @param successCallback A callback that is called to return the File selected or created.
164
+     * @param errorCallback   A callback that is called when errors happen.
165
+     */
166
+    getFile(path: string, options?: Flags,
167
+        successCallback?: (entry: FileEntry) => void,
168
+        errorCallback?: (error: FileError) => void): void;
169
+    /**
170
+     * Creates or looks up a directory.
171
+     * @param path    Either an absolute path or a relative path from this DirectoryEntry
172
+     *                to the directory to be looked up or created.
173
+     *                It is an error to attempt to create a directory whose immediate parent does not yet exist.
174
+     * @param options If create and exclusive are both true and the path already exists, getDirectory must fail.
175
+     *                If create is true, the path doesn't exist, and no other error occurs, getDirectory must create and return a corresponding DirectoryEntry.
176
+     *                If create is not true and the path doesn't exist, getDirectory must fail.
177
+     *                If create is not true and the path exists, but is a file, getDirectory must fail.
178
+     *                Otherwise, if no other error occurs, getDirectory must return a DirectoryEntry corresponding to path.
179
+     * @param successCallback A callback that is called to return the Directory selected or created.
180
+     * @param errorCallback   A callback that is called when errors happen.
181
+     */
182
+    getDirectory(path: string, options?: Flags,
183
+        successCallback?: (entry: DirectoryEntry) => void,
184
+        errorCallback?: (error: FileError) => void): void;
185
+    /**
186
+     * Deletes a directory and all of its contents, if any. In the event of an error (e.g. trying
187
+     * to delete a directory that contains a file that cannot be removed), some of the contents
188
+     * of the directory may be deleted. It is an error to attempt to delete the root directory of a filesystem.
189
+     * @param successCallback A callback that is called on success.
190
+     * @param errorCallback   A callback that is called when errors happen.
191
+     */
192
+    removeRecursively(successCallback: () => void,
193
+        errorCallback?: (error: FileError) => void): void;
194
+}
195
+
196
+/**
197
+ * This dictionary is used to supply arguments to methods
198
+ * that look up or create files or directories.
199
+ */
200
+interface Flags {
201
+    /** Used to indicate that the user wants to create a file or directory if it was not previously there. */
202
+    create?: boolean;
203
+    /** By itself, exclusive must have no effect. Used with create, it must cause getFile and getDirectory to fail if the target path already exists. */
204
+    exclusive?: boolean;
205
+}
206
+
207
+/**
208
+ * This interface lets a user list files and directories in a directory. If there are
209
+ * no additions to or deletions from a directory between the first and last call to
210
+ * readEntries, and no errors occur, then:
211
+ *     A series of calls to readEntries must return each entry in the directory exactly once.
212
+ *     Once all entries have been returned, the next call to readEntries must produce an empty array.
213
+ *     If not all entries have been returned, the array produced by readEntries must not be empty.
214
+ *     The entries produced by readEntries must not include the directory itself ["."] or its parent [".."].
215
+ */
216
+interface DirectoryReader {
217
+    /**
218
+     * Read the next block of entries from this directory.
219
+     * @param successCallback Called once per successful call to readEntries to deliver the next
220
+     *                        previously-unreported set of Entries in the associated Directory.
221
+     *                        If all Entries have already been returned from previous invocations
222
+     *                        of readEntries, successCallback must be called with a zero-length array as an argument.
223
+     * @param errorCallback   A callback indicating that there was an error reading from the Directory.
224
+     */
225
+    readEntries(
226
+        successCallback: (entries: Entry[]) => void,
227
+        errorCallback?: (error: FileError) => void): void;
228
+}
229
+
230
+/** This interface represents a file on a file system. */
231
+interface FileEntry extends Entry {
232
+    /**
233
+     * Creates a new FileWriter associated with the file that this FileEntry represents.
234
+     * @param successCallback A callback that is called with the new FileWriter.
235
+     * @param errorCallback   A callback that is called when errors happen.
236
+     */
237
+    createWriter(successCallback: (
238
+        writer: FileWriter) => void,
239
+        errorCallback?: (error: FileError) => void): void;
240
+    /**
241
+     * Returns a File that represents the current state of the file that this FileEntry represents.
242
+     * @param successCallback A callback that is called with the File.
243
+     * @param errorCallback   A callback that is called when errors happen.
244
+     */
245
+    file(successCallback: (file: File) => void,
246
+        errorCallback?: (error: FileError) => void): void;
247
+}
248
+
249
+/**
250
+ * This interface provides methods to monitor the asynchronous writing of blobs
251
+ * to disk using progress events and event handler attributes.
252
+ */
253
+interface FileSaver extends EventTarget {
254
+    /** Terminate file operation */
255
+    abort(): void;
256
+    /**
257
+     * The FileSaver object can be in one of 3 states. The readyState attribute, on getting,
258
+     * must return the current state, which must be one of the following values:
259
+     *     INIT
260
+     *     WRITING
261
+     *     DONE
262
+     */
263
+    readyState: number;
264
+    /** Handler for writestart events. */
265
+    onwritestart: (event: ProgressEvent) => void;
266
+    /** Handler for progress events. */
267
+    onprogress: (event: ProgressEvent) => void;
268
+    /** Handler for write events. */
269
+    onwrite: (event: ProgressEvent) => void;
270
+    /** Handler for abort events. */
271
+    onabort: (event: ProgressEvent) => void;
272
+    /** Handler for error events. */
273
+    onerror: (event: ProgressEvent) => void;
274
+    /** Handler for writeend events. */
275
+    onwriteend: (event: ProgressEvent) => void;
276
+    /** The last error that occurred on the FileSaver. */
277
+    error: Error;
278
+}
279
+
280
+/**
281
+ * This interface expands on the FileSaver interface to allow for multiple write
282
+ * actions, rather than just saving a single Blob.
283
+ */
284
+interface FileWriter extends FileSaver {
285
+    /**
286
+     * The byte offset at which the next write to the file will occur. This always less or equal than length.
287
+     * A newly-created FileWriter will have position set to 0.
288
+     */
289
+    position: number;
290
+    /**
291
+     * The length of the file. If the user does not have read access to the file,
292
+     * this will be the highest byte offset at which the user has written.
293
+     */
294
+    length: number;
295
+    /**
296
+     * Write the supplied data to the file at position.
297
+     * @param {Blob|string} data The blob to write.
298
+     */
299
+    write(data: Blob|string): void;
300
+    /**
301
+     * The file position at which the next write will occur.
302
+     * @param offset If nonnegative, an absolute byte offset into the file.
303
+     *               If negative, an offset back from the end of the file.
304
+     */
305
+    seek(offset: number): void;
306
+    /**
307
+     * Changes the length of the file to that specified. If shortening the file, data beyond the new length
308
+     * will be discarded. If extending the file, the existing data will be zero-padded up to the new length.
309
+     * @param size The size to which the length of the file is to be adjusted, measured in bytes.
310
+     */
311
+    truncate(size: number): void;
312
+}
313
+
314
+/* FileWriter states */
315
+declare var FileWriter: {
316
+    INIT: number;
317
+    WRITING: number;
318
+    DONE: number
319
+};
320
+
321
+interface FileError {
322
+    /** Error code */
323
+    code: number;
324
+}
325
+
326
+declare var FileError: {
327
+    new (code: number): FileError;
328
+    NOT_FOUND_ERR: number;
329
+    SECURITY_ERR: number;
330
+    ABORT_ERR: number;
331
+    NOT_READABLE_ERR: number;
332
+    ENCODING_ERR: number;
333
+    NO_MODIFICATION_ALLOWED_ERR: number;
334
+    INVALID_STATE_ERR: number;
335
+    SYNTAX_ERR: number;
336
+    INVALID_MODIFICATION_ERR: number;
337
+    QUOTA_EXCEEDED_ERR: number;
338
+    TYPE_MISMATCH_ERR: number;
339
+    PATH_EXISTS_ERR: number;
340
+};
341
+
342
+/*
343
+ * Constants defined in fileSystemPaths
344
+ */
345
+interface Cordova {
346
+    file: {
347
+        /* Read-only directory where the application is installed. */
348
+        applicationDirectory: string;
349
+        /* Root of app's private writable storage */
350
+        applicationStorageDirectory: string;
351
+        /* Where to put app-specific data files. */
352
+        dataDirectory: string;
353
+        /* Cached files that should survive app restarts. Apps should not rely on the OS to delete files in here. */
354
+        cacheDirectory: string;
355
+        /* Android: the application space on external storage. */
356
+        externalApplicationStorageDirectory: string;
357
+        /* Android: Where to put app-specific data files on external storage. */
358
+        externalDataDirectory: string;
359
+        /* Android: the application cache on external storage. */
360
+        externalCacheDirectory: string;
361
+        /* Android: the external storage (SD card) root. */
362
+        externalRootDirectory: string;
363
+        /* iOS: Temp directory that the OS can clear at will. */
364
+        tempDirectory: string;
365
+        /* iOS: Holds app-specific files that should be synced (e.g. to iCloud). */
366
+        syncedDataDirectory: string;
367
+        /* iOS: Files private to the app, but that are meaningful to other applciations (e.g. Office files) */
368
+        documentsDirectory: string;
369
+        /* BlackBerry10: Files globally available to all apps */
370
+        sharedDirectory: string
371
+    }
372
+}
373
+
374
+
375
+declare enum LocalFileSystem {
376
+    PERSISTENT=1,
377
+    TEMPORARY=0
378
+}

+ 117
- 0
plugins/cordova-plugin-file/www/DirectoryEntry.js View File

@@ -0,0 +1,117 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+var argscheck = require('cordova/argscheck');
23
+var utils = require('cordova/utils');
24
+var exec = require('cordova/exec');
25
+var Entry = require('./Entry');
26
+var FileError = require('./FileError');
27
+var DirectoryReader = require('./DirectoryReader');
28
+
29
+/**
30
+ * An interface representing a directory on the file system.
31
+ *
32
+ * {boolean} isFile always false (readonly)
33
+ * {boolean} isDirectory always true (readonly)
34
+ * {DOMString} name of the directory, excluding the path leading to it (readonly)
35
+ * {DOMString} fullPath the absolute full path to the directory (readonly)
36
+ * {FileSystem} filesystem on which the directory resides (readonly)
37
+ */
38
+var DirectoryEntry = function (name, fullPath, fileSystem, nativeURL) {
39
+
40
+    // add trailing slash if it is missing
41
+    if ((fullPath) && !/\/$/.test(fullPath)) {
42
+        fullPath += '/';
43
+    }
44
+    // add trailing slash if it is missing
45
+    if (nativeURL && !/\/$/.test(nativeURL)) {
46
+        nativeURL += '/';
47
+    }
48
+    DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem, nativeURL);
49
+};
50
+
51
+utils.extend(DirectoryEntry, Entry);
52
+
53
+/**
54
+ * Creates a new DirectoryReader to read entries from this directory
55
+ */
56
+DirectoryEntry.prototype.createReader = function () {
57
+    return new DirectoryReader(this.toInternalURL());
58
+};
59
+
60
+/**
61
+ * Creates or looks up a directory
62
+ *
63
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
64
+ * @param {Flags} options to create or exclusively create the directory
65
+ * @param {Function} successCallback is called with the new entry
66
+ * @param {Function} errorCallback is called with a FileError
67
+ */
68
+DirectoryEntry.prototype.getDirectory = function (path, options, successCallback, errorCallback) {
69
+    argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments);
70
+    var fs = this.filesystem;
71
+    var win = successCallback && function (result) {
72
+        var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
73
+        successCallback(entry);
74
+    };
75
+    var fail = errorCallback && function (code) {
76
+        errorCallback(new FileError(code));
77
+    };
78
+    exec(win, fail, 'File', 'getDirectory', [this.toInternalURL(), path, options]);
79
+};
80
+
81
+/**
82
+ * Deletes a directory and all of it's contents
83
+ *
84
+ * @param {Function} successCallback is called with no parameters
85
+ * @param {Function} errorCallback is called with a FileError
86
+ */
87
+DirectoryEntry.prototype.removeRecursively = function (successCallback, errorCallback) {
88
+    argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments);
89
+    var fail = errorCallback && function (code) {
90
+        errorCallback(new FileError(code));
91
+    };
92
+    exec(successCallback, fail, 'File', 'removeRecursively', [this.toInternalURL()]);
93
+};
94
+
95
+/**
96
+ * Creates or looks up a file
97
+ *
98
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
99
+ * @param {Flags} options to create or exclusively create the file
100
+ * @param {Function} successCallback is called with the new entry
101
+ * @param {Function} errorCallback is called with a FileError
102
+ */
103
+DirectoryEntry.prototype.getFile = function (path, options, successCallback, errorCallback) {
104
+    argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments);
105
+    var fs = this.filesystem;
106
+    var win = successCallback && function (result) {
107
+        var FileEntry = require('./FileEntry');
108
+        var entry = new FileEntry(result.name, result.fullPath, fs, result.nativeURL);
109
+        successCallback(entry);
110
+    };
111
+    var fail = errorCallback && function (code) {
112
+        errorCallback(new FileError(code));
113
+    };
114
+    exec(win, fail, 'File', 'getFile', [this.toInternalURL(), path, options]);
115
+};
116
+
117
+module.exports = DirectoryEntry;

+ 72
- 0
plugins/cordova-plugin-file/www/DirectoryReader.js View File

@@ -0,0 +1,72 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+var exec = require('cordova/exec');
23
+var FileError = require('./FileError');
24
+
25
+/**
26
+ * An interface that lists the files and directories in a directory.
27
+ */
28
+function DirectoryReader (localURL) {
29
+    this.localURL = localURL || null;
30
+    this.hasReadEntries = false;
31
+}
32
+
33
+/**
34
+ * Returns a list of entries from a directory.
35
+ *
36
+ * @param {Function} successCallback is called with a list of entries
37
+ * @param {Function} errorCallback is called with a FileError
38
+ */
39
+DirectoryReader.prototype.readEntries = function (successCallback, errorCallback) {
40
+    // If we've already read and passed on this directory's entries, return an empty list.
41
+    if (this.hasReadEntries) {
42
+        successCallback([]);
43
+        return;
44
+    }
45
+    var reader = this;
46
+    var win = typeof successCallback !== 'function' ? null : function (result) {
47
+        var retVal = [];
48
+        for (var i = 0; i < result.length; i++) {
49
+            var entry = null;
50
+            if (result[i].isDirectory) {
51
+                entry = new (require('./DirectoryEntry'))();
52
+            } else if (result[i].isFile) {
53
+                entry = new (require('./FileEntry'))();
54
+            }
55
+            entry.isDirectory = result[i].isDirectory;
56
+            entry.isFile = result[i].isFile;
57
+            entry.name = result[i].name;
58
+            entry.fullPath = result[i].fullPath;
59
+            entry.filesystem = new (require('./FileSystem'))(result[i].filesystemName);
60
+            entry.nativeURL = result[i].nativeURL;
61
+            retVal.push(entry);
62
+        }
63
+        reader.hasReadEntries = true;
64
+        successCallback(retVal);
65
+    };
66
+    var fail = typeof errorCallback !== 'function' ? null : function (code) {
67
+        errorCallback(new FileError(code));
68
+    };
69
+    exec(win, fail, 'File', 'readEntries', [this.localURL]);
70
+};
71
+
72
+module.exports = DirectoryReader;

+ 260
- 0
plugins/cordova-plugin-file/www/Entry.js View File

@@ -0,0 +1,260 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+var argscheck = require('cordova/argscheck');
23
+var exec = require('cordova/exec');
24
+var FileError = require('./FileError');
25
+var Metadata = require('./Metadata');
26
+
27
+/**
28
+ * Represents a file or directory on the local file system.
29
+ *
30
+ * @param isFile
31
+ *            {boolean} true if Entry is a file (readonly)
32
+ * @param isDirectory
33
+ *            {boolean} true if Entry is a directory (readonly)
34
+ * @param name
35
+ *            {DOMString} name of the file or directory, excluding the path
36
+ *            leading to it (readonly)
37
+ * @param fullPath
38
+ *            {DOMString} the absolute full path to the file or directory
39
+ *            (readonly)
40
+ * @param fileSystem
41
+ *            {FileSystem} the filesystem on which this entry resides
42
+ *            (readonly)
43
+ * @param nativeURL
44
+ *            {DOMString} an alternate URL which can be used by native
45
+ *            webview controls, for example media players.
46
+ *            (optional, readonly)
47
+ */
48
+function Entry (isFile, isDirectory, name, fullPath, fileSystem, nativeURL) {
49
+    this.isFile = !!isFile;
50
+    this.isDirectory = !!isDirectory;
51
+    this.name = name || '';
52
+    this.fullPath = fullPath || '';
53
+    this.filesystem = fileSystem || null;
54
+    this.nativeURL = nativeURL || null;
55
+}
56
+
57
+/**
58
+ * Look up the metadata of the entry.
59
+ *
60
+ * @param successCallback
61
+ *            {Function} is called with a Metadata object
62
+ * @param errorCallback
63
+ *            {Function} is called with a FileError
64
+ */
65
+Entry.prototype.getMetadata = function (successCallback, errorCallback) {
66
+    argscheck.checkArgs('FF', 'Entry.getMetadata', arguments);
67
+    var success = successCallback && function (entryMetadata) {
68
+        var metadata = new Metadata({
69
+            size: entryMetadata.size,
70
+            modificationTime: entryMetadata.lastModifiedDate
71
+        });
72
+        successCallback(metadata);
73
+    };
74
+    var fail = errorCallback && function (code) {
75
+        errorCallback(new FileError(code));
76
+    };
77
+    exec(success, fail, 'File', 'getFileMetadata', [this.toInternalURL()]);
78
+};
79
+
80
+/**
81
+ * Set the metadata of the entry.
82
+ *
83
+ * @param successCallback
84
+ *            {Function} is called with a Metadata object
85
+ * @param errorCallback
86
+ *            {Function} is called with a FileError
87
+ * @param metadataObject
88
+ *            {Object} keys and values to set
89
+ */
90
+Entry.prototype.setMetadata = function (successCallback, errorCallback, metadataObject) {
91
+    argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments);
92
+    exec(successCallback, errorCallback, 'File', 'setMetadata', [this.toInternalURL(), metadataObject]);
93
+};
94
+
95
+/**
96
+ * Move a file or directory to a new location.
97
+ *
98
+ * @param parent
99
+ *            {DirectoryEntry} the directory to which to move this entry
100
+ * @param newName
101
+ *            {DOMString} new name of the entry, defaults to the current name
102
+ * @param successCallback
103
+ *            {Function} called with the new DirectoryEntry object
104
+ * @param errorCallback
105
+ *            {Function} called with a FileError
106
+ */
107
+Entry.prototype.moveTo = function (parent, newName, successCallback, errorCallback) {
108
+    argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments);
109
+    var fail = errorCallback && function (code) {
110
+        errorCallback(new FileError(code));
111
+    };
112
+    var srcURL = this.toInternalURL();
113
+    // entry name
114
+    var name = newName || this.name;
115
+    var success = function (entry) {
116
+        if (entry) {
117
+            if (successCallback) {
118
+                // create appropriate Entry object
119
+                var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
120
+                var fs = newFSName ? new FileSystem(newFSName, { name: '', fullPath: '/' }) : new FileSystem(parent.filesystem.name, { name: '', fullPath: '/' }); // eslint-disable-line no-undef
121
+                var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
122
+                successCallback(result);
123
+            }
124
+        } else {
125
+            // no Entry object returned
126
+            if (fail) {
127
+                fail(FileError.NOT_FOUND_ERR);
128
+            }
129
+        }
130
+    };
131
+
132
+    // copy
133
+    exec(success, fail, 'File', 'moveTo', [srcURL, parent.toInternalURL(), name]);
134
+};
135
+
136
+/**
137
+ * Copy a directory to a different location.
138
+ *
139
+ * @param parent
140
+ *            {DirectoryEntry} the directory to which to copy the entry
141
+ * @param newName
142
+ *            {DOMString} new name of the entry, defaults to the current name
143
+ * @param successCallback
144
+ *            {Function} called with the new Entry object
145
+ * @param errorCallback
146
+ *            {Function} called with a FileError
147
+ */
148
+Entry.prototype.copyTo = function (parent, newName, successCallback, errorCallback) {
149
+    argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments);
150
+    var fail = errorCallback && function (code) {
151
+        errorCallback(new FileError(code));
152
+    };
153
+    var srcURL = this.toInternalURL();
154
+        // entry name
155
+    var name = newName || this.name;
156
+    // success callback
157
+    var success = function (entry) {
158
+        if (entry) {
159
+            if (successCallback) {
160
+                // create appropriate Entry object
161
+                var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
162
+                var fs = newFSName ? new FileSystem(newFSName, { name: '', fullPath: '/' }) : new FileSystem(parent.filesystem.name, { name: '', fullPath: '/' }); // eslint-disable-line no-undef
163
+                var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
164
+                successCallback(result);
165
+            }
166
+        } else {
167
+            // no Entry object returned
168
+            if (fail) {
169
+                fail(FileError.NOT_FOUND_ERR);
170
+            }
171
+        }
172
+    };
173
+
174
+    // copy
175
+    exec(success, fail, 'File', 'copyTo', [srcURL, parent.toInternalURL(), name]);
176
+};
177
+
178
+/**
179
+ * Return a URL that can be passed across the bridge to identify this entry.
180
+ */
181
+Entry.prototype.toInternalURL = function () {
182
+    if (this.filesystem && this.filesystem.__format__) {
183
+        return this.filesystem.__format__(this.fullPath, this.nativeURL);
184
+    }
185
+};
186
+
187
+/**
188
+ * Return a URL that can be used to identify this entry.
189
+ * Use a URL that can be used to as the src attribute of a <video> or
190
+ * <audio> tag. If that is not possible, construct a cdvfile:// URL.
191
+ */
192
+Entry.prototype.toURL = function () {
193
+    if (this.nativeURL) {
194
+        return this.nativeURL;
195
+    }
196
+    // fullPath attribute may contain the full URL in the case that
197
+    // toInternalURL fails.
198
+    return this.toInternalURL() || 'file://localhost' + this.fullPath;
199
+};
200
+
201
+/**
202
+ * Backwards-compatibility: In v1.0.0 - 1.0.2, .toURL would only return a
203
+ * cdvfile:// URL, and this method was necessary to obtain URLs usable by the
204
+ * webview.
205
+ * See CB-6051, CB-6106, CB-6117, CB-6152, CB-6199, CB-6201, CB-6243, CB-6249,
206
+ * and CB-6300.
207
+ */
208
+Entry.prototype.toNativeURL = function () {
209
+    console.log("DEPRECATED: Update your code to use 'toURL'");
210
+    return this.toURL();
211
+};
212
+
213
+/**
214
+ * Returns a URI that can be used to identify this entry.
215
+ *
216
+ * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
217
+ * @return uri
218
+ */
219
+Entry.prototype.toURI = function (mimeType) {
220
+    console.log("DEPRECATED: Update your code to use 'toURL'");
221
+    return this.toURL();
222
+};
223
+
224
+/**
225
+ * Remove a file or directory. It is an error to attempt to delete a
226
+ * directory that is not empty. It is an error to attempt to delete a
227
+ * root directory of a file system.
228
+ *
229
+ * @param successCallback {Function} called with no parameters
230
+ * @param errorCallback {Function} called with a FileError
231
+ */
232
+Entry.prototype.remove = function (successCallback, errorCallback) {
233
+    argscheck.checkArgs('FF', 'Entry.remove', arguments);
234
+    var fail = errorCallback && function (code) {
235
+        errorCallback(new FileError(code));
236
+    };
237
+    exec(successCallback, fail, 'File', 'remove', [this.toInternalURL()]);
238
+};
239
+
240
+/**
241
+ * Look up the parent DirectoryEntry of this entry.
242
+ *
243
+ * @param successCallback {Function} called with the parent DirectoryEntry object
244
+ * @param errorCallback {Function} called with a FileError
245
+ */
246
+Entry.prototype.getParent = function (successCallback, errorCallback) {
247
+    argscheck.checkArgs('FF', 'Entry.getParent', arguments);
248
+    var fs = this.filesystem;
249
+    var win = successCallback && function (result) {
250
+        var DirectoryEntry = require('./DirectoryEntry');
251
+        var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
252
+        successCallback(entry);
253
+    };
254
+    var fail = errorCallback && function (code) {
255
+        errorCallback(new FileError(code));
256
+    };
257
+    exec(win, fail, 'File', 'getParent', [this.toInternalURL()]);
258
+};
259
+
260
+module.exports = Entry;

+ 78
- 0
plugins/cordova-plugin-file/www/File.js View File

@@ -0,0 +1,78 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+/**
23
+ * Constructor.
24
+ * name {DOMString} name of the file, without path information
25
+ * fullPath {DOMString} the full path of the file, including the name
26
+ * type {DOMString} mime type
27
+ * lastModifiedDate {Date} last modified date
28
+ * size {Number} size of the file in bytes
29
+ */
30
+
31
+var File = function (name, localURL, type, lastModifiedDate, size) {
32
+    this.name = name || '';
33
+    this.localURL = localURL || null;
34
+    this.type = type || null;
35
+    this.lastModified = lastModifiedDate || null;
36
+    // For backwards compatibility, store the timestamp in lastModifiedDate as well
37
+    this.lastModifiedDate = lastModifiedDate || null;
38
+    this.size = size || 0;
39
+
40
+    // These store the absolute start and end for slicing the file.
41
+    this.start = 0;
42
+    this.end = this.size;
43
+};
44
+
45
+/**
46
+ * Returns a "slice" of the file. Since Cordova Files don't contain the actual
47
+ * content, this really returns a File with adjusted start and end.
48
+ * Slices of slices are supported.
49
+ * start {Number} The index at which to start the slice (inclusive).
50
+ * end {Number} The index at which to end the slice (exclusive).
51
+ */
52
+File.prototype.slice = function (start, end) {
53
+    var size = this.end - this.start;
54
+    var newStart = 0;
55
+    var newEnd = size;
56
+    if (arguments.length) {
57
+        if (start < 0) {
58
+            newStart = Math.max(size + start, 0);
59
+        } else {
60
+            newStart = Math.min(size, start);
61
+        }
62
+    }
63
+
64
+    if (arguments.length >= 2) {
65
+        if (end < 0) {
66
+            newEnd = Math.max(size + end, 0);
67
+        } else {
68
+            newEnd = Math.min(end, size);
69
+        }
70
+    }
71
+
72
+    var newFile = new File(this.name, this.localURL, this.type, this.lastModified, this.size);
73
+    newFile.start = this.start + newStart;
74
+    newFile.end = this.start + newEnd;
75
+    return newFile;
76
+};
77
+
78
+module.exports = File;

+ 92
- 0
plugins/cordova-plugin-file/www/FileEntry.js View File

@@ -0,0 +1,92 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+var utils = require('cordova/utils');
23
+var exec = require('cordova/exec');
24
+var Entry = require('./Entry');
25
+var FileWriter = require('./FileWriter');
26
+var File = require('./File');
27
+var FileError = require('./FileError');
28
+
29
+/**
30
+ * An interface representing a file on the file system.
31
+ *
32
+ * {boolean} isFile always true (readonly)
33
+ * {boolean} isDirectory always false (readonly)
34
+ * {DOMString} name of the file, excluding the path leading to it (readonly)
35
+ * {DOMString} fullPath the absolute full path to the file (readonly)
36
+ * {FileSystem} filesystem on which the file resides (readonly)
37
+ */
38
+var FileEntry = function (name, fullPath, fileSystem, nativeURL) {
39
+    // remove trailing slash if it is present
40
+    if (fullPath && /\/$/.test(fullPath)) {
41
+        fullPath = fullPath.substring(0, fullPath.length - 1);
42
+    }
43
+    if (nativeURL && /\/$/.test(nativeURL)) {
44
+        nativeURL = nativeURL.substring(0, nativeURL.length - 1);
45
+    }
46
+
47
+    FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem, nativeURL]);
48
+};
49
+
50
+utils.extend(FileEntry, Entry);
51
+
52
+/**
53
+ * Creates a new FileWriter associated with the file that this FileEntry represents.
54
+ *
55
+ * @param {Function} successCallback is called with the new FileWriter
56
+ * @param {Function} errorCallback is called with a FileError
57
+ */
58
+FileEntry.prototype.createWriter = function (successCallback, errorCallback) {
59
+    this.file(function (filePointer) {
60
+        var writer = new FileWriter(filePointer);
61
+
62
+        if (writer.localURL === null || writer.localURL === '') {
63
+            if (errorCallback) {
64
+                errorCallback(new FileError(FileError.INVALID_STATE_ERR));
65
+            }
66
+        } else {
67
+            if (successCallback) {
68
+                successCallback(writer);
69
+            }
70
+        }
71
+    }, errorCallback);
72
+};
73
+
74
+/**
75
+ * Returns a File that represents the current state of the file that this FileEntry represents.
76
+ *
77
+ * @param {Function} successCallback is called with the new File object
78
+ * @param {Function} errorCallback is called with a FileError
79
+ */
80
+FileEntry.prototype.file = function (successCallback, errorCallback) {
81
+    var localURL = this.toInternalURL();
82
+    var win = successCallback && function (f) {
83
+        var file = new File(f.name, localURL, f.type, f.lastModifiedDate, f.size);
84
+        successCallback(file);
85
+    };
86
+    var fail = errorCallback && function (code) {
87
+        errorCallback(new FileError(code));
88
+    };
89
+    exec(win, fail, 'File', 'getFileMetadata', [localURL]);
90
+};
91
+
92
+module.exports = FileEntry;

+ 46
- 0
plugins/cordova-plugin-file/www/FileError.js View File

@@ -0,0 +1,46 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+/**
23
+ * FileError
24
+ */
25
+function FileError (error) {
26
+    this.code = error || null;
27
+}
28
+
29
+// File error codes
30
+// Found in DOMException
31
+FileError.NOT_FOUND_ERR = 1;
32
+FileError.SECURITY_ERR = 2;
33
+FileError.ABORT_ERR = 3;
34
+
35
+// Added by File API specification
36
+FileError.NOT_READABLE_ERR = 4;
37
+FileError.ENCODING_ERR = 5;
38
+FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
39
+FileError.INVALID_STATE_ERR = 7;
40
+FileError.SYNTAX_ERR = 8;
41
+FileError.INVALID_MODIFICATION_ERR = 9;
42
+FileError.QUOTA_EXCEEDED_ERR = 10;
43
+FileError.TYPE_MISMATCH_ERR = 11;
44
+FileError.PATH_EXISTS_ERR = 12;
45
+
46
+module.exports = FileError;

+ 298
- 0
plugins/cordova-plugin-file/www/FileReader.js View File

@@ -0,0 +1,298 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+var exec = require('cordova/exec');
23
+var modulemapper = require('cordova/modulemapper');
24
+var utils = require('cordova/utils');
25
+var FileError = require('./FileError');
26
+var ProgressEvent = require('./ProgressEvent');
27
+var origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader');
28
+
29
+/**
30
+ * This class reads the mobile device file system.
31
+ *
32
+ * For Android:
33
+ *      The root directory is the root of the file system.
34
+ *      To read from the SD card, the file name is "sdcard/my_file.txt"
35
+ * @constructor
36
+ */
37
+var FileReader = function () {
38
+    this._readyState = 0;
39
+    this._error = null;
40
+    this._result = null;
41
+    this._progress = null;
42
+    this._localURL = '';
43
+    this._realReader = origFileReader ? new origFileReader() : {}; // eslint-disable-line new-cap
44
+};
45
+
46
+/**
47
+ * Defines the maximum size to read at a time via the native API. The default value is a compromise between
48
+ * minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files.
49
+ * (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause
50
+ * OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.)
51
+ */
52
+FileReader.READ_CHUNK_SIZE = 256 * 1024;
53
+
54
+// States
55
+FileReader.EMPTY = 0;
56
+FileReader.LOADING = 1;
57
+FileReader.DONE = 2;
58
+
59
+utils.defineGetter(FileReader.prototype, 'readyState', function () {
60
+    return this._localURL ? this._readyState : this._realReader.readyState;
61
+});
62
+
63
+utils.defineGetter(FileReader.prototype, 'error', function () {
64
+    return this._localURL ? this._error : this._realReader.error;
65
+});
66
+
67
+utils.defineGetter(FileReader.prototype, 'result', function () {
68
+    return this._localURL ? this._result : this._realReader.result;
69
+});
70
+
71
+function defineEvent (eventName) {
72
+    utils.defineGetterSetter(FileReader.prototype, eventName, function () {
73
+        return this._realReader[eventName] || null;
74
+    }, function (value) {
75
+        this._realReader[eventName] = value;
76
+    });
77
+}
78
+defineEvent('onloadstart');    // When the read starts.
79
+defineEvent('onprogress');     // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total)
80
+defineEvent('onload');         // When the read has successfully completed.
81
+defineEvent('onerror');        // When the read has failed (see errors).
82
+defineEvent('onloadend');      // When the request has completed (either in success or failure).
83
+defineEvent('onabort');        // When the read has been aborted. For instance, by invoking the abort() method.
84
+
85
+function initRead (reader, file) {
86
+    // Already loading something
87
+    if (reader.readyState === FileReader.LOADING) {
88
+        throw new FileError(FileError.INVALID_STATE_ERR);
89
+    }
90
+
91
+    reader._result = null;
92
+    reader._error = null;
93
+    reader._progress = 0;
94
+    reader._readyState = FileReader.LOADING;
95
+
96
+    if (typeof file.localURL === 'string') {
97
+        reader._localURL = file.localURL;
98
+    } else {
99
+        reader._localURL = '';
100
+        return true;
101
+    }
102
+
103
+    if (reader.onloadstart) {
104
+        reader.onloadstart(new ProgressEvent('loadstart', {target: reader}));
105
+    }
106
+}
107
+
108
+/**
109
+ * Callback used by the following read* functions to handle incremental or final success.
110
+ * Must be bound to the FileReader's this along with all but the last parameter,
111
+ * e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate)
112
+ * @param readType The name of the read function to call.
113
+ * @param encoding Text encoding, or null if this is not a text type read.
114
+ * @param offset Starting offset of the read.
115
+ * @param totalSize Total number of bytes or chars to read.
116
+ * @param accumulate A function that takes the callback result and accumulates it in this._result.
117
+ * @param r Callback result returned by the last read exec() call, or null to begin reading.
118
+ */
119
+function readSuccessCallback (readType, encoding, offset, totalSize, accumulate, r) {
120
+    if (this._readyState === FileReader.DONE) {
121
+        return;
122
+    }
123
+
124
+    var CHUNK_SIZE = FileReader.READ_CHUNK_SIZE;
125
+    if (readType === 'readAsDataURL') {
126
+        // Windows proxy does not support reading file slices as Data URLs
127
+        // so read the whole file at once.
128
+        CHUNK_SIZE = cordova.platformId === 'windows' ? totalSize : // eslint-disable-line no-undef
129
+            // Calculate new chunk size for data URLs to be multiply of 3
130
+            // Otherwise concatenated base64 chunks won't be valid base64 data
131
+            FileReader.READ_CHUNK_SIZE - (FileReader.READ_CHUNK_SIZE % 3) + 3;
132
+    }
133
+
134
+    if (typeof r !== 'undefined') {
135
+        accumulate(r);
136
+        this._progress = Math.min(this._progress + CHUNK_SIZE, totalSize);
137
+
138
+        if (typeof this.onprogress === 'function') {
139
+            this.onprogress(new ProgressEvent('progress', {loaded: this._progress, total: totalSize}));
140
+        }
141
+    }
142
+
143
+    if (typeof r === 'undefined' || this._progress < totalSize) {
144
+        var execArgs = [
145
+            this._localURL,
146
+            offset + this._progress,
147
+            offset + this._progress + Math.min(totalSize - this._progress, CHUNK_SIZE)];
148
+        if (encoding) {
149
+            execArgs.splice(1, 0, encoding);
150
+        }
151
+        exec(
152
+            readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate),
153
+            readFailureCallback.bind(this),
154
+            'File', readType, execArgs);
155
+    } else {
156
+        this._readyState = FileReader.DONE;
157
+
158
+        if (typeof this.onload === 'function') {
159
+            this.onload(new ProgressEvent('load', {target: this}));
160
+        }
161
+
162
+        if (typeof this.onloadend === 'function') {
163
+            this.onloadend(new ProgressEvent('loadend', {target: this}));
164
+        }
165
+    }
166
+}
167
+
168
+/**
169
+ * Callback used by the following read* functions to handle errors.
170
+ * Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this)
171
+ */
172
+function readFailureCallback (e) {
173
+    if (this._readyState === FileReader.DONE) {
174
+        return;
175
+    }
176
+
177
+    this._readyState = FileReader.DONE;
178
+    this._result = null;
179
+    this._error = new FileError(e);
180
+
181
+    if (typeof this.onerror === 'function') {
182
+        this.onerror(new ProgressEvent('error', {target: this}));
183
+    }
184
+
185
+    if (typeof this.onloadend === 'function') {
186
+        this.onloadend(new ProgressEvent('loadend', {target: this}));
187
+    }
188
+}
189
+
190
+/**
191
+ * Abort reading file.
192
+ */
193
+FileReader.prototype.abort = function () {
194
+    if (origFileReader && !this._localURL) {
195
+        return this._realReader.abort();
196
+    }
197
+    this._result = null;
198
+
199
+    if (this._readyState === FileReader.DONE || this._readyState === FileReader.EMPTY) {
200
+        return;
201
+    }
202
+
203
+    this._readyState = FileReader.DONE;
204
+
205
+    // If abort callback
206
+    if (typeof this.onabort === 'function') {
207
+        this.onabort(new ProgressEvent('abort', {target: this}));
208
+    }
209
+    // If load end callback
210
+    if (typeof this.onloadend === 'function') {
211
+        this.onloadend(new ProgressEvent('loadend', {target: this}));
212
+    }
213
+};
214
+
215
+/**
216
+ * Read text file.
217
+ *
218
+ * @param file          {File} File object containing file properties
219
+ * @param encoding      [Optional] (see http://www.iana.org/assignments/character-sets)
220
+ */
221
+FileReader.prototype.readAsText = function (file, encoding) {
222
+    if (initRead(this, file)) {
223
+        return this._realReader.readAsText(file, encoding);
224
+    }
225
+
226
+    // Default encoding is UTF-8
227
+    var enc = encoding || 'UTF-8';
228
+
229
+    var totalSize = file.end - file.start;
230
+    readSuccessCallback.bind(this)('readAsText', enc, file.start, totalSize, function (r) {
231
+        if (this._progress === 0) {
232
+            this._result = '';
233
+        }
234
+        this._result += r;
235
+    }.bind(this));
236
+};
237
+
238
+/**
239
+ * Read file and return data as a base64 encoded data url.
240
+ * A data url is of the form:
241
+ *      data:[<mediatype>][;base64],<data>
242
+ *
243
+ * @param file          {File} File object containing file properties
244
+ */
245
+FileReader.prototype.readAsDataURL = function (file) {
246
+    if (initRead(this, file)) {
247
+        return this._realReader.readAsDataURL(file);
248
+    }
249
+
250
+    var totalSize = file.end - file.start;
251
+    readSuccessCallback.bind(this)('readAsDataURL', null, file.start, totalSize, function (r) {
252
+        var commaIndex = r.indexOf(',');
253
+        if (this._progress === 0) {
254
+            this._result = r;
255
+        } else {
256
+            this._result += r.substring(commaIndex + 1);
257
+        }
258
+    }.bind(this));
259
+};
260
+
261
+/**
262
+ * Read file and return data as a binary data.
263
+ *
264
+ * @param file          {File} File object containing file properties
265
+ */
266
+FileReader.prototype.readAsBinaryString = function (file) {
267
+    if (initRead(this, file)) {
268
+        return this._realReader.readAsBinaryString(file);
269
+    }
270
+
271
+    var totalSize = file.end - file.start;
272
+    readSuccessCallback.bind(this)('readAsBinaryString', null, file.start, totalSize, function (r) {
273
+        if (this._progress === 0) {
274
+            this._result = '';
275
+        }
276
+        this._result += r;
277
+    }.bind(this));
278
+};
279
+
280
+/**
281
+ * Read file and return data as a binary data.
282
+ *
283
+ * @param file          {File} File object containing file properties
284
+ */
285
+FileReader.prototype.readAsArrayBuffer = function (file) {
286
+    if (initRead(this, file)) {
287
+        return this._realReader.readAsArrayBuffer(file);
288
+    }
289
+
290
+    var totalSize = file.end - file.start;
291
+    readSuccessCallback.bind(this)('readAsArrayBuffer', null, file.start, totalSize, function (r) {
292
+        var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result));
293
+        resultArray.set(new Uint8Array(r), this._progress);
294
+        this._result = resultArray.buffer;
295
+    }.bind(this));
296
+};
297
+
298
+module.exports = FileReader;

+ 55
- 0
plugins/cordova-plugin-file/www/FileSystem.js View File

@@ -0,0 +1,55 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+var DirectoryEntry = require('./DirectoryEntry');
23
+
24
+/**
25
+ * An interface representing a file system
26
+ *
27
+ * @constructor
28
+ * {DOMString} name the unique name of the file system (readonly)
29
+ * {DirectoryEntry} root directory of the file system (readonly)
30
+ */
31
+var FileSystem = function (name, root) {
32
+    this.name = name;
33
+    if (root) {
34
+        this.root = new DirectoryEntry(root.name, root.fullPath, this, root.nativeURL);
35
+    } else {
36
+        this.root = new DirectoryEntry(this.name, '/', this);
37
+    }
38
+};
39
+
40
+FileSystem.prototype.__format__ = function (fullPath, nativeUrl) {
41
+    return fullPath;
42
+};
43
+
44
+FileSystem.prototype.toJSON = function () {
45
+    return '<FileSystem: ' + this.name + '>';
46
+};
47
+
48
+// Use instead of encodeURI() when encoding just the path part of a URI rather than an entire URI.
49
+FileSystem.encodeURIPath = function (path) {
50
+    // Because # is a valid filename character, it must be encoded to prevent part of the
51
+    // path from being parsed as a URI fragment.
52
+    return encodeURI(path).replace(/#/g, '%23');
53
+};
54
+
55
+module.exports = FileSystem;

+ 41
- 0
plugins/cordova-plugin-file/www/FileUploadOptions.js View File

@@ -0,0 +1,41 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+/**
23
+ * Options to customize the HTTP request used to upload files.
24
+ * @constructor
25
+ * @param fileKey {String}   Name of file request parameter.
26
+ * @param fileName {String}  Filename to be used by the server. Defaults to image.jpg.
27
+ * @param mimeType {String}  Mimetype of the uploaded file. Defaults to image/jpeg.
28
+ * @param params {Object}    Object with key: value params to send to the server.
29
+ * @param headers {Object}   Keys are header names, values are header values. Multiple
30
+ *                           headers of the same name are not supported.
31
+ */
32
+var FileUploadOptions = function (fileKey, fileName, mimeType, params, headers, httpMethod) {
33
+    this.fileKey = fileKey || null;
34
+    this.fileName = fileName || null;
35
+    this.mimeType = mimeType || null;
36
+    this.params = params || null;
37
+    this.headers = headers || null;
38
+    this.httpMethod = httpMethod || null;
39
+};
40
+
41
+module.exports = FileUploadOptions;

+ 30
- 0
plugins/cordova-plugin-file/www/FileUploadResult.js View File

@@ -0,0 +1,30 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+/**
23
+ * FileUploadResult
24
+ * @constructor
25
+ */
26
+module.exports = function FileUploadResult (size, code, content) {
27
+    this.bytesSent = size;
28
+    this.responseCode = code;
29
+    this.response = content;
30
+};

+ 325
- 0
plugins/cordova-plugin-file/www/FileWriter.js View File

@@ -0,0 +1,325 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+var exec = require('cordova/exec');
23
+var FileError = require('./FileError');
24
+var FileReader = require('./FileReader');
25
+var ProgressEvent = require('./ProgressEvent');
26
+
27
+/**
28
+ * This class writes to the mobile device file system.
29
+ *
30
+ * For Android:
31
+ *      The root directory is the root of the file system.
32
+ *      To write to the SD card, the file name is "sdcard/my_file.txt"
33
+ *
34
+ * @constructor
35
+ * @param file {File} File object containing file properties
36
+ * @param append if true write to the end of the file, otherwise overwrite the file
37
+ */
38
+var FileWriter = function (file) {
39
+    this.fileName = '';
40
+    this.length = 0;
41
+    if (file) {
42
+        this.localURL = file.localURL || file;
43
+        this.length = file.size || 0;
44
+    }
45
+    // default is to write at the beginning of the file
46
+    this.position = 0;
47
+
48
+    this.readyState = 0; // EMPTY
49
+
50
+    this.result = null;
51
+
52
+    // Error
53
+    this.error = null;
54
+
55
+    // Event handlers
56
+    this.onwritestart = null;   // When writing starts
57
+    this.onprogress = null;     // While writing the file, and reporting partial file data
58
+    this.onwrite = null;        // When the write has successfully completed.
59
+    this.onwriteend = null;     // When the request has completed (either in success or failure).
60
+    this.onabort = null;        // When the write has been aborted. For instance, by invoking the abort() method.
61
+    this.onerror = null;        // When the write has failed (see errors).
62
+};
63
+
64
+// States
65
+FileWriter.INIT = 0;
66
+FileWriter.WRITING = 1;
67
+FileWriter.DONE = 2;
68
+
69
+/**
70
+ * Abort writing file.
71
+ */
72
+FileWriter.prototype.abort = function () {
73
+    // check for invalid state
74
+    if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
75
+        throw new FileError(FileError.INVALID_STATE_ERR);
76
+    }
77
+
78
+    // set error
79
+    this.error = new FileError(FileError.ABORT_ERR);
80
+
81
+    this.readyState = FileWriter.DONE;
82
+
83
+    // If abort callback
84
+    if (typeof this.onabort === 'function') {
85
+        this.onabort(new ProgressEvent('abort', {'target': this}));
86
+    }
87
+
88
+    // If write end callback
89
+    if (typeof this.onwriteend === 'function') {
90
+        this.onwriteend(new ProgressEvent('writeend', {'target': this}));
91
+    }
92
+};
93
+
94
+/**
95
+ * Writes data to the file
96
+ *
97
+ * @param data text or blob to be written
98
+ * @param isPendingBlobReadResult {Boolean} true if the data is the pending blob read operation result
99
+ */
100
+FileWriter.prototype.write = function (data, isPendingBlobReadResult) {
101
+
102
+    var that = this;
103
+    var supportsBinary = (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined');
104
+    /* eslint-disable no-undef */
105
+    var isProxySupportBlobNatively = (cordova.platformId === 'windows8' || cordova.platformId === 'windows');
106
+    var isBinary;
107
+
108
+    // Check to see if the incoming data is a blob
109
+    if (data instanceof File || (!isProxySupportBlobNatively && supportsBinary && data instanceof Blob)) {
110
+        var fileReader = new FileReader();
111
+        /* eslint-enable no-undef */
112
+        fileReader.onload = function () {
113
+            // Call this method again, with the arraybuffer as argument
114
+            FileWriter.prototype.write.call(that, this.result, true /* isPendingBlobReadResult */);
115
+        };
116
+        fileReader.onerror = function () {
117
+            // DONE state
118
+            that.readyState = FileWriter.DONE;
119
+
120
+            // Save error
121
+            that.error = this.error;
122
+
123
+            // If onerror callback
124
+            if (typeof that.onerror === 'function') {
125
+                that.onerror(new ProgressEvent('error', {'target': that}));
126
+            }
127
+
128
+            // If onwriteend callback
129
+            if (typeof that.onwriteend === 'function') {
130
+                that.onwriteend(new ProgressEvent('writeend', {'target': that}));
131
+            }
132
+        };
133
+
134
+        // WRITING state
135
+        this.readyState = FileWriter.WRITING;
136
+
137
+        if (supportsBinary) {
138
+            fileReader.readAsArrayBuffer(data);
139
+        } else {
140
+            fileReader.readAsText(data);
141
+        }
142
+        return;
143
+    }
144
+
145
+    // Mark data type for safer transport over the binary bridge
146
+    isBinary = supportsBinary && (data instanceof ArrayBuffer);
147
+    if (isBinary && cordova.platformId === 'windowsphone') { // eslint-disable-line no-undef
148
+        // create a plain array, using the keys from the Uint8Array view so that we can serialize it
149
+        data = Array.apply(null, new Uint8Array(data));
150
+    }
151
+
152
+    // Throw an exception if we are already writing a file
153
+    if (this.readyState === FileWriter.WRITING && !isPendingBlobReadResult) {
154
+        throw new FileError(FileError.INVALID_STATE_ERR);
155
+    }
156
+
157
+    // WRITING state
158
+    this.readyState = FileWriter.WRITING;
159
+
160
+    var me = this;
161
+
162
+    // If onwritestart callback
163
+    if (typeof me.onwritestart === 'function') {
164
+        me.onwritestart(new ProgressEvent('writestart', {'target': me}));
165
+    }
166
+
167
+    // Write file
168
+    exec(
169
+        // Success callback
170
+        function (r) {
171
+            // If DONE (cancelled), then don't do anything
172
+            if (me.readyState === FileWriter.DONE) {
173
+                return;
174
+            }
175
+
176
+            // position always increases by bytes written because file would be extended
177
+            me.position += r;
178
+            // The length of the file is now where we are done writing.
179
+
180
+            me.length = me.position;
181
+
182
+            // DONE state
183
+            me.readyState = FileWriter.DONE;
184
+
185
+            // If onwrite callback
186
+            if (typeof me.onwrite === 'function') {
187
+                me.onwrite(new ProgressEvent('write', {'target': me}));
188
+            }
189
+
190
+            // If onwriteend callback
191
+            if (typeof me.onwriteend === 'function') {
192
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
193
+            }
194
+        },
195
+        // Error callback
196
+        function (e) {
197
+            // If DONE (cancelled), then don't do anything
198
+            if (me.readyState === FileWriter.DONE) {
199
+                return;
200
+            }
201
+
202
+            // DONE state
203
+            me.readyState = FileWriter.DONE;
204
+
205
+            // Save error
206
+            me.error = new FileError(e);
207
+
208
+            // If onerror callback
209
+            if (typeof me.onerror === 'function') {
210
+                me.onerror(new ProgressEvent('error', {'target': me}));
211
+            }
212
+
213
+            // If onwriteend callback
214
+            if (typeof me.onwriteend === 'function') {
215
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
216
+            }
217
+        }, 'File', 'write', [this.localURL, data, this.position, isBinary]);
218
+};
219
+
220
+/**
221
+ * Moves the file pointer to the location specified.
222
+ *
223
+ * If the offset is a negative number the position of the file
224
+ * pointer is rewound.  If the offset is greater than the file
225
+ * size the position is set to the end of the file.
226
+ *
227
+ * @param offset is the location to move the file pointer to.
228
+ */
229
+FileWriter.prototype.seek = function (offset) {
230
+    // Throw an exception if we are already writing a file
231
+    if (this.readyState === FileWriter.WRITING) {
232
+        throw new FileError(FileError.INVALID_STATE_ERR);
233
+    }
234
+
235
+    if (!offset && offset !== 0) {
236
+        return;
237
+    }
238
+
239
+    // See back from end of file.
240
+    if (offset < 0) {
241
+        this.position = Math.max(offset + this.length, 0);
242
+    // Offset is bigger than file size so set position
243
+    // to the end of the file.
244
+    } else if (offset > this.length) {
245
+        this.position = this.length;
246
+    // Offset is between 0 and file size so set the position
247
+    // to start writing.
248
+    } else {
249
+        this.position = offset;
250
+    }
251
+};
252
+
253
+/**
254
+ * Truncates the file to the size specified.
255
+ *
256
+ * @param size to chop the file at.
257
+ */
258
+FileWriter.prototype.truncate = function (size) {
259
+    // Throw an exception if we are already writing a file
260
+    if (this.readyState === FileWriter.WRITING) {
261
+        throw new FileError(FileError.INVALID_STATE_ERR);
262
+    }
263
+
264
+    // WRITING state
265
+    this.readyState = FileWriter.WRITING;
266
+
267
+    var me = this;
268
+
269
+    // If onwritestart callback
270
+    if (typeof me.onwritestart === 'function') {
271
+        me.onwritestart(new ProgressEvent('writestart', {'target': this}));
272
+    }
273
+
274
+    // Write file
275
+    exec(
276
+        // Success callback
277
+        function (r) {
278
+            // If DONE (cancelled), then don't do anything
279
+            if (me.readyState === FileWriter.DONE) {
280
+                return;
281
+            }
282
+
283
+            // DONE state
284
+            me.readyState = FileWriter.DONE;
285
+
286
+            // Update the length of the file
287
+            me.length = r;
288
+            me.position = Math.min(me.position, r);
289
+
290
+            // If onwrite callback
291
+            if (typeof me.onwrite === 'function') {
292
+                me.onwrite(new ProgressEvent('write', {'target': me}));
293
+            }
294
+
295
+            // If onwriteend callback
296
+            if (typeof me.onwriteend === 'function') {
297
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
298
+            }
299
+        },
300
+        // Error callback
301
+        function (e) {
302
+            // If DONE (cancelled), then don't do anything
303
+            if (me.readyState === FileWriter.DONE) {
304
+                return;
305
+            }
306
+
307
+            // DONE state
308
+            me.readyState = FileWriter.DONE;
309
+
310
+            // Save error
311
+            me.error = new FileError(e);
312
+
313
+            // If onerror callback
314
+            if (typeof me.onerror === 'function') {
315
+                me.onerror(new ProgressEvent('error', {'target': me}));
316
+            }
317
+
318
+            // If onwriteend callback
319
+            if (typeof me.onwriteend === 'function') {
320
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
321
+            }
322
+        }, 'File', 'truncate', [this.localURL, size]);
323
+};
324
+
325
+module.exports = FileWriter;

+ 36
- 0
plugins/cordova-plugin-file/www/Flags.js View File

@@ -0,0 +1,36 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+/**
23
+ * Supplies arguments to methods that lookup or create files and directories.
24
+ *
25
+ * @param create
26
+ *            {boolean} file or directory if it doesn't exist
27
+ * @param exclusive
28
+ *            {boolean} used with create; if true the command will fail if
29
+ *            target path exists
30
+ */
31
+function Flags (create, exclusive) {
32
+    this.create = create || false;
33
+    this.exclusive = exclusive || false;
34
+}
35
+
36
+module.exports = Flags;

+ 23
- 0
plugins/cordova-plugin-file/www/LocalFileSystem.js View File

@@ -0,0 +1,23 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+exports.TEMPORARY = 0;
23
+exports.PERSISTENT = 1;

+ 40
- 0
plugins/cordova-plugin-file/www/Metadata.js View File

@@ -0,0 +1,40 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+/**
23
+ * Information about the state of the file or directory
24
+ *
25
+ * {Date} modificationTime (readonly)
26
+ */
27
+var Metadata = function (metadata) {
28
+    if (typeof metadata === 'object') {
29
+        this.modificationTime = new Date(metadata.modificationTime);
30
+        this.size = metadata.size || 0;
31
+    } else if (typeof metadata === 'undefined') {
32
+        this.modificationTime = null;
33
+        this.size = 0;
34
+    } else {
35
+        /* Backwards compatiblity with platforms that only return a timestamp */
36
+        this.modificationTime = new Date(metadata);
37
+    }
38
+};
39
+
40
+module.exports = Metadata;

+ 67
- 0
plugins/cordova-plugin-file/www/ProgressEvent.js View File

@@ -0,0 +1,67 @@
1
+/*
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements.  See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership.  The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License.  You may obtain a copy of the License at
10
+ *
11
+ *   http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied.  See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ *
20
+*/
21
+
22
+// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill
23
+// Feature test: See if we can instantiate a native ProgressEvent;
24
+// if so, use that approach,
25
+// otherwise fill-in with our own implementation.
26
+//
27
+// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview.
28
+var ProgressEvent = (function () {
29
+    /*
30
+    var createEvent = function(data) {
31
+        var event = document.createEvent('Events');
32
+        event.initEvent('ProgressEvent', false, false);
33
+        if (data) {
34
+            for (var i in data) {
35
+                if (data.hasOwnProperty(i)) {
36
+                    event[i] = data[i];
37
+                }
38
+            }
39
+            if (data.target) {
40
+                // TODO: cannot call <some_custom_object>.dispatchEvent
41
+                // need to first figure out how to implement EventTarget
42
+            }
43
+        }
44
+        return event;
45
+    };
46
+    try {
47
+        var ev = createEvent({type:"abort",target:document});
48
+        return function ProgressEvent(type, data) {
49
+            data.type = type;
50
+            return createEvent(data);
51
+        };
52
+    } catch(e){
53
+    */
54
+    return function ProgressEvent (type, dict) {
55
+        this.type = type;
56
+        this.bubbles = false;
57
+        this.cancelBubble = false;
58
+        this.cancelable = false;
59
+        this.lengthComputable = false;
60
+        this.loaded = dict && dict.loaded ? dict.loaded : 0;
61
+        this.total = dict && dict.total ? dict.total : 0;
62
+        this.target = dict && dict.target ? dict.target : null;
63
+    };
64
+    // }
65
+})();
66
+
67
+module.exports = ProgressEvent;

+ 0
- 0
plugins/cordova-plugin-file/www/android/FileSystem.js View File


Some files were not shown because too many files changed in this diff