I don't think we should try to make cross-platform-compromise decisions on this one (e.g. mapping desktop to /mnt/sdcard). requestLocalFileSystem() gives a x-platform "data" and "temp" directory. What I want to expose here are the platform-specific directories (requires users to know their platform).
This puts properties on "cordova.file.fooDirectory". Paths that don't exist on a platform are set to null. Proposed patch (missing docs, but I'll add before committing): >From 1a5d8e3306e9b31aa5a4dec136451b92264ee01a Mon Sep 17 00:00:00 2001 From: Andrew Grieve <agri...@chromium.org> Date: Tue, 13 May 2014 21:46:13 -0400 Subject: [PATCH] CB-285 Add cordova.file.*Directory properties for iOS & Android --- plugin.xml | 9 +++++++ src/android/FileUtils.java | 20 +++++++++++++++ src/ios/CDVFile.m | 30 +++++++++++++++++++++++ www/fileSystemPaths.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 www/fileSystemPaths.js diff --git a/plugin.xml b/plugin.xml index 3bced58..2154ed7 100644 --- a/plugin.xml +++ b/plugin.xml @@ -135,6 +135,10 @@ xmlns:android=" http://schemas.android.com/apk/res/android" <js-module src="www/fileSystems-roots.js" name="fileSystems-roots"> <runs/> </js-module> + <js-module src="www/fileSystemPaths.js" name="fileSystemPaths"> + <merges target="cordova" /> + <runs/> + </js-module> </platform> <!-- amazon-fireos --> @@ -208,6 +212,11 @@ xmlns:android=" http://schemas.android.com/apk/res/android" <runs/> </js-module> + <js-module src="www/fileSystemPaths.js" name="fileSystemPaths"> + <merges target="cordova" /> + <runs/> + </js-module> + <framework src="AssetsLibrary.framework" /> <framework src="MobileCoreServices.framework" /> </platform> diff --git a/src/android/FileUtils.java b/src/android/FileUtils.java index 5fbe1f6..9233a62 100644 --- a/src/android/FileUtils.java +++ b/src/android/FileUtils.java @@ -344,6 +344,8 @@ public class FileUtils extends CordovaPlugin { callbackContext.success(requestAllFileSystems()); } }, callbackContext); + } else if (action.equals("requestAllPaths")) { + callbackContext.success(requestAllPaths()); } else if (action.equals("requestFileSystem")) { final int fstype=args.getInt(0); final long size = args.optLong(1); @@ -850,6 +852,24 @@ public class FileUtils extends CordovaPlugin { return ret; } + private static String toDirUrl(File f) { + return Uri.fromFile(f).toString() + '/'; + } + + private JSONObject requestAllPaths() throws JSONException { + Context context = cordova.getActivity(); + JSONObject ret = new JSONObject(); + ret.put("applicationDirectory", "file:///android_asset/"); + ret.put("applicationStorageDirectory", toDirUrl(context.getFilesDir().getParentFile())); + ret.put("dataDirectory", toDirUrl(context.getFilesDir())); + ret.put("cacheDirectory", toDirUrl(context.getCacheDir())); + ret.put("externalApplicationStorageDirectory", toDirUrl(context.getExternalFilesDir(null).getParentFile())); + ret.put("externalDataDirectory", toDirUrl(context.getExternalFilesDir(null))); + ret.put("externalCacheDirectory", toDirUrl(context.getExternalCacheDir())); + ret.put("externalRootDirectory", toDirUrl(Environment.getExternalStorageDirectory())); + return ret; + } + /** * Returns a JSON object representing the given File. Internal APIs should be modified * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin. diff --git a/src/ios/CDVFile.m b/src/ios/CDVFile.m index 11b8dd3..b22b7cf 100644 --- a/src/ios/CDVFile.m +++ b/src/ios/CDVFile.m @@ -458,6 +458,36 @@ NSString* const kCDVFilesystemURLPrefix = @"cdvfile"; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } +- (void)requestAllPaths:(CDVInvokedUrlCommand*)command +{ + NSString* libPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0]; + NSString* libPathSync = [libPath stringByAppendingPathComponent:@ "Cloud"]; + NSString* libPathNoSync = [libPath stringByAppendingPathComponent:@ "NoCloud"]; + NSString* docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + NSString* storagePath = [libPath stringByDeletingLastPathComponent]; + NSString* cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; + + // Create the directories if necessary. + [[NSFileManager defaultManager] createDirectoryAtPath:libPathSync withIntermediateDirectories:YES attributes:nil error:nil]; + [[NSFileManager defaultManager] createDirectoryAtPath:libPathNoSync withIntermediateDirectories:YES attributes:nil error:nil]; + // Mark NoSync as non-iCloud. + [[NSURL fileURLWithPath:libPathNoSync] setResourceValue: [NSNumber numberWithBool: YES] + forKey: NSURLIsExcludedFromBackupKey error:nil]; + + NSDictionary* ret = @{ + @"applicationDirectory": [[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]] absoluteString], + @"applicationStorageDirectory": [[NSURL fileURLWithPath:storagePath] absoluteString], + @"dataDirectory": [[NSURL fileURLWithPath:libPathNoSync] absoluteString], + @"syncedDataDirectory": [[NSURL fileURLWithPath:libPathSync] absoluteString], + @"documentsDirectory": [[NSURL fileURLWithPath:docPath] absoluteString], + @"cacheDirectory": [[NSURL fileURLWithPath:cachePath] absoluteString], + @"tempDirectory": [[NSURL fileURLWithPath:NSTemporaryDirectory()] absoluteString] + }; + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:ret]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + /* Creates and returns a dictionary representing an Entry Object * * IN: diff --git a/www/fileSystemPaths.js b/www/fileSystemPaths.js new file mode 100644 index 0000000..8ef0bb8 --- /dev/null +++ b/www/fileSystemPaths.js @@ -0,0 +1,61 @@ +/* + * + * 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 channel = require('cordova/channel'); + +exports.file = { + // Read-only directory where the application is installed. + applicationDirectory: null, + // Root of app's private writable storage + applicationStorageDirectory: null, + // Where to put app-specific data files. + dataDirectory: null, + // Cached files that should survive app restarts. + // Apps should not rely on the OS to delete files in here. + cacheDirectory: null, + // Android: the application space on external storage. + externalApplicationStorageDirectory: null, + // Android: Where to put app-specific data files on external storage. + externalDataDirectory: null, + // Android: the application cache on external storage. + externalCacheDirectory: null, + // Android: the external storage (SD card) root. + externalRootDirectory: null, + // iOS: Temp directory that the OS can clear at will. + tempDirectory: null, + // iOS: Holds app-specific files that should be synced (e.g. to iCloud). + syncedDataDirectory: null, + // iOS: Files private to the app, but that are meaningful to other applciations (e.g. Office files) + documentsDirectory: null +}; + +channel.waitForInitialization('onFileSystemPathsReady'); +channel.onCordovaReady.subscribe(function() { + function after(paths) { + for (var k in paths) { + exports.file[k] = paths[k]; + } + channel.initializationComplete('onFileSystemPathsReady'); + } + exec(after, null, 'File', 'requestAllPaths', []); +}); + -- 1.8.3.4 (Apple Git-47) On Tue, May 13, 2014 at 6:32 PM, Jesse <purplecabb...@gmail.com> wrote: > I think it is acceptable to have the desktopDirectory, and userDirectory > point to the same dir as documentsDirectory on devices that do not support > un-sandboxed file locations. > > On Android, this would mean desktop, user, documents all equal : > /mnt/sdcard > > And on iOS : > /var/mobile/Applications/uid/Documents > > Having these there is more just for when we incorporate more desktop > environments, ie Win32 and/or Mac. > I am fine with removing them, however, we have to be mindful that there are > potentially many more file locations. For example, WinRT ( Windows8.1 + > WP8.1 ) support the downloads folder, as well as 'known' folders [1] [2] > > > [1] > > http://msdn.microsoft.com/en-ca/library/windows/apps/windows.storage.knownfolders.aspx > [2] > > http://msdn.microsoft.com/en-ca/library/windows/apps/windows.storage.downloadsfolder.aspx > > > > @purplecabbage > risingj.com > > > On Tue, May 13, 2014 at 1:57 PM, Andrew Grieve <agri...@chromium.org> > wrote: > > > Thanks Jesse, > > > > Less async is definitely nicer. > > > > Providing URLs vs DirectoryEntry I don't think is a huge difference > either, > > so fine with that (although it means you need to do an async call on the > > URL to get a DirectoryEntry in order to create a file). Often though, you > > just use the URLs with Camera FileTransfer, so there are some cases where > > it's nice. > > > > Don't think desktopDirectory or userDirectory make sense since apps are > > sandboxed these days. > > > > Will take a stab at a commit and report back via pull request. > > > > > > > > On Tue, May 13, 2014 at 4:37 PM, Jesse <purplecabb...@gmail.com> wrote: > > > > > Okay, here goes. > > > > > > Re: > > > > > > > > > cordova.plugins.file.getDirectoryForPurpose(purpose, options, win, > fail) > > > Where purpose can be one of: > > > 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) > > > } > > > // And the aliases > > > cordova.plugins.file.getDataDirectory(syncable, win) > > > cordova.plugins.file.getDocumentsDirectory(win) > > > cordova.plugins.file.getTempDirectory(win) > > > cordova.plugins.file.getCacheDirectory(win) > > > > > > > > > Ultimately these will never change while the app running, why not just > > have > > > them be properties that are populated on startup? > > > Also, given that they won't change, the async alias calls would not > > > required. > > > > > > My suggestion is based in part on the Adobe Air File class which solves > > > many of the same problems[1] > > > > > > I would rather see an API that looked like : > > > > > > // a storage directory unique to each installed application > > > File.applicationStorageDirectory; > > > // the read-only directory where the application is installed (along > with > > > any installed assets) > > > File.applicationDirectory; > > > // the user's desktop directory, not available on all devices > > > File.desktopDirectory; > > > // the user's documents directory, not available on all devices > > > File.documentsDirectory; > > > // Cached files that should survive app restarts > > > File.cacheDirectory; > > > > > > // temp will only live for the lifetime of the app, so if you want a > ref, > > > you will have to keep it. > > > // note also that you can create several temp directories if you want. > > > var tempDir; > > > File.createTempDirectory(function onSuccess(dirResult){ > > > tempDir = dirResult; > > > },function onError(errResult){ > > > tempDir = null; > > > console.log("Error creating temp directory :" + > > > JSON.stringify(errResult)); > > > }); > > > > > > As an altenative, a root temp dir could be created when the app > launches, > > > making this async call unnecessary, and then these could be referenced > > just > > > like the other dirs > > > That would make this just : > > > > > > // location unknown, and volatile > > > File.tempDirectory; > > > > > > > > > [1] > > > > > > > > > http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/filesystem/File.html > > > > > > > > > > > > @purplecabbage > > > risingj.com > > > > > > > > > On Tue, May 13, 2014 at 9:02 AM, purplecabbage < > purplecabb...@gmail.com > > > >wrote: > > > > > > > Yeah almost. Still brewing a little. > > > > Give me a couple hours. > > > > > > > > > > > > > > > > > On May 12, 2014, at 10:05 AM, Andrew Grieve <agri...@chromium.org> > > > > wrote: > > > > > > > > > > Now that email works again - Jesse, were you thinking of proposing > a > > > > tweaks > > > > > API, or something different altogether. > > > > > New related bug here: > > > > > > > > > > https://issues.apache.org/jira/browse/CB-6670 > > > > > > > > > > > > > > >> On Tue, May 6, 2014 at 6:21 PM, Brian LeRoux <b...@brian.io> wrote: > > > > >> > > > > >> That is a very good point! I say it is good enough for now. > > Something > > > to > > > > >> flag for our W3C friends to look at and consider in the spec. > > > > >> > > > > >> > > > > >> On Tue, May 6, 2014 at 1:43 PM, Andrew Grieve < > agri...@chromium.org > > > > > > > >> wrote: > > > > >> > > > > >>> There are two types of config for file: > > > > >>> 1. You can do is disable parts of the filesystem (doubt anyone > > would > > > do > > > > >>> this) > > > > >>> 2. You can switch where PERSISTENT filesystem maps to (sane place > > vs > > > > >> legacy > > > > >>> place) > > > > >>> > > > > >>> What's missing is a way to retrieve the paths that you might > want. > > No > > > > >>> configuration required for this part. > > > > >>> > > > > >>> I'd like to avoid making the calls look like they are a part of > the > > > > file > > > > >>> spec, so that users won't be tempted to think that it would work > > in a > > > > >>> non-Cordova environment. > > > > >>> > > > > >>> > > > > >>>> On Tue, May 6, 2014 at 1:47 PM, Brian LeRoux <b...@brian.io> > wrote: > > > > >>>> > > > > >>>> This plugin is helpful though I can't help but wonder if we > can't > > > > >>> shoehorn > > > > >>>> into specs (or at least provide spec feedback). > > > > >>>> > > > > >>>> Right now all config is done w/ config.xml instead of > programmatic > > > (?) > > > > >>>> > > > > >>>> > > > > >>>> On Tue, May 6, 2014 at 7:06 AM, Andrew Grieve < > > agri...@chromium.org > > > > > > > > >>>> wrote: > > > > >>>> > > > > >>>>> Closer than ever to resolving this (woo!) > > > > >>>>> > > > > >>>>> The file plugin is now able to read & write to roots on the > > > > >> filesystem > > > > >>>>> beyond PERSISTENT and TEMPORARY on iOS, Android, and BlackBerry > > > (and > > > > >>>> maybe > > > > >>>>> others?) > > > > >>>>> > > > > >>>>> However, you still can't query for the location of these places > > > > >> (doh!) > > > > >>>>> > > > > >>>>> There's a file-extras plugin in cordova-labs: > > > > >> > > > > > > > > > > https://git-wip-us.apache.org/repos/asf?p=cordova-labs.git;a=blob;f=file-extras/fileextras.js;h=1f8f88f7222bd4022f2f802f6825c189b10445d9;hb=aaf61d4 > > > > >>>>> > > > > >>>>> That was used to experiment with an API for this. I think the > API > > > is > > > > >>>> pretty > > > > >>>>> much fine, and I'd like to add it to the core file plugin > rather > > > than > > > > >>>> have > > > > >>>>> it as a separate plugin. > > > > >>>>> > > > > >>>>> This would add: > > > > >>>>> cordova.plugins.file.getDirectoryForPurpose(purpose, options, > > win, > > > > >>> fail) > > > > >>>>> > > > > >>>>> Where purpose can be one of: > > > > >>>>> 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) > > > > >>>>> } > > > > >>>>> > > > > >>>>> And also add convenience wrappers: > > > > >>>>> cordova.plugins.file.getDataDirectory(syncable, win) > > > > >>>>> cordova.plugins.file.getDocumentsDirectory(win) > > > > >>>>> cordova.plugins.file.getTempDirectory(win) > > > > >>>>> cordova.plugins.file.getCacheDirectory(win) > > > > >>>>> > > > > >>>>> > > > > >>>>> Any comments on this? > > > > >> > > > > > > > > > >