cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From i..@apache.org
Subject git commit: Rewrite file-extras plugin as file-system-roots
Date Thu, 20 Feb 2014 16:07:51 GMT
Repository: cordova-plugins
Updated Branches:
  refs/heads/master b24a99e3b -> 31597ca5a


Rewrite file-extras plugin as file-system-roots

This plugin has been updated to work with the new version of File. It
now comes with additional documentation describing the available
filesystem roots, how to use them in applications, and how to configure
them.

The old file-extras plugin still remains, but should be considered
deprecated.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugins/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugins/commit/31597ca5
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugins/tree/31597ca5
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugins/diff/31597ca5

Branch: refs/heads/master
Commit: 31597ca5a7ae5606597d4fe7f589a7fb8c566dc8
Parents: b24a99e
Author: Ian Clelland <iclelland@chromium.org>
Authored: Wed Feb 12 10:35:00 2014 -0500
Committer: Ian Clelland <iclelland@chromium.org>
Committed: Thu Feb 20 11:07:21 2014 -0500

----------------------------------------------------------------------
 file-system-roots/README.md                    |  65 +++++++++
 file-system-roots/android/FileSystemRoots.java | 148 +++++++++++++++++++
 file-system-roots/filesystemroots.js           |  89 ++++++++++++
 file-system-roots/ios/FileSystemRoots.m        | 152 ++++++++++++++++++++
 file-system-roots/plugin.xml                   |  55 +++++++
 5 files changed, 509 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/31597ca5/file-system-roots/README.md
----------------------------------------------------------------------
diff --git a/file-system-roots/README.md b/file-system-roots/README.md
new file mode 100644
index 0000000..68ed7f9
--- /dev/null
+++ b/file-system-roots/README.md
@@ -0,0 +1,65 @@
+org.apache.cordova.file-system-roots plugin
+===========================================
+
+This plugin grants a Cordova application access to several additional filesystem roots, and
provides a cross-platform means of accessing these filesystems based on their intended purpose.
+
+The set of available filesystems depends on the platform.
+
+Android
+-------
+
+    - files: The application's internal file storage directory
+    - files-external: The application's external file storage directory
+    - sdcard: The global external file storage directory (this is the root of the SD card,
if one is installed)
+    - cache: The application's internal cache directory
+    - cache-external: The application's external cache directory
+    - root: The entire device filesystem
+
+Android also supports a special filesystem named "documents", which represents a "/Documents/"
subdirectory within the "files" filesystem.
+
+iOS
+---
+
+    - library: The application's Library directory
+    - documents: The application's Documents directory
+    - cache: The application's Cache directory
+    - app-bundle: The application's bundle; the location of the app itself on disk
+    - root: The entire device filesystem
+
+By default, the library and documents directories can be synced to iCloud. You can also request
two additional filesystems, "library-nosync" and "documents-nosync", which represent a special
non-synced directory within the Library or Documents filesystem.
+
+Configuring the plugin
+----------------------
+
+The set of available filesystems can be configured at build time per-platform. Both iOS and
Android recognize a <preference> tag in `config.xml` which names the filesystems to
be installed. The defaults for these preferences, if not set, are
+
+    <preference name="iosExtraFilesystems" value="library,library-nosync,documents,documents-nosync,cache,bundle"
/>
+
+    <preference name="AndroidExtraFilesystems" value="files,files-external,documents,sdcard,cache,cache-external"
/>
+
+These defaults contain all available filesystems except for "root".
+
+Accessing the filesystems
+-------------------------
+
+The simplest method of using these new filesystems is to call `cordova.filesystem.getFilesystem`
with the name of the filesystem you want to use.
+
+    cordova.filesystem.getFilesystemRoot(filesystemName, successCallback, errorCallback);
+
+If successful, `successCallback` will be called with a `DirectoryEntry` object representing
the root of the filesystem. Otherwise, `errorCallback` will be called with a `FileError`.
+
+It is also possible to request a `DirectoryEntry` object for a particular purpose. This provides
a cross-platform way of accessing the various filesystem locations.
+
+    cordova.filesystem.getDirectoryForPurpose(purpose, options, successCallback, failureCallback)
+
+will call successCallback with a `DirectoryEntry` object suitable for the specified purpose.
+
+The following string constants are defined for the `purpose` field:
+
+    data
+    documents
+    cache
+    temp        (returns the TEMPORARY filesystem)
+    app-bundle  (iOS only)
+
+The `options` field is an object. On iOS, it is possible to set it to `{syncable: false}`
to obtain the non-synced versions of `data` or `documents`. On Android, you can set `{sandboxed:
false}` to get the external (not confined to your app) versions of `data`, `documents` or
`cache`.

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/31597ca5/file-system-roots/android/FileSystemRoots.java
----------------------------------------------------------------------
diff --git a/file-system-roots/android/FileSystemRoots.java b/file-system-roots/android/FileSystemRoots.java
new file mode 100644
index 0000000..a75c14a
--- /dev/null
+++ b/file-system-roots/android/FileSystemRoots.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+package org.apache.cordova.filesystemroots;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Environment;
+import android.util.Log;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaArgs;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.file.FileUtils;
+import org.apache.cordova.file.LocalFilesystem;
+import org.json.JSONException;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class FileSystemRoots extends CordovaPlugin {
+    private static final int PURPOSE_DATA = 0;
+    private static final int PURPOSE_DOCUMENTS = 1;
+    private static final int PURPOSE_CACHE = 2;
+    private static final int PURPOSE_TEMP = 3;
+    private static final String TAG = "file-system-roots";
+
+    private HashSet<String> installedFilesystems;
+    private HashMap<String, String> availableFilesystems;
+
+    @Override
+    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+        super.initialize(cordova, webView);
+
+        Activity activity = cordova.getActivity();
+        Context context = activity.getApplicationContext();
+
+        availableFilesystems = new HashMap<String,String>();
+        availableFilesystems.put("files", context.getFilesDir().getAbsolutePath());
+        availableFilesystems.put("files-external", context.getExternalFilesDir(null).getAbsolutePath());
+        availableFilesystems.put("documents", new File(context.getFilesDir(), "Documents").getAbsolutePath());
+        availableFilesystems.put("sdcard", Environment.getExternalStorageDirectory().getAbsolutePath());
+        availableFilesystems.put("cache", context.getCacheDir().getAbsolutePath());
+        availableFilesystems.put("cache-external", context.getExternalCacheDir().getAbsolutePath());
+        availableFilesystems.put("root", "/");
+
+        installedFilesystems = new HashSet<String>();
+
+        String filesystemsStr = activity.getIntent().getStringExtra("androidextrafilesystems");
+        if (filesystemsStr == null) {
+            filesystemsStr = "files,files-external,documents,sdcard,cache,cache-external";
+        }
+
+        String[] filesystems = filesystemsStr.split(",");
+
+        FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File");
+        if (filePlugin != null) {
+            /* Register filesystems in order */
+            for (String fsName : filesystems) {
+                if (!installedFilesystems.contains(fsName)) {
+                    String fsRoot = availableFilesystems.get(fsName);
+                    if (fsRoot != null) {
+                        File newRoot = new File(fsRoot);
+                        if (newRoot.mkdirs() || newRoot.isDirectory()) {
+                            filePlugin.registerFilesystem(new LocalFilesystem(fsName, cordova,
fsRoot));
+                            installedFilesystems.add(fsName);
+                        } else {
+                           Log.d(TAG, "Unable to create root dir for fileystem \"" + fsName
+ "\", skipping");
+                        }
+                    } else {
+                        Log.d(TAG, "Unrecognized extra filesystem identifier: " + fsName);
+                    }
+                }
+            }
+        } else {
+            Log.w(TAG, "File plugin not found; cannot initialize file-system-roots plugin");
+        }
+
+    }
+
+    @Override
+    public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext)
throws JSONException {
+        if ("getDirectoryForPurpose".equals(action)) {
+            getDirectoryForPurpose(args, callbackContext);
+            return true;
+        }
+
+        return false;
+    }
+
+    private void getDirectoryForPurpose(final CordovaArgs args, final CallbackContext callbackContext)
throws JSONException {
+        int purpose = args.getInt(0);
+        boolean sandboxed = args.getBoolean(1);
+
+        String path = null;
+        switch (purpose) {
+            case PURPOSE_DATA:
+                if (sandboxed && installedFilesystems.contains("files")) {
+                    path = "cdvfile://localhost/files/";
+                } else if (installedFilesystems.contains("files-external")) {
+                    path = "cdvfile://localhost/files-external/";
+                }
+                break;
+            case PURPOSE_DOCUMENTS:
+                if (sandboxed && installedFilesystems.contains("documents")) {
+                    path = "cdvfile://localhost/documents/";
+                } else if (installedFilesystems.contains("scdard")) {
+                    path = "cdvfile://localhost/sdcard/";
+                }
+                break;
+            case PURPOSE_TEMP:
+                path = "cdvfile://localhost/temporary/";
+            case PURPOSE_CACHE:
+                if (sandboxed && installedFilesystems.contains("cache")) {
+                    path = "cdvfile://localhost/cache/";
+                } else if (installedFilesystems.contains("cache-external")) {
+                    path = "cdvfile://localhost/cache-external";
+                }
+                break;
+        }
+
+        if (path == null) {
+            callbackContext.error("No path found.");
+            return;
+        }
+
+        callbackContext.success(path);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/31597ca5/file-system-roots/filesystemroots.js
----------------------------------------------------------------------
diff --git a/file-system-roots/filesystemroots.js b/file-system-roots/filesystemroots.js
new file mode 100644
index 0000000..fa51afb
--- /dev/null
+++ b/file-system-roots/filesystemroots.js
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var argscheck = require('cordova/argscheck');
+
+
+// Open Discussion:
+// * Should we have a purpose to expose the assets?
+//   * Would it be any different from resolveLocalFileSystemURL('/')
+// * Should we instead expose URLs instead of DirectoryEntry objects?
+//   * e.g. On iOS, app-data://icloud=yes/, app-documents://icloud=no/, app-temp://, app-bundle://
+//   * e.g. On Android, could use same schemes for 3.0+, or use content://cordova-app/app-data://...
for 2.3
+//   * This would mean APIs could be synchronous, and platform-specific locations can be
kept on native side.
+//   * This would allow things to be used as URLs for images.
+//   * APIs (such as FileTransfer) work better with URLs (Paths are annoying, esp with Windows
using \)
+//   * Entry have a toURL() already. Without custom schemes, it won't work for Android resources
& assets
+// * Add support resolveLocalFileSystemURL()?
+
+
+var Purpose = {
+    'data': 0, // General application data (default)
+    'documents': 1, // Files that are meaningful to other applciations (e.g. Office files)
+    'cache': 2, // Temporary files that should survive app restarts
+    'temp': 3, // Files that can should be deleted on app restarts
+    'app-bundle': 4 // The application bundle (iOS only)
+};
+
+/**
+ * Supplies a DirectoryEntry that matches the given constraints to the given callback.
+ */
+exports.getDirectoryForPurpose = function(purpose, options, successCallback, failureCallback)
{
+    argscheck.checkArgs('sOfF', 'cordova.filesystem.getDirectoryForPurpose', arguments);
+    var augmentedSuccessCallback = successCallback && function(fullPath) {
+        resolveLocalFileSystemURL(fullPath, successCallback, failureCallback);
+    };
+
+    var purposeInt = Purpose[purpose];
+    if (typeof purposeInt == 'undefined') {
+        throw new Error('getDirectoryForPurpose: invalid purpose: ' + purpose);
+    }
+    options = options || {};
+
+    var sandboxed = typeof options.sandboxed == 'undefined' ? true : !!options.sandboxed;
+    var syncable = typeof options.syncable == 'undefined' ? true : !!options.syncable;
+
+    var args = [purposeInt, sandboxed, syncable];
+    exec(augmentedSuccessCallback, failureCallback, "FileSystemRoots", "getDirectoryForPurpose",
args);
+};
+
+exports.getDataDirectory = function(syncable, successCallback) {
+    argscheck.checkArgs('*f', 'cordova.filesystem.getDataDirectory', arguments);
+    exports.getDirectoryForPurpose('data', { syncable: syncable }, successCallback);
+};
+
+// On Android, this is the root of the SD card.
+exports.getDocumentsDirectory = function(successCallback) {
+    exports.getDirectoryForPurpose('documents', { syncable: true, sandboxed: false }, successCallback);
+};
+
+exports.getTempDirectory = function(successCallback) {
+    exports.getDirectoryForPurpose('temp', null, successCallback);
+};
+
+exports.getCacheDirectory = function(successCallback) {
+    exports.getDirectoryForPurpose('cache', null, successCallback);
+};
+
+exports.getFileSystemRoot = function(fileSystemName, successCallback, failureCallback) {
+    argscheck.checkArgs('sfF', 'cordova.filesystem.getFileSystemRoot', arguments);
+    resolveLocalFileSystemURL('cdvfile://localhost/'+fileSystemName+'/', successCallback,
failureCallback);
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/31597ca5/file-system-roots/ios/FileSystemRoots.m
----------------------------------------------------------------------
diff --git a/file-system-roots/ios/FileSystemRoots.m b/file-system-roots/ios/FileSystemRoots.m
new file mode 100644
index 0000000..b9b4f22
--- /dev/null
+++ b/file-system-roots/ios/FileSystemRoots.m
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+#import <Cordova/CDVPlugin.h>
+#import "CDVFile.h"
+#import "CDVLocalFilesystem.h"
+
+enum FileSystemPurpose {
+    DATA = 0,
+    DOCUMENTS = 1,
+    CACHE = 2,
+    TEMP = 3,
+    IOS_BUNDLE = 4,
+};
+typedef int FileSystemPurpose;
+
+@interface FileSystemRoots : CDVPlugin {
+    NSDictionary *availableFilesystems;
+    NSMutableSet *installedFilesystems;
+}
+@end
+
+@implementation FileSystemRoots
+
+- (void)pluginInitialize
+{
+    NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask,
YES) objectAtIndex:0];
+    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,
YES) objectAtIndex:0];
+    availableFilesystems = @{
+        @"library": libPath,
+        @"library-nosync": [libPath stringByAppendingPathComponent:@"NoCloud"],
+        @"documents": docPath,
+        @"documents-nosync": [docPath stringByAppendingPathComponent:@"NoCloud"],
+        @"cache": [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask,
YES) objectAtIndex:0],
+        @"bundle": [[NSBundle mainBundle] bundlePath],
+        @"root": @"/"
+    };
+    installedFilesystems = [[NSMutableSet alloc] initWithCapacity:7];
+
+    /* Get filesystems to be installed from settings */
+    id vc = self.viewController;
+    
+    NSDictionary *settings = [vc settings];
+    NSString *filesystemsStr = [settings[@"iosextrafilesystems"] lowercaseString];
+    if (!filesystemsStr) {
+        filesystemsStr = @"library,library-nosync,documents,documents-nosync,cache,bundle";
+    }
+    NSArray *filesystems = [filesystemsStr componentsSeparatedByString:@","];
+    
+    /* Build non-syncable directories as necessary */
+    for (NSString *nonSyncFS in @[@"library-nosync", @"documents-nosync"]) {
+        if ([filesystems containsObject:nonSyncFS]) {
+            [self makeNonSyncable:availableFilesystems[nonSyncFS]];
+        }
+    }
+    
+    CDVFile *filePlugin = [[vc commandDelegate] getCommandInstance:@"File"];
+    if (filePlugin) {
+        /* Register filesystems in order */
+        for (NSString *fsName in filesystems) {
+            if (![installedFilesystems containsObject:fsName]) {
+                NSString *fsRoot = availableFilesystems[fsName];
+                if (fsRoot) {
+                    [filePlugin registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:fsName
root:fsRoot]];
+                    [installedFilesystems addObject:fsName];
+                } else {
+                    NSLog(@"Unrecognized extra filesystem identifier: %@", fsName);
+                }
+            }
+        }
+    } else {
+        NSLog(@"File plugin not found; cannot initialize file-system-roots plugin");
+    }
+}
+
+- (void)makeNonSyncable:(NSString*)path {
+    [[NSFileManager defaultManager] createDirectoryAtPath:path
+              withIntermediateDirectories:YES
+                               attributes:nil
+                                    error:nil];
+    NSURL* url = [NSURL fileURLWithPath:path];
+    [url setResourceValue: [NSNumber numberWithBool: YES]
+                   forKey: NSURLIsExcludedFromBackupKey error:nil];
+
+}
+
+- (void)getDirectoryForPurpose:(CDVInvokedUrlCommand *)command {
+    FileSystemPurpose purpose = [[command argumentAtIndex:0] intValue];
+    // BOOL sandboxed = [[command argumentAtIndex:1] boolValue];
+    BOOL syncable = [[command argumentAtIndex:2] boolValue];
+
+    NSString *path = nil;
+
+    switch (purpose) {
+      case DATA:
+        if (syncable && [installedFilesystems containsObject:@"library"]) {
+            path = @"cdvfile://localhost/library/";
+        } else if ([installedFilesystems containsObject:@"library-nosync"]) {
+            path = @"cdvfile://localhost/library-nosync/";
+        }
+        break;
+      case DOCUMENTS:
+        if (syncable && [installedFilesystems containsObject:@"documents"]) {
+            path = @"cdvfile://localhost/documents/";
+        } else if ([installedFilesystems containsObject:@"documents-nosync"]) {
+            path = @"cdvfile://localhost/documents-nosync/";
+        }
+        break;
+      case CACHE:
+        if ([installedFilesystems containsObject:@"cache"]) {
+            path = @"cdvfile://localhost/cache/";
+        }
+        break;
+      case TEMP:
+        path = @"cdvfile://localhost/temporary/";
+        break;
+      case IOS_BUNDLE:
+        if ([installedFilesystems containsObject:@"bundle"]) {
+            path = @"cdvfile://localhost/bundle/";
+        }
+        break;
+    }
+
+    CDVPluginResult *pluginResult = nil;
+
+    if (!path) {
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
+    } else {
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:path];
+    }
+
+    [[self commandDelegate] sendPluginResult:pluginResult callbackId:[command callbackId]];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/31597ca5/file-system-roots/plugin.xml
----------------------------------------------------------------------
diff --git a/file-system-roots/plugin.xml b/file-system-roots/plugin.xml
new file mode 100644
index 0000000..9058892
--- /dev/null
+++ b/file-system-roots/plugin.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<plugin xmlns="http://cordova.apache.org/ns/plugins/1.0"
+    id="org.apache.cordova.file-system-roots"
+    version="0.2.0-dev">
+  <engines>
+    <engine name="cordova" version=">=3.0.0" />
+  </engines>
+
+  <name>File Extras</name>
+  <dependency id="org.apache.cordova.file" version=">=1.0.0" />
+
+  <js-module src="FileSystemRoots.js" name="FileSystemRoots">
+    <clobbers target="cordova.filesystem" />
+  </js-module>
+
+  <platform name="android">
+    <source-file src="android/FileSystemRoots.java" target-dir="src/org/apache/cordova/filesystemroots"
/>
+
+    <config-file target="res/xml/config.xml" parent="/*">
+      <feature name="FileSystemRoots">
+        <param name="android-package" value="org.apache.cordova.filesystemroots.FileSystemRoots"/>
+        <param name="onload" value="true"/>
+      </feature>
+    </config-file>
+  </platform>
+
+  <platform name="ios">
+    <source-file src="ios/FileSystemRoots.m" />
+
+    <config-file target="config.xml" parent="/*">
+      <feature name="FileSystemRoots">
+        <param name="ios-package" value="FileSystemRoots"/>
+        <param name="onload" value="true"/>
+      </feature>
+    </config-file>
+  </platform>
+</plugin>


Mime
View raw message