diff options
| author | PliablePixels <pliablepixels@gmail.com> | 2015-06-24 18:47:42 -0400 |
|---|---|---|
| committer | PliablePixels <pliablepixels@gmail.com> | 2015-06-24 18:47:42 -0400 |
| commit | 855a0e8ddc273b58066530a1b55a946021dfc56e (patch) | |
| tree | 26550033e855a31a265fc2da4da3df0cc2733dc1 /plugins/org.apache.cordova.file | |
| parent | d442629aa825aab6bc55ab6be19e3aba060867fe (diff) | |
Cleaned up code, commented, preparing for HTTPS via CordovaHTTP
Diffstat (limited to 'plugins/org.apache.cordova.file')
79 files changed, 18093 insertions, 0 deletions
diff --git a/plugins/org.apache.cordova.file/LICENSE b/plugins/org.apache.cordova.file/LICENSE new file mode 100644 index 00000000..7a4a3ea2 --- /dev/null +++ b/plugins/org.apache.cordova.file/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.
\ No newline at end of file diff --git a/plugins/org.apache.cordova.file/README.md b/plugins/org.apache.cordova.file/README.md new file mode 100644 index 00000000..260c816f --- /dev/null +++ b/plugins/org.apache.cordova.file/README.md @@ -0,0 +1,5 @@ +cordova-plugin-file +-------------------------- +To install this plugin, follow the [Command-line Interface Guide](http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-line%20Interface). + +If you are not using the Cordova Command-line Interface, follow [Using Plugman to Manage Plugins](http://cordova.apache.org/docs/en/edge/plugin_ref_plugman.md.html). diff --git a/plugins/org.apache.cordova.file/RELEASENOTES.md b/plugins/org.apache.cordova.file/RELEASENOTES.md new file mode 100644 index 00000000..2fd1c95d --- /dev/null +++ b/plugins/org.apache.cordova.file/RELEASENOTES.md @@ -0,0 +1,51 @@ +<!-- +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--> +# Release Notes + +### 0.2.1 (Sept 5, 2013) +* [CB-4656] Don't add newlines in data urls within readAsDataUrl. +* [CB-4514] Making DirectoryCopy Recursive +* [iOS] Simplify the code in resolveLocalFileSystemURI + +### 0.2.3 (Sept 25, 2013) +* CB-4889 bumping&resetting version +* [CB-4903] File Plugin not loading Windows8 +* [CB-4903] File Plugin not loading Windows8 +* CB-4889 renaming references +* CB-4889 renaming org.apache.cordova.core.file to org.apache.cordova.file +* Rename CHANGELOG.md -> RELEASENOTES.md +* [CB-4771] Expose TEMPORARY and PERSISTENT constants on window. +* Fix compiler/lint warnings +* [CB-4764] Move DirectoryManager.java into file plugin +* [CB-4763] Copy FileHelper.java into the plugin. +* [CB-2901] [BlackBerry10] Automatically unsandbox filesystem if path is not in app sandbox +* [CB-4752] Incremented plugin version on dev branch. + +### 0.2.4 (Oct 9, 2013) +* CB-5020 - File plugin should execute on a separate thread +* [CB-4915] Incremented plugin version on dev branch. +* CB-4504: Updating FileUtils.java to compensate for Java porting failures in the Android SDK. This fails because Java knows nothing about android_asset not being an actual filesystem + + ### 0.2.5 (Oct 28, 2013) +* CB-5129: Add a consistent filesystem attribute to FileEntry and DirectoryEntry objects +* CB-5128: added repo + issue tag to plugin.xml for file plugin +* CB-5015 [BlackBerry10] Add missing dependency for File.slice +* [CB-5010] Incremented plugin version on dev branch. diff --git a/plugins/org.apache.cordova.file/docs/directoryentry/directoryentry.md b/plugins/org.apache.cordova.file/docs/directoryentry/directoryentry.md new file mode 100644 index 00000000..e21aa5f5 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/directoryentry/directoryentry.md @@ -0,0 +1,390 @@ +--- +license: 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. +--- + +DirectoryEntry +============== + +This object represents a directory on a file system, as defined by the +[W3C Directories and Systems](http://www.w3.org/TR/file-system-api/) +specification. + +Properties +---------- + +- __isFile__: Always false. _(boolean)_ +- __isDirectory__: Always true. _(boolean)_ +- __name__: The name of the `DirectoryEntry`, excluding the path leading to it. _(DOMString)_ +- __fullPath__: The full absolute path from the root to the `DirectoryEntry`. _(DOMString)_ + +__NOTE:__ The following attribute is defined by the W3C specification, +but is _not_ supported: + +- __filesystem__: The file system on which the `DirectoryEntry` resides. _(FileSystem)_ + +Methods +------- + +The following methods can be invoked on a `DirectoryEntry` object: + +- __getMetadata__: Look up metadata about a directory. +- __setMetadata__: Set metadata on a directory. +- __moveTo__: Move a directory to a different location on the file system. +- __copyTo__: Copy a directory to a different location on the file system. +- __toURL__: Return a URL to help locate a directory. +- __remove__: Delete a directory. The directory must be empty. +- __getParent__: Look up the parent directory. +- __createReader__: Create a new `DirectoryReader` that can read entries from a directory. +- __getDirectory__: Create or look up a directory. +- __getFile__: Create or look up a file. +- __removeRecursively__: Delete a directory and all of its contents. + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +getMetadata +----------- + +Look up metadata about a directory. + +__Parameters:__ + +- __successCallback__: A callback function to execute with a `Metadata` object. _(Function)_ +- __errorCallback__: A callback function to execute if an error occurs when retrieving the `Metadata`. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(metadata) { + console.log("Last Modified: " + metadata.modificationTime); + } + + function fail(error) { + alert(error.code); + } + + // Request the metadata object for this entry + entry.getMetadata(success, fail); + +setMetadata +---------------- + +Set metadata on a directory. +__Currently works only on iOS.__ - this will set the extended attributes of a directory. + +__Parameters:__ + +- __successCallback__: A callback that executes when the metadata is successfully set. _(Function)_ +- __errorCallback__: A callback that executes when the metadata fails to be set. _(Function)_ +- __metadataObject__: An object that contains the metadata's keys and values. _(Object)_ + +__Quick Example__ + + function success() { + console.log("The metadata was successfully set."); + } + + function fail() { + alert("There was an error in setting the metadata"); + } + + // Set the metadata + entry.setMetadata(success, fail, { "com.apple.MobileBackup": 1}); + +__iOS Quirk__ + +- Only the `com.apple.MobileBackup` extended attribute is supported. Set the value to `1` to prevent the directory from being backed up to iCloud. Set the value to `0` to re-enable the directory to be backed up to iCloud. + +__Quick Example__ + + function setFolderMetadata(localFileSystem, subFolder, metadataKey, metadataValue) + { + var onSetMetadataWin = function() { + console.log("success setting metadata") + } + var onSetMetadataFail = function() { + console.log("error setting metadata") + } + + var onGetDirectoryWin = function(parent) { + var data = {}; + data[metadataKey] = metadataValue; + parent.setMetadata(onSetMetadataWin, onSetMetadataFail, data); + } + var onGetDirectoryFail = function() { + console.log("error getting dir") + } + + var onFSWin = function(fileSystem) { + fileSystem.root.getDirectory(subFolder, {create: true, exclusive: false}, onGetDirectoryWin, onGetDirectoryFail); + } + + var onFSFail = function(evt) { + console.log(evt.target.error.code); + } + + window.requestFileSystem(localFileSystem, 0, onFSWin, onFSFail); + } + + setFolderMetadata(LocalFileSystem.PERSISTENT, "Backups", "com.apple.MobileBackup", 1); + +moveTo +------ + +Move a directory to a different location on the file system. An error results if the app attempts to: + +- move a directory inside itself or to any child at any depth. +- move a directory into its parent if a name different from its current directory is not provided. +- move a directory to a path occupied by a file. +- move a directory to a path occupied by a directory that is not empty. + +Moving a directory on top of an existing empty directory attempts to +delete and replace that directory. + +__Parameters:__ + +- __parent__: The parent directory to which to move the directory. _(DirectoryEntry)_ +- __newName__: The new name of the directory. Defaults to the current name if unspecified. _(DOMString)_ +- __successCallback__: A callback that executes with the `DirectoryEntry` object for the new directory. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to move the directory. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(entry) { + console.log("New Path: " + entry.fullPath); + } + + function fail(error) { + alert(error.code); + } + + function moveDir(entry) { + var parent = document.getElementById('parent').value, + parentName = parent.substring(parent.lastIndexOf('/')+1), + newName = document.getElementById('newName').value, + parentEntry = new DirectoryEntry(parentName, parent); + + // move the directory to a new directory and rename it + entry.moveTo(parentEntry, newName, success, fail); + } + +copyTo +------ + +Copy a directory to a different location on the file system. An error results if the app attempts to: + +- copy a directory inside itself at any depth. +- copy a directory into its parent if a name different from its current directory is not provided. + +Directory copies are always recursive, and copy all contents of the directory. + +__Parameters:__ + +- __parent__: The parent directory to which to copy the directory. _(DirectoryEntry)_ +- __newName__: The new name of the directory. Defaults to the current name if unspecified. _(DOMString)_ +- __successCallback__: A callback that executes with the `DirectoryEntry` object for the new directory. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to copy the underlying directory. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function win(entry) { + console.log("New Path: " + entry.fullPath); + } + + function fail(error) { + alert(error.code); + } + + function copyDir(entry) { + var parent = document.getElementById('parent').value, + parentName = parent.substring(parent.lastIndexOf('/')+1), + newName = document.getElementById('newName').value, + parentEntry = new DirectoryEntry(parentName, parent); + + // copy the directory to a new directory and rename it + entry.copyTo(parentEntry, newName, success, fail); + } + +toURL +----- + +Returns a URL that can be used to locate the directory. + +__Quick Example__ + + // Get the URL for this directory + var dirURL = entry.toURL(); + console.log(dirURL); + +remove +------ + +Deletes a directory. An error results if the app attempts to: + +- delete a directory that is not empty. +- delete the root directory of a filesystem. + +__Parameters:__ + +- __successCallback__: A callback that executes after the directory is deleted. Invoked with no parameters. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to delete the directory. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(entry) { + console.log("Removal succeeded"); + } + + function fail(error) { + alert('Error removing directory: ' + error.code); + } + + // remove this directory + entry.remove(success, fail); + +getParent +--------- + +Look up the parent `DirectoryEntry` containing the directory. + +__Parameters:__ + +- __successCallback__: A callback that is passed the directory's parent `DirectoryEntry`. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to retrieve the parent `DirectoryEntry`. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(parent) { + console.log("Parent Name: " + parent.name); + } + + function fail(error) { + alert('Failed to get parent directory: ' + error.code); + } + + // Get the parent DirectoryEntry + entry.getParent(success, fail); + +createReader +------------ + +Creates a new DirectoryReader to read entries in a directory. + +__Quick Example__ + + // create a directory reader + var directoryReader = entry.createReader(); + +getDirectory +------------ + +Creates or looks up an existing directory. An error results if the app attempts to: + +- create a directory whose immediate parent does not yet exist. + +__Parameters:__ + +- __path__: The path to the directory to be looked up or created. Either an absolute path, or a relative path from this `DirectoryEntry`. _(DOMString)_ +- __options__: Options to specify whether the directory is to be created if it doesn't exist. _(Flags)_ +- __successCallback__: A callback that executes with a `DirectoryEntry` object. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when creating or looking up the directory. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(parent) { + console.log("Parent Name: " + parent.name); + } + + function fail(error) { + alert("Unable to create new directory: " + error.code); + } + + // Retrieve an existing directory, or create it if it does not already exist + entry.getDirectory("newDir", {create: true, exclusive: false}, success, fail); + +getFile +------- + +Creates or looks up a file. An error results if the app attempts to: + +- create a file whose immediate parent does not yet exist. + +__Parameters:__ + +- __path__: The path to the file to be looked up or created. Either an absolute path, or a relative path from this `DirectoryEntry`. _(DOMString)_ +- __options__: Options to specify whether the file is created if it doesn't exist. _(Flags)_ +- __successCallback__: A callback that is passed a `FileEntry` object. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when creating or looking up the file. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(parent) { + console.log("Parent Name: " + parent.name); + } + + function fail(error) { + alert("Failed to retrieve file: " + error.code); + } + + // Retrieve an existing file, or create it if it does not exist + entry.getFile("newFile.txt", {create: true, exclusive: false}, success, fail); + +removeRecursively +----------------- + +Deletes a directory and all of its contents. In the event of an error (such as trying to delete +a directory containing a file that cannot be removed), some of the contents of the directory may +be deleted. An error results if the app attempts to: + +- delete the root directory of a filesystem. + +__Parameters:__ + +- __successCallback__: A callback that executes after the `DirectoryEntry` has been deleted. Invoked with no parameters. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to delete the `DirectoryEntry`. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(parent) { + console.log("Remove Recursively Succeeded"); + } + + function fail(error) { + alert("Failed to remove directory or it's contents: " + error.code); + } + + // remove the directory and all it's contents + entry.removeRecursively(success, fail); + +BlackBerry Quirks +----------------- + +May fail with a `ControlledAccessException` in the following cases: + +- An app attempts to access a directory created by a previous installation of the app. + +> Solution: ensure temporary directories are cleaned manually, or by the application prior to reinstallation. + +- If the device is connected by USB. + +> Solution: disconnect the USB cable from the device and run again. diff --git a/plugins/org.apache.cordova.file/docs/directoryreader/directoryreader.md b/plugins/org.apache.cordova.file/docs/directoryreader/directoryreader.md new file mode 100644 index 00000000..b37edb90 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/directoryreader/directoryreader.md @@ -0,0 +1,69 @@ +--- +license: 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. +--- + +DirectoryReader +=============== + +An object that lists files and directories within a directory, as +defined in the +[W3C Directories and Systems](http://www.w3.org/TR/file-system-api/) +specification. + +Methods +------- + +- __readEntries__: Read the entries in a directory. + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +readEntries +----------- + +Read the entries in this directory. + +__Parameters:__ + +- __successCallback__: A callback that is passed an array of `FileEntry` and `DirectoryEntry` objects. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when retrieving the directory listing. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(entries) { + var i; + for (i=0; i<entries.length; i++) { + console.log(entries[i].name); + } + } + + function fail(error) { + alert("Failed to list directory contents: " + error.code); + } + + // Get a directory reader + var directoryReader = dirEntry.createReader(); + + // Get a list of all the entries in the directory + directoryReader.readEntries(success,fail); diff --git a/plugins/org.apache.cordova.file/docs/file.md b/plugins/org.apache.cordova.file/docs/file.md new file mode 100644 index 00000000..eed64c0c --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/file.md @@ -0,0 +1,91 @@ +--- +license: 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. +--- + +File +========== + +> An API to read, write and navigate file system hierarchies, based on the [W3C File API](http://www.w3.org/TR/FileAPI). + +Objects +------- + +- DirectoryEntry +- DirectoryReader +- File +- FileEntry +- FileError +- FileReader +- FileSystem +- FileTransfer +- FileTransferError +- FileUploadOptions +- FileUploadResult +- FileWriter +- Flags +- LocalFileSystem +- Metadata + +Permissions +----------- + +### Android + +#### app/res/xml/config.xml + + <plugin name="File" value="org.apache.cordova.FileUtils" /> + <plugin name="FileTransfer" value="org.apache.cordova.FileTransfer" /> + +#### app/AndroidManifest.xml + + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + +### Bada + + No permissions are required. + +### BlackBerry WebWorks + +#### www/plugins.xml + + <plugin name="File" value="org.apache.cordova.file.FileManager" /> + <plugin name="FileTransfer" value="org.apache.cordova.http.FileTransfer" /> + +#### www/config.xml + + <feature id="blackberry.io.file" required="true" version="1.0.0.0" /> + <feature id="blackberry.utils" required="true" version="1.0.0.0" /> + <feature id="blackberry.io.dir" required="true" version="1.0.0.0" /> + <rim:permissions> + <rim:permit>access_shared</rim:permit> + </rim:permissions> + +### iOS + +#### config.xml + + <plugin name="File" value="CDVFile" /> + <plugin name="FileTransfer" value="CDVFileTransfer" /> + +### webOS + + No permissions are required. + +### Windows Phone + + No permissions are required. diff --git a/plugins/org.apache.cordova.file/docs/fileentry/fileentry.md b/plugins/org.apache.cordova.file/docs/fileentry/fileentry.md new file mode 100644 index 00000000..f362833c --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/fileentry/fileentry.md @@ -0,0 +1,323 @@ +--- +license: 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. +--- + +FileEntry +========== + +Represents a file on a file system, as defined in the +[W3C Directories and Systems](http://www.w3.org/TR/file-system-api/) +specification. + +Properties +---------- + +- __isFile__: Always true. _(boolean)_ +- __isDirectory__: Always false. _(boolean)_ +- __name__: The name of the `FileEntry`, excluding the path leading to it. _(DOMString)_ +- __fullPath__: The full absolute path from the root to the `FileEntry`. _(DOMString)_ + +__NOTE:__ The following attribute is defined by the W3C specification, +but is _not_ supported: + +- __filesystem__: The file system on which the `FileEntry` resides. _(FileSystem)_ + +Methods +------- + +- __getMetadata__: Look up metadata about a file. +- __setMetadata__: Set metadata on a file. +- __moveTo__: Move a file to a different location on the file system. +- __copyTo__: Copy a file to a different location on the file system. +- __toURL__: Return a URL that can be used to locate a file. +- __remove__: Delete a file. +- __getParent__: Look up the parent directory. +- __createWriter__: Creates a `FileWriter` object that can be used to write to a file. +- __file__: Creates a `File` object containing file properties. + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +getMetadata +---------------- + +Look up metadata about a file. + +__Parameters:__ + +- __successCallback__: A callback that is passed a `Metadata` object. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when retrieving the `Metadata`. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(metadata) { + console.log("Last Modified: " + metadata.modificationTime); + } + + function fail(error) { + alert(error.code); + } + + // Request the metadata object for this entry + entry.getMetadata(success, fail); + +setMetadata +---------------- + +Set metadata on a file. + +__Currently works only on iOS.__ +- this will set the extended attributes of a file. + +__Parameters:__ + +- __successCallback__: A callback that executes when the metadata is set. _(Function)_ +- __errorCallback__: A callback that executes when the metadata is not successfully set. _(Function)_ +- __metadataObject__: An object that contains the metadata's keys and values. _(Object)_ + +__Quick Example__ + + function success() { + console.log("The metadata was successfully set."); + } + + function fail() { + alert("There was an error in setting the metadata"); + } + + // Set the metadata + entry.setMetadata(success, fail, { "com.apple.MobileBackup": 1}); + +__iOS Quirk__ + +- Only the `com.apple.MobileBackup` extended attribute is supported. Set the value to `1` to prevent the file from being backed up to iCloud. Set the value to `0` to re-enable the file to be backed up to iCloud. + +__Quick Example__ + + function setFileMetadata(localFileSystem, filePath, metadataKey, metadataValue) + { + var onSetMetadataWin = function() { + console.log("success setting metadata") + } + var onSetMetadataFail = function() { + console.log("error setting metadata") + } + + var onGetFileWin = function(parent) { + var data = {}; + data[metadataKey] = metadataValue; + parent.setMetadata(onSetMetadataWin, onSetMetadataFail, data); + } + var onGetFileFail = function() { + console.log("error getting file") + } + + var onFSWin = function(fileSystem) { + fileSystem.root.getFile(filePath, {create: true, exclusive: false}, onGetFileWin, onGetFileFail); + } + + var onFSFail = function(evt) { + console.log(evt.target.error.code); + } + + window.requestFileSystem(localFileSystem, 0, onFSWin, onFSFail); + } + + setFileMetadata(LocalFileSystem.PERSISTENT, "Backups/sqlite.db", "com.apple.MobileBackup", 1); + +moveTo +------ + +Move a file to a different location on the file system. An error +results if the app attempts to: + +- move a file into its parent if a name different from its current one isn't provided; +- move a file to a path occupied by a directory; + +In addition, moving a file on top of an existing file attempts to +delete and replace that file. + +__Parameters:__ + +- __parent__: The parent directory to which to move the file. _(DirectoryEntry)_ +- __newName__: The new name of the file. Defaults to the current name if unspecified. _(DOMString)_ +- __successCallback__: A callback that is passed the new files `FileEntry` object. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to move the file. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(entry) { + console.log("New Path: " + entry.fullPath); + } + + function fail(error) { + alert(error.code); + } + + function moveFile(entry) { + var parent = document.getElementById('parent').value, + parentName = parent.substring(parent.lastIndexOf('/')+1), + parentEntry = new DirectoryEntry(parentName, parent); + + // move the file to a new directory and rename it + entry.moveTo(parentEntry, "newFile.txt", success, fail); + } + +copyTo +------ + +Copy a file to a new location on the file system. An error results if +the app attempts to: + +- copy a file into its parent if a name different from its current one is not provided. + +__Parameters:__ + +- __parent__: The parent directory to which to copy the file. _(DirectoryEntry)_ +- __newName__: The new name of the file. Defaults to the current name if unspecified. _(DOMString)_ +- __successCallback__: A callback that is passed the new file's `FileEntry` object. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to copy the file. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function win(entry) { + console.log("New Path: " + entry.fullPath); + } + + function fail(error) { + alert(error.code); + } + + function copyFile(entry) { + var parent = document.getElementById('parent').value, + parentName = parent.substring(parent.lastIndexOf('/')+1), + parentEntry = new DirectoryEntry(parentName, parent); + + // copy the file to a new directory and rename it + entry.copyTo(parentEntry, "file.copy", success, fail); + } + +toURL +----- + +Returns a URL that can be used to locate the file. + +__Quick Example__ + + // Request the URL for this entry + var fileURL = entry.toURL(); + console.log(fileURL); + +remove +------ + +Deletes a file. + +__Parameters:__ + +- __successCallback__: A callback that executes after the file has been deleted. Invoked with no parameters. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to delete the file. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(entry) { + console.log("Removal succeeded"); + } + + function fail(error) { + alert('Error removing file: ' + error.code); + } + + // remove the file + entry.remove(success, fail); + +getParent +--------- + +Look up the parent `DirectoryEntry` containing the file. + +__Parameters:__ + +- __successCallback__: A callback that is passed the file's parent `DirectoryEntry`. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when attempting to retrieve the parent `DirectoryEntry`. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(parent) { + console.log("Parent Name: " + parent.name); + } + + function fail(error) { + alert(error.code); + } + + // Get the parent DirectoryEntry + entry.getParent(success, fail); + +createWriter +------------ + +Create a `FileWriter` object associated with the file represented by the `FileEntry`. + +__Parameters:__ + +- __successCallback__: A callback that is passed a `FileWriter` object. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs while attempting to create the FileWriter. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(writer) { + writer.write("Some text to the file"); + } + + function fail(error) { + alert(error.code); + } + + // create a FileWriter to write to the file + entry.createWriter(success, fail); + +file +---- + +Return a `File` object that represents the current state of the file +that this `FileEntry` represents. + +__Parameters:__ + +- __successCallback__: A callback that is passed a `File` object. _(Function)_ +- __errorCallback__: A callback that executes if an error occurs when creating the `File` object, such as when the file no longer exists. Invoked with a `FileError` object. _(Function)_ + +__Quick Example__ + + function success(file) { + console.log("File size: " + file.size); + } + + function fail(error) { + alert("Unable to retrieve file properties: " + error.code); + } + + // obtain properties of a file + entry.file(success, fail); diff --git a/plugins/org.apache.cordova.file/docs/fileerror/fileerror.md b/plugins/org.apache.cordova.file/docs/fileerror/fileerror.md new file mode 100644 index 00000000..1d7f39f7 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/fileerror/fileerror.md @@ -0,0 +1,51 @@ +--- +license: 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. +--- + +FileError +======== + +A `FileError` object is set when an error occurs in any of the File API methods. + +Properties +---------- + +- __code__: One of the predefined error codes listed below. + +Constants +--------- + +- `FileError.NOT_FOUND_ERR` +- `FileError.SECURITY_ERR` +- `FileError.ABORT_ERR` +- `FileError.NOT_READABLE_ERR` +- `FileError.ENCODING_ERR` +- `FileError.NO_MODIFICATION_ALLOWED_ERR` +- `FileError.INVALID_STATE_ERR` +- `FileError.SYNTAX_ERR` +- `FileError.INVALID_MODIFICATION_ERR` +- `FileError.QUOTA_EXCEEDED_ERR` +- `FileError.TYPE_MISMATCH_ERR` +- `FileError.PATH_EXISTS_ERR` + +Description +----------- + +The `FileError` object is the only parameter provided to any of the +File API's error callbacks. To determine the type of error, compare +its `code` property to any of the listings above. diff --git a/plugins/org.apache.cordova.file/docs/fileobj/fileobj.md b/plugins/org.apache.cordova.file/docs/fileobj/fileobj.md new file mode 100644 index 00000000..ee5f1d4b --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/fileobj/fileobj.md @@ -0,0 +1,83 @@ +--- +license: 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. +--- + +File +==== + +This object contains attributes of a single file. + +Properties +---------- + +- __name__: The name of the file. _(DOMString)_ +- __fullPath__: The full path of the file including the file name. _(DOMString)_ +- __type__: The mime type of the file. _(DOMString)_ +- __lastModifiedDate__: The last time the file was modified. _(Date)_ +- __size__: The size of the file in bytes. _(long)_ + +Methods +------- + +- __slice__: Select only a portion of the file to be read. + +Details +------- + +The `File` object contains attributes of a single file. You can get +an instance of a `File` object by calling a `FileEntry` object's +`file()` method. + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +slice +-------------- + +Return a new `File` object, for which `FileReader` returns only the +specified portion of the file. Negative values for `start` or `end` +are measured from the end of the file. Indexes are positioned +relative to the current slice. (See the full example below.) + +__Parameters:__ + +- __start__: The index of the first byte to read, inclusive. +- __end__: The index of the byte after the last one to read. + +__Quick Example__ + + var slicedFile = file.slice(10, 30); + +__Full Example__ + + var slice1 = file.slice(100, 400); + var slice2 = slice1.slice(20, 35); + + var slice3 = file.slice(120, 135); + // slice2 and slice3 are equivalent. + +__Supported Platforms:__ + +- Android +- iOS diff --git a/plugins/org.apache.cordova.file/docs/filereader/filereader.md b/plugins/org.apache.cordova.file/docs/filereader/filereader.md new file mode 100644 index 00000000..25f6ff06 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/filereader/filereader.md @@ -0,0 +1,259 @@ +--- +license: 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. +--- + +FileReader +========== + +The `FileReader` allows basic access to a file. + +Properties +---------- + +- __readyState__: One of the reader's three possible states, either `EMPTY`, `LOADING` or `DONE`. +- __result__: The contents of the file that have been read. _(DOMString)_ +- __error__: An object containing errors. _(FileError)_ +- __onloadstart__: Called when the read starts. _(Function)_ +- __onload__: Called when the read has successfully completed. _(Function)_ +- __onabort__: Called when the read has been aborted. For instance, by invoking the `abort()` method. _(Function)_ +- __onerror__: Called when the read has failed. _(Function)_ +- __onloadend__: Called when the request has completed (either in success or failure). _(Function)_ + +__NOTE:__ The following porperty is not supported: + +- __onprogress__: Called while reading the file, reporting progress in terms of `progress.loaded`/`progress.total`. _(Function)_ + +Methods +------- + +- __abort__: Aborts reading file. +- __readAsDataURL__: Read file and return data as a base64-encoded data URL. +- __readAsText__: Reads text file. +- __readAsBinaryString__: Reads file as binary and returns a binary string. +- __readAsArrayBuffer__: Reads file as an `ArrayBuffer`. + +Details +------- + +The `FileReader` object offers a way to read files from the device's +file system. Files can be read as text or as a base64 data-encoded +string. Event listeners receive the `loadstart`, `progress`, `load`, +`loadend`, `error`, and `abort` events. + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +Read As Data URL +---------------- + +__Parameters:__ + +- __file__: the file object to read. + +Quick Example +------------- + + function win(file) { + var reader = new FileReader(); + reader.onloadend = function (evt) { + console.log("read success"); + console.log(evt.target.result); + }; + reader.readAsDataURL(file); + }; + + var fail = function (evt) { + console.log(error.code); + }; + + entry.file(win, fail); + +Read As Text +------------ + +__Parameters:__ + +- __file__: the file object to read. +- __encoding__: the encoding to use to encode the file's content. Default is UTF8. + +Quick Example +------------- + + function win(file) { + var reader = new FileReader(); + reader.onloadend = function (evt) { + console.log("read success"); + console.log(evt.target.result); + }; + reader.readAsText(file); + }; + + var fail = function (evt) { + console.log(error.code); + }; + + entry.file(win, fail); + +Abort Quick Example +------------------- + + function win(file) { + var reader = new FileReader(); + reader.onloadend = function(evt) { + console.log("read success"); + console.log(evt.target.result); + }; + reader.readAsText(file); + reader.abort(); + }; + + function fail(error) { + console.log(error.code); + } + + entry.file(win, fail); + +Full Example +------------ + + <!DOCTYPE html> + <html> + <head> + <title>FileReader Example</title> + + <script type="text/javascript" charset="utf-8" src="cordova-x.x.x.js"></script> + <script type="text/javascript" charset="utf-8"> + + // Wait for device API libraries to load + // + function onLoad() { + document.addEventListener("deviceready", onDeviceReady, false); + } + + // device APIs are available + // + function onDeviceReady() { + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, gotFS, fail); + } + + function gotFS(fileSystem) { + fileSystem.root.getFile("readme.txt", null, gotFileEntry, fail); + } + + function gotFileEntry(fileEntry) { + fileEntry.file(gotFile, fail); + } + + function gotFile(file){ + readDataUrl(file); + readAsText(file); + } + + function readDataUrl(file) { + var reader = new FileReader(); + reader.onloadend = function(evt) { + console.log("Read as data URL"); + console.log(evt.target.result); + }; + reader.readAsDataURL(file); + } + + function readAsText(file) { + var reader = new FileReader(); + reader.onloadend = function(evt) { + console.log("Read as text"); + console.log(evt.target.result); + }; + reader.readAsText(file); + } + + function fail(evt) { + console.log(evt.target.error.code); + } + + </script> + </head> + <body> + <h1>Example</h1> + <p>Read File</p> + </body> + </html> + +iOS Quirks +---------- +- The __encoding__ parameter is not supported, and UTF8 encoding is always in effect. + +Read As Binary String +--------------------- + +Currently supported on iOS and Android only. + +__Parameters:__ + +- __file__: the file object to read. + +Quick Example +------------- + + function win(file) { + var reader = new FileReader(); + reader.onloadend = function (evt) { + console.log("read success"); + console.log(evt.target.result); + }; + reader.readAsBinaryString(file); + }; + + var fail = function (evt) { + console.log(error.code); + }; + + entry.file(win, fail); + +Read As Array Buffer +-------------------- + +Currently supported on iOS and Android only. + +__Parameters:__ + +- __file__: the file object to read. + +Quick Example +------------- + + function win(file) { + var reader = new FileReader(); + reader.onloadend = function (evt) { + console.log("read success"); + console.log(new Uint8Array(evt.target.result)); + }; + reader.readAsArrayBuffer(file); + }; + + var fail = function (evt) { + console.log(error.code); + }; + + entry.file(win, fail); diff --git a/plugins/org.apache.cordova.file/docs/filesystem/filesystem.md b/plugins/org.apache.cordova.file/docs/filesystem/filesystem.md new file mode 100644 index 00000000..c8eaa2c4 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/filesystem/filesystem.md @@ -0,0 +1,95 @@ +--- +license: 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. +--- + +FileSystem +========== + +This object represents a file system. + +Properties +---------- + +- __name__: The name of the file system. _(DOMString)_ +- __root__: The root directory of the file system. _(DirectoryEntry)_ + +Details +------- + +The `FileSystem` object represents information about the file system. +The name of the file system is unique across the list of exposed +file systems. The root property contains a `DirectoryEntry` object +that represents the file system's root directory. + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +File System Quick Example +------------------------- + + function onSuccess(fileSystem) { + console.log(fileSystem.name); + console.log(fileSystem.root.name); + } + + // request the persistent file system + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, null); + +Full Example +------------ + + <!DOCTYPE html> + <html> + <head> + <title>File System Example</title> + + <script type="text/javascript" charset="utf-8" src="cordova-x.x.x.js"></script> + <script type="text/javascript" charset="utf-8"> + + // Wait for device API libraries to load + // + document.addEventListener("deviceready", onDeviceReady, false); + + // device APIs are available + // + function onDeviceReady() { + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFileSystemSuccess, fail); + } + + function onFileSystemSuccess(fileSystem) { + console.log(fileSystem.name); + console.log(fileSystem.root.name); + } + + function fail(evt) { + console.log(evt.target.error.code); + } + + </script> + </head> + <body> + <h1>Example</h1> + <p>File System</p> + </body> + </html> diff --git a/plugins/org.apache.cordova.file/docs/fileuploadoptions/fileuploadoptions.md b/plugins/org.apache.cordova.file/docs/fileuploadoptions/fileuploadoptions.md new file mode 100644 index 00000000..e0e6fe24 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/fileuploadoptions/fileuploadoptions.md @@ -0,0 +1,47 @@ +--- +license: 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. +--- + +FileUploadOptions +======== + +A `FileUploadOptions` object can be passed to the `FileTransfer` +object's `upload()` method to specify additional parameters to the +upload script. + +Properties +---------- + +- __fileKey__: The name of the form element. Defaults to `file`. (DOMString) +- __fileName__: The file name to use when saving the file on the server. Defaults to `image.jpg`. (DOMString) +- __mimeType__: The mime type of the data to upload. Defaults to `image/jpeg`. (DOMString) +- __params__: A set of optional key/value pairs to pass in the HTTP request. (Object) +- __chunkedMode__: Whether to upload the data in chunked streaming mode. Defaults to `true`. (Boolean) +- __headers__: A map of header name/header values. Use an array to specify more than one value. (Object) + +Description +----------- + +A `FileUploadOptions` object can be passed to the `FileTransfer` +object's `upload()` method to specify additional parameters to the +upload script. + +WP7 Quirk +--------- + +- __chunkedMode:__: Ignored on WP7. diff --git a/plugins/org.apache.cordova.file/docs/fileuploadresult/fileuploadresult.md b/plugins/org.apache.cordova.file/docs/fileuploadresult/fileuploadresult.md new file mode 100644 index 00000000..4d9305ad --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/fileuploadresult/fileuploadresult.md @@ -0,0 +1,42 @@ +--- +license: 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. +--- + +FileUploadResult +======== + +A `FileUploadResult` object is passed to the success callback of the +`FileTransfer` object's `upload()` method. + +Properties +---------- + +- __bytesSent__: The number of bytes sent to the server as part of the upload. (long) +- __responseCode__: The HTTP response code returned by the server. (long) +- __response__: The HTTP response returned by the server. (DOMString) + +Description +----------- + +The `FileUploadResult` object is returned via the success callback of +the `FileTransfer` object's `upload()` method. + +iOS Quirks +---------- + +- Does not support `responseCode` or `bytesSent`. diff --git a/plugins/org.apache.cordova.file/docs/filewriter/filewriter.md b/plugins/org.apache.cordova.file/docs/filewriter/filewriter.md new file mode 100644 index 00000000..2269c063 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/filewriter/filewriter.md @@ -0,0 +1,233 @@ +--- +license: 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. +--- + +FileWriter +========== + +As object that allows you to create and write data to a file. + +Properties +---------- + +- __readyState__: One of the three possible states, either `INIT`, `WRITING`, or `DONE`. +- __fileName__: The name of the file to be written. _(DOMString)_ +- __length__: The length of the file to be written. _(long)_ +- __position__: The current position of the file pointer. _(long)_ +- __error__: An object containing errors. _(FileError)_ +- __onwritestart__: Called when the write starts. _(Function)_ +- __onwrite__: Called when the request has completed successfully. _(Function)_ +- __onabort__: Called when the write has been aborted. For instance, by invoking the abort() method. _(Function)_ +- __onerror__: Called when the write has failed. _(Function)_ +- __onwriteend__: Called when the request has completed (either in success or failure). _(Function)_ + +The following property is _not_ supported: + +- __onprogress__: Called while writing the file, reporting progress in terms of `progress.loaded`/`progress.total`. _(Function)_ +Methods +------- + +- __abort__: Aborts writing the file. +- __seek__: Moves the file pointer to the specified byte. +- __truncate__: Shortens the file to the specified length. +- __write__: Writes data to the file. + +Details +------- + +The `FileWriter` object offers a way to write UTF-8 encoded files to +the device file system. Applications respond to `writestart`, +`progress`, `write`, `writeend`, `error`, and `abort` events. + +Each `FileWriter` corresponds to a single file, to which data can be +written many times. The `FileWriter` maintains the file's `position` +and `length` attributes, which allow the app to `seek` and `write` +anywhere in the file. By default, the `FileWriter` writes to the +beginning of the file, overwriting existing data. Set the optional +`append` boolean to `true` in the `FileWriter`'s constructor to +write to the end of the file. + +Text data is supported by all platforms listed below. Text is encoded as UTF-8 before being written to the filesystem. Some platforms also support binary data, which can be passed in as either an ArrayBuffer or a Blob. + +Supported Platforms +------------------- + +### Text and Binary suport + +- Android +- iOS + +### Text only support + +- BlackBerry WebWorks (OS 5.0 and higher) +- Windows Phone 7 and 8 +- Windows 8 + +Seek Quick Example +------------------------------ + + function win(writer) { + // fast forwards file pointer to end of file + writer.seek(writer.length); + }; + + var fail = function(evt) { + console.log(error.code); + }; + + entry.createWriter(win, fail); + +Truncate Quick Example +-------------------------- + + function win(writer) { + writer.truncate(10); + }; + + var fail = function(evt) { + console.log(error.code); + }; + + entry.createWriter(win, fail); + +Write Quick Example +------------------- + + function win(writer) { + writer.onwrite = function(evt) { + console.log("write success"); + }; + writer.write("some sample text"); + }; + + var fail = function(evt) { + console.log(error.code); + }; + + entry.createWriter(win, fail); + +Binary Write Quick Example +-------------------------- + + function win(writer) { + var data = new ArrayBuffer(5), + dataView = new Int8Array(data); + for (i=0; i < 5; i++) { + dataView[i] = i; + } + writer.onwrite = function(evt) { + console.log("write success"); + }; + writer.write(data); + }; + + var fail = function(evt) { + console.log(error.code); + }; + + entry.createWriter(win, fail); + +Append Quick Example +-------------------- + + function win(writer) { + writer.onwrite = function(evt) { + console.log("write success"); + }; + writer.seek(writer.length); + writer.write("appended text"); + }; + + var fail = function(evt) { + console.log(error.code); + }; + + entry.createWriter(win, fail); + +Abort Quick Example +------------------- + + function win(writer) { + writer.onwrite = function(evt) { + console.log("write success"); + }; + writer.write("some sample text"); + writer.abort(); + }; + + var fail = function(evt) { + console.log(error.code); + }; + + entry.createWriter(win, fail); + +Full Example +------------ + <!DOCTYPE html> + <html> + <head> + <title>FileWriter Example</title> + + <script type="text/javascript" charset="utf-8" src="cordova-x.x.x.js"></script> + <script type="text/javascript" charset="utf-8"> + + // Wait for device API libraries to load + // + document.addEventListener("deviceready", onDeviceReady, false); + + // device APIs are available + // + function onDeviceReady() { + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, gotFS, fail); + } + + function gotFS(fileSystem) { + fileSystem.root.getFile("readme.txt", {create: true, exclusive: false}, gotFileEntry, fail); + } + + function gotFileEntry(fileEntry) { + fileEntry.createWriter(gotFileWriter, fail); + } + + function gotFileWriter(writer) { + writer.onwriteend = function(evt) { + console.log("contents of file now 'some sample text'"); + writer.truncate(11); + writer.onwriteend = function(evt) { + console.log("contents of file now 'some sample'"); + writer.seek(4); + writer.write(" different text"); + writer.onwriteend = function(evt){ + console.log("contents of file now 'some different text'"); + } + }; + }; + writer.write("some sample text"); + } + + function fail(error) { + console.log(error.code); + } + + </script> + </head> + <body> + <h1>Example</h1> + <p>Write File</p> + </body> + </html> diff --git a/plugins/org.apache.cordova.file/docs/flags/flags.md b/plugins/org.apache.cordova.file/docs/flags/flags.md new file mode 100644 index 00000000..504f3235 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/flags/flags.md @@ -0,0 +1,49 @@ +--- +license: 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. +--- + +Flags +===== + +Supplies arguments to the `DirectoryEntry` object's `getFile()` and +`getDirectory()` methods, which look up or create files and +directories, respectively. + +Properties +---------- + +- __create__: Indicates that the file or directory should be created if it does not already exist. _(boolean)_ +- __exclusive__: Has has no effect by itself, but when used with `create` causes the file or directory creation to fail if the target path already exists. _(boolean)_ + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +Quick Example +------------- + + // Get the data directory, creating it if it doesn't exist. + dataDir = fileSystem.root.getDirectory("data", {create: true}); + + // Create the lock file, if and only if it doesn't exist. + lockFile = dataDir.getFile("lockfile.txt", {create: true, exclusive: true}); diff --git a/plugins/org.apache.cordova.file/docs/localfilesystem/localfilesystem.md b/plugins/org.apache.cordova.file/docs/localfilesystem/localfilesystem.md new file mode 100644 index 00000000..2b933c94 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/localfilesystem/localfilesystem.md @@ -0,0 +1,110 @@ +--- +license: 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. +--- + +LocalFileSystem +=============== + +This object provides a way to obtain root file systems. + +Methods +---------- + +- __requestFileSystem__: Requests a filesystem. _(Function)_ +- __resolveLocalFileSystemURI__: Retrieve a `DirectoryEntry` or `FileEntry` using local URI. _(Function)_ + +Constants +--------- + +- `LocalFileSystem.PERSISTENT`: Used for storage that should not be removed by the user agent without application or user permission. +- `LocalFileSystem.TEMPORARY`: Used for storage with no guarantee of persistence. + +Details +------- + +The `LocalFileSystem` object methods are defined on the `window` object. + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +Request File System Quick Example +--------------------------------- + + function onSuccess(fileSystem) { + console.log(fileSystem.name); + } + + // request the persistent file system + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError); + +Resolve Local File System URI Quick Example +------------------------------------------- + + function onSuccess(fileEntry) { + console.log(fileEntry.name); + } + + window.resolveLocalFileSystemURI("file:///example.txt", onSuccess, onError); + +Full Example +------------ + + <!DOCTYPE html> + <html> + <head> + <title>Local File System Example</title> + + <script type="text/javascript" charset="utf-8" src="cordova-x.x.x.js"></script> + <script type="text/javascript" charset="utf-8"> + + // Wait for device API libraries to load + // + document.addEventListener("deviceready", onDeviceReady, false); + + // device APIs are available + // + function onDeviceReady() { + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFileSystemSuccess, fail); + window.resolveLocalFileSystemURI("file:///example.txt", onResolveSuccess, fail); + } + + function onFileSystemSuccess(fileSystem) { + console.log(fileSystem.name); + } + + function onResolveSuccess(fileEntry) { + console.log(fileEntry.name); + } + + function fail(evt) { + console.log(evt.target.error.code); + } + + </script> + </head> + <body> + <h1>Example</h1> + <p>Local File System</p> + </body> + </html> diff --git a/plugins/org.apache.cordova.file/docs/metadata/metadata.md b/plugins/org.apache.cordova.file/docs/metadata/metadata.md new file mode 100644 index 00000000..bfa3a460 --- /dev/null +++ b/plugins/org.apache.cordova.file/docs/metadata/metadata.md @@ -0,0 +1,54 @@ +--- +license: 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. +--- + +Metadata +========== + +An interface that supplies information about the state of a file or directory. + +Properties +---------- + +- __modificationTime__: The time when the file or directory was last modified. _(Date)_ + +Details +------- + +The `Metadata` object represents information about the state of a file +or directory. Calling a `DirectoryEntry` or `FileEntry` object's +`getMetadata()` method results in a `Metadata` instance. + +Supported Platforms +------------------- + +- Android +- BlackBerry WebWorks (OS 5.0 and higher) +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +Quick Example +------------- + + function win(metadata) { + console.log("Last Modified: " + metadata.modificationTime); + } + + // Request the metadata object for this entry + entry.getMetadata(win, null); diff --git a/plugins/org.apache.cordova.file/plugin.xml b/plugins/org.apache.cordova.file/plugin.xml new file mode 100644 index 00000000..24f538de --- /dev/null +++ b/plugins/org.apache.cordova.file/plugin.xml @@ -0,0 +1,207 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" +xmlns:android="http://schemas.android.com/apk/res/android" + id="org.apache.cordova.file" + version="0.2.5"> + <name>File</name> + <description>Cordova File Plugin</description> + <license>Apache 2.0</license> + <keywords>cordova,file</keywords> + <repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git</repo> + <issue>https://issues.apache.org/jira/browse/CB/component/12320651</issue> + + <js-module src="www/DirectoryEntry.js" name="DirectoryEntry"> + <clobbers target="window.DirectoryEntry" /> + </js-module> + + <js-module src="www/DirectoryReader.js" name="DirectoryReader"> + <clobbers target="window.DirectoryReader" /> + </js-module> + + <js-module src="www/Entry.js" name="Entry"> + <clobbers target="window.Entry" /> + </js-module> + + <js-module src="www/File.js" name="File"> + <clobbers target="window.File" /> + </js-module> + + <js-module src="www/FileEntry.js" name="FileEntry"> + <clobbers target="window.FileEntry" /> + </js-module> + + <js-module src="www/FileError.js" name="FileError"> + <clobbers target="window.FileError" /> + </js-module> + + <js-module src="www/FileReader.js" name="FileReader"> + <clobbers target="window.FileReader" /> + </js-module> + + <js-module src="www/FileSystem.js" name="FileSystem"> + <clobbers target="window.FileSystem" /> + </js-module> + + <js-module src="www/FileUploadOptions.js" name="FileUploadOptions"> + <clobbers target="window.FileUploadOptions" /> + </js-module> + + <js-module src="www/FileUploadResult.js" name="FileUploadResult"> + <clobbers target="window.FileUploadResult" /> + </js-module> + + <js-module src="www/FileWriter.js" name="FileWriter"> + <clobbers target="window.FileWriter" /> + </js-module> + + <js-module src="www/Flags.js" name="Flags"> + <clobbers target="window.Flags" /> + </js-module> + + <js-module src="www/LocalFileSystem.js" name="LocalFileSystem"> + <!-- Non-standards way --> + <clobbers target="window.LocalFileSystem" /> + <!-- Standards-compliant way --> + <merges target="window" /> + </js-module> + + <js-module src="www/Metadata.js" name="Metadata"> + <clobbers target="window.Metadata" /> + </js-module> + + <js-module src="www/ProgressEvent.js" name="ProgressEvent"> + <clobbers target="window.ProgressEvent" /> + </js-module> + + <js-module src="www/requestFileSystem.js" name="requestFileSystem"> + <clobbers target="window.requestFileSystem" /> + </js-module> + + <js-module src="www/resolveLocalFileSystemURI.js" name="resolveLocalFileSystemURI"> + <clobbers target="window.resolveLocalFileSystemURI" /> + </js-module> + + <!-- android --> + <platform name="android"> + <config-file target="res/xml/config.xml" parent="/*"> + <feature name="File" > + <param name="android-package" value="org.apache.cordova.file.FileUtils"/> + </feature> + </config-file> + + <config-file target="AndroidManifest.xml" parent="/*"> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + </config-file> + + <source-file src="src/android/EncodingException.java" target-dir="src/org/apache/cordova/file" /> + <source-file src="src/android/FileExistsException.java" target-dir="src/org/apache/cordova/file" /> + <source-file src="src/android/InvalidModificationException.java" target-dir="src/org/apache/cordova/file" /> + <source-file src="src/android/NoModificationAllowedException.java" target-dir="src/org/apache/cordova/file" /> + <source-file src="src/android/TypeMismatchException.java" target-dir="src/org/apache/cordova/file" /> + <source-file src="src/android/FileUtils.java" target-dir="src/org/apache/cordova/file" /> + <source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/file" /> + <source-file src="src/android/DirectoryManager.java" target-dir="src/org/apache/cordova/file" /> + </platform> + + <!-- ios --> + <platform name="ios"> + <config-file target="config.xml" parent="/*"> + <feature name="File"> + <param name="ios-package" value="CDVFile" /> + </feature> + </config-file> + <header-file src="src/ios/CDVFile.h" /> + <source-file src="src/ios/CDVFile.m" /> + + <!-- ios specific file apis --> + <js-module src="www/ios/Entry.js" name="Entry1"> + <merges target="window.Entry" /> + </js-module> + + <framework src="AssetsLibrary.framework" /> + <framework src="MobileCoreServices.framework" /> + </platform> + + <!-- wp7 --> + <platform name="wp7"> + <config-file target="config.xml" parent="/*"> + <feature name="File"> + <param name="wp-package" value="File"/> + </feature> + </config-file> + + <source-file src="src/wp/File.cs" /> + + <!-- wp specific file apis --> + <js-module src="www/wp/FileUploadOptions.js" name="FileUploadOptions1"> + <merges target="window.FileUploadOptions" /> + </js-module> + + </platform> + + <!-- wp8 --> + <platform name="wp8"> + <config-file target="config.xml" parent="/*"> + <feature name="File"> + <param name="wp-package" value="File"/> + </feature> + </config-file> + + <source-file src="src/wp/File.cs" /> + + <!-- wp specific file apis --> + <js-module src="www/wp/FileUploadOptions.js" name="FileUploadOptions1"> + <merges target="window.FileUploadOptions" /> + </js-module> + + </platform> + + <!-- blackberry10 --> + <platform name="blackberry10"> + <config-file target="www/config.xml" parent="/widget"> + <feature name="File" value="File" /> + </config-file> + <js-module src="www/blackberry10/fileUtils.js" name="BB10Utils" /> + <js-module src="www/blackberry10/DirectoryEntry.js" name="BB10DirectoryEntry"> + <clobbers target="window.DirectoryEntry" /> + </js-module> + <js-module src="www/blackberry10/DirectoryReader.js" name="BB10DirectoryReader"> + <clobbers target="window.DirectoryReader" /> + </js-module> + <js-module src="www/blackberry10/Entry.js" name="BB10Entry"> + <clobbers target="window.Entry" /> + </js-module> + <js-module src="www/blackberry10/File.js" name="BB10File"> + <clobbers target="window.File" /> + </js-module> + <js-module src="www/blackberry10/FileEntry.js" name="BB10FileEntry"> + <clobbers target="window.FileEntry" /> + </js-module> + <js-module src="www/blackberry10/FileReader.js" name="BB10FileReader"> + <clobbers target="window.FileReader" /> + </js-module> + <js-module src="www/blackberry10/FileSystem.js" name="BB10FileSystem"> + <clobbers target="window.FileSystem" /> + </js-module> + <js-module src="www/blackberry10/FileWriter.js" name="BB10FileWriter"> + <clobbers target="window.FileWriter" /> + </js-module> + <js-module src="www/blackberry10/requestFileSystem.js" name="BB10requestFileSystem"> + <clobbers target="window.requestFileSystem" /> + </js-module> + <js-module src="www/blackberry10/resolveLocalFileSystemURI.js" name="BB10resolveLocalFileSystemURI"> + <clobbers target="window.resolveLocalFileSystemURI" /> + </js-module> + + <source-file src="src/blackberry10/index.js"></source-file> + </platform> + + <!-- windows8 --> + <platform name="windows8"> + <js-module src="src/windows8/FileProxy.js" name="FileProxy"> + <merges target="" /> + </js-module> + </platform> + +</plugin> diff --git a/plugins/org.apache.cordova.file/src/android/DirectoryManager.java b/plugins/org.apache.cordova.file/src/android/DirectoryManager.java new file mode 100644 index 00000000..bcc005b2 --- /dev/null +++ b/plugins/org.apache.cordova.file/src/android/DirectoryManager.java @@ -0,0 +1,133 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +package org.apache.cordova.file; + +import android.os.Environment; +import android.os.StatFs; + +import java.io.File; + +/** + * This class provides file directory utilities. + * All file operations are performed on the SD card. + * + * It is used by the FileUtils class. + */ +public class DirectoryManager { + + @SuppressWarnings("unused") + private static final String LOG_TAG = "DirectoryManager"; + + /** + * Determine if a file or directory exists. + * @param name The name of the file to check. + * @return T=exists, F=not found + */ + public static boolean testFileExists(String name) { + boolean status; + + // If SD card exists + if ((testSaveLocationExists()) && (!name.equals(""))) { + File path = Environment.getExternalStorageDirectory(); + File newPath = constructFilePaths(path.toString(), name); + status = newPath.exists(); + } + // If no SD card + else { + status = false; + } + return status; + } + + /** + * Get the free disk space + * + * @return Size in KB or -1 if not available + */ + public static long getFreeDiskSpace(boolean checkInternal) { + String status = Environment.getExternalStorageState(); + long freeSpace = 0; + + // If SD card exists + if (status.equals(Environment.MEDIA_MOUNTED)) { + freeSpace = freeSpaceCalculation(Environment.getExternalStorageDirectory().getPath()); + } + else if (checkInternal) { + freeSpace = freeSpaceCalculation("/"); + } + // If no SD card and we haven't been asked to check the internal directory then return -1 + else { + return -1; + } + + return freeSpace; + } + + /** + * Given a path return the number of free KB + * + * @param path to the file system + * @return free space in KB + */ + private static long freeSpaceCalculation(String path) { + StatFs stat = new StatFs(path); + long blockSize = stat.getBlockSize(); + long availableBlocks = stat.getAvailableBlocks(); + return availableBlocks * blockSize / 1024; + } + + /** + * Determine if SD card exists. + * + * @return T=exists, F=not found + */ + public static boolean testSaveLocationExists() { + String sDCardStatus = Environment.getExternalStorageState(); + boolean status; + + // If SD card is mounted + if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) { + status = true; + } + + // If no SD card + else { + status = false; + } + return status; + } + + /** + * Create a new file object from two file paths. + * + * @param file1 Base file path + * @param file2 Remaining file path + * @return File object + */ + private static File constructFilePaths (String file1, String file2) { + File newPath; + if (file2.startsWith(file1)) { + newPath = new File(file2); + } + else { + newPath = new File(file1 + "/" + file2); + } + return newPath; + } +} diff --git a/plugins/org.apache.cordova.file/src/android/EncodingException.java b/plugins/org.apache.cordova.file/src/android/EncodingException.java new file mode 100644 index 00000000..e9e1653b --- /dev/null +++ b/plugins/org.apache.cordova.file/src/android/EncodingException.java @@ -0,0 +1,29 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova.file; + +@SuppressWarnings("serial") +public class EncodingException extends Exception { + + public EncodingException(String message) { + super(message); + } + +} diff --git a/plugins/org.apache.cordova.file/src/android/FileExistsException.java b/plugins/org.apache.cordova.file/src/android/FileExistsException.java new file mode 100644 index 00000000..5c4d83dc --- /dev/null +++ b/plugins/org.apache.cordova.file/src/android/FileExistsException.java @@ -0,0 +1,29 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova.file; + +@SuppressWarnings("serial") +public class FileExistsException extends Exception { + + public FileExistsException(String msg) { + super(msg); + } + +} diff --git a/plugins/org.apache.cordova.file/src/android/FileHelper.java b/plugins/org.apache.cordova.file/src/android/FileHelper.java new file mode 100644 index 00000000..867f128c --- /dev/null +++ b/plugins/org.apache.cordova.file/src/android/FileHelper.java @@ -0,0 +1,158 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ +package org.apache.cordova.file; + +import android.database.Cursor; +import android.net.Uri; +import android.webkit.MimeTypeMap; + +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.LOG; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +public class FileHelper { + private static final String LOG_TAG = "FileUtils"; + private static final String _DATA = "_data"; + + /** + * Returns the real path of the given URI string. + * If the given URI string represents a content:// URI, the real path is retrieved from the media store. + * + * @param uriString the URI string of the audio/image/video + * @param cordova the current application context + * @return the full path to the file + */ + @SuppressWarnings("deprecation") + public static String getRealPath(String uriString, CordovaInterface cordova) { + String realPath = null; + + if (uriString.startsWith("content://")) { + String[] proj = { _DATA }; + Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(_DATA); + cursor.moveToFirst(); + realPath = cursor.getString(column_index); + if (realPath == null) { + LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString); + } + } else if (uriString.startsWith("file://")) { + realPath = uriString.substring(7); + if (realPath.startsWith("/android_asset/")) { + LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString); + realPath = null; + } + } else { + realPath = uriString; + } + + return realPath; + } + + /** + * Returns the real path of the given URI. + * If the given URI is a content:// URI, the real path is retrieved from the media store. + * + * @param uri the URI of the audio/image/video + * @param cordova the current application context + * @return the full path to the file + */ + public static String getRealPath(Uri uri, CordovaInterface cordova) { + return FileHelper.getRealPath(uri.toString(), cordova); + } + + /** + * Returns an input stream based on given URI string. + * + * @param uriString the URI string from which to obtain the input stream + * @param cordova the current application context + * @return an input stream into the data at the given URI or null if given an invalid URI string + * @throws IOException + */ + public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException { + if (uriString.startsWith("content")) { + Uri uri = Uri.parse(uriString); + return cordova.getActivity().getContentResolver().openInputStream(uri); + } else if (uriString.startsWith("file://")) { + int question = uriString.indexOf("?"); + if (question > -1) { + uriString = uriString.substring(0,question); + } + if (uriString.startsWith("file:///android_asset/")) { + Uri uri = Uri.parse(uriString); + String relativePath = uri.getPath().substring(15); + return cordova.getActivity().getAssets().open(relativePath); + } else { + return new FileInputStream(getRealPath(uriString, cordova)); + } + } else { + return new FileInputStream(getRealPath(uriString, cordova)); + } + } + + /** + * Removes the "file://" prefix from the given URI string, if applicable. + * If the given URI string doesn't have a "file://" prefix, it is returned unchanged. + * + * @param uriString the URI string to operate on + * @return a path without the "file://" prefix + */ + public static String stripFileProtocol(String uriString) { + if (uriString.startsWith("file://")) { + uriString = uriString.substring(7); + } + return uriString; + } + + public static String getMimeTypeForExtension(String path) { + String extension = path; + int lastDot = extension.lastIndexOf('.'); + if (lastDot != -1) { + extension = extension.substring(lastDot + 1); + } + // Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185). + extension = extension.toLowerCase(Locale.getDefault()); + if (extension.equals("3ga")) { + return "audio/3gpp"; + } + return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + + /** + * Returns the mime type of the data specified by the given URI string. + * + * @param uriString the URI string of the data + * @return the mime type of the specified data + */ + public static String getMimeType(String uriString, CordovaInterface cordova) { + String mimeType = null; + + Uri uri = Uri.parse(uriString); + if (uriString.startsWith("content://")) { + mimeType = cordova.getActivity().getContentResolver().getType(uri); + } else { + mimeType = getMimeTypeForExtension(uri.getPath()); + } + + return mimeType; + } +} diff --git a/plugins/org.apache.cordova.file/src/android/FileUtils.java b/plugins/org.apache.cordova.file/src/android/FileUtils.java new file mode 100755 index 00000000..9a9452fb --- /dev/null +++ b/plugins/org.apache.cordova.file/src/android/FileUtils.java @@ -0,0 +1,1191 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ +package org.apache.cordova.file; + +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.util.Base64; +import android.util.Log; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.PluginResult; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.channels.FileChannel; + +/** + * This class provides SD card file and directory services to JavaScript. + * Only files on the SD card can be accessed. + */ +public class FileUtils extends CordovaPlugin { + private static final String LOG_TAG = "FileUtils"; + + public static int NOT_FOUND_ERR = 1; + public static int SECURITY_ERR = 2; + public static int ABORT_ERR = 3; + + public static int NOT_READABLE_ERR = 4; + public static int ENCODING_ERR = 5; + public static int NO_MODIFICATION_ALLOWED_ERR = 6; + public static int INVALID_STATE_ERR = 7; + public static int SYNTAX_ERR = 8; + public static int INVALID_MODIFICATION_ERR = 9; + public static int QUOTA_EXCEEDED_ERR = 10; + public static int TYPE_MISMATCH_ERR = 11; + public static int PATH_EXISTS_ERR = 12; + + public static int TEMPORARY = 0; + public static int PERSISTENT = 1; + public static int RESOURCE = 2; + public static int APPLICATION = 3; + + private interface FileOp { + void run( ) throws Exception; + } + + /** + * Executes the request and returns whether the action was valid. + * + * @param action The action to execute. + * @param args JSONArray of arguments for the plugin. + * @param callbackContext The callback context used when calling back into JavaScript. + * @return True if the action was valid, false otherwise. + */ + public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { + if (action.equals("testSaveLocationExists")) { + threadhelper( new FileOp( ){ + public void run() { + boolean b = DirectoryManager.testSaveLocationExists(); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b)); + } + },callbackContext); + } + else if (action.equals("getFreeDiskSpace")) { + threadhelper( new FileOp( ){ + public void run() { + long l = DirectoryManager.getFreeDiskSpace(false); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l)); + } + },callbackContext); + } + else if (action.equals("testFileExists")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() { + boolean b = DirectoryManager.testFileExists(fname); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b)); + } + }, callbackContext); + } + else if (action.equals("testDirectoryExists")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() { + boolean b = DirectoryManager.testFileExists(fname); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b)); + } + }, callbackContext); + } + else if (action.equals("readAsText")) { + final String encoding = args.getString(1); + final int start = args.getInt(2); + final int end = args.getInt(3); + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() { + readFileAs(fname, start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING); + } + }, callbackContext); + } + else if (action.equals("readAsDataURL")) { + final int start = args.getInt(1); + final int end = args.getInt(2); + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() { + readFileAs(fname, start, end, callbackContext, null, -1); + } + }, callbackContext); + } + else if (action.equals("readAsArrayBuffer")) { + final int start = args.getInt(1); + final int end = args.getInt(2); + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() { + readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER); + } + },callbackContext); + } + else if (action.equals("readAsBinaryString")) { + final int start = args.getInt(1); + final int end = args.getInt(2); + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() { + readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING); + } + }, callbackContext); + } + else if (action.equals("write")) { + final String fname=args.getString(0); + final String data=args.getString(1); + final int offset=args.getInt(2); + final Boolean isBinary=args.getBoolean(3); + threadhelper( new FileOp( ){ + public void run() throws FileNotFoundException, IOException, NoModificationAllowedException { + long fileSize = write(fname, data, offset, isBinary); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize)); + } + }, callbackContext); + } + else if (action.equals("truncate")) { + final String fname=args.getString(0); + final int offset=args.getInt(1); + threadhelper( new FileOp( ){ + public void run( ) throws FileNotFoundException, IOException, NoModificationAllowedException { + long fileSize = truncateFile(fname, offset); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize)); + } + }, callbackContext); + } + else if (action.equals("requestFileSystem")) { + final int fstype=args.getInt(0); + final long size = args.optLong(1); + threadhelper( new FileOp( ){ + public void run() throws IOException, JSONException { + if (size != 0 && size > (DirectoryManager.getFreeDiskSpace(true) * 1024)) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR)); + } else { + JSONObject obj = requestFileSystem(fstype); + callbackContext.success(obj); + } + } + }, callbackContext); + } + else if (action.equals("resolveLocalFileSystemURI")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() throws IOException, JSONException { + JSONObject obj = resolveLocalFileSystemURI(fname); + callbackContext.success(obj); + } + },callbackContext); + } + else if (action.equals("getMetadata")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() throws FileNotFoundException, JSONException { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getMetadata(fname))); + } + }, callbackContext); + } + else if (action.equals("getFileMetadata")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() throws FileNotFoundException, JSONException { + JSONObject obj = getFileMetadata(fname); + callbackContext.success(obj); + } + },callbackContext); + } + else if (action.equals("getParent")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() throws JSONException { + JSONObject obj = getParent(fname); + callbackContext.success(obj); + } + },callbackContext); + } + else if (action.equals("getDirectory")) { + final String dirname=args.getString(0); + final String fname=args.getString(1); + threadhelper( new FileOp( ){ + public void run() throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException { + JSONObject obj = getFile(dirname, fname, args.optJSONObject(2), true); + callbackContext.success(obj); + } + },callbackContext); + } + else if (action.equals("getFile")) { + final String dirname=args.getString(0); + final String fname=args.getString(1); + threadhelper( new FileOp( ){ + public void run() throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException { + JSONObject obj = getFile(dirname, fname, args.optJSONObject(2), false); + callbackContext.success(obj); + } + },callbackContext); + } + else if (action.equals("remove")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() throws NoModificationAllowedException, InvalidModificationException { + boolean success= remove(fname); + if (success) { + notifyDelete(fname); + callbackContext.success(); + } else { + callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR); + } + } + },callbackContext); + } + else if (action.equals("removeRecursively")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() throws FileExistsException { + boolean success = removeRecursively(fname); + if (success) { + callbackContext.success(); + } else { + callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR); + } + } + },callbackContext); + } + else if (action.equals("moveTo")) { + final String fname=args.getString(0); + final String newParent=args.getString(1); + final String newName=args.getString(2); + threadhelper( new FileOp( ){ + public void run() throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException { + JSONObject entry = transferTo(fname, newParent, newName, true); + callbackContext.success(entry); + } + },callbackContext); + } + else if (action.equals("copyTo")) { + final String fname=args.getString(0); + final String newParent=args.getString(1); + final String newName=args.getString(2); + threadhelper( new FileOp( ){ + public void run() throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException { + JSONObject entry = transferTo(fname, newParent, newName, false); + callbackContext.success(entry); + } + },callbackContext); + } + else if (action.equals("readEntries")) { + final String fname=args.getString(0); + threadhelper( new FileOp( ){ + public void run() throws FileNotFoundException, JSONException { + JSONArray entries = readEntries(fname); + callbackContext.success(entries); + } + },callbackContext); + } + else { + return false; + } + return true; + } + + /* helper to execute functions async and handle the result codes + * + */ + private void threadhelper(final FileOp f, final CallbackContext callbackContext){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + f.run(); + } catch ( Exception e) { + e.printStackTrace(); + if( e instanceof EncodingException){ + callbackContext.error(FileUtils.ENCODING_ERR); + } else if(e instanceof FileNotFoundException) { + callbackContext.error(FileUtils.NOT_FOUND_ERR); + } else if(e instanceof FileExistsException) { + callbackContext.error(FileUtils.PATH_EXISTS_ERR); + } else if(e instanceof NoModificationAllowedException ) { + callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR); + } else if(e instanceof InvalidModificationException ) { + callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR); + } else if(e instanceof MalformedURLException ) { + callbackContext.error(FileUtils.ENCODING_ERR); + } else if(e instanceof IOException ) { + callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR); + } else if(e instanceof EncodingException ) { + callbackContext.error(FileUtils.ENCODING_ERR); + } else if(e instanceof TypeMismatchException ) { + callbackContext.error(FileUtils.TYPE_MISMATCH_ERR); + } + } + } + }); + } + + /** + * Need to check to see if we need to clean up the content store + * + * @param filePath the path to check + */ + private void notifyDelete(String filePath) { + String newFilePath = FileHelper.getRealPath(filePath, cordova); + try { + this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + MediaStore.Images.Media.DATA + " = ?", + new String[] { newFilePath }); + } catch (UnsupportedOperationException t) { + // Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator. + // The ContentResolver applies only when the file was registered in the + // first case, which is generally only the case with images. + } + } + + /** + * Allows the user to look up the Entry for a file or directory referred to by a local URI. + * + * @param url of the file/directory to look up + * @return a JSONObject representing a Entry from the filesystem + * @throws MalformedURLException if the url is not valid + * @throws FileNotFoundException if the file does not exist + * @throws IOException if the user can't read the file + * @throws JSONException + */ + @SuppressWarnings("deprecation") + private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException { + String decoded = URLDecoder.decode(url, "UTF-8"); + + File fp = null; + + // Handle the special case where you get an Android content:// uri. + if (decoded.startsWith("content:")) { + Cursor cursor = this.cordova.getActivity().managedQuery(Uri.parse(decoded), new String[] { MediaStore.Images.Media.DATA }, null, null, null); + // Note: MediaStore.Images/Audio/Video.Media.DATA is always "_data" + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + fp = new File(cursor.getString(column_index)); + } else { + // Test to see if this is a valid URL first + @SuppressWarnings("unused") + URL testUrl = new URL(decoded); + + if (decoded.startsWith("file://")) { + int questionMark = decoded.indexOf("?"); + if (questionMark < 0) { + fp = new File(decoded.substring(7, decoded.length())); + } else { + fp = new File(decoded.substring(7, questionMark)); + } + } else { + fp = new File(decoded); + } + } + + if (!fp.exists()) { + throw new FileNotFoundException(); + } + if (!fp.canRead()) { + throw new IOException(); + } + return getEntry(fp); + } + + /** + * Read the list of files from this directory. + * + * @param fileName the directory to read from + * @return a JSONArray containing JSONObjects that represent Entry objects. + * @throws FileNotFoundException if the directory is not found. + * @throws JSONException + */ + private JSONArray readEntries(String fileName) throws FileNotFoundException, JSONException { + File fp = createFileObject(fileName); + + if (!fp.exists()) { + // The directory we are listing doesn't exist so we should fail. + throw new FileNotFoundException(); + } + + JSONArray entries = new JSONArray(); + + if (fp.isDirectory()) { + File[] files = fp.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].canRead()) { + entries.put(getEntry(files[i])); + } + } + } + + return entries; + } + + /** + * A setup method that handles the move/copy of files/directories + * + * @param fileName to be copied/moved + * @param newParent is the location where the file will be copied/moved to + * @param newName for the file directory to be called, if null use existing file name + * @param move if false do a copy, if true do a move + * @return a Entry object + * @throws NoModificationAllowedException + * @throws IOException + * @throws InvalidModificationException + * @throws EncodingException + * @throws JSONException + * @throws FileExistsException + */ + private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException { + String newFileName = FileHelper.getRealPath(fileName, cordova); + newParent = FileHelper.getRealPath(newParent, cordova); + + // Check for invalid file name + if (newName != null && newName.contains(":")) { + throw new EncodingException("Bad file name"); + } + + File source = new File(newFileName); + + if (!source.exists()) { + // The file/directory we are copying doesn't exist so we should fail. + throw new FileNotFoundException("The source does not exist"); + } + + File destinationDir = new File(newParent); + if (!destinationDir.exists()) { + // The destination does not exist so we should fail. + throw new FileNotFoundException("The source does not exist"); + } + + // Figure out where we should be copying to + File destination = createDestination(newName, source, destinationDir); + + //Log.d(LOG_TAG, "Source: " + source.getAbsolutePath()); + //Log.d(LOG_TAG, "Destin: " + destination.getAbsolutePath()); + + // Check to see if source and destination are the same file + if (source.getAbsolutePath().equals(destination.getAbsolutePath())) { + throw new InvalidModificationException("Can't copy a file onto itself"); + } + + if (source.isDirectory()) { + if (move) { + return moveDirectory(source, destination); + } else { + return copyDirectory(source, destination); + } + } else { + if (move) { + JSONObject newFileEntry = moveFile(source, destination); + + // If we've moved a file given its content URI, we need to clean up. + if (fileName.startsWith("content://")) { + notifyDelete(fileName); + } + + return newFileEntry; + } else { + return copyFile(source, destination); + } + } + } + + /** + * Creates the destination File object based on name passed in + * + * @param newName for the file directory to be called, if null use existing file name + * @param fp represents the source file + * @param destination represents the destination file + * @return a File object that represents the destination + */ + private File createDestination(String newName, File fp, File destination) { + File destFile = null; + + // I know this looks weird but it is to work around a JSON bug. + if ("null".equals(newName) || "".equals(newName)) { + newName = null; + } + + if (newName != null) { + destFile = new File(destination.getAbsolutePath() + File.separator + newName); + } else { + destFile = new File(destination.getAbsolutePath() + File.separator + fp.getName()); + } + return destFile; + } + + /** + * Copy a file + * + * @param srcFile file to be copied + * @param destFile destination to be copied to + * @return a FileEntry object + * @throws IOException + * @throws InvalidModificationException + * @throws JSONException + */ + private JSONObject copyFile(File srcFile, File destFile) throws IOException, InvalidModificationException, JSONException { + // Renaming a file to an existing directory should fail + if (destFile.exists() && destFile.isDirectory()) { + throw new InvalidModificationException("Can't rename a file to a directory"); + } + + copyAction(srcFile, destFile); + + return getEntry(destFile); + } + + /** + * Moved this code into it's own method so moveTo could use it when the move is across file systems + */ + private void copyAction(File srcFile, File destFile) + throws FileNotFoundException, IOException { + FileInputStream istream = new FileInputStream(srcFile); + FileOutputStream ostream = new FileOutputStream(destFile); + FileChannel input = istream.getChannel(); + FileChannel output = ostream.getChannel(); + + try { + input.transferTo(0, input.size(), output); + } finally { + istream.close(); + ostream.close(); + input.close(); + output.close(); + } + } + + /** + * Copy a directory + * + * @param srcDir directory to be copied + * @param destinationDir destination to be copied to + * @return a DirectoryEntry object + * @throws JSONException + * @throws IOException + * @throws NoModificationAllowedException + * @throws InvalidModificationException + */ + private JSONObject copyDirectory(File srcDir, File destinationDir) throws JSONException, IOException, NoModificationAllowedException, InvalidModificationException { + // Renaming a file to an existing directory should fail + if (destinationDir.exists() && destinationDir.isFile()) { + throw new InvalidModificationException("Can't rename a file to a directory"); + } + + // Check to make sure we are not copying the directory into itself + if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) { + throw new InvalidModificationException("Can't copy itself into itself"); + } + + // See if the destination directory exists. If not create it. + if (!destinationDir.exists()) { + if (!destinationDir.mkdir()) { + // If we can't create the directory then fail + throw new NoModificationAllowedException("Couldn't create the destination directory"); + } + } + + + for (File file : srcDir.listFiles()) { + File destination = new File(destinationDir.getAbsoluteFile() + File.separator + file.getName()); + if (file.isDirectory()) { + copyDirectory(file, destination); + } else { + copyFile(file, destination); + } + } + + return getEntry(destinationDir); + } + + /** + * Check to see if the user attempted to copy an entry into its parent without changing its name, + * or attempted to copy a directory into a directory that it contains directly or indirectly. + * + * @param srcDir + * @param destinationDir + * @return + */ + private boolean isCopyOnItself(String src, String dest) { + + // This weird test is to determine if we are copying or moving a directory into itself. + // Copy /sdcard/myDir to /sdcard/myDir-backup is okay but + // Copy /sdcard/myDir to /sdcard/myDir/backup should throw an INVALID_MODIFICATION_ERR + if (dest.startsWith(src) && dest.indexOf(File.separator, src.length() - 1) != -1) { + return true; + } + + return false; + } + + /** + * Move a file + * + * @param srcFile file to be copied + * @param destFile destination to be copied to + * @return a FileEntry object + * @throws IOException + * @throws InvalidModificationException + * @throws JSONException + */ + private JSONObject moveFile(File srcFile, File destFile) throws IOException, JSONException, InvalidModificationException { + // Renaming a file to an existing directory should fail + if (destFile.exists() && destFile.isDirectory()) { + throw new InvalidModificationException("Can't rename a file to a directory"); + } + + // Try to rename the file + if (!srcFile.renameTo(destFile)) { + // Trying to rename the file failed. Possibly because we moved across file system on the device. + // Now we have to do things the hard way + // 1) Copy all the old file + // 2) delete the src file + copyAction(srcFile, destFile); + if (destFile.exists()) { + srcFile.delete(); + } else { + throw new IOException("moved failed"); + } + } + + return getEntry(destFile); + } + + /** + * Move a directory + * + * @param srcDir directory to be copied + * @param destinationDir destination to be copied to + * @return a DirectoryEntry object + * @throws JSONException + * @throws IOException + * @throws InvalidModificationException + * @throws NoModificationAllowedException + * @throws FileExistsException + */ + private JSONObject moveDirectory(File srcDir, File destinationDir) throws IOException, JSONException, InvalidModificationException, NoModificationAllowedException, FileExistsException { + // Renaming a file to an existing directory should fail + if (destinationDir.exists() && destinationDir.isFile()) { + throw new InvalidModificationException("Can't rename a file to a directory"); + } + + // Check to make sure we are not copying the directory into itself + if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) { + throw new InvalidModificationException("Can't move itself into itself"); + } + + // If the destination directory already exists and is empty then delete it. This is according to spec. + if (destinationDir.exists()) { + if (destinationDir.list().length > 0) { + throw new InvalidModificationException("directory is not empty"); + } + } + + // Try to rename the directory + if (!srcDir.renameTo(destinationDir)) { + // Trying to rename the directory failed. Possibly because we moved across file system on the device. + // Now we have to do things the hard way + // 1) Copy all the old files + // 2) delete the src directory + copyDirectory(srcDir, destinationDir); + if (destinationDir.exists()) { + removeDirRecursively(srcDir); + } else { + throw new IOException("moved failed"); + } + } + + return getEntry(destinationDir); + } + + /** + * Deletes a directory and all of its contents, if any. In the event of an error + * [e.g. trying to delete a directory that contains a file that cannot be removed], + * some of the contents of the directory may be deleted. + * It is an error to attempt to delete the root directory of a filesystem. + * + * @param filePath the directory to be removed + * @return a boolean representing success of failure + * @throws FileExistsException + */ + private boolean removeRecursively(String filePath) throws FileExistsException { + File fp = createFileObject(filePath); + + // You can't delete the root directory. + if (atRootDirectory(filePath)) { + return false; + } + + return removeDirRecursively(fp); + } + + /** + * Loops through a directory deleting all the files. + * + * @param directory to be removed + * @return a boolean representing success of failure + * @throws FileExistsException + */ + private boolean removeDirRecursively(File directory) throws FileExistsException { + if (directory.isDirectory()) { + for (File file : directory.listFiles()) { + removeDirRecursively(file); + } + } + + if (!directory.delete()) { + throw new FileExistsException("could not delete: " + directory.getName()); + } else { + return true; + } + } + + /** + * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty. + * It is an error to attempt to delete the root directory of a filesystem. + * + * @param filePath file or directory to be removed + * @return a boolean representing success of failure + * @throws NoModificationAllowedException + * @throws InvalidModificationException + */ + private boolean remove(String filePath) throws NoModificationAllowedException, InvalidModificationException { + File fp = createFileObject(filePath); + + // You can't delete the root directory. + if (atRootDirectory(filePath)) { + throw new NoModificationAllowedException("You can't delete the root directory"); + } + + // You can't delete a directory that is not empty + if (fp.isDirectory() && fp.list().length > 0) { + throw new InvalidModificationException("You can't delete a directory that is not empty."); + } + + return fp.delete(); + } + + /** + * Creates or looks up a file. + * + * @param dirPath base directory + * @param fileName file/directory to lookup or create + * @param options specify whether to create or not + * @param directory if true look up directory, if false look up file + * @return a Entry object + * @throws FileExistsException + * @throws IOException + * @throws TypeMismatchException + * @throws EncodingException + * @throws JSONException + */ + private JSONObject getFile(String dirPath, String fileName, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException { + boolean create = false; + boolean exclusive = false; + if (options != null) { + create = options.optBoolean("create"); + if (create) { + exclusive = options.optBoolean("exclusive"); + } + } + + // Check for a ":" character in the file to line up with BB and iOS + if (fileName.contains(":")) { + throw new EncodingException("This file has a : in it's name"); + } + + File fp = createFileObject(dirPath, fileName); + + if (create) { + if (exclusive && fp.exists()) { + throw new FileExistsException("create/exclusive fails"); + } + if (directory) { + fp.mkdir(); + } else { + fp.createNewFile(); + } + if (!fp.exists()) { + throw new FileExistsException("create fails"); + } + } + else { + if (!fp.exists()) { + throw new FileNotFoundException("path does not exist"); + } + if (directory) { + if (fp.isFile()) { + throw new TypeMismatchException("path doesn't exist or is file"); + } + } else { + if (fp.isDirectory()) { + throw new TypeMismatchException("path doesn't exist or is directory"); + } + } + } + + // Return the directory + return getEntry(fp); + } + + /** + * If the path starts with a '/' just return that file object. If not construct the file + * object from the path passed in and the file name. + * + * @param dirPath root directory + * @param fileName new file name + * @return + */ + private File createFileObject(String dirPath, String fileName) { + File fp = null; + if (fileName.startsWith("/")) { + fp = new File(fileName); + } else { + dirPath = FileHelper.getRealPath(dirPath, cordova); + fp = new File(dirPath + File.separator + fileName); + } + return fp; + } + + /** + * Look up the parent DirectoryEntry containing this Entry. + * If this Entry is the root of its filesystem, its parent is itself. + * + * @param filePath + * @return + * @throws JSONException + */ + private JSONObject getParent(String filePath) throws JSONException { + filePath = FileHelper.getRealPath(filePath, cordova); + + if (atRootDirectory(filePath)) { + return getEntry(filePath); + } + return getEntry(new File(filePath).getParent()); + } + + /** + * Checks to see if we are at the root directory. Useful since we are + * not allow to delete this directory. + * + * @param filePath to directory + * @return true if we are at the root, false otherwise. + */ + private boolean atRootDirectory(String filePath) { + filePath = FileHelper.getRealPath(filePath, cordova); + + if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache") || + filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath()) || + filePath.equals("/data/data/" + cordova.getActivity().getPackageName())) { + return true; + } + return false; + } + + /** + * Create a File object from the passed in path + * + * @param filePath + * @return + */ + private File createFileObject(String filePath) { + filePath = FileHelper.getRealPath(filePath, cordova); + + File file = new File(filePath); + return file; + } + + /** + * Look up metadata about this entry. + * + * @param filePath to entry + * @return a long + * @throws FileNotFoundException + */ + private long getMetadata(String filePath) throws FileNotFoundException { + File file = createFileObject(filePath); + + if (!file.exists()) { + throw new FileNotFoundException("Failed to find file in getMetadata"); + } + + return file.lastModified(); + } + + /** + * Returns a File that represents the current state of the file that this FileEntry represents. + * + * @param filePath to entry + * @return returns a JSONObject represent a W3C File object + * @throws FileNotFoundException + * @throws JSONException + */ + private JSONObject getFileMetadata(String filePath) throws FileNotFoundException, JSONException { + File file = createFileObject(filePath); + + if (!file.exists()) { + throw new FileNotFoundException("File: " + filePath + " does not exist."); + } + + JSONObject metadata = new JSONObject(); + metadata.put("size", file.length()); + metadata.put("type", FileHelper.getMimeType(filePath, cordova)); + metadata.put("name", file.getName()); + metadata.put("fullPath", filePath); + metadata.put("lastModifiedDate", file.lastModified()); + + return metadata; + } + + /** + * Requests a filesystem in which to store application data. + * + * @param type of file system requested + * @return a JSONObject representing the file system + * @throws IOException + * @throws JSONException + */ + private JSONObject requestFileSystem(int type) throws IOException, JSONException { + JSONObject fs = new JSONObject(); + if (type == TEMPORARY) { + File fp; + fs.put("name", "temporary"); + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + fp = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/"); + // Create the cache dir if it doesn't exist. + fp.mkdirs(); + fs.put("root", getEntry(Environment.getExternalStorageDirectory().getAbsolutePath() + + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/")); + } else { + fp = new File("/data/data/" + cordova.getActivity().getPackageName() + "/cache/"); + // Create the cache dir if it doesn't exist. + fp.mkdirs(); + fs.put("root", getEntry("/data/data/" + cordova.getActivity().getPackageName() + "/cache/")); + } + } + else if (type == PERSISTENT) { + fs.put("name", "persistent"); + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + fs.put("root", getEntry(Environment.getExternalStorageDirectory())); + } else { + fs.put("root", getEntry("/data/data/" + cordova.getActivity().getPackageName())); + } + } + else { + throw new IOException("No filesystem of type requested"); + } + + return fs; + } + + /** + * Returns a JSON object representing the given File. + * + * @param file the File to convert + * @return a JSON representation of the given File + * @throws JSONException + */ + public static JSONObject getEntry(File file) throws JSONException { + JSONObject entry = new JSONObject(); + + entry.put("isFile", file.isFile()); + entry.put("isDirectory", file.isDirectory()); + entry.put("name", file.getName()); + entry.put("fullPath", "file://" + file.getAbsolutePath()); + // The file system can't be specified, as it would lead to an infinite loop. + // entry.put("filesystem", null); + + return entry; + } + + /** + * Returns a JSON Object representing a directory on the device's file system + * + * @param path to the directory + * @return + * @throws JSONException + */ + private JSONObject getEntry(String path) throws JSONException { + return getEntry(new File(path)); + } + + /** + * Read the contents of a file. + * This is done in a background thread; the result is sent to the callback. + * + * @param filename The name of the file. + * @param start Start position in the file. + * @param end End position to stop at (exclusive). + * @param callbackContext The context through which to send the result. + * @param encoding The encoding to return contents as. Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets) + * @param resultType The desired type of data to send to the callback. + * @return Contents of file. + */ + public void readFileAs(final String filename, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) { + this.cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + byte[] bytes = readAsBinaryHelper(filename, start, end); + + PluginResult result; + switch (resultType) { + case PluginResult.MESSAGE_TYPE_STRING: + result = new PluginResult(PluginResult.Status.OK, new String(bytes, encoding)); + break; + case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: + result = new PluginResult(PluginResult.Status.OK, bytes); + break; + case PluginResult.MESSAGE_TYPE_BINARYSTRING: + result = new PluginResult(PluginResult.Status.OK, bytes, true); + break; + default: // Base64. + String contentType = FileHelper.getMimeType(filename, cordova); + byte[] base64 = Base64.encode(bytes, Base64.NO_WRAP); + String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII"); + result = new PluginResult(PluginResult.Status.OK, s); + } + + callbackContext.sendPluginResult(result); + } catch (FileNotFoundException e) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR)); + } catch (IOException e) { + Log.d(LOG_TAG, e.getLocalizedMessage()); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR)); + } + } + }); + } + + /** + * Read the contents of a file as binary. + * This is done synchronously; the result is returned. + * + * @param filename The name of the file. + * @param start Start position in the file. + * @param end End position to stop at (exclusive). + * @return Contents of the file as a byte[]. + * @throws IOException + */ + private byte[] readAsBinaryHelper(String filename, int start, int end) throws IOException { + int numBytesToRead = end - start; + byte[] bytes = new byte[numBytesToRead]; + InputStream inputStream = FileHelper.getInputStreamFromUriString(filename, cordova); + int numBytesRead = 0; + + if (start > 0) { + inputStream.skip(start); + } + + while (numBytesToRead > 0 && (numBytesRead = inputStream.read(bytes, numBytesRead, numBytesToRead)) >= 0) { + numBytesToRead -= numBytesRead; + } + + return bytes; + } + + /** + * Write contents of file. + * + * @param filename The name of the file. + * @param data The contents of the file. + * @param offset The position to begin writing the file. + * @param isBinary True if the file contents are base64-encoded binary data + * @throws FileNotFoundException, IOException + * @throws NoModificationAllowedException + */ + /**/ + public long write(String filename, String data, int offset, boolean isBinary) throws FileNotFoundException, IOException, NoModificationAllowedException { + if (filename.startsWith("content://")) { + throw new NoModificationAllowedException("Couldn't write to file given its content URI"); + } + + filename = FileHelper.getRealPath(filename, cordova); + + boolean append = false; + if (offset > 0) { + this.truncateFile(filename, offset); + append = true; + } + + byte[] rawData; + if (isBinary) { + rawData = Base64.decode(data, Base64.DEFAULT); + } else { + rawData = data.getBytes(); + } + ByteArrayInputStream in = new ByteArrayInputStream(rawData); + try + { + FileOutputStream out = new FileOutputStream(filename, append); + byte buff[] = new byte[rawData.length]; + in.read(buff, 0, buff.length); + out.write(buff, 0, rawData.length); + out.flush(); + out.close(); + } + catch (NullPointerException e) + { + // This is a bug in the Android implementation of the Java Stack + NoModificationAllowedException realException = new NoModificationAllowedException(filename); + throw realException; + } + + return rawData.length; + } + + /** + * Truncate the file to size + * + * @param filename + * @param size + * @throws FileNotFoundException, IOException + * @throws NoModificationAllowedException + */ + private long truncateFile(String filename, long size) throws FileNotFoundException, IOException, NoModificationAllowedException { + if (filename.startsWith("content://")) { + throw new NoModificationAllowedException("Couldn't truncate file given its content URI"); + } + + filename = FileHelper.getRealPath(filename, cordova); + + RandomAccessFile raf = new RandomAccessFile(filename, "rw"); + try { + if (raf.length() >= size) { + FileChannel channel = raf.getChannel(); + channel.truncate(size); + return size; + } + + return raf.length(); + } finally { + raf.close(); + } + } +} diff --git a/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java b/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java new file mode 100644 index 00000000..8f6bec59 --- /dev/null +++ b/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java @@ -0,0 +1,30 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + + +package org.apache.cordova.file; + +@SuppressWarnings("serial") +public class InvalidModificationException extends Exception { + + public InvalidModificationException(String message) { + super(message); + } + +} diff --git a/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java b/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java new file mode 100644 index 00000000..627eafb5 --- /dev/null +++ b/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java @@ -0,0 +1,29 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova.file; + +@SuppressWarnings("serial") +public class NoModificationAllowedException extends Exception { + + public NoModificationAllowedException(String message) { + super(message); + } + +} diff --git a/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java b/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java new file mode 100644 index 00000000..1315f9a9 --- /dev/null +++ b/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java @@ -0,0 +1,30 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + + +package org.apache.cordova.file; + +@SuppressWarnings("serial") +public class TypeMismatchException extends Exception { + + public TypeMismatchException(String message) { + super(message); + } + +} diff --git a/plugins/org.apache.cordova.file/src/blackberry10/index.js b/plugins/org.apache.cordova.file/src/blackberry10/index.js new file mode 100644 index 00000000..914d9663 --- /dev/null +++ b/plugins/org.apache.cordova.file/src/blackberry10/index.js @@ -0,0 +1,10 @@ +module.exports = { + setSandbox : function (success, fail, args, env) { + require("lib/webview").setSandbox(JSON.parse(decodeURIComponent(args[0]))); + new PluginResult(args, env).noResult(false); + }, + + isSandboxed : function (success, fail, args, env) { + new PluginResult(args, env).ok(require("lib/webview").getSandbox() === "1"); + } +}; diff --git a/plugins/org.apache.cordova.file/src/ios/CDVFile.h b/plugins/org.apache.cordova.file/src/ios/CDVFile.h new file mode 100644 index 00000000..54d25f08 --- /dev/null +++ b/plugins/org.apache.cordova.file/src/ios/CDVFile.h @@ -0,0 +1,107 @@ +/* + 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 <Foundation/Foundation.h> +#import <Cordova/CDVPlugin.h> + +enum CDVFileError { + NO_ERROR = 0, + NOT_FOUND_ERR = 1, + SECURITY_ERR = 2, + ABORT_ERR = 3, + NOT_READABLE_ERR = 4, + ENCODING_ERR = 5, + NO_MODIFICATION_ALLOWED_ERR = 6, + INVALID_STATE_ERR = 7, + SYNTAX_ERR = 8, + INVALID_MODIFICATION_ERR = 9, + QUOTA_EXCEEDED_ERR = 10, + TYPE_MISMATCH_ERR = 11, + PATH_EXISTS_ERR = 12 +}; +typedef int CDVFileError; + +enum CDVFileSystemType { + TEMPORARY = 0, + PERSISTENT = 1 +}; +typedef int CDVFileSystemType; + +extern NSString* const kCDVAssetsLibraryPrefix; + +@interface CDVFile : CDVPlugin { + NSString* appDocsPath; + NSString* appLibraryPath; + NSString* appTempPath; + NSString* persistentPath; + NSString* temporaryPath; + + BOOL userHasAllowed; +} +- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath; +- (NSString*)getAppPath:(NSString*)pathFragment; +// -(NSString*) getFullPath: (NSString*)pathFragment; +- (void)requestFileSystem:(CDVInvokedUrlCommand*)command; +- (NSDictionary*)getDirectoryEntry:(NSString*)fullPath isDirectory:(BOOL)isDir; +- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command; +- (void)getDirectory:(CDVInvokedUrlCommand*)command; +- (void)getFile:(CDVInvokedUrlCommand*)command; +- (void)getParent:(CDVInvokedUrlCommand*)command; +- (void)getMetadata:(CDVInvokedUrlCommand*)command; +- (void)removeRecursively:(CDVInvokedUrlCommand*)command; +- (void)remove:(CDVInvokedUrlCommand*)command; +- (CDVPluginResult*)doRemove:(NSString*)fullPath; +- (void)copyTo:(CDVInvokedUrlCommand*)command; +- (void)moveTo:(CDVInvokedUrlCommand*)command; +- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest; +- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy; +// - (void) toURI:(CDVInvokedUrlCommand*)command; +- (void)getFileMetadata:(CDVInvokedUrlCommand*)command; +- (void)readEntries:(CDVInvokedUrlCommand*)command; + +- (void)readAsText:(CDVInvokedUrlCommand*)command; +- (void)readAsDataURL:(CDVInvokedUrlCommand*)command; +- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command; +- (NSString*)getMimeTypeFromPath:(NSString*)fullPath; +- (void)write:(CDVInvokedUrlCommand*)command; +- (void)testFileExists:(CDVInvokedUrlCommand*)command; +- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command; +// - (void) createDirectory:(CDVInvokedUrlCommand*)command; +// - (void) deleteDirectory:(CDVInvokedUrlCommand*)command; +// - (void) deleteFile:(CDVInvokedUrlCommand*)command; +- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command; +- (void)truncate:(CDVInvokedUrlCommand*)command; + +// - (BOOL) fileExists:(NSString*)fileName; +// - (BOOL) directoryExists:(NSString*)dirName; +- (void)writeToFile:(NSString*)fileName withData:(NSData*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId; +- (void)writeToFile:(NSString*)fileName withString:(NSString*)data encoding:(NSStringEncoding)encoding append:(BOOL)shouldAppend callback:(NSString*)callbackId; +- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos; + +@property (nonatomic, strong) NSString* appDocsPath; +@property (nonatomic, strong) NSString* appLibraryPath; +@property (nonatomic, strong) NSString* appTempPath; +@property (nonatomic, strong) NSString* persistentPath; +@property (nonatomic, strong) NSString* temporaryPath; +@property BOOL userHasAllowed; + +@end + +#define kW3FileTemporary @"temporary" +#define kW3FilePersistent @"persistent" diff --git a/plugins/org.apache.cordova.file/src/ios/CDVFile.m b/plugins/org.apache.cordova.file/src/ios/CDVFile.m new file mode 100644 index 00000000..6b2602d3 --- /dev/null +++ b/plugins/org.apache.cordova.file/src/ios/CDVFile.m @@ -0,0 +1,1417 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVFile.h" +#import <Cordova/CDV.h> +#import <AssetsLibrary/ALAsset.h> +#import <AssetsLibrary/ALAssetRepresentation.h> +#import <AssetsLibrary/ALAssetsLibrary.h> +#import <MobileCoreServices/MobileCoreServices.h> +#import <sys/xattr.h> + +extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import)); + +#ifndef __IPHONE_5_1 + NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey"; +#endif + +NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; + +@implementation CDVFile + +@synthesize appDocsPath, appLibraryPath, appTempPath, persistentPath, temporaryPath, userHasAllowed; + +- (id)initWithWebView:(UIWebView*)theWebView +{ + self = (CDVFile*)[super initWithWebView:theWebView]; + if (self) { + // get the documents directory path + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + self.appDocsPath = [paths objectAtIndex:0]; + + paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + self.appLibraryPath = [paths objectAtIndex:0]; + + self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory() + + self.persistentPath = [NSString stringWithFormat:@"/%@", [self.appDocsPath lastPathComponent]]; + self.temporaryPath = [NSString stringWithFormat:@"/%@", [self.appTempPath lastPathComponent]]; + // NSLog(@"docs: %@ - temp: %@", self.appDocsPath, self.appTempPath); + } + + return self; +} + +- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath +{ + NSFileManager* fMgr = [[NSFileManager alloc] init]; + + NSError* __autoreleasing pError = nil; + + NSDictionary* pDict = [fMgr attributesOfFileSystemForPath:appPath error:&pError]; + NSNumber* pNumAvail = (NSNumber*)[pDict objectForKey:NSFileSystemFreeSize]; + + return pNumAvail; +} + +// figure out if the pathFragment represents a persistent of temporary directory and return the full application path. +// returns nil if path is not persistent or temporary +- (NSString*)getAppPath:(NSString*)pathFragment +{ + NSString* appPath = nil; + NSRange rangeP = [pathFragment rangeOfString:self.persistentPath]; + NSRange rangeT = [pathFragment rangeOfString:self.temporaryPath]; + + if ((rangeP.location != NSNotFound) && (rangeT.location != NSNotFound)) { + // we found both in the path, return whichever one is first + if (rangeP.length < rangeT.length) { + appPath = self.appDocsPath; + } else { + appPath = self.appTempPath; + } + } else if (rangeP.location != NSNotFound) { + appPath = self.appDocsPath; + } else if (rangeT.location != NSNotFound) { + appPath = self.appTempPath; + } + return appPath; +} + +/* get the full path to this resource + * IN + * NSString* pathFragment - full Path from File or Entry object (includes system path info) + * OUT + * NSString* fullPath - full iOS path to this resource, nil if not found + */ + +/* Was here in order to NOT have to return full path, but W3C synchronous DirectoryEntry.toURI() killed that idea since I can't call into iOS to + * resolve full URI. Leaving this code here in case W3C spec changes. +-(NSString*) getFullPath: (NSString*)pathFragment +{ + return pathFragment; + NSString* fullPath = nil; + NSString *appPath = [ self getAppPath: pathFragment]; + if (appPath){ + + // remove last component from appPath + NSRange range = [appPath rangeOfString:@"/" options: NSBackwardsSearch]; + NSString* newPath = [appPath substringToIndex:range.location]; + // add pathFragment to get test Path + fullPath = [newPath stringByAppendingPathComponent:pathFragment]; + } + return fullPath; +} */ + +/* Request the File System info + * + * IN: + * arguments[0] - type (number as string) + * TEMPORARY = 0, PERSISTENT = 1; + * arguments[1] - size + * + * OUT: + * Dictionary representing FileSystem object + * name - the human readable directory name + * root = DirectoryEntry object + * bool isDirectory + * bool isFile + * string name + * string fullPath + * fileSystem = FileSystem object - !! ignored because creates circular reference !! + */ + +- (void)requestFileSystem:(CDVInvokedUrlCommand*)command +{ + NSArray* arguments = command.arguments; + + // arguments + NSString* strType = [arguments objectAtIndex:0]; + unsigned long long size = [[arguments objectAtIndex:1] longLongValue]; + + int type = [strType intValue]; + CDVPluginResult* result = nil; + + if (type > 1) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR]; + NSLog(@"iOS only supports TEMPORARY and PERSISTENT file systems"); + } else { + // NSString* fullPath = [NSString stringWithFormat:@"/%@", (type == 0 ? [self.appTempPath lastPathComponent] : [self.appDocsPath lastPathComponent])]; + NSString* fullPath = (type == 0 ? self.appTempPath : self.appDocsPath); + // check for avail space for size request + NSNumber* pNumAvail = [self checkFreeDiskSpace:fullPath]; + // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]); + if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR]; + } else { + NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2]; + [fileSystem setObject:(type == TEMPORARY ? kW3FileTemporary : kW3FilePersistent) forKey:@"name"]; + NSDictionary* dirEntry = [self getDirectoryEntry:fullPath isDirectory:YES]; + [fileSystem setObject:dirEntry forKey:@"root"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; + } + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* Creates a dictionary representing an Entry Object + * + * IN: + * NSString* fullPath of the entry + * FileSystem type + * BOOL isDirectory - YES if this is a directory, NO if is a file + * OUT: + * NSDictionary* + Entry object + * bool as NSNumber isDirectory + * bool as NSNumber isFile + * NSString* name - last part of path + * NSString* fullPath + * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!! + */ +- (NSDictionary*)getDirectoryEntry:(NSString*)fullPath isDirectory:(BOOL)isDir +{ + NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:4]; + NSString* lastPart = [fullPath lastPathComponent]; + + [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"]; + [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"]; + // NSURL* fileUrl = [NSURL fileURLWithPath:fullPath]; + // [dirEntry setObject: [fileUrl absoluteString] forKey: @"fullPath"]; + [dirEntry setObject:fullPath forKey:@"fullPath"]; + [dirEntry setObject:lastPart forKey:@"name"]; + + return dirEntry; +} + +/* + * Given a URI determine the File System information associated with it and return an appropriate W3C entry object + * IN + * NSString* fileURI - currently requires full file URI + * OUT + * Entry object + * bool isDirectory + * bool isFile + * string name + * string fullPath + * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!! + */ +- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* inputUri = [command.arguments objectAtIndex:0]; + + // don't know if string is encoded or not so unescape + NSString* cleanUri = [inputUri stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + // now escape in order to create URL + NSString* strUri = [cleanUri stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSURL* testUri = [NSURL URLWithString:strUri]; + CDVPluginResult* result = nil; + + if (!testUri) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; + } else if ([testUri isFileURL]) { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSString* path = [testUri path]; + // NSLog(@"url path: %@", path); + BOOL isDir = NO; + // see if exists and is file or dir + BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir]; + if (bExists) { + // see if it contains docs path or temp path + NSString* foundFullPath = nil; + if ([path hasPrefix:self.appDocsPath]) { + foundFullPath = self.appDocsPath; + } else if ([path hasPrefix:self.appTempPath]) { + foundFullPath = self.appTempPath; + } + + if (foundFullPath == nil) { + // error SECURITY_ERR - not one of the two paths types supported + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SECURITY_ERR]; + } else { + NSDictionary* fileSystem = [self getDirectoryEntry:path isDirectory:isDir]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; + } + } else { + // return NOT_FOUND_ERR + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } + } else if ([strUri hasPrefix:@"assets-library://"]) { + NSDictionary* fileSystem = [self getDirectoryEntry:strUri isDirectory:NO]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; + } + + if (result != nil) { + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } +} + +/* Part of DirectoryEntry interface, creates or returns the specified directory + * IN: + * NSString* fullPath - full path for this directory + * NSString* path - directory to be created/returned; may be full path or relative path + * NSDictionary* - Flags object + * boolean as NSNumber create - + * if create is true and directory does not exist, create dir and return directory entry + * if create is true and exclusive is true and directory does exist, return error + * if create is false and directory does not exist, return error + * if create is false and the path represents a file, return error + * boolean as NSNumber exclusive - used in conjunction with create + * if exclusive is true and create is true - specifies failure if directory already exists + * + * + */ +- (void)getDirectory:(CDVInvokedUrlCommand*)command +{ + NSMutableArray* arguments = [NSMutableArray arrayWithArray:command.arguments]; + NSMutableDictionary* options = nil; + + if ([arguments count] >= 3) { + options = [arguments objectAtIndex:2 withDefault:nil]; + } + // add getDir to options and call getFile() + if (options != nil) { + options = [NSMutableDictionary dictionaryWithDictionary:options]; + } else { + options = [NSMutableDictionary dictionaryWithCapacity:1]; + } + [options setObject:[NSNumber numberWithInt:1] forKey:@"getDir"]; + if ([arguments count] >= 3) { + [arguments replaceObjectAtIndex:2 withObject:options]; + } else { + [arguments addObject:options]; + } + CDVInvokedUrlCommand* subCommand = + [[CDVInvokedUrlCommand alloc] initWithArguments:arguments + callbackId:command.callbackId + className:command.className + methodName:command.methodName]; + + [self getFile:subCommand]; +} + +/* Part of DirectoryEntry interface, creates or returns the specified file + * IN: + * NSString* fullPath - full path for this file + * NSString* path - file to be created/returned; may be full path or relative path + * NSDictionary* - Flags object + * boolean as NSNumber create - + * if create is true and file does not exist, create file and return File entry + * if create is true and exclusive is true and file does exist, return error + * if create is false and file does not exist, return error + * if create is false and the path represents a directory, return error + * boolean as NSNumber exclusive - used in conjunction with create + * if exclusive is true and create is true - specifies failure if file already exists + * + * + */ +- (void)getFile:(CDVInvokedUrlCommand*)command +{ + // arguments are URL encoded + NSString* fullPath = [command.arguments objectAtIndex:0]; + NSString* requestedPath = [command.arguments objectAtIndex:1]; + NSDictionary* options = [command.arguments objectAtIndex:2 withDefault:nil]; + + // return unsupported result for assets-library URLs + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + CDVPluginResult* result = nil; + BOOL bDirRequest = NO; + BOOL create = NO; + BOOL exclusive = NO; + int errorCode = 0; // !!! risky - no error code currently defined for 0 + + if ([options valueForKeyIsNumber:@"create"]) { + create = [(NSNumber*)[options valueForKey:@"create"] boolValue]; + } + if ([options valueForKeyIsNumber:@"exclusive"]) { + exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue]; + } + + if ([options valueForKeyIsNumber:@"getDir"]) { + // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method + bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue]; + } + // see if the requested path has invalid characters - should we be checking for more than just ":"? + if ([requestedPath rangeOfString:@":"].location != NSNotFound) { + errorCode = ENCODING_ERR; + } else { + // was full or relative path provided? + NSRange range = [requestedPath rangeOfString:fullPath]; + BOOL bIsFullPath = range.location != NSNotFound; + + NSString* reqFullPath = nil; + + if (!bIsFullPath) { + reqFullPath = [fullPath stringByAppendingPathComponent:requestedPath]; + } else { + reqFullPath = requestedPath; + } + + // NSLog(@"reqFullPath = %@", reqFullPath); + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir; + BOOL bExists = [fileMgr fileExistsAtPath:reqFullPath isDirectory:&bIsDir]; + if (bExists && (create == NO) && (bIsDir == !bDirRequest)) { + // path exists and is of requested type - return TYPE_MISMATCH_ERR + errorCode = TYPE_MISMATCH_ERR; + } else if (!bExists && (create == NO)) { + // path does not exist and create is false - return NOT_FOUND_ERR + errorCode = NOT_FOUND_ERR; + } else if (bExists && (create == YES) && (exclusive == YES)) { + // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR + errorCode = PATH_EXISTS_ERR; + } else { + // if bExists and create == YES - just return data + // if bExists and create == NO - just return data + // if !bExists and create == YES - create and return data + BOOL bSuccess = YES; + NSError __autoreleasing* pError = nil; + if (!bExists && (create == YES)) { + if (bDirRequest) { + // create the dir + bSuccess = [fileMgr createDirectoryAtPath:reqFullPath withIntermediateDirectories:NO attributes:nil error:&pError]; + } else { + // create the empty file + bSuccess = [fileMgr createFileAtPath:reqFullPath contents:nil attributes:nil]; + } + } + if (!bSuccess) { + errorCode = ABORT_ERR; + if (pError) { + NSLog(@"error creating directory: %@", [pError localizedDescription]); + } + } else { + // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]); + // file existed or was created + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:reqFullPath isDirectory:bDirRequest]]; + } + } // are all possible conditions met? + } + + if (errorCode > 0) { + // create error callback + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* + * Look up the parent Entry containing this Entry. + * If this Entry is the root of its filesystem, its parent is itself. + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * NSMutableDictionary* options + * empty + */ +- (void)getParent:(CDVInvokedUrlCommand*)command +{ + // arguments are URL encoded + NSString* fullPath = [command.arguments objectAtIndex:0]; + + // we don't (yet?) support getting the parent of an asset + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + CDVPluginResult* result = nil; + NSString* newPath = nil; + + if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { + // return self + newPath = fullPath; + } else { + // since this call is made from an existing Entry object - the parent should already exist so no additional error checking + // remove last component and return Entry + NSRange range = [fullPath rangeOfString:@"/" options:NSBackwardsSearch]; + newPath = [fullPath substringToIndex:range.location]; + } + + if (newPath) { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir; + BOOL bExists = [fileMgr fileExistsAtPath:newPath isDirectory:&bIsDir]; + if (bExists) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:newPath isDirectory:bIsDir]]; + } + } + if (!result) { + // invalid path or file does not exist + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* + * get MetaData of entry + * Currently MetaData only includes modificationTime. + */ +- (void)getMetadata:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + __block CDVPluginResult* result = nil; + + if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { + if (asset) { + // We have the asset! Retrieve the metadata and send it off. + NSDate* date = [asset valueForProperty:ALAssetPropertyDate]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:[date timeIntervalSince1970] * 1000]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } else { + // We couldn't find the asset. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }; + // TODO(maxw): Consider making this a class variable since it's the same every time. + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; + return; + } + + NSString* testPath = argPath; // [self getFullPath: argPath]; + + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSError* __autoreleasing error = nil; + + NSDictionary* fileAttribs = [fileMgr attributesOfItemAtPath:testPath error:&error]; + + if (fileAttribs) { + NSDate* modDate = [fileAttribs fileModificationDate]; + if (modDate) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:[modDate timeIntervalSince1970] * 1000]; + } + } else { + // didn't get fileAttribs + CDVFileError errorCode = ABORT_ERR; + NSLog(@"error getting metadata: %@", [error localizedDescription]); + if ([error code] == NSFileNoSuchFileError) { + errorCode = NOT_FOUND_ERR; + } + // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode]; + } + if (!result) { + // invalid path or file does not exist + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* + * set MetaData of entry + * Currently we only support "com.apple.MobileBackup" (boolean) + */ +- (void)setMetadata:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* filePath = [command.arguments objectAtIndex:0]; + NSDictionary* options = [command.arguments objectAtIndex:1 withDefault:nil]; + CDVPluginResult* result = nil; + BOOL ok = NO; + + // setMetadata doesn't make sense for asset library files + if (![filePath hasPrefix:kCDVAssetsLibraryPrefix]) { + // we only care about this iCloud key for now. + // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute) + NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup"; + id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey]; + + if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) { + if (IsAtLeastiOSVersion(@"5.1")) { + NSURL* url = [NSURL fileURLWithPath:filePath]; + NSError* __autoreleasing error = nil; + + ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error]; + } else { // below 5.1 (deprecated - only really supported in 5.01) + u_int8_t value = [iCloudBackupExtendedAttributeValue intValue]; + if (value == 0) { // remove the attribute (allow backup, the default) + ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0); + } else { // set the attribute (skip backup) + ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0); + } + } + } + } + + if (ok) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* removes the directory or file entry + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * + * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir + * returns INVALID_MODIFICATION_ERR if is non-empty dir or asset library file + * returns NOT_FOUND_ERR if file or dir is not found +*/ +- (void)remove:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* fullPath = [command.arguments objectAtIndex:0]; + CDVPluginResult* result = nil; + CDVFileError errorCode = 0; // !! 0 not currently defined + + // return error for assets-library URLs + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + errorCode = INVALID_MODIFICATION_ERR; + } else if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { + // error if try to remove top level (documents or tmp) dir + errorCode = NO_MODIFICATION_ALLOWED_ERR; + } else { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir = NO; + BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir]; + if (!bExists) { + errorCode = NOT_FOUND_ERR; + } + if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fullPath error:nil] count] != 0)) { + // dir is not empty + errorCode = INVALID_MODIFICATION_ERR; + } + } + if (errorCode > 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } else { + // perform actual remove + result = [self doRemove:fullPath]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* recursively removes the directory + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * + * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir + * returns NOT_FOUND_ERR if file or dir is not found + */ +- (void)removeRecursively:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* fullPath = [command.arguments objectAtIndex:0]; + + // return unsupported result for assets-library URLs + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + CDVPluginResult* result = nil; + + // error if try to remove top level (documents or tmp) dir + if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; + } else { + result = [self doRemove:fullPath]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* remove the file or directory (recursively) + * IN: + * NSString* fullPath - the full path to the file or directory to be removed + * NSString* callbackId + * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling + */ + +- (CDVPluginResult*)doRemove:(NSString*)fullPath +{ + CDVPluginResult* result = nil; + BOOL bSuccess = NO; + NSError* __autoreleasing pError = nil; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + + @try { + bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError]; + if (bSuccess) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else { + // see if we can give a useful error + CDVFileError errorCode = ABORT_ERR; + NSLog(@"error getting metadata: %@", [pError localizedDescription]); + if ([pError code] == NSFileNoSuchFileError) { + errorCode = NOT_FOUND_ERR; + } else if ([pError code] == NSFileWriteNoPermissionError) { + errorCode = NO_MODIFICATION_ALLOWED_ERR; + } + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + } @catch(NSException* e) { // NSInvalidArgumentException if path is . or .. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR]; + } + + return result; +} + +- (void)copyTo:(CDVInvokedUrlCommand*)command +{ + [self doCopyMove:command isCopy:YES]; +} + +- (void)moveTo:(CDVInvokedUrlCommand*)command +{ + [self doCopyMove:command isCopy:NO]; +} + +/** + * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name, + * or attempted to copy a directory into a directory that it contains directly or indirectly. + * + * IN: + * NSString* srcDir + * NSString* destinationDir + * OUT: + * YES copy/ move is allows + * NO move is onto itself + */ +- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest +{ + // This weird test is to determine if we are copying or moving a directory into itself. + // Copy /Documents/myDir to /Documents/myDir-backup is okay but + // Copy /Documents/myDir to /Documents/myDir/backup not okay + BOOL copyOK = YES; + NSRange range = [dest rangeOfString:src]; + + if (range.location != NSNotFound) { + NSRange testRange = {range.length - 1, ([dest length] - range.length)}; + NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange]; + if (resultRange.location != NSNotFound) { + copyOK = NO; + } + } + return copyOK; +} + +/* Copy/move a file or directory to a new location + * IN: + * NSArray* arguments + * 0 - NSString* fullPath of entry + * 1 - NSString* newName the new name of the entry, defaults to the current name + * NSMutableDictionary* options - DirectoryEntry to which to copy the entry + * BOOL - bCopy YES if copy, NO if move + * + */ +- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy +{ + NSArray* arguments = command.arguments; + + // arguments + NSString* srcFullPath = [arguments objectAtIndex:0]; + NSString* destRootPath = [arguments objectAtIndex:1]; + // optional argument + NSString* newName = ([arguments count] > 2) ? [arguments objectAtIndex:2] : [srcFullPath lastPathComponent]; // use last component from appPath if new name not provided + + __block CDVPluginResult* result = nil; + CDVFileError errCode = 0; // !! Currently 0 is not defined, use this to signal error !! + + /*NSString* destRootPath = nil; + NSString* key = @"fullPath"; + if([options valueForKeyIsString:key]){ + destRootPath = [options objectForKey:@"fullPath"]; + }*/ + + if (!destRootPath) { + // no destination provided + errCode = NOT_FOUND_ERR; + } else if ([newName rangeOfString:@":"].location != NSNotFound) { + // invalid chars in new name + errCode = ENCODING_ERR; + } else { + NSString* newFullPath = [destRootPath stringByAppendingPathComponent:newName]; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + if ([newFullPath isEqualToString:srcFullPath]) { + // source and destination can not be the same + errCode = INVALID_MODIFICATION_ERR; + } else if ([srcFullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + if (bCopy) { + // Copying (as opposed to moving) an assets library file is okay. + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { + if (asset) { + // We have the asset! Get the data and try to copy it over. + if (![fileMgr fileExistsAtPath:destRootPath]) { + // The destination directory doesn't exist. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } else if ([fileMgr fileExistsAtPath:newFullPath]) { + // A file already exists at the destination path. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:PATH_EXISTS_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + // We're good to go! Write the file to the new destination. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + Byte* buffer = (Byte*)malloc([assetRepresentation size]); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; + NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + [data writeToFile:newFullPath atomically:YES]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:newFullPath isDirectory:NO]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } else { + // We couldn't find the asset. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:srcFullPath] resultBlock:resultBlock failureBlock:failureBlock]; + return; + } else { + // Moving an assets library file is not doable, since we can't remove it. + errCode = INVALID_MODIFICATION_ERR; + } + } else { + BOOL bSrcIsDir = NO; + BOOL bDestIsDir = NO; + BOOL bNewIsDir = NO; + BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir]; + BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir]; + BOOL bNewExists = [fileMgr fileExistsAtPath:newFullPath isDirectory:&bNewIsDir]; + if (!bSrcExists || !bDestExists) { + // the source or the destination root does not exist + errCode = NOT_FOUND_ERR; + } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) { + // can't copy/move dir to file + errCode = INVALID_MODIFICATION_ERR; + } else { // no errors yet + NSError* __autoreleasing error = nil; + BOOL bSuccess = NO; + if (bCopy) { + if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFullPath] /*[newFullPath hasPrefix:srcFullPath]*/) { + // can't copy dir into self + errCode = INVALID_MODIFICATION_ERR; + } else if (bNewExists) { + // the full destination should NOT already exist if a copy + errCode = PATH_EXISTS_ERR; + } else { + bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFullPath error:&error]; + } + } else { // move + // iOS requires that destination must not exist before calling moveTo + // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents + // + if (!bSrcIsDir && (bNewExists && bNewIsDir)) { + // can't move a file to directory + errCode = INVALID_MODIFICATION_ERR; + } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFullPath]) { // [newFullPath hasPrefix:srcFullPath]){ + // can't move a dir into itself + errCode = INVALID_MODIFICATION_ERR; + } else if (bNewExists) { + if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFullPath error:NULL] count] != 0)) { + // can't move dir to a dir that is not empty + errCode = INVALID_MODIFICATION_ERR; + newFullPath = nil; // so we won't try to move + } else { + // remove destination so can perform the moveItemAtPath + bSuccess = [fileMgr removeItemAtPath:newFullPath error:NULL]; + if (!bSuccess) { + errCode = INVALID_MODIFICATION_ERR; // is this the correct error? + newFullPath = nil; + } + } + } else if (bNewIsDir && [newFullPath hasPrefix:srcFullPath]) { + // can't move a directory inside itself or to any child at any depth; + errCode = INVALID_MODIFICATION_ERR; + newFullPath = nil; + } + + if (newFullPath != nil) { + bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFullPath error:&error]; + } + } + if (bSuccess) { + // should verify it is there and of the correct type??? + NSDictionary* newEntry = [self getDirectoryEntry:newFullPath isDirectory:bSrcIsDir]; // should be the same type as source + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry]; + } else { + errCode = INVALID_MODIFICATION_ERR; // catch all + if (error) { + if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) { + errCode = NOT_READABLE_ERR; + } else if ([error code] == NSFileWriteOutOfSpaceError) { + errCode = QUOTA_EXCEEDED_ERR; + } else if ([error code] == NSFileWriteNoPermissionError) { + errCode = NO_MODIFICATION_ALLOWED_ERR; + } + } + } + } + } + } + if (errCode > 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +/* return the URI to the entry + * IN: + * NSArray* arguments + * 0 - NSString* fullPath of entry + * 1 - desired mime type of entry - ignored - always returns file:// + */ + +/* Not needed since W3C toURI is synchronous. Leaving code here for now in case W3C spec changes..... +- (void) toURI:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSString* argPath = [command.arguments objectAtIndex:0]; + PluginResult* result = nil; + NSString* jsString = nil; + + NSString* fullPath = [self getFullPath: argPath]; + if (fullPath) { + // do we need to make sure the file actually exists? + // create file uri + NSString* strUri = [fullPath stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; + NSURL* fileUrl = [NSURL fileURLWithPath:strUri]; + if (fileUrl) { + result = [PluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: [fileUrl absoluteString]]; + jsString = [result toSuccessCallbackString:callbackId]; + } // else NOT_FOUND_ERR + } + if(!jsString) { + // was error + result = [PluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt: NOT_FOUND_ERR cast: @"window.localFileSystem._castError"]; + jsString = [result toErrorCallbackString:callbackId]; + } + + [self writeJavascript:jsString]; +}*/ +- (void)getFileMetadata:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + + __block CDVPluginResult* result = nil; + + NSString* fullPath = argPath; // [self getFullPath: argPath]; + + if (fullPath) { + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { + if (asset) { + // We have the asset! Populate the dictionary and send it off. + NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[assetRepresentation size]] forKey:@"size"]; + [fileInfo setObject:argPath forKey:@"fullPath"]; + NSString* filename = [assetRepresentation filename]; + [fileInfo setObject:filename forKey:@"name"]; + [fileInfo setObject:[self getMimeTypeFromPath:filename] forKey:@"type"]; + NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate]; + NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000]; + [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } else { + // We couldn't find the asset. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; + return; + } else { + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir = NO; + // make sure it exists and is not a directory + BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir]; + if (!bExists || bIsDir) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } else { + // create dictionary of file info + NSError* __autoreleasing error = nil; + NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:&error]; + NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; + [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[fileAttrs fileSize]] forKey:@"size"]; + [fileInfo setObject:argPath forKey:@"fullPath"]; + [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping + [fileInfo setObject:[argPath lastPathComponent] forKey:@"name"]; + NSDate* modDate = [fileAttrs fileModificationDate]; + NSNumber* msDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000]; + [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; + } + } + } + if (!result) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_INSTANTIATION_EXCEPTION]; + } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (void)readEntries:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* fullPath = [command.arguments objectAtIndex:0]; + + // return unsupported result for assets-library URLs + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + CDVPluginResult* result = nil; + + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + NSError* __autoreleasing error = nil; + NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fullPath error:&error]; + + if (contents) { + NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1]; + if ([contents count] > 0) { + // create an Entry (as JSON) for each file/dir + for (NSString* name in contents) { + // see if is dir or file + NSString* entryPath = [fullPath stringByAppendingPathComponent:name]; + BOOL bIsDir = NO; + [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir]; + NSDictionary* entryDict = [self getDirectoryEntry:entryPath isDirectory:bIsDir]; + [entries addObject:entryDict]; + } + } + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries]; + } else { + // assume not found but could check error for more specific error conditions + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (void)readFileWithPath:(NSString*)path start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback +{ + if (path == nil) { + callback(nil, nil, SYNTAX_ERR); + } else { + [self.commandDelegate runInBackground:^ { + if ([path hasPrefix:kCDVAssetsLibraryPrefix]) { + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { + if (asset) { + // We have the asset! Get the data and send it off. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + Byte* buffer = (Byte*)malloc([assetRepresentation size]); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; + NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); + + callback(data, MIMEType, NO_ERROR); + } else { + callback(nil, nil, NOT_FOUND_ERR); + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + NSLog(@"Error: %@", error); + callback(nil, nil, SECURITY_ERR); + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:path] resultBlock:resultBlock failureBlock:failureBlock]; + } else { + NSString* mimeType = [self getMimeTypeFromPath:path]; + if (mimeType == nil) { + mimeType = @"*/*"; + } + NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path]; + if (start > 0) { + [file seekToFileOffset:start]; + } + + NSData* readData; + if (end < 0) { + readData = [file readDataToEndOfFile]; + } else { + readData = [file readDataOfLength:(end - start)]; + } + + [file closeFile]; + + callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR); + } + }]; + } +} + +/* read and return file data + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * 1 - NSString* encoding + * 2 - NSString* start + * 3 - NSString* end + */ +- (void)readAsText:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* path = [command argumentAtIndex:0]; + NSString* encoding = [command argumentAtIndex:1]; + NSInteger start = [[command argumentAtIndex:2] integerValue]; + NSInteger end = [[command argumentAtIndex:3] integerValue]; + + // TODO: implement + if ([@"UTF-8" caseInsensitiveCompare : encoding] != NSOrderedSame) { + NSLog(@"Only UTF-8 encodings are currently supported by readAsText"); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + NSString* str = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSUTF8StringEncoding freeWhenDone:NO]; + // Check that UTF8 conversion did not fail. + if (str != nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:str]; + result.associatedObject = data; + } else { + errorCode = ENCODING_ERR; + } + } + if (result == nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +/* Read content of text file and return as base64 encoded data url. + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * 1 - NSString* start + * 2 - NSString* end + * + * Determines the mime type from the file extension, returns ENCODING_ERR if mimetype can not be determined. + */ + +- (void)readAsDataURL:(CDVInvokedUrlCommand*)command +{ + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + // TODO: Would be faster to base64 encode directly to the final string. + NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [data base64EncodedString]]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +/* Read content of text file and return as an arraybuffer + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * 1 - NSString* start + * 2 - NSString* end + */ + +- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command +{ + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)readAsBinaryString:(CDVInvokedUrlCommand*)command +{ + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + NSString* payload = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSASCIIStringEncoding freeWhenDone:NO]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload]; + result.associatedObject = data; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +/* helper function to get the mimeType from the file extension + * IN: + * NSString* fullPath - filename (may include path) + * OUT: + * NSString* the mime type as type/subtype. nil if not able to determine + */ +- (NSString*)getMimeTypeFromPath:(NSString*)fullPath +{ + NSString* mimeType = nil; + + if (fullPath) { + CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL); + if (typeId) { + mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType); + if (!mimeType) { + // special case for m4a + if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) { + mimeType = @"audio/mp4"; + } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) { + mimeType = @"audio/wav"; + } + } + CFRelease(typeId); + } + } + return mimeType; +} + +- (void)truncate:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + unsigned long long pos = (unsigned long long)[[command.arguments objectAtIndex:1] longLongValue]; + + // assets-library files can't be truncated + if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + NSString* appFile = argPath; // [self getFullPath:argPath]; + + unsigned long long newPos = [self truncateFile:appFile atPosition:pos]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:newPos]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos +{ + unsigned long long newPos = 0UL; + + NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath]; + + if (file) { + [file truncateFileAtOffset:(unsigned long long)pos]; + newPos = [file offsetInFile]; + [file synchronizeFile]; + [file closeFile]; + } + return newPos; +} + +/* write + * IN: + * NSArray* arguments + * 0 - NSString* file path to write to + * 1 - NSString* or NSData* data to write + * 2 - NSNumber* position to begin writing + */ +- (void)write:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSArray* arguments = command.arguments; + + // arguments + NSString* argPath = [arguments objectAtIndex:0]; + id argData = [arguments objectAtIndex:1]; + unsigned long long pos = (unsigned long long)[[arguments objectAtIndex:2] longLongValue]; + + // text can't be written into assets-library files + if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + + NSString* fullPath = argPath; // [self getFullPath:argPath]; + + [self truncateFile:fullPath atPosition:pos]; + + if ([argData isKindOfClass:[NSString class]]) { + [self writeToFile:fullPath withString:argData encoding:NSUTF8StringEncoding append:YES callback:callbackId]; + } else if ([argData isKindOfClass:[NSData class]]) { + [self writeToFile:fullPath withData:argData append:YES callback:callbackId]; + } else { + CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + +} + +- (void)writeToFile:(NSString*)filePath withData:(NSData*)encData append:(BOOL)shouldAppend callback:(NSString*)callbackId +{ + CDVPluginResult* result = nil; + CDVFileError errCode = INVALID_MODIFICATION_ERR; + int bytesWritten = 0; + + if (filePath) { + NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend]; + if (fileStream) { + NSUInteger len = [encData length]; + [fileStream open]; + + bytesWritten = [fileStream write:[encData bytes] maxLength:len]; + + [fileStream close]; + if (bytesWritten > 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten]; + // } else { + // can probably get more detailed error info via [fileStream streamError] + // errCode already set to INVALID_MODIFICATION_ERR; + // bytesWritten = 0; // may be set to -1 on error + } + } // else fileStream not created return INVALID_MODIFICATION_ERR + } else { + // invalid filePath + errCode = NOT_FOUND_ERR; + } + if (!result) { + // was an error + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; + } + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; +} + +- (void)writeToFile:(NSString*)filePath withString:(NSString*)stringData encoding:(NSStringEncoding)encoding append:(BOOL)shouldAppend callback:(NSString*)callbackId +{ + [self writeToFile:filePath withData:[stringData dataUsingEncoding:encoding allowLossyConversion:YES] append:shouldAppend callback:callbackId]; +} + +- (void)testFileExists:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + + // Get the file manager + NSFileManager* fMgr = [NSFileManager defaultManager]; + NSString* appFile = argPath; // [ self getFullPath: argPath]; + + BOOL bExists = [fMgr fileExistsAtPath:appFile]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1 : 0)]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command +{ + // arguments + NSString* argPath = [command.arguments objectAtIndex:0]; + + // Get the file manager + NSFileManager* fMgr = [[NSFileManager alloc] init]; + NSString* appFile = argPath; // [self getFullPath: argPath]; + BOOL bIsDir = NO; + BOOL bExists = [fMgr fileExistsAtPath:appFile isDirectory:&bIsDir]; + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1 : 0)]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +// Returns number of bytes available via callback +- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command +{ + // no arguments + + NSNumber* pNumAvail = [self checkFreeDiskSpace:self.appDocsPath]; + + NSString* strFreeSpace = [NSString stringWithFormat:@"%qu", [pNumAvail unsignedLongLongValue]]; + // NSLog(@"Free space is %@", strFreeSpace ); + + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:strFreeSpace]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +@end diff --git a/plugins/org.apache.cordova.file/src/windows8/FileProxy.js b/plugins/org.apache.cordova.file/src/windows8/FileProxy.js new file mode 100644 index 00000000..1445ae70 --- /dev/null +++ b/plugins/org.apache.cordova.file/src/windows8/FileProxy.js @@ -0,0 +1,845 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var cordova = require('cordova'); +var Entry = require('./Entry'), + File = require('./File'), + FileEntry = require('./FileEntry'), + FileError = require('./FileError'), + DirectoryEntry = require('./DirectoryEntry'), + Flags = require('./Flags'), + FileSystem = require('./FileSystem'), + LocalFileSystem = require('./LocalFileSystem'); + +module.exports = { + + getFileMetadata:function(win,fail,args) { + var fullPath = args[0]; + + Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).done( + function (storageFile) { + storageFile.getBasicPropertiesAsync().then( + function (basicProperties) { + win(new File(storageFile.name, storageFile.path, storageFile.fileType, basicProperties.dateModified, basicProperties.size)); + }, function () { + fail && fail(FileError.NOT_READABLE_ERR); + } + ); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + }, + + getMetadata:function(success,fail,args) { + var fullPath = args[0]; + + var dealFile = function (sFile) { + Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).then( + function (storageFile) { + return storageFile.getBasicPropertiesAsync(); + }, + function () { + fail && fail(FileError.NOT_READABLE_ERR); + } + // get the basic properties of the file. + ).then( + function (basicProperties) { + success(basicProperties.dateModified); + }, + function () { + fail && fail(FileError.NOT_READABLE_ERR); + } + ); + }; + + var dealFolder = function (sFolder) { + Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then( + function (storageFolder) { + return storageFolder.getBasicPropertiesAsync(); + }, + function () { + fail && fail(FileError.NOT_READABLE_ERR); + } + // get the basic properties of the folder. + ).then( + function (basicProperties) { + success(basicProperties.dateModified); + }, + function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + }; + + Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).then( + // the path is file. + function (sFile) { + dealFile(sFile); + }, + // the path is folder + function () { + Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then( + function (sFolder) { + dealFolder(sFolder); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + } + ); + }, + + getParent:function(win,fail,args) { // ["fullPath"] + var fullPath = args[0]; + + var storageFolderPer = Windows.Storage.ApplicationData.current.localFolder; + var storageFolderTem = Windows.Storage.ApplicationData.current.temporaryFolder; + + if (fullPath == storageFolderPer.path) { + win(new DirectoryEntry(storageFolderPer.name, storageFolderPer.path)); + return; + } else if (fullPath == storageFolderTem.path) { + win(new DirectoryEntry(storageFolderTem.name, storageFolderTem.path)); + return; + } + var splitArr = fullPath.split(new RegExp(/\/|\\/g)); + + var popItem = splitArr.pop(); + + var result = new DirectoryEntry(popItem, fullPath.substr(0, fullPath.length - popItem.length - 1)); + Windows.Storage.StorageFolder.getFolderFromPathAsync(result.fullPath).done( + function () { win(result); }, + function () { fail && fail(FileError.INVALID_STATE_ERR); } + ); + }, + + readAsText:function(win,fail,args) { + var fileName = args[0]; + var enc = args[1]; + + Windows.Storage.StorageFile.getFileFromPathAsync(fileName).done( + function (storageFile) { + var value = Windows.Storage.Streams.UnicodeEncoding.utf8; + if (enc == 'Utf16LE' || enc == 'utf16LE') { + value = Windows.Storage.Streams.UnicodeEncoding.utf16LE; + }else if (enc == 'Utf16BE' || enc == 'utf16BE') { + value = Windows.Storage.Streams.UnicodeEncoding.utf16BE; + } + Windows.Storage.FileIO.readTextAsync(storageFile, value).done( + function (fileContent) { + win(fileContent); + }, + function () { + fail && fail(FileError.ENCODING_ERR); + } + ); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + }, + + readAsDataURL:function(win,fail,args) { + var fileName = args[0]; + + + Windows.Storage.StorageFile.getFileFromPathAsync(fileName).then( + function (storageFile) { + Windows.Storage.FileIO.readBufferAsync(storageFile).done( + function (buffer) { + var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer); + //the method encodeToBase64String will add "77u/" as a prefix, so we should remove it + if(String(strBase64).substr(0,4) == "77u/") { + strBase64 = strBase64.substr(4); + } + var mediaType = storageFile.contentType; + var result = "data:" + mediaType + ";base64," + strBase64; + win(result); + } + ); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + }, + + getDirectory:function(win,fail,args) { + var fullPath = args[0]; + var path = args[1]; + var options = args[2]; + + var flag = ""; + if (options !== null) { + flag = new Flags(options.create, options.exclusive); + } else { + flag = new Flags(false, false); + } + + Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then( + function (storageFolder) { + if (flag.create === true && flag.exclusive === true) { + storageFolder.createFolderAsync(path, Windows.Storage.CreationCollisionOption.failIfExists).done( + function (storageFolder) { + win(new DirectoryEntry(storageFolder.name, storageFolder.path)); + }, function () { + fail && fail(FileError.PATH_EXISTS_ERR); + } + ); + } else if (flag.create === true && flag.exclusive === false) { + storageFolder.createFolderAsync(path, Windows.Storage.CreationCollisionOption.openIfExists).done( + function (storageFolder) { + win(new DirectoryEntry(storageFolder.name, storageFolder.path)); + }, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + } + ); + } else if (flag.create === false) { + if (/\?|\\|\*|\||\"|<|>|\:|\//g.test(path)) { + fail && fail(FileError.ENCODING_ERR); + return; + } + + storageFolder.getFolderAsync(path).done( + function (storageFolder) { + win(new DirectoryEntry(storageFolder.name, storageFolder.path)); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + } + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + }, + + remove:function(win,fail,args) { + var fullPath = args[0]; + + Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).then( + function (sFile) { + Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).done(function (storageFile) { + storageFile.deleteAsync().done(win, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + + }); + }); + }, + function () { + Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then( + function (sFolder) { + var removeEntry = function () { + var storageFolderTop = null; + + Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then( + function (storageFolder) { + // FileSystem root can't be removed! + var storageFolderPer = Windows.Storage.ApplicationData.current.localFolder; + var storageFolderTem = Windows.Storage.ApplicationData.current.temporaryFolder; + if (fullPath == storageFolderPer.path || fullPath == storageFolderTem.path) { + fail && fail(FileError.NO_MODIFICATION_ALLOWED_ERR); + return; + } + storageFolderTop = storageFolder; + return storageFolder.createFileQuery().getFilesAsync(); + }, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + + } + // check sub-files. + ).then(function (fileList) { + if (fileList) { + if (fileList.length === 0) { + return storageFolderTop.createFolderQuery().getFoldersAsync(); + } else { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + } + } + // check sub-folders. + }).then(function (folderList) { + if (folderList) { + if (folderList.length === 0) { + storageFolderTop.deleteAsync().done(win, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + + }); + } else { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + } + } + + }); + }; + removeEntry(); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + } + ); + }, + + removeRecursively:function(successCallback,fail,args) { + var fullPath = args[0]; + + Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).done(function (storageFolder) { + var storageFolderPer = Windows.Storage.ApplicationData.current.localFolder; + var storageFolderTem = Windows.Storage.ApplicationData.current.temporaryFolder; + + if (storageFolder.path == storageFolderPer.path || storageFolder.path == storageFolderTem.path) { + fail && fail(FileError.NO_MODIFICATION_ALLOWED_ERR); + return; + } + + var removeFolders = function (path) { + return new WinJS.Promise(function (complete) { + var filePromiseArr = []; + var storageFolderTop = null; + Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then( + function (storageFolder) { + var fileListPromise = storageFolder.createFileQuery().getFilesAsync(); + + storageFolderTop = storageFolder; + return fileListPromise; + } + // remove all the files directly under the folder. + ).then(function (fileList) { + if (fileList !== null) { + for (var i = 0; i < fileList.length; i++) { + var filePromise = fileList[i].deleteAsync(); + filePromiseArr.push(filePromise); + } + } + WinJS.Promise.join(filePromiseArr).then(function () { + var folderListPromise = storageFolderTop.createFolderQuery().getFoldersAsync(); + return folderListPromise; + // remove empty folders. + }).then(function (folderList) { + var folderPromiseArr = []; + if (folderList.length !== 0) { + for (var j = 0; j < folderList.length; j++) { + + folderPromiseArr.push(removeFolders(folderList[j].path)); + } + WinJS.Promise.join(folderPromiseArr).then(function () { + storageFolderTop.deleteAsync().then(complete); + }); + } else { + storageFolderTop.deleteAsync().then(complete); + } + }, function () { }); + }, function () { }); + }); + }; + removeFolders(storageFolder.path).then(function () { + Windows.Storage.StorageFolder.getFolderFromPathAsync(storageFolder.path).then( + function () {}, + function () { + if (typeof successCallback !== 'undefined' && successCallback !== null) { successCallback(); } + }); + }); + }); + }, + + getFile:function(win,fail,args) { + var fullPath = args[0]; + var path = args[1]; + var options = args[2]; + + var flag = ""; + if (options !== null) { + flag = new Flags(options.create, options.exclusive); + } else { + flag = new Flags(false, false); + } + + Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then( + function (storageFolder) { + if (flag.create === true && flag.exclusive === true) { + storageFolder.createFileAsync(path, Windows.Storage.CreationCollisionOption.failIfExists).done( + function (storageFile) { + win(new FileEntry(storageFile.name, storageFile.path)); + }, function () { + fail && fail(FileError.PATH_EXISTS_ERR); + } + ); + } else if (flag.create === true && flag.exclusive === false) { + storageFolder.createFileAsync(path, Windows.Storage.CreationCollisionOption.openIfExists).done( + function (storageFile) { + win(new FileEntry(storageFile.name, storageFile.path)); + }, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + } + ); + } else if (flag.create === false) { + if (/\?|\\|\*|\||\"|<|>|\:|\//g.test(path)) { + fail && fail(FileError.ENCODING_ERR); + return; + } + storageFolder.getFileAsync(path).done( + function (storageFile) { + win(new FileEntry(storageFile.name, storageFile.path)); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + } + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + }, + + readEntries:function(win,fail,args) { // ["fullPath"] + var path = args[0]; + + var result = []; + + Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then(function (storageFolder) { + var promiseArr = []; + var index = 0; + promiseArr[index++] = storageFolder.createFileQuery().getFilesAsync().then(function (fileList) { + if (fileList !== null) { + for (var i = 0; i < fileList.length; i++) { + result.push(new FileEntry(fileList[i].name, fileList[i].path)); + } + } + }); + promiseArr[index++] = storageFolder.createFolderQuery().getFoldersAsync().then(function (folderList) { + if (folderList !== null) { + for (var j = 0; j < folderList.length; j++) { + result.push(new FileEntry(folderList[j].name, folderList[j].path)); + } + } + }); + WinJS.Promise.join(promiseArr).then(function () { + win(result); + }); + + }, function () { fail && fail(FileError.NOT_FOUND_ERR); }); + }, + + write:function(win,fail,args) { + var fileName = args[0]; + var text = args[1]; + var position = args[2]; + + Windows.Storage.StorageFile.getFileFromPathAsync(fileName).done( + function (storageFile) { + Windows.Storage.FileIO.writeTextAsync(storageFile,text,Windows.Storage.Streams.UnicodeEncoding.utf8).done( + function() { + win(String(text).length); + }, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + } + ); + }, function() { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + }, + + truncate:function(win,fail,args) { // ["fileName","size"] + var fileName = args[0]; + var size = args[1]; + + Windows.Storage.StorageFile.getFileFromPathAsync(fileName).done(function(storageFile){ + //the current length of the file. + var leng = 0; + + storageFile.getBasicPropertiesAsync().then(function (basicProperties) { + leng = basicProperties.size; + if (Number(size) >= leng) { + win(this.length); + return; + } + if (Number(size) >= 0) { + Windows.Storage.FileIO.readTextAsync(storageFile, Windows.Storage.Streams.UnicodeEncoding.utf8).then(function (fileContent) { + fileContent = fileContent.substr(0, size); + var fullPath = storageFile.path; + var name = storageFile.name; + var entry = new Entry(true, false, name, fullPath); + var parentPath = ""; + var successCallBack = function (entry) { + parentPath = entry.fullPath; + storageFile.deleteAsync().then(function () { + return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath); + }).then(function (storageFolder) { + storageFolder.createFileAsync(name).then(function (newStorageFile) { + Windows.Storage.FileIO.writeTextAsync(newStorageFile, fileContent).done(function () { + win(String(fileContent).length); + }, function () { + fail && fail(FileError.NO_MODIFICATION_ALLOWED_ERR); + }); + }); + }); + }; + entry.getParent(successCallBack, null); + }, function () { fail && fail(FileError.NOT_FOUND_ERR); }); + } + }); + }, function () { fail && fail(FileError.NOT_FOUND_ERR); }); + }, + + copyTo:function(success,fail,args) { // ["fullPath","parent", "newName"] + var srcPath = args[0]; + var parentFullPath = args[1]; + var name = args[2]; + + //name can't be invalid + if (/\?|\\|\*|\||\"|<|>|\:|\//g.test(name)) { + fail && fail(FileError.ENCODING_ERR); + return; + } + // copy + var copyFiles = ""; + Windows.Storage.StorageFile.getFileFromPathAsync(srcPath).then( + function (sFile) { + copyFiles = function (srcPath, parentPath) { + var storageFileTop = null; + Windows.Storage.StorageFile.getFileFromPathAsync(srcPath).then(function (storageFile) { + storageFileTop = storageFile; + return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath); + }, function () { + + fail && fail(FileError.NOT_FOUND_ERR); + }).then(function (storageFolder) { + storageFileTop.copyAsync(storageFolder, name, Windows.Storage.NameCollisionOption.failIfExists).then(function (storageFile) { + + success(new FileEntry(storageFile.name, storageFile.path)); + }, function () { + + fail && fail(FileError.INVALID_MODIFICATION_ERR); + }); + }, function () { + + fail && fail(FileError.NOT_FOUND_ERR); + }); + }; + var copyFinish = function (srcPath, parentPath) { + copyFiles(srcPath, parentPath); + }; + copyFinish(srcPath, parentFullPath); + }, + function () { + Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then( + function (sFolder) { + copyFiles = function (srcPath, parentPath) { + var coreCopy = function (storageFolderTop, complete) { + storageFolderTop.createFolderQuery().getFoldersAsync().then(function (folderList) { + var folderPromiseArr = []; + if (folderList.length === 0) { complete(); } + else { + Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath).then(function (storageFolderTarget) { + var tempPromiseArr = []; + var index = 0; + for (var j = 0; j < folderList.length; j++) { + tempPromiseArr[index++] = storageFolderTarget.createFolderAsync(folderList[j].name).then(function (targetFolder) { + folderPromiseArr.push(copyFiles(folderList[j].path, targetFolder.path)); + }); + } + WinJS.Promise.join(tempPromiseArr).then(function () { + WinJS.Promise.join(folderPromiseArr).then(complete); + }); + }); + } + }); + }; + + return new WinJS.Promise(function (complete) { + var storageFolderTop = null; + var filePromiseArr = []; + var fileListTop = null; + Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then(function (storageFolder) { + storageFolderTop = storageFolder; + return storageFolder.createFileQuery().getFilesAsync(); + }).then(function (fileList) { + fileListTop = fileList; + if (fileList) { + return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath); + } + }).then(function (targetStorageFolder) { + for (var i = 0; i < fileListTop.length; i++) { + filePromiseArr.push(fileListTop[i].copyAsync(targetStorageFolder)); + } + WinJS.Promise.join(filePromiseArr).then(function () { + coreCopy(storageFolderTop, complete); + }); + }); + }); + }; + var copyFinish = function (srcPath, parentPath) { + Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath).then(function (storageFolder) { + storageFolder.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists).then(function (newStorageFolder) { + //can't copy onto itself + if (srcPath == newStorageFolder.path) { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + return; + } + //can't copy into itself + if (srcPath == parentPath) { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + return; + } + copyFiles(srcPath, newStorageFolder.path).then(function () { + Windows.Storage.StorageFolder.getFolderFromPathAsync(newStorageFolder.path).done( + function (storageFolder) { + success(new DirectoryEntry(storageFolder.name, storageFolder.path)); + }, + function () { fail && fail(FileError.NOT_FOUND_ERR); } + ); + }); + }, function () { fail && fail(FileError.INVALID_MODIFICATION_ERR); }); + }, function () { fail && fail(FileError.INVALID_MODIFICATION_ERR); }); + }; + copyFinish(srcPath, parentFullPath); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + } + ); + }, + + moveTo:function(success,fail,args) { + var srcPath = args[0]; + var parentFullPath = args[1]; + var name = args[2]; + + + //name can't be invalid + if (/\?|\\|\*|\||\"|<|>|\:|\//g.test(name)) { + fail && fail(FileError.ENCODING_ERR); + return; + } + + var moveFiles = ""; + Windows.Storage.StorageFile.getFileFromPathAsync(srcPath).then( + function (sFile) { + moveFiles = function (srcPath, parentPath) { + var storageFileTop = null; + Windows.Storage.StorageFile.getFileFromPathAsync(srcPath).then(function (storageFile) { + storageFileTop = storageFile; + return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + }).then(function (storageFolder) { + storageFileTop.moveAsync(storageFolder, name, Windows.Storage.NameCollisionOption.replaceExisting).then(function () { + success(new FileEntry(name, storageFileTop.path)); + }, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + }); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + }); + }; + var moveFinish = function (srcPath, parentPath) { + //can't copy onto itself + if (srcPath == parentPath + "\\" + name) { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + return; + } + moveFiles(srcPath, parentFullPath); + }; + moveFinish(srcPath, parentFullPath); + }, + function () { + Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then( + function (sFolder) { + moveFiles = function (srcPath, parentPath) { + var coreMove = function (storageFolderTop, complete) { + storageFolderTop.createFolderQuery().getFoldersAsync().then(function (folderList) { + var folderPromiseArr = []; + if (folderList.length === 0) { + // If failed, we must cancel the deletion of folders & files.So here wo can't delete the folder. + complete(); + } + else { + Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath).then(function (storageFolderTarget) { + var tempPromiseArr = []; + var index = 0; + for (var j = 0; j < folderList.length; j++) { + tempPromiseArr[index++] = storageFolderTarget.createFolderAsync(folderList[j].name).then(function (targetFolder) { + folderPromiseArr.push(moveFiles(folderList[j].path, targetFolder.path)); + }); + } + WinJS.Promise.join(tempPromiseArr).then(function () { + WinJS.Promise.join(folderPromiseArr).then(complete); + }); + }); + } + }); + }; + return new WinJS.Promise(function (complete) { + var storageFolderTop = null; + Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then(function (storageFolder) { + storageFolderTop = storageFolder; + return storageFolder.createFileQuery().getFilesAsync(); + }).then(function (fileList) { + var filePromiseArr = []; + Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath).then(function (dstStorageFolder) { + if (fileList) { + for (var i = 0; i < fileList.length; i++) { + filePromiseArr.push(fileList[i].moveAsync(dstStorageFolder)); + } + } + WinJS.Promise.join(filePromiseArr).then(function () { + coreMove(storageFolderTop, complete); + }, function () { }); + }); + }); + }); + }; + var moveFinish = function (srcPath, parentPath) { + var originFolderTop = null; + Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then(function (originFolder) { + originFolderTop = originFolder; + return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath); + }, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + }).then(function (storageFolder) { + return storageFolder.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists); + }, function () { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + }).then(function (newStorageFolder) { + //can't move onto directory that is not empty + newStorageFolder.createFileQuery().getFilesAsync().then(function (fileList) { + newStorageFolder.createFolderQuery().getFoldersAsync().then(function (folderList) { + if (fileList.length !== 0 || folderList.length !== 0) { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + return; + } + //can't copy onto itself + if (srcPath == newStorageFolder.path) { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + return; + } + //can't copy into itself + if (srcPath == parentPath) { + fail && fail(FileError.INVALID_MODIFICATION_ERR); + return; + } + moveFiles(srcPath, newStorageFolder.path).then(function () { + var successCallback = function () { + success(new DirectoryEntry(name, newStorageFolder.path)); + }; + var temp = new DirectoryEntry(originFolderTop.name, originFolderTop.path).removeRecursively(successCallback, fail); + + }, function () { console.log("error!"); }); + }); + }); + }, function () { fail && fail(FileError.INVALID_MODIFICATION_ERR); }); + + }; + moveFinish(srcPath, parentFullPath); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + } + ); + }, + tempFileSystem:null, + + persistentFileSystem:null, + + requestFileSystem:function(win,fail,args) { + var type = args[0]; + var size = args[1]; + + var filePath = ""; + var result = null; + var fsTypeName = ""; + + switch (type) { + case LocalFileSystem.TEMPORARY: + filePath = Windows.Storage.ApplicationData.current.temporaryFolder.path; + fsTypeName = "temporary"; + break; + case LocalFileSystem.PERSISTENT: + filePath = Windows.Storage.ApplicationData.current.localFolder.path; + fsTypeName = "persistent"; + break; + } + + var MAX_SIZE = 10000000000; + if (size > MAX_SIZE) { + fail && fail(FileError.QUOTA_EXCEEDED_ERR); + return; + } + + var fileSystem = new FileSystem(fsTypeName, new DirectoryEntry(fsTypeName, filePath)); + result = fileSystem; + win(result); + }, + + resolveLocalFileSystemURI:function(success,fail,args) { + var uri = args[0]; + + var path = uri; + + // support for file name with parameters + if (/\?/g.test(path)) { + path = String(path).split("?")[0]; + } + + // support for encodeURI + if (/\%5/g.test(path)) { + path = decodeURI(path); + } + + // support for special path start with file:/// + if (path.substr(0, 8) == "file:///") { + path = Windows.Storage.ApplicationData.current.localFolder.path + "\\" + String(path).substr(8).split("/").join("\\"); + Windows.Storage.StorageFile.getFileFromPathAsync(path).then( + function (storageFile) { + success(new FileEntry(storageFile.name, storageFile.path)); + }, function () { + Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then( + function (storageFolder) { + success(new DirectoryEntry(storageFolder.name, storageFolder.path)); + }, function () { + fail && fail(FileError.NOT_FOUND_ERR); + } + ); + } + ); + } else { + Windows.Storage.StorageFile.getFileFromPathAsync(path).then( + function (storageFile) { + success(new FileEntry(storageFile.name, storageFile.path)); + }, function () { + Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then( + function (storageFolder) { + success(new DirectoryEntry(storageFolder.name, storageFolder.path)); + }, function () { + fail && fail(FileError.ENCODING_ERR); + } + ); + } + ); + } + } + +}; + +require("cordova/windows8/commandProxy").add("File",module.exports); diff --git a/plugins/org.apache.cordova.file/src/wp/File.cs b/plugins/org.apache.cordova.file/src/wp/File.cs new file mode 100644 index 00000000..4fc61dde --- /dev/null +++ b/plugins/org.apache.cordova.file/src/wp/File.cs @@ -0,0 +1,1676 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.IsolatedStorage; +using System.Runtime.Serialization; +using System.Security; +using System.Text; +using System.Windows; +using System.Windows.Resources; + +namespace WPCordovaClassLib.Cordova.Commands +{ + /// <summary> + /// Provides access to isolated storage + /// </summary> + public class File : BaseCommand + { + // Error codes + public const int NOT_FOUND_ERR = 1; + public const int SECURITY_ERR = 2; + public const int ABORT_ERR = 3; + public const int NOT_READABLE_ERR = 4; + public const int ENCODING_ERR = 5; + public const int NO_MODIFICATION_ALLOWED_ERR = 6; + public const int INVALID_STATE_ERR = 7; + public const int SYNTAX_ERR = 8; + public const int INVALID_MODIFICATION_ERR = 9; + public const int QUOTA_EXCEEDED_ERR = 10; + public const int TYPE_MISMATCH_ERR = 11; + public const int PATH_EXISTS_ERR = 12; + + // File system options + public const int TEMPORARY = 0; + public const int PERSISTENT = 1; + public const int RESOURCE = 2; + public const int APPLICATION = 3; + + /// <summary> + /// Temporary directory name + /// </summary> + private readonly string TMP_DIRECTORY_NAME = "tmp"; + + /// <summary> + /// Represents error code for callback + /// </summary> + [DataContract] + public class ErrorCode + { + /// <summary> + /// Error code + /// </summary> + [DataMember(IsRequired = true, Name = "code")] + public int Code { get; set; } + + /// <summary> + /// Creates ErrorCode object + /// </summary> + public ErrorCode(int code) + { + this.Code = code; + } + } + + /// <summary> + /// Represents File action options. + /// </summary> + [DataContract] + public class FileOptions + { + /// <summary> + /// File path + /// </summary> + /// + private string _fileName; + [DataMember(Name = "fileName")] + public string FilePath + { + get + { + return this._fileName; + } + + set + { + int index = value.IndexOfAny(new char[] { '#', '?' }); + this._fileName = index > -1 ? value.Substring(0, index) : value; + } + } + + /// <summary> + /// Full entryPath + /// </summary> + [DataMember(Name = "fullPath")] + public string FullPath { get; set; } + + /// <summary> + /// Directory name + /// </summary> + [DataMember(Name = "dirName")] + public string DirectoryName { get; set; } + + /// <summary> + /// Path to create file/directory + /// </summary> + [DataMember(Name = "path")] + public string Path { get; set; } + + /// <summary> + /// The encoding to use to encode the file's content. Default is UTF8. + /// </summary> + [DataMember(Name = "encoding")] + public string Encoding { get; set; } + + /// <summary> + /// Uri to get file + /// </summary> + /// + private string _uri; + [DataMember(Name = "uri")] + public string Uri + { + get + { + return this._uri; + } + + set + { + int index = value.IndexOfAny(new char[] { '#', '?' }); + this._uri = index > -1 ? value.Substring(0, index) : value; + } + } + + /// <summary> + /// Size to truncate file + /// </summary> + [DataMember(Name = "size")] + public long Size { get; set; } + + /// <summary> + /// Data to write in file + /// </summary> + [DataMember(Name = "data")] + public string Data { get; set; } + + /// <summary> + /// Position the writing starts with + /// </summary> + [DataMember(Name = "position")] + public int Position { get; set; } + + /// <summary> + /// Type of file system requested + /// </summary> + [DataMember(Name = "type")] + public int FileSystemType { get; set; } + + /// <summary> + /// New file/directory name + /// </summary> + [DataMember(Name = "newName")] + public string NewName { get; set; } + + /// <summary> + /// Destination directory to copy/move file/directory + /// </summary> + [DataMember(Name = "parent")] + public string Parent { get; set; } + + /// <summary> + /// Options for getFile/getDirectory methods + /// </summary> + [DataMember(Name = "options")] + public CreatingOptions CreatingOpt { get; set; } + + /// <summary> + /// Creates options object with default parameters + /// </summary> + public FileOptions() + { + this.SetDefaultValues(new StreamingContext()); + } + + /// <summary> + /// Initializes default values for class fields. + /// Implemented in separate method because default constructor is not invoked during deserialization. + /// </summary> + /// <param name="context"></param> + [OnDeserializing()] + public void SetDefaultValues(StreamingContext context) + { + this.Encoding = "UTF-8"; + this.FilePath = ""; + this.FileSystemType = -1; + } + } + + /// <summary> + /// Stores image info + /// </summary> + [DataContract] + public class FileMetadata + { + [DataMember(Name = "fileName")] + public string FileName { get; set; } + + [DataMember(Name = "fullPath")] + public string FullPath { get; set; } + + [DataMember(Name = "type")] + public string Type { get; set; } + + [DataMember(Name = "lastModifiedDate")] + public string LastModifiedDate { get; set; } + + [DataMember(Name = "size")] + public long Size { get; set; } + + public FileMetadata(string filePath) + { + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new FileNotFoundException("File doesn't exist"); + } + else if (!isoFile.FileExists(filePath)) + { + // attempt to get it from the resources + if (filePath.IndexOf("www") == 0) + { + Uri fileUri = new Uri(filePath, UriKind.Relative); + StreamResourceInfo streamInfo = Application.GetResourceStream(fileUri); + if (streamInfo != null) + { + this.Size = streamInfo.Stream.Length; + this.FileName = filePath.Substring(filePath.LastIndexOf("/") + 1); + this.FullPath = filePath; + } + } + else + { + throw new FileNotFoundException("File doesn't exist"); + } + } + else + { + //TODO get file size the other way if possible + using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.Read, isoFile)) + { + this.Size = stream.Length; + } + this.FullPath = filePath; + this.FileName = System.IO.Path.GetFileName(filePath); + this.LastModifiedDate = isoFile.GetLastWriteTime(filePath).DateTime.ToString(); + } + this.Type = MimeTypeMapper.GetMimeType(this.FileName); + } + } + } + + /// <summary> + /// Represents file or directory modification metadata + /// </summary> + [DataContract] + public class ModificationMetadata + { + /// <summary> + /// Modification time + /// </summary> + [DataMember] + public string modificationTime { get; set; } + } + + /// <summary> + /// Represents file or directory entry + /// </summary> + [DataContract] + public class FileEntry + { + + /// <summary> + /// File type + /// </summary> + [DataMember(Name = "isFile")] + public bool IsFile { get; set; } + + /// <summary> + /// Directory type + /// </summary> + [DataMember(Name = "isDirectory")] + public bool IsDirectory { get; set; } + + /// <summary> + /// File/directory name + /// </summary> + [DataMember(Name = "name")] + public string Name { get; set; } + + /// <summary> + /// Full path to file/directory + /// </summary> + [DataMember(Name = "fullPath")] + public string FullPath { get; set; } + + public bool IsResource { get; set; } + + public static FileEntry GetEntry(string filePath, bool bIsRes=false) + { + FileEntry entry = null; + try + { + entry = new FileEntry(filePath, bIsRes); + + } + catch (Exception ex) + { + Debug.WriteLine("Exception in GetEntry for filePath :: " + filePath + " " + ex.Message); + } + return entry; + } + + /// <summary> + /// Creates object and sets necessary properties + /// </summary> + /// <param name="filePath"></param> + public FileEntry(string filePath, bool bIsRes = false) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new ArgumentException(); + } + + if(filePath.Contains(" ")) + { + Debug.WriteLine("FilePath with spaces :: " + filePath); + } + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + IsResource = bIsRes; + IsFile = isoFile.FileExists(filePath); + IsDirectory = isoFile.DirectoryExists(filePath); + if (IsFile) + { + this.Name = Path.GetFileName(filePath); + } + else if (IsDirectory) + { + this.Name = this.GetDirectoryName(filePath); + if (string.IsNullOrEmpty(Name)) + { + this.Name = "/"; + } + } + else + { + if (IsResource) + { + this.Name = Path.GetFileName(filePath); + } + else + { + throw new FileNotFoundException(); + } + } + + try + { + this.FullPath = filePath.Replace('\\', '/'); // new Uri(filePath).LocalPath; + } + catch (Exception) + { + this.FullPath = filePath; + } + } + } + + /// <summary> + /// Extracts directory name from path string + /// Path should refer to a directory, for example \foo\ or /foo. + /// </summary> + /// <param name="path"></param> + /// <returns></returns> + private string GetDirectoryName(string path) + { + if (String.IsNullOrEmpty(path)) + { + return path; + } + + string[] split = path.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + if (split.Length < 1) + { + return null; + } + else + { + return split[split.Length - 1]; + } + } + } + + + /// <summary> + /// Represents info about requested file system + /// </summary> + [DataContract] + public class FileSystemInfo + { + /// <summary> + /// file system type + /// </summary> + [DataMember(Name = "name", IsRequired = true)] + public string Name { get; set; } + + /// <summary> + /// Root directory entry + /// </summary> + [DataMember(Name = "root", EmitDefaultValue = false)] + public FileEntry Root { get; set; } + + /// <summary> + /// Creates class instance + /// </summary> + /// <param name="name"></param> + /// <param name="rootEntry"> Root directory</param> + public FileSystemInfo(string name, FileEntry rootEntry = null) + { + Name = name; + Root = rootEntry; + } + } + + [DataContract] + public class CreatingOptions + { + /// <summary> + /// Create file/directory if is doesn't exist + /// </summary> + [DataMember(Name = "create")] + public bool Create { get; set; } + + /// <summary> + /// Generate an exception if create=true and file/directory already exists + /// </summary> + [DataMember(Name = "exclusive")] + public bool Exclusive { get; set; } + + + } + + // returns null value if it fails. + private string[] getOptionStrings(string options) + { + string[] optStings = null; + try + { + optStings = JSON.JsonHelper.Deserialize<string[]>(options); + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), CurrentCommandCallbackId); + } + return optStings; + } + + /// <summary> + /// Gets amount of free space available for Isolated Storage + /// </summary> + /// <param name="options">No options is needed for this method</param> + public void getFreeDiskSpace(string options) + { + string callbackId = getOptionStrings(options)[0]; + + try + { + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isoFile.AvailableFreeSpace), callbackId); + } + } + catch (IsolatedStorageException) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + } + } + + /// <summary> + /// Check if file exists + /// </summary> + /// <param name="options">File path</param> + public void testFileExists(string options) + { + IsDirectoryOrFileExist(options, false); + } + + /// <summary> + /// Check if directory exists + /// </summary> + /// <param name="options">directory name</param> + public void testDirectoryExists(string options) + { + IsDirectoryOrFileExist(options, true); + } + + /// <summary> + /// Check if file or directory exist + /// </summary> + /// <param name="options">File path/Directory name</param> + /// <param name="isDirectory">Flag to recognize what we should check</param> + public void IsDirectoryOrFileExist(string options, bool isDirectory) + { + string[] args = getOptionStrings(options); + string callbackId = args[1]; + FileOptions fileOptions = JSON.JsonHelper.Deserialize<FileOptions>(args[0]); + string filePath = args[0]; + + if (fileOptions == null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + } + + try + { + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + bool isExist; + if (isDirectory) + { + isExist = isoFile.DirectoryExists(fileOptions.DirectoryName); + } + else + { + isExist = isoFile.FileExists(fileOptions.FilePath); + } + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isExist), callbackId); + } + } + catch (IsolatedStorageException) // default handler throws INVALID_MODIFICATION_ERR + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + } + } + + } + + public void readAsDataURL(string options) + { + string[] optStrings = getOptionStrings(options); + string filePath = optStrings[0]; + int startPos = int.Parse(optStrings[1]); + int endPos = int.Parse(optStrings[2]); + string callbackId = optStrings[3]; + + if (filePath != null) + { + try + { + string base64URL = null; + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (!isoFile.FileExists(filePath)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + return; + } + string mimeType = MimeTypeMapper.GetMimeType(filePath); + + using (IsolatedStorageFileStream stream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read)) + { + string base64String = GetFileContent(stream); + base64URL = "data:" + mimeType + ";base64," + base64String; + } + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, base64URL), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + } + } + } + + public void readAsArrayBuffer(string options) + { + string[] optStrings = getOptionStrings(options); + string filePath = optStrings[0]; + int startPos = int.Parse(optStrings[1]); + int endPos = int.Parse(optStrings[2]); + string callbackId = optStrings[3]; + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR), callbackId); + } + + public void readAsBinaryString(string options) + { + string[] optStrings = getOptionStrings(options); + string filePath = optStrings[0]; + int startPos = int.Parse(optStrings[1]); + int endPos = int.Parse(optStrings[2]); + string callbackId = optStrings[3]; + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR), callbackId); + } + + public void readAsText(string options) + { + string[] optStrings = getOptionStrings(options); + string filePath = optStrings[0]; + string encStr = optStrings[1]; + int startPos = int.Parse(optStrings[2]); + int endPos = int.Parse(optStrings[3]); + string callbackId = optStrings[4]; + + try + { + string text = ""; + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (!isoFile.FileExists(filePath)) + { + readResourceAsText(options); + return; + } + Encoding encoding = Encoding.GetEncoding(encStr); + + using (TextReader reader = new StreamReader(isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read), encoding)) + { + text = reader.ReadToEnd(); + if (startPos < 0) + { + startPos = Math.Max(text.Length + startPos, 0); + } + else if (startPos > 0) + { + startPos = Math.Min(text.Length, startPos); + } + + if (endPos > 0) + { + endPos = Math.Min(text.Length, endPos); + } + else if (endPos < 0) + { + endPos = Math.Max(endPos + text.Length, 0); + } + + + text = text.Substring(startPos, endPos - startPos); + + } + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex, callbackId)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + } + } + + /// <summary> + /// Reads application resource as a text + /// </summary> + /// <param name="options">Path to a resource</param> + public void readResourceAsText(string options) + { + string[] optStrings = getOptionStrings(options); + string pathToResource = optStrings[0]; + string encStr = optStrings[1]; + int start = int.Parse(optStrings[2]); + int endMarker = int.Parse(optStrings[3]); + string callbackId = optStrings[4]; + + try + { + if (pathToResource.StartsWith("/")) + { + pathToResource = pathToResource.Remove(0, 1); + } + + var resource = Application.GetResourceStream(new Uri(pathToResource, UriKind.Relative)); + + if (resource == null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + return; + } + + string text; + StreamReader streamReader = new StreamReader(resource.Stream); + text = streamReader.ReadToEnd(); + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex, callbackId)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + } + } + + public void truncate(string options) + { + string[] optStrings = getOptionStrings(options); + + string filePath = optStrings[0]; + int size = int.Parse(optStrings[1]); + string callbackId = optStrings[2]; + + try + { + long streamLength = 0; + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (!isoFile.FileExists(filePath)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + return; + } + + using (FileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.ReadWrite, isoFile)) + { + if (0 <= size && size <= stream.Length) + { + stream.SetLength(size); + } + streamLength = stream.Length; + } + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, streamLength), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex, callbackId)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + } + } + + //write:[filePath,data,position,isBinary,callbackId] + public void write(string options) + { + string[] optStrings = getOptionStrings(options); + + string filePath = optStrings[0]; + string data = optStrings[1]; + int position = int.Parse(optStrings[2]); + bool isBinary = bool.Parse(optStrings[3]); + string callbackId = optStrings[4]; + + try + { + if (string.IsNullOrEmpty(data)) + { + Debug.WriteLine("Expected some data to be send in the write command to {0}", filePath); + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + return; + } + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + // create the file if not exists + if (!isoFile.FileExists(filePath)) + { + var file = isoFile.CreateFile(filePath); + file.Close(); + } + + using (FileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.ReadWrite, isoFile)) + { + if (0 <= position && position <= stream.Length) + { + stream.SetLength(position); + } + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Seek(0, SeekOrigin.End); + writer.Write(data.ToCharArray()); + } + } + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, data.Length), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex, callbackId)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + } + } + + /// <summary> + /// Look up metadata about this entry. + /// </summary> + /// <param name="options">filePath to entry</param> + public void getMetadata(string options) + { + string[] optStings = getOptionStrings(options); + string filePath = optStings[0]; + string callbackId = optStings[1]; + + if (filePath != null) + { + try + { + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (isoFile.FileExists(filePath)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, + new ModificationMetadata() { modificationTime = isoFile.GetLastWriteTime(filePath).DateTime.ToString() }), callbackId); + } + else if (isoFile.DirectoryExists(filePath)) + { + string modTime = isoFile.GetLastWriteTime(filePath).DateTime.ToString(); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new ModificationMetadata() { modificationTime = modTime }), callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + } + + } + } + catch (IsolatedStorageException) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + } + } + + } + + + /// <summary> + /// Returns a File that represents the current state of the file that this FileEntry represents. + /// </summary> + /// <param name="filePath">filePath to entry</param> + /// <returns></returns> + public void getFileMetadata(string options) + { + string[] optStings = getOptionStrings(options); + string filePath = optStings[0]; + string callbackId = optStings[1]; + + if (filePath != null) + { + try + { + FileMetadata metaData = new FileMetadata(filePath); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, metaData), callbackId); + } + catch (IsolatedStorageException) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); + } + } + } + } + + /// <summary> + /// Look up the parent DirectoryEntry containing this Entry. + /// If this Entry is the root of IsolatedStorage, its parent is itself. + /// </summary> + /// <param name="options"></param> + public void getParent(string options) + { + string[] optStings = getOptionStrings(options); + string filePath = optStings[0]; + string callbackId = optStings[1]; + + if (filePath != null) + { + try + { + if (string.IsNullOrEmpty(filePath)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId); + return; + } + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + FileEntry entry; + + if (isoFile.FileExists(filePath) || isoFile.DirectoryExists(filePath)) + { + + + string path = this.GetParentDirectory(filePath); + entry = FileEntry.GetEntry(path); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry),callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); + } + + } + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); + } + } + } + } + + public void remove(string options) + { + string[] args = getOptionStrings(options); + string filePath = args[0]; + string callbackId = args[1]; + + if (filePath != null) + { + try + { + if (filePath == "/" || filePath == "" || filePath == @"\") + { + throw new Exception("Cannot delete root file system") ; + } + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (isoFile.FileExists(filePath)) + { + isoFile.DeleteFile(filePath); + } + else + { + if (isoFile.DirectoryExists(filePath)) + { + isoFile.DeleteDirectory(filePath); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); + return; + } + } + DispatchCommandResult(new PluginResult(PluginResult.Status.OK),callbackId); + } + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId); + } + } + } + } + + public void removeRecursively(string options) + { + string[] args = getOptionStrings(options); + string filePath = args[0]; + string callbackId = args[1]; + + if (filePath != null) + { + if (string.IsNullOrEmpty(filePath)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId); + } + else + { + if (removeDirRecursively(filePath, callbackId)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); + } + } + } + } + + public void readEntries(string options) + { + string[] args = getOptionStrings(options); + string filePath = args[0]; + string callbackId = args[1]; + + if (filePath != null) + { + try + { + if (string.IsNullOrEmpty(filePath)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId); + return; + } + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (isoFile.DirectoryExists(filePath)) + { + string path = File.AddSlashToDirectory(filePath); + List<FileEntry> entries = new List<FileEntry>(); + string[] files = isoFile.GetFileNames(path + "*"); + string[] dirs = isoFile.GetDirectoryNames(path + "*"); + foreach (string file in files) + { + entries.Add(FileEntry.GetEntry(path + file)); + } + foreach (string dir in dirs) + { + entries.Add(FileEntry.GetEntry(path + dir + "/")); + } + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entries),callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); + } + } + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId); + } + } + } + } + + public void requestFileSystem(string options) + { + // TODO: try/catch + string[] optVals = getOptionStrings(options); + //FileOptions fileOptions = new FileOptions(); + int fileSystemType = int.Parse(optVals[0]); + double size = double.Parse(optVals[1]); + string callbackId = optVals[2]; + + + IsolatedStorageFile.GetUserStoreForApplication(); + + if (size > (10 * 1024 * 1024)) // 10 MB, compier will clean this up! + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, QUOTA_EXCEEDED_ERR), callbackId); + return; + } + + try + { + if (size != 0) + { + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + long availableSize = isoFile.AvailableFreeSpace; + if (size > availableSize) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, QUOTA_EXCEEDED_ERR), callbackId); + return; + } + } + } + + if (fileSystemType == PERSISTENT) + { + // TODO: this should be in it's own folder to prevent overwriting of the app assets, which are also in ISO + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("persistent", FileEntry.GetEntry("/"))), callbackId); + } + else if (fileSystemType == TEMPORARY) + { + using (IsolatedStorageFile isoStorage = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (!isoStorage.FileExists(TMP_DIRECTORY_NAME)) + { + isoStorage.CreateDirectory(TMP_DIRECTORY_NAME); + } + } + + string tmpFolder = "/" + TMP_DIRECTORY_NAME + "/"; + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("temporary", FileEntry.GetEntry(tmpFolder))), callbackId); + } + else if (fileSystemType == RESOURCE) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("resource")), callbackId); + } + else if (fileSystemType == APPLICATION) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("application")), callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); + } + + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); + } + } + } + + public void resolveLocalFileSystemURI(string options) + { + + string[] optVals = getOptionStrings(options); + string uri = optVals[0].Split('?')[0]; + string callbackId = optVals[1]; + + if (uri != null) + { + // a single '/' is valid, however, '/someDir' is not, but '/tmp//somedir' and '///someDir' are valid + if (uri.StartsWith("/") && uri.IndexOf("//") < 0 && uri != "/") + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); + return; + } + try + { + // fix encoded spaces + string path = Uri.UnescapeDataString(uri); + + FileEntry uriEntry = FileEntry.GetEntry(path); + if (uriEntry != null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, uriEntry), callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + } + } + catch (Exception ex) + { + if (!this.HandleException(ex, callbackId)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); + } + } + } + } + + public void copyTo(string options) + { + TransferTo(options, false); + } + + public void moveTo(string options) + { + TransferTo(options, true); + } + + public void getFile(string options) + { + GetFileOrDirectory(options, false); + } + + public void getDirectory(string options) + { + GetFileOrDirectory(options, true); + } + + #region internal functionality + + /// <summary> + /// Retrieves the parent directory name of the specified path, + /// </summary> + /// <param name="path">Path</param> + /// <returns>Parent directory name</returns> + private string GetParentDirectory(string path) + { + if (String.IsNullOrEmpty(path) || path == "/") + { + return "/"; + } + + if (path.EndsWith(@"/") || path.EndsWith(@"\")) + { + return this.GetParentDirectory(Path.GetDirectoryName(path)); + } + + string result = Path.GetDirectoryName(path); + if (result == null) + { + result = "/"; + } + + return result; + } + + private bool removeDirRecursively(string fullPath,string callbackId) + { + try + { + if (fullPath == "/") + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId); + return false; + } + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (isoFile.DirectoryExists(fullPath)) + { + string tempPath = File.AddSlashToDirectory(fullPath); + string[] files = isoFile.GetFileNames(tempPath + "*"); + if (files.Length > 0) + { + foreach (string file in files) + { + isoFile.DeleteFile(tempPath + file); + } + } + string[] dirs = isoFile.GetDirectoryNames(tempPath + "*"); + if (dirs.Length > 0) + { + foreach (string dir in dirs) + { + if (!removeDirRecursively(tempPath + dir, callbackId)) + { + return false; + } + } + } + isoFile.DeleteDirectory(fullPath); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); + } + } + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId); + return false; + } + } + return true; + } + + private bool CanonicalCompare(string pathA, string pathB) + { + string a = pathA.Replace("//", "/"); + string b = pathB.Replace("//", "/"); + + return a.Equals(b, StringComparison.OrdinalIgnoreCase); + } + + /* + * copyTo:["fullPath","parent", "newName"], + * moveTo:["fullPath","parent", "newName"], + */ + private void TransferTo(string options, bool move) + { + // TODO: try/catch + string[] optStrings = getOptionStrings(options); + string fullPath = optStrings[0]; + string parent = optStrings[1]; + string newFileName = optStrings[2]; + string callbackId = optStrings[3]; + + char[] invalids = Path.GetInvalidPathChars(); + + if (newFileName.IndexOfAny(invalids) > -1 || newFileName.IndexOf(":") > -1 ) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); + return; + } + + try + { + if ((parent == null) || (string.IsNullOrEmpty(parent)) || (string.IsNullOrEmpty(fullPath))) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + return; + } + + string parentPath = File.AddSlashToDirectory(parent); + string currentPath = fullPath; + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + bool isFileExist = isoFile.FileExists(currentPath); + bool isDirectoryExist = isoFile.DirectoryExists(currentPath); + bool isParentExist = isoFile.DirectoryExists(parentPath); + + if ( ( !isFileExist && !isDirectoryExist ) || !isParentExist ) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + return; + } + string newName; + string newPath; + if (isFileExist) + { + newName = (string.IsNullOrEmpty(newFileName)) + ? Path.GetFileName(currentPath) + : newFileName; + + newPath = Path.Combine(parentPath, newName); + + // sanity check .. + // cannot copy file onto itself + if (CanonicalCompare(newPath,currentPath)) //(parent + newFileName)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId); + return; + } + else if (isoFile.DirectoryExists(newPath)) + { + // there is already a folder with the same name, operation is not allowed + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId); + return; + } + else if (isoFile.FileExists(newPath)) + { // remove destination file if exists, in other case there will be exception + isoFile.DeleteFile(newPath); + } + + if (move) + { + isoFile.MoveFile(currentPath, newPath); + } + else + { + isoFile.CopyFile(currentPath, newPath, true); + } + } + else + { + newName = (string.IsNullOrEmpty(newFileName)) + ? currentPath + : newFileName; + + newPath = Path.Combine(parentPath, newName); + + if (move) + { + // remove destination directory if exists, in other case there will be exception + // target directory should be empty + if (!newPath.Equals(currentPath) && isoFile.DirectoryExists(newPath)) + { + isoFile.DeleteDirectory(newPath); + } + + isoFile.MoveDirectory(currentPath, newPath); + } + else + { + CopyDirectory(currentPath, newPath, isoFile); + } + } + FileEntry entry = FileEntry.GetEntry(newPath); + if (entry != null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + } + } + + } + catch (Exception ex) + { + if (!this.HandleException(ex, callbackId)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); + } + } + } + + private bool HandleException(Exception ex, string cbId="") + { + bool handled = false; + string callbackId = String.IsNullOrEmpty(cbId) ? this.CurrentCommandCallbackId : cbId; + if (ex is SecurityException) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR), callbackId); + handled = true; + } + else if (ex is FileNotFoundException) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + handled = true; + } + else if (ex is ArgumentException) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); + handled = true; + } + else if (ex is IsolatedStorageException) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId); + handled = true; + } + else if (ex is DirectoryNotFoundException) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + handled = true; + } + return handled; + } + + private void CopyDirectory(string sourceDir, string destDir, IsolatedStorageFile isoFile) + { + string path = File.AddSlashToDirectory(sourceDir); + + bool bExists = isoFile.DirectoryExists(destDir); + + if (!bExists) + { + isoFile.CreateDirectory(destDir); + } + + destDir = File.AddSlashToDirectory(destDir); + + string[] files = isoFile.GetFileNames(path + "*"); + + if (files.Length > 0) + { + foreach (string file in files) + { + isoFile.CopyFile(path + file, destDir + file,true); + } + } + string[] dirs = isoFile.GetDirectoryNames(path + "*"); + if (dirs.Length > 0) + { + foreach (string dir in dirs) + { + CopyDirectory(path + dir, destDir + dir, isoFile); + } + } + } + + private void GetFileOrDirectory(string options, bool getDirectory) + { + FileOptions fOptions = new FileOptions(); + string[] args = getOptionStrings(options); + + fOptions.FullPath = args[0]; + fOptions.Path = args[1]; + + string callbackId = args[3]; + + try + { + fOptions.CreatingOpt = JSON.JsonHelper.Deserialize<CreatingOptions>(args[2]); + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + return; + } + + try + { + if ((string.IsNullOrEmpty(fOptions.Path)) || (string.IsNullOrEmpty(fOptions.FullPath))) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + return; + } + + string path; + + if (fOptions.Path.Split(':').Length > 2) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); + return; + } + + try + { + path = Path.Combine(fOptions.FullPath + "/", fOptions.Path); + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); + return; + } + + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + bool isFile = isoFile.FileExists(path); + bool isDirectory = isoFile.DirectoryExists(path); + bool create = (fOptions.CreatingOpt == null) ? false : fOptions.CreatingOpt.Create; + bool exclusive = (fOptions.CreatingOpt == null) ? false : fOptions.CreatingOpt.Exclusive; + if (create) + { + if (exclusive && (isoFile.FileExists(path) || isoFile.DirectoryExists(path))) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, PATH_EXISTS_ERR), callbackId); + return; + } + + // need to make sure the parent exists + // it is an error to create a directory whose immediate parent does not yet exist + // see issue: https://issues.apache.org/jira/browse/CB-339 + string[] pathParts = path.Split('/'); + string builtPath = pathParts[0]; + for (int n = 1; n < pathParts.Length - 1; n++) + { + builtPath += "/" + pathParts[n]; + if (!isoFile.DirectoryExists(builtPath)) + { + Debug.WriteLine(String.Format("Error :: Parent folder \"{0}\" does not exist, when attempting to create \"{1}\"",builtPath,path)); + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + return; + } + } + + if ((getDirectory) && (!isDirectory)) + { + isoFile.CreateDirectory(path); + } + else + { + if ((!getDirectory) && (!isFile)) + { + + IsolatedStorageFileStream fileStream = isoFile.CreateFile(path); + fileStream.Close(); + } + } + } + else // (not create) + { + if ((!isFile) && (!isDirectory)) + { + if (path.IndexOf("//www") == 0) + { + Uri fileUri = new Uri(path.Remove(0,2), UriKind.Relative); + StreamResourceInfo streamInfo = Application.GetResourceStream(fileUri); + if (streamInfo != null) + { + FileEntry _entry = FileEntry.GetEntry(fileUri.OriginalString,true); + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, _entry), callbackId); + + //using (BinaryReader br = new BinaryReader(streamInfo.Stream)) + //{ + // byte[] data = br.ReadBytes((int)streamInfo.Stream.Length); + + //} + + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + } + + + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + } + return; + } + if (((getDirectory) && (!isDirectory)) || ((!getDirectory) && (!isFile))) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, TYPE_MISMATCH_ERR), callbackId); + return; + } + } + FileEntry entry = FileEntry.GetEntry(path); + if (entry != null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); + } + } + } + catch (Exception ex) + { + if (!this.HandleException(ex)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); + } + } + } + + private static string AddSlashToDirectory(string dirPath) + { + if (dirPath.EndsWith("/")) + { + return dirPath; + } + else + { + return dirPath + "/"; + } + } + + /// <summary> + /// Returns file content in a form of base64 string + /// </summary> + /// <param name="stream">File stream</param> + /// <returns>Base64 representation of the file</returns> + private string GetFileContent(Stream stream) + { + int streamLength = (int)stream.Length; + byte[] fileData = new byte[streamLength + 1]; + stream.Read(fileData, 0, streamLength); + stream.Close(); + return Convert.ToBase64String(fileData); + } + + #endregion + + } +} diff --git a/plugins/org.apache.cordova.file/test/autotest/html/HtmlReporter.js b/plugins/org.apache.cordova.file/test/autotest/html/HtmlReporter.js new file mode 100644 index 00000000..7d9d9240 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/html/HtmlReporter.js @@ -0,0 +1,101 @@ +jasmine.HtmlReporter = function(_doc) { + var self = this; + var doc = _doc || window.document; + + var reporterView; + + var dom = {}; + + // Jasmine Reporter Public Interface + self.logRunningSpecs = false; + + self.reportRunnerStarting = function(runner) { + var specs = runner.specs() || []; + + if (specs.length == 0) { + return; + } + + createReporterDom(runner.env.versionString()); + doc.body.appendChild(dom.reporter); + + reporterView = new jasmine.HtmlReporter.ReporterView(dom); + reporterView.addSpecs(specs, self.specFilter); + }; + + self.reportRunnerResults = function(runner) { + reporterView && reporterView.complete(); + }; + + self.reportSuiteResults = function(suite) { + reporterView.suiteComplete(suite); + }; + + self.reportSpecStarting = function(spec) { + if (self.logRunningSpecs) { + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } + }; + + self.reportSpecResults = function(spec) { + reporterView.specComplete(spec); + }; + + self.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } + }; + + self.specFilter = function(spec) { + if (!focusedSpecName()) { + return true; + } + + return spec.getFullName().indexOf(focusedSpecName()) === 0; + }; + + return self; + + function focusedSpecName() { + var specName; + + (function memoizeFocusedSpec() { + if (specName) { + return; + } + + var paramMap = []; + var params = doc.location.search.substring(1).split('&'); + + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + specName = paramMap.spec; + })(); + + return specName; + } + + function createReporterDom(version) { + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, + dom.banner = self.createDom('div', { className: 'banner' }, + self.createDom('span', { className: 'title' }, "Jasmine "), + self.createDom('span', { className: 'version' }, version)), + + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), + dom.alert = self.createDom('div', {className: 'alert'}), + dom.results = self.createDom('div', {className: 'results'}, + dom.summary = self.createDom('div', { className: 'summary' }), + dom.details = self.createDom('div', { id: 'details' })) + ); + } +}; +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); diff --git a/plugins/org.apache.cordova.file/test/autotest/html/HtmlReporterHelpers.js b/plugins/org.apache.cordova.file/test/autotest/html/HtmlReporterHelpers.js new file mode 100644 index 00000000..745e1e09 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/html/HtmlReporterHelpers.js @@ -0,0 +1,60 @@ +jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + diff --git a/plugins/org.apache.cordova.file/test/autotest/html/ReporterView.js b/plugins/org.apache.cordova.file/test/autotest/html/ReporterView.js new file mode 100644 index 00000000..6a6d0056 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/html/ReporterView.js @@ -0,0 +1,164 @@ +jasmine.HtmlReporter.ReporterView = function(dom) { + this.startedAt = new Date(); + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + + this.createResultsMenu = function() { + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), + ' | ', + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); + + this.summaryMenuItem.onclick = function() { + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); + }; + + this.detailsMenuItem.onclick = function() { + showDetails(); + }; + }; + + this.addSpecs = function(specs, specFilter) { + this.totalSpecCount = specs.length; + + this.views = { + specs: {}, + suites: {} + }; + + for (var i = 0; i < specs.length; i++) { + var spec = specs[i]; + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); + if (specFilter(spec)) { + this.runningSpecCount++; + } + } + }; + + this.specComplete = function(spec) { + this.completeSpecCount++; + + if (isUndefined(this.views.specs[spec.id])) { + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); + } + + var specView = this.views.specs[spec.id]; + + switch (specView.status()) { + case 'passed': + this.passedCount++; + break; + + case 'failed': + this.failedCount++; + break; + + case 'skipped': + this.skippedCount++; + break; + } + + specView.refresh(); + this.refresh(); + }; + + this.suiteComplete = function(suite) { + var suiteView = this.views.suites[suite.id]; + if (isUndefined(suiteView)) { + return; + } + suiteView.refresh(); + }; + + this.refresh = function() { + + if (isUndefined(this.resultsMenu)) { + this.createResultsMenu(); + } + + // currently running UI + if (isUndefined(this.runningAlert)) { + this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); + dom.alert.appendChild(this.runningAlert); + } + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); + + // skipped specs UI + if (isUndefined(this.skippedAlert)) { + this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); + } + + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.skippedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.skippedAlert); + } + + // passing specs UI + if (isUndefined(this.passedAlert)) { + this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); + } + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); + + // failing specs UI + if (isUndefined(this.failedAlert)) { + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); + } + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); + + if (this.failedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.failedAlert); + dom.alert.appendChild(this.resultsMenu); + } + + // summary info + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; + }; + + this.complete = function() { + dom.alert.removeChild(this.runningAlert); + + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.failedCount === 0) { + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); + } else { + showDetails(); + } + + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); + }; + + return this; + + function showDetails() { + if (dom.reporter.className.search(/showDetails/) === -1) { + dom.reporter.className += " showDetails"; + } + } + + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + + function isDefined(obj) { + return !isUndefined(obj); + } + + function specPluralizedFor(count) { + var str = count + " spec"; + if (count > 1) { + str += "s" + } + return str; + } + +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); + + diff --git a/plugins/org.apache.cordova.file/test/autotest/html/SpecView.js b/plugins/org.apache.cordova.file/test/autotest/html/SpecView.js new file mode 100644 index 00000000..e8a3c23d --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/html/SpecView.js @@ -0,0 +1,79 @@ +jasmine.HtmlReporter.SpecView = function(spec, dom, views) { + this.spec = spec; + this.dom = dom; + this.views = views; + + this.symbol = this.createDom('li', { className: 'pending' }); + this.dom.symbolSummary.appendChild(this.symbol); + + this.summary = this.createDom('div', { className: 'specSummary' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) + ); + + this.detail = this.createDom('div', { className: 'specDetail' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.getFullName()) + ); +}; + +jasmine.HtmlReporter.SpecView.prototype.status = function() { + return this.getSpecStatus(this.spec); +}; + +jasmine.HtmlReporter.SpecView.prototype.refresh = function() { + this.symbol.className = this.status(); + + switch (this.status()) { + case 'skipped': + break; + + case 'passed': + this.appendSummaryToSuiteDiv(); + break; + + case 'failed': + this.appendSummaryToSuiteDiv(); + this.appendFailureDetail(); + break; + } +}; + +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { + this.summary.className += ' ' + this.status(); + this.appendToSummary(this.spec, this.summary); +}; + +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { + this.detail.className += ' ' + this.status(); + + var resultItems = this.spec.results().getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + this.detail.appendChild(messagesDiv); + this.dom.details.appendChild(this.detail); + } +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView); diff --git a/plugins/org.apache.cordova.file/test/autotest/html/SuiteView.js b/plugins/org.apache.cordova.file/test/autotest/html/SuiteView.js new file mode 100644 index 00000000..19a1efaf --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/html/SuiteView.js @@ -0,0 +1,22 @@ +jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { + this.suite = suite; + this.dom = dom; + this.views = views; + + this.element = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) + ); + + this.appendToSummary(this.suite, this.element); +}; + +jasmine.HtmlReporter.SuiteView.prototype.status = function() { + return this.getSpecStatus(this.suite); +}; + +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { + this.element.className += " " + this.status(); +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); + diff --git a/plugins/org.apache.cordova.file/test/autotest/html/TrivialReporter.js b/plugins/org.apache.cordova.file/test/autotest/html/TrivialReporter.js new file mode 100644 index 00000000..167ac506 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/html/TrivialReporter.js @@ -0,0 +1,192 @@ +/* @deprecated Use jasmine.HtmlReporter instead + */ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/plugins/org.apache.cordova.file/test/autotest/index.html b/plugins/org.apache.cordova.file/test/autotest/index.html new file mode 100644 index 00000000..6ebccbd0 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/index.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<!-- + + 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. + +--> + + +<html> + <head> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> + <meta name="viewport" content="width=device-width, height=device-height, user-scalable=yes, initial-scale=1.0;" /> + + <title>Cordova API Specs</title> + + <link rel="stylesheet" href="../master.css" type="text/css" media="screen" title="no title" charset="utf-8"> + <script type="text/javascript" src="../cordova-incl.js"></script> + </head> + <body id="stage" class="theme"> + <h1>Cordova API Specs</h1> + + <a href="pages/all.html" class="btn large" style="width:100%;">Run All Tests</a> + <a href="pages/accelerometer.html" class="btn large" style="width:100%;">Run Accelerometer Tests</a> + <a href="pages/battery.html" class="btn large" style="width:100%;">Run Battery Tests</a> + <a href="pages/camera.html" class="btn large" style="width:100%;">Run Camera Tests</a> + <a href="pages/capture.html" class="btn large" style="width:100%;">Run Capture Tests</a> + <a href="pages/compass.html" class="btn large" style="width:100%;">Run Compass Tests</a> + <a href="pages/contacts.html" class="btn large" style="width:100%;">Run Contacts Tests</a> + <a href="pages/datauri.html" class="btn large" style="width:100%;">Run Data URI Tests</a> + <a href="pages/device.html" class="btn large" style="width:100%;">Run Device Tests</a> + <a href="pages/file.html" class="btn large" style="width:100%;">Run File Tests</a> + <a href="pages/filetransfer.html" class="btn large" style="width:100%;">Run FileTransfer Tests</a> + <a href="pages/geolocation.html" class="btn large" style="width:100%;">Run Geolocation Tests</a> + <a href="pages/globalization.html" class="btn large" style="width:100%;">Run Globalization Tests</a> + <a href="pages/media.html" class="btn large" style="width:100%;">Run Media Tests</a> + <a href="pages/network.html" class="btn large" style="width:100%;">Run Network Tests</a> + <a href="pages/notification.html" class="btn large" style="width:100%;">Run Notification Tests</a> + <a href="pages/platform.html" class="btn large" style="width:100%;">Run Platform Tests</a> + <a href="pages/storage.html" class="btn large" style="width:100%;">Run Storage Tests</a> + <a href="pages/bridge.html" class="btn large" style="width:100%;">Run Bridge Tests</a> + + <h2> </h2><div class="backBtn" onclick="backHome();">Back</div> + </body> +</html> diff --git a/plugins/org.apache.cordova.file/test/autotest/jasmine.css b/plugins/org.apache.cordova.file/test/autotest/jasmine.css new file mode 100644 index 00000000..826e5753 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/jasmine.css @@ -0,0 +1,81 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +#HTMLReporter a { text-decoration: none; } +#HTMLReporter a:hover { text-decoration: underline; } +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } +#HTMLReporter #jasmine_content { position: fixed; right: 100%; } +#HTMLReporter .version { color: #aaaaaa; } +#HTMLReporter .banner { margin-top: 14px; } +#HTMLReporter .duration { color: #aaaaaa; float: right; } +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } +#HTMLReporter .symbolSummary li.passed { font-size: 14px; } +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } +#HTMLReporter .symbolSummary li.failed { line-height: 9px; } +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } +#HTMLReporter .symbolSummary li.pending { line-height: 11px; } +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +#HTMLReporter .runningAlert { background-color: #666666; } +#HTMLReporter .skippedAlert { background-color: #aaaaaa; } +#HTMLReporter .skippedAlert:first-child { background-color: #333333; } +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } +#HTMLReporter .passingAlert { background-color: #a6b779; } +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } +#HTMLReporter .failingAlert { background-color: #cf867e; } +#HTMLReporter .failingAlert:first-child { background-color: #b03911; } +#HTMLReporter .results { margin-top: 14px; } +#HTMLReporter #details { display: none; } +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter.showDetails .summary { display: none; } +#HTMLReporter.showDetails #details { display: block; } +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter .summary { margin-top: 14px; } +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } +#HTMLReporter .summary .specSummary.failed a { color: #b03911; } +#HTMLReporter .description + .suite { margin-top: 0; } +#HTMLReporter .suite { margin-top: 14px; } +#HTMLReporter .suite a { color: #333333; } +#HTMLReporter #details .specDetail { margin-bottom: 28px; } +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } +#HTMLReporter .resultMessage span.result { display: block; } +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } + +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } +#TrivialReporter a:visited, #TrivialReporter a { color: #303; } +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } +#TrivialReporter .runner.running { background-color: yellow; } +#TrivialReporter .options { text-align: right; font-size: .8em; } +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } +#TrivialReporter .suite .suite { margin: 5px; } +#TrivialReporter .suite.passed { background-color: #dfd; } +#TrivialReporter .suite.failed { background-color: #fdd; } +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } +#TrivialReporter .spec.skipped { background-color: #bbb; } +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } +#TrivialReporter .passed { background-color: #cfc; display: none; } +#TrivialReporter .failed { background-color: #fbb; } +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } +#TrivialReporter .resultMessage .mismatch { color: black; } +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } +#TrivialReporter #jasmine_content { position: fixed; right: 100%; } +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/plugins/org.apache.cordova.file/test/autotest/jasmine.js b/plugins/org.apache.cordova.file/test/autotest/jasmine.js new file mode 100644 index 00000000..9f284260 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/jasmine.js @@ -0,0 +1,2530 @@ +var isCommonJS = typeof window == "undefined"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the + * attributes on the object. + * + * @example + * // don't care about any other attributes than foo. + * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); + * + * @param sample {Object} sample + * @returns matchable object for the sample + */ +jasmine.objectContaining = function (sample) { + return new jasmine.Matchers.ObjectContaining(sample); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to <code>jasmine.log</code> in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a <em>disabled</em> Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a.jasmineMatches) { + return a.jasmineMatches(b); + } + + if (b.jasmineMatches) { + return b.jasmineMatches(a); + } + + if (a instanceof jasmine.Matchers.ObjectContaining) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.ObjectContaining) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + if (this.actual.callCount === 0) { + // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." + ]; + } else { + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) + ]; + } + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + var multiplier = Math.pow(10, precision); + var actual = Math.round(this.actual * multiplier); + expected = Math.round(expected * multiplier); + return expected == actual; +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} expected + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineToString = function() { + return '<jasmine.any(' + this.expectedClass + ')>'; +}; + +jasmine.Matchers.ObjectContaining = function (sample) { + this.sample = sample; +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + var env = jasmine.getEnv(); + + var hasKey = function(obj, keyName) { + return obj != null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in this.sample) { + if (!hasKey(other, property) && hasKey(this.sample, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); + } + } + + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { + return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>"; +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if <b>everything</b> below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + if (this.ppNestLevel_ > 40) { + throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); + } + + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar('<global>'); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append('<getter>'); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block) { + this.blocks.unshift(block); +}; + +jasmine.Queue.prototype.add = function(block) { + this.blocks.push(block); +}; + +jasmine.Queue.prototype.insertNext = function(block) { + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !this.abort) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to <code>jasmine.log</code> in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this)); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 2, + "build": 0, + "revision": 1333310630, + "release_candidate": 1 +}; diff --git a/plugins/org.apache.cordova.file/test/autotest/pages/.DS_Store b/plugins/org.apache.cordova.file/test/autotest/pages/.DS_Store Binary files differnew file mode 100644 index 00000000..5008ddfc --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/pages/.DS_Store diff --git a/plugins/org.apache.cordova.file/test/autotest/pages/file.html b/plugins/org.apache.cordova.file/test/autotest/pages/file.html new file mode 100644 index 00000000..d143c744 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/pages/file.html @@ -0,0 +1,90 @@ +<!DOCTYPE html> +<!-- + + 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. + +--> + + +<html> +<head> + <title>Cordova: File API Specs</title> + + <meta name="viewport" content="width=device-width, height=device-height, user-scalable=yes, initial-scale=1.0;" /> + <!-- Load jasmine --> + <link href="../jasmine.css" rel="stylesheet"/> + <script type="text/javascript" src="../jasmine.js"></script> + <script type="text/javascript" src="../html/HtmlReporterHelpers.js"></script> + <script type="text/javascript" src="../html/HtmlReporter.js"></script> + <script type="text/javascript" src="../html/ReporterView.js"></script> + <script type="text/javascript" src="../html/SpecView.js"></script> + <script type="text/javascript" src="../html/SuiteView.js"></script> + <script type="text/javascript" src="../html/TrivialReporter.js"></script> + + <!-- Source --> + <script type="text/javascript" src="../../cordova-incl.js"></script> + + <!-- Load Test Runner --> + <script type="text/javascript" src="../test-runner.js"></script> + + <!-- Tests --> + <script type="text/javascript" src="../tests/file.tests.js"></script> + + <script type="text/javascript"> + var root, temp_root, persistent_root; + + document.addEventListener('deviceready', function () { + // one-time retrieval of the root file system entry + var onError = function(e) { + console.log('[ERROR] Problem setting up root filesystem for test running! Error to follow.'); + console.log(JSON.stringify(e)); + }; + + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, + function(fileSystem) { + console.log('File API test Init: Setting PERSISTENT FS.'); + root = fileSystem.root; // set in file.tests.js + persistent_root = root; + + // Once root is set up, fire off tests + var jasmineEnv = jasmine.getEnv(); + jasmineEnv.updateInterval = 1000; + + var htmlReporter = new jasmine.HtmlReporter(); + + jasmineEnv.addReporter(htmlReporter); + + jasmineEnv.specFilter = function(spec) { + return htmlReporter.specFilter(spec); + }; + + jasmineEnv.execute(); + }, onError); + window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, + function(fileSystem) { + console.log('File API test Init: Setting TEMPORARY FS.'); + temp_root = fileSystem.root; // set in file.tests.js + }, onError); + }, false); + </script> +</head> + +<body> + <a href="javascript:" class="backBtn" onclick="backHome();">Back</a> +</body> +</html> diff --git a/plugins/org.apache.cordova.file/test/autotest/test-runner.js b/plugins/org.apache.cordova.file/test/autotest/test-runner.js new file mode 100644 index 00000000..f72b3cc5 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/test-runner.js @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +if (window.sessionStorage != null) { + window.sessionStorage.clear(); +} + +// Timeout is 2 seconds to allow physical devices enough +// time to query the response. This is important for some +// Android devices. +var Tests = function() {}; +Tests.TEST_TIMEOUT = 7500; + +// Creates a spy that will fail if called. +function createDoNotCallSpy(name, opt_extraMessage) { + return jasmine.createSpy().andCallFake(function() { + var errorMessage = name + ' should not have been called.'; + if (arguments.length) { + errorMessage += ' Got args: ' + JSON.stringify(arguments); + } + if (opt_extraMessage) { + errorMessage += '\n' + opt_extraMessage; + } + expect(false).toBe(true, errorMessage); + }); +} + +// Waits for any of the given spys to be called. +// Last param may be a custom timeout duration. +function waitsForAny() { + var spys = [].slice.call(arguments); + var timeout = Tests.TEST_TIMEOUT; + if (typeof spys[spys.length - 1] == 'number') { + timeout = spys.pop(); + } + waitsFor(function() { + for (var i = 0; i < spys.length; ++i) { + if (spys[i].wasCalled) { + return true; + } + } + return false; + }, "Expecting callbacks to be called.", timeout); +} diff --git a/plugins/org.apache.cordova.file/test/autotest/tests/.DS_Store b/plugins/org.apache.cordova.file/test/autotest/tests/.DS_Store Binary files differnew file mode 100644 index 00000000..5008ddfc --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/tests/.DS_Store diff --git a/plugins/org.apache.cordova.file/test/autotest/tests/file.tests.js b/plugins/org.apache.cordova.file/test/autotest/tests/file.tests.js new file mode 100644 index 00000000..3ebaffcc --- /dev/null +++ b/plugins/org.apache.cordova.file/test/autotest/tests/file.tests.js @@ -0,0 +1,3590 @@ +/* + * + * 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. + * +*/ + +describe('File API', function() { + // Adding a Jasmine helper matcher, to report errors when comparing to FileError better. + var fileErrorMap = { + 1: 'NOT_FOUND_ERR', + 2: 'SECURITY_ERR', + 3: 'ABORT_ERR', + 4: 'NOT_READABLE_ERR', + 5: 'ENCODING_ERR', + 6: 'NO_MODIFICATION_ALLOWED_ERR', + 7: 'INVALID_STATE_ERR', + 8: 'SYNTAX_ERR', + 9: 'INVALID_MODIFICATION_ERR', + 10:'QUOTA_EXCEEDED_ERR', + 11:'TYPE_MISMATCH_ERR', + 12:'PATH_EXISTS_ERR' + }; + beforeEach(function() { + this.addMatchers({ + toBeFileError: function(code) { + var error = this.actual; + this.message = function(){ + return "Expected FileError with code " + fileErrorMap[error.code] + " (" + error.code + ") to be " + fileErrorMap[code] + "(" + code + ")"; + }; + return (error.code == code); + }, + toCanonicallyMatch:function(path){ + this.message = function(){ + return "Expected paths to match : " + path + " should be " + this.actual; + }; + + var a = path.split("/").join("").split("\\").join(""); + var b = this.actual.split("/").join("").split("\\").join(""); + + return a == b; + } + }); + }); + + // HELPER FUNCTIONS + + // deletes specified file or directory + var deleteEntry = function(name, success, error) { + // deletes entry, if it exists + window.resolveLocalFileSystemURI(root.toURL() + '/' + name, + function(entry) { + if (entry.isDirectory === true) { + entry.removeRecursively(success, error); + } else { + entry.remove(success, error); + } + }, success); + }; + // deletes file, if it exists, then invokes callback + var deleteFile = function(fileName, callback) { + root.getFile(fileName, null, + // remove file system entry + function(entry) { + entry.remove(callback, function() { console.log('[ERROR] deleteFile cleanup method invoked fail callback.'); }); + }, + // doesn't exist + callback); + }; + // deletes and re-creates the specified file + var createFile = function(fileName, success, error) { + deleteEntry(fileName, function() { + root.getFile(fileName, {create: true}, success, error); + }, error); + }; + // deletes and re-creates the specified directory + var createDirectory = function(dirName, success, error) { + deleteEntry(dirName, function() { + root.getDirectory(dirName, {create: true}, success, error); + }, error); + }; + + var createFail = function(module) { + return jasmine.createSpy().andCallFake(function(err) { + console.log('[ERROR ' + module + '] ' + JSON.stringify(err)); + }); + }; + + var createWin = function(module) { + return jasmine.createSpy().andCallFake(function() { + console.log('[ERROR ' + module + '] Unexpected success callback'); + }); + }; + + describe('FileError object', function() { + it("file.spec.1 should define FileError constants", function() { + expect(FileError.NOT_FOUND_ERR).toBe(1); + expect(FileError.SECURITY_ERR).toBe(2); + expect(FileError.ABORT_ERR).toBe(3); + expect(FileError.NOT_READABLE_ERR).toBe(4); + expect(FileError.ENCODING_ERR).toBe(5); + expect(FileError.NO_MODIFICATION_ALLOWED_ERR).toBe(6); + expect(FileError.INVALID_STATE_ERR).toBe(7); + expect(FileError.SYNTAX_ERR).toBe(8); + expect(FileError.INVALID_MODIFICATION_ERR).toBe(9); + expect(FileError.QUOTA_EXCEEDED_ERR).toBe(10); + expect(FileError.TYPE_MISMATCH_ERR).toBe(11); + expect(FileError.PATH_EXISTS_ERR).toBe(12); + }); + }); + + describe('LocalFileSystem', function() { + + it("file.spec.2 should define LocalFileSystem constants", function() { + expect(LocalFileSystem.TEMPORARY).toBe(0); + expect(LocalFileSystem.PERSISTENT).toBe(1); + }); + + describe('window.requestFileSystem', function() { + it("file.spec.3 should be defined", function() { + expect(window.requestFileSystem).toBeDefined(); + }); + it("file.spec.4 should be able to retrieve a PERSISTENT file system", function() { + var win = jasmine.createSpy().andCallFake(function(fileSystem) { + expect(fileSystem).toBeDefined(); + expect(fileSystem.name).toBeDefined(); + expect(fileSystem.name).toBe("persistent"); + expect(fileSystem.root).toBeDefined(); + }), + fail = createFail('window.requestFileSystem'); + + // retrieve PERSISTENT file system + runs(function() { + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "success callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).not.toHaveBeenCalled(); + expect(win).toHaveBeenCalled(); + }); + }); + it("file.spec.5 should be able to retrieve a TEMPORARY file system", function() { + var win = jasmine.createSpy().andCallFake(function(fileSystem) { + expect(fileSystem).toBeDefined(); + expect(fileSystem.name).toBeDefined(); + expect(fileSystem.name).toBe("temporary"); + expect(fileSystem.root).toBeDefined(); + }), + fail = createFail('window.requestFileSystem'); + + // Request the file system + runs(function() { + window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "success callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).not.toHaveBeenCalled(); + expect(win).toHaveBeenCalled(); + }); + }); + it("file.spec.6 should error if you request a file system that is too large", function() { + var fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.QUOTA_EXCEEDED_ERR); + }), + win = createWin('window.requestFileSystem'); + + // Request the file system + runs(function() { + window.requestFileSystem(LocalFileSystem.TEMPORARY, 1000000000000000, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "error callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).toHaveBeenCalled(); + }); + }); + it("file.spec.7 should error out if you request a file system that does not exist", function() { + var fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.SYNTAX_ERR); + }), + win = createWin('window.requestFileSystem'); + + // Request the file system + runs(function() { + window.requestFileSystem(-1, 0, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "error callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).toHaveBeenCalled(); + }); + }); + }); + + describe('window.resolveLocalFileSystemURI', function() { + it("file.spec.3 should be defined", function() { + expect(window.resolveLocalFileSystemURI).toBeDefined(); + }); + it("file.spec.9 should resolve a valid file name", function() { + var fileName = "resolve.file.uri", + win = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.name).toCanonicallyMatch(fileName); + + // cleanup + deleteEntry(fileName); + }), + fail = createFail('window.resolveLocalFileSystemURI'); + resolveCallback = jasmine.createSpy().andCallFake(function(entry) { + // lookup file system entry + runs(function() { + window.resolveLocalFileSystemURI(entry.toURL(), win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "resolveLocalFileSystemURI callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + + // create a new file entry + runs(function() { + createFile(fileName, resolveCallback, fail); + }); + + waitsFor(function() { return resolveCallback.wasCalled; }, "createFile callback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.10 resolve valid file name with parameters", function() { + var fileName = "resolve.file.uri.params", + win = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.name).toBe(fileName); + + // cleanup + deleteEntry(fileName); + }), + fail = createFail('window.resolveLocalFileSystemURI'); + resolveCallback = jasmine.createSpy().andCallFake(function(entry) { + // lookup file system entry + runs(function() { + window.resolveLocalFileSystemURI(entry.toURL() + "?1234567890", win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "resolveLocalFileSystemURI callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + + // create a new file entry + runs(function() { + createFile(fileName, resolveCallback, fail); + }); + + waitsFor(function() { return resolveCallback.wasCalled; }, "createFile callback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.11 should error (NOT_FOUND_ERR) when resolving (non-existent) invalid file name", function() { + var fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + }), + win = createWin('window.resolveLocalFileSystemURI'); + + // lookup file system entry + runs(function() { + window.resolveLocalFileSystemURI("file:///this.is.not.a.valid.file.txt", win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "error callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }); + it("file.spec.12 should error (ENCODING_ERR) when resolving invalid URI with leading /", function() { + var fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.ENCODING_ERR); + }), + win = createWin('window.resolveLocalFileSystemURI'); + + // lookup file system entry + runs(function() { + window.resolveLocalFileSystemURI("/this.is.not.a.valid.url", win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "error callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe('Metadata interface', function() { + it("file.spec.13 should exist and have the right properties", function() { + var metadata = new Metadata(); + expect(metadata).toBeDefined(); + expect(metadata.modificationTime).toBeDefined(); + }); + }); + + describe('Flags interface', function() { + it("file.spec.13 should exist and have the right properties", function() { + var flags = new Flags(false, true); + expect(flags).toBeDefined(); + expect(flags.create).toBeDefined(); + expect(flags.create).toBe(false); + expect(flags.exclusive).toBeDefined(); + expect(flags.exclusive).toBe(true); + }); + }); + + describe('FileSystem interface', function() { + it("file.spec.15 should have a root that is a DirectoryEntry", function() { + var win = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(false); + expect(entry.isDirectory).toBe(true); + expect(entry.name).toBeDefined(); + expect(entry.fullPath).toBeDefined(); + expect(entry.getMetadata).toBeDefined(); + expect(entry.moveTo).toBeDefined(); + expect(entry.copyTo).toBeDefined(); + expect(entry.toURL).toBeDefined(); + expect(entry.remove).toBeDefined(); + expect(entry.getParent).toBeDefined(); + expect(entry.createReader).toBeDefined(); + expect(entry.getFile).toBeDefined(); + expect(entry.getDirectory).toBeDefined(); + expect(entry.removeRecursively).toBeDefined(); + }), + fail = createFail('FileSystem'); + + runs(function() { + window.resolveLocalFileSystemURI(root.toURL(), win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "success callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).not.toHaveBeenCalled(); + expect(win).toHaveBeenCalled(); + }); + }); + }); + + describe('DirectoryEntry', function() { + it("file.spec.16 getFile: get Entry for file that does not exist", function() { + var fileName = "de.no.file", + filePath = root.fullPath + '/' + fileName, + fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + }), + win = createWin('DirectoryEntry'); + + // create:false, exclusive:false, file does not exist + runs(function() { + root.getFile(fileName, {create:false}, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "error callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }); + it("file.spec.17 etFile: create new file", function() { + var fileName = "de.create.file", + filePath = root.fullPath + '/' + fileName, + win = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.name).toCanonicallyMatch(fileName); + expect(entry.fullPath).toBe(filePath); + // cleanup + entry.remove(null, null); + }), + fail = createFail('DirectoryEntry'); + + // create:true, exclusive:false, file does not exist + runs(function() { + root.getFile(fileName, {create: true}, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "success callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.18 getFile: create new file (exclusive)", function() { + var fileName = "de.create.exclusive.file", + filePath = root.fullPath + '/' + fileName, + win = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.name).toBe(fileName); + expect(entry.fullPath).toBe(filePath); + + // cleanup + entry.remove(null, null); + }), + fail = createFail('DirectoryEntry'); + + // create:true, exclusive:true, file does not exist + runs(function() { + root.getFile(fileName, {create: true, exclusive:true}, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "success callback never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.19 getFile: create file that already exists", function() { + var fileName = "de.create.existing.file", + filePath = root.fullPath + '/' + fileName, + getFile = jasmine.createSpy().andCallFake(function(file) { + // create:true, exclusive:false, file exists + runs(function() { + root.getFile(fileName, {create:true}, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "win was never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + fail = createFail('DirectoryEntry'), + win = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.name).toCanonicallyMatch(fileName); + expect(entry.fullPath).toBe(filePath); + + // cleanup + entry.remove(null, fail); + }); + // create file to kick off it + runs(function() { + root.getFile(fileName, {create:true}, getFile, fail); + }); + + waitsFor(function() { return getFile.wasCalled; }, "getFile was never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.20 getFile: create file that already exists (exclusive)", function() { + var fileName = "de.create.exclusive.existing.file", + filePath = root.fullPath + '/' + fileName, + existingFile, + getFile = jasmine.createSpy().andCallFake(function(file) { + existingFile = file; + // create:true, exclusive:true, file exists + runs(function() { + root.getFile(fileName, {create:true, exclusive:true}, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "fail never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }), + fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.PATH_EXISTS_ERR); + + // cleanup + existingFile.remove(null, fail); + }), + win = createWin('DirectoryEntry'); + + // create file to kick off it + runs(function() { + root.getFile(fileName, {create:true}, getFile, fail); + }); + + waitsFor(function() { return getFile.wasCalled; }, "getFile never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.21 getFile: get Entry for existing file", function() { + var fileName = "de.get.file", + filePath = root.fullPath + '/' + fileName, + win = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.name).toCanonicallyMatch(fileName); + expect(entry.fullPath).toCanonicallyMatch(filePath); + + entry.remove(null, fail); //clean up + }), + fail = createFail('DirectoryEntry'), + getFile = jasmine.createSpy().andCallFake(function(file) { + // create:false, exclusive:false, file exists + runs(function() { + root.getFile(fileName, {create:false}, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "getFile success callback", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + + // create file to kick off it + runs(function() { + root.getFile(fileName, {create:true}, getFile, fail); + }); + + waitsFor(function() { return getFile.wasCalled; }, "file creation", Tests.TEST_TIMEOUT); + }); + it("file.spec.22 DirectoryEntry.getFile: get FileEntry for invalid path", function() { + var fileName = "de:invalid:path", + fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.ENCODING_ERR); + }), + win = createWin('DirectoryEntry'); + + // create:false, exclusive:false, invalid path + runs(function() { + root.getFile(fileName, {create:false}, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "fail never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + + }); + it("file.spec.23 DirectoryEntry.getDirectory: get Entry for directory that does not exist", function() { + var dirName = "de.no.dir", + dirPath = root.fullPath + '/' + dirName, + fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + }), + win = createWin('DirectoryEntry'); + + // create:false, exclusive:false, directory does not exist + runs(function() { + root.getDirectory(dirName, {create:false}, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "fail never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }); + it("file.spec.24 DirectoryEntry.getDirectory: create new dir with space then resolveFileSystemURI", function() { + var dirName = "de create dir", + dirPath = root.fullPath + '/' + dirName, + getDir = jasmine.createSpy().andCallFake(function(dirEntry) { + var dirURI = dirEntry.toURL(); + // now encode URI and try to resolve + runs(function() { + window.resolveLocalFileSystemURI(dirURI, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "win never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + + }), win = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.name).toCanonicallyMatch(dirName); + expect(directory.fullPath).toCanonicallyMatch(dirPath); + + // cleanup + directory.remove(null, fail); + }), + fail = createFail('DirectoryEntry'); + + // create:true, exclusive:false, directory does not exist + runs(function() { + root.getDirectory(dirName, {create: true}, getDir, fail); + }); + + waitsFor(function() { return getDir.wasCalled; }, "getDir never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.25 DirectoryEntry.getDirectory: create new dir with space resolveFileSystemURI with encoded URI", function() { + var dirName = "de create dir", + dirPath = root.fullPath + '/' + dirName, + getDir = jasmine.createSpy().andCallFake(function(dirEntry) { + var dirURI = dirEntry.toURL(); + // now encode URI and try to resolve + runs(function() { + window.resolveLocalFileSystemURI(encodeURI(dirURI), win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "win never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + win = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.name).toCanonicallyMatch(dirName); + expect(directory.fullPath).toCanonicallyMatch(dirPath); + // cleanup + directory.remove(null, fail); + }), + fail = createFail('DirectoryEntry'); + + // create:true, exclusive:false, directory does not exist + runs(function() { + root.getDirectory(dirName, {create: true}, getDir, fail); + }); + + waitsFor(function() { return getDir.wasCalled; }, "getDir never called", Tests.TEST_TIMEOUT); + }); + + it("file.spec.26 DirectoryEntry.getDirectory: create new directory", function() { + var dirName = "de.create.dir", + dirPath = root.fullPath + '/' + dirName, + win = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.name).toCanonicallyMatch(dirName); + expect(directory.fullPath).toCanonicallyMatch(dirPath); + + // cleanup + directory.remove(null, fail); + }), + fail = createFail('DirectoryEntry'); + + // create:true, exclusive:false, directory does not exist + runs(function() { + root.getDirectory(dirName, {create: true}, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "win never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + + it("file.spec.27 DirectoryEntry.getDirectory: create new directory (exclusive)", function() { + var dirName = "de.create.exclusive.dir", + dirPath = root.fullPath + '/' + dirName, + win = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.name).toCanonicallyMatch(dirName); + expect(directory.fullPath).toCanonicallyMatch(dirPath); + + // cleanup + directory.remove(null, fail); + }), + fail = createFail('DirectoryEntry'); + // create:true, exclusive:true, directory does not exist + runs(function() { + root.getDirectory(dirName, {create: true, exclusive:true}, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "win never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.28 DirectoryEntry.getDirectory: create directory that already exists", function() { + var dirName = "de.create.existing.dir", + dirPath = root.fullPath + '/' + dirName, + getDir = jasmine.createSpy().andCallFake(function(directory) { + // create:true, exclusive:false, directory exists + runs(function() { + root.getDirectory(dirName, {create:true}, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "win never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + win = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.name).toCanonicallyMatch(dirName); + expect(directory.fullPath).toCanonicallyMatch(dirPath); + + // cleanup + directory.remove(null, fail); + }), + fail = createFail('DirectoryEntry'); + + // create directory to kick off it + runs(function() { + root.getDirectory(dirName, {create:true}, getDir, this.fail); + }); + + waitsFor(function() { return getDir.wasCalled; }, "getDir never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.29 DirectoryEntry.getDirectory: create directory that already exists (exclusive)", function() { + var dirName = "de.create.exclusive.existing.dir", + dirPath = root.fullPath + '/' + dirName, + existingDir, + getDir = jasmine.createSpy().andCallFake(function(directory) { + existingDir = directory; + // create:true, exclusive:true, directory exists + runs(function() { + root.getDirectory(dirName, {create:true, exclusive:true}, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "fail never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }), + fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.PATH_EXISTS_ERR); + + // cleanup + existingDir.remove(null, fail); + }), + win = createWin('DirectoryEntry'); + + // create directory to kick off it + runs(function() { + root.getDirectory(dirName, {create:true}, getDir, fail); + }); + + waitsFor(function() { return getDir.wasCalled; }, "getDir never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.30 DirectoryEntry.getDirectory: get Entry for existing directory", function() { + var dirName = "de.get.dir", + dirPath = root.fullPath + '/' + dirName, + getDir = jasmine.createSpy().andCallFake(function(directory) { + // create:false, exclusive:false, directory exists + runs(function() { + root.getDirectory(dirName, {create:false}, win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "win never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + win = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.name).toCanonicallyMatch(dirName); + + expect(directory.fullPath).toCanonicallyMatch(dirPath); + + // cleanup + directory.remove(null, fail); + }), + fail = createFail('DirectoryEntry'); + + // create directory to kick off it + root.getDirectory(dirName, {create:true}, getDir, fail); + }); + it("file.spec.31 DirectoryEntry.getDirectory: get DirectoryEntry for invalid path", function() { + var dirName = "de:invalid:path", + fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.ENCODING_ERR); + }), + win = createWin('DirectoryEntry'); + + // create:false, exclusive:false, invalid path + runs(function() { + root.getDirectory(dirName, {create:false}, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "fail never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }); + it("file.spec.32 DirectoryEntry.getDirectory: get DirectoryEntry for existing file", function() { + var fileName = "de.existing.file", + existingFile, + filePath = root.fullPath + '/' + fileName, + getDir = jasmine.createSpy().andCallFake(function(file) { + existingFile = file; + // create:false, exclusive:false, existing file + runs(function() { + root.getDirectory(fileName, {create:false}, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "fail never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }), + fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.TYPE_MISMATCH_ERR); + + // cleanup + existingFile.remove(null, null); + }), + win = createWin('DirectoryEntry'); + + // create file to kick off it + runs(function() { + root.getFile(fileName, {create:true}, getDir, fail); + }); + + waitsFor(function() { return getDir.wasCalled; }, "getDir was called", Tests.TEST_TIMEOUT); + }); + it("file.spec.33 DirectoryEntry.getFile: get FileEntry for existing directory", function() { + var dirName = "de.existing.dir", + existingDir, + dirPath = root.fullPath + '/' + dirName, + getFile = jasmine.createSpy().andCallFake(function(directory) { + existingDir = directory; + // create:false, exclusive:false, existing directory + runs(function() { + root.getFile(dirName, {create:false}, win, fail); + }); + + waitsFor(function() { return fail.wasCalled; }, "fail never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }), + fail = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.TYPE_MISMATCH_ERR); + + // cleanup + existingDir.remove(null, null); + }), + win = createWin('DirectoryEntry'); + + // create directory to kick off it + runs(function() { + root.getDirectory(dirName, {create:true}, getFile, fail); + }); + + waitsFor(function() { return getFile.wasCalled; }, "getFile never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.34 DirectoryEntry.removeRecursively on directory", function() { + var dirName = "de.removeRecursively", + subDirName = "dir", + dirPath = root.fullPath + '/' + dirName, + //subDirPath = this.root.fullPath + '/' + subDirName, + subDirPath = dirPath + '/' + subDirName, + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + // delete directory + var deleteDirectory = jasmine.createSpy().andCallFake(function(directory) { + runs(function() { + entry.removeRecursively(remove, fail); + }); + + waitsFor(function() { return remove.wasCalled; }, "remove never called", Tests.TEST_TIMEOUT); + }); + // create a sub-directory within directory + runs(function() { + entry.getDirectory(subDirName, {create: true}, deleteDirectory, fail); + }); + + waitsFor(function() { return deleteDirectory.wasCalled; }, "deleteDirectory never called", Tests.TEST_TIMEOUT); + }), + remove = jasmine.createSpy().andCallFake(function() { + // it that removed directory no longer exists + runs(function() { + root.getDirectory(dirName, {create:false}, win, dirExists); + }); + + waitsFor(function() { return dirExists.wasCalled; }, "dirExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(dirExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }), + dirExists = jasmine.createSpy().andCallFake(function(error){ + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + }), + fail = createFail('DirectoryEntry'), + win = createWin('DirectoryEntry'); + + // create a new directory entry to kick off it + runs(function() { + root.getDirectory(dirName, {create:true}, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.35 createReader: create reader on existing directory", function() { + // create reader for root directory + var reader = root.createReader(); + expect(reader).toBeDefined(); + expect(typeof reader.readEntries).toBe('function'); + }); + it("file.spec.36 removeRecursively on root file system", function() { + var remove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NO_MODIFICATION_ALLOWED_ERR); + }), + win = createWin('DirectoryEntry'); + + // remove root file system + runs(function() { + root.removeRecursively(win, remove); + }); + + waitsFor(function() { return remove.wasCalled; }, "remove never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(remove).toHaveBeenCalled(); + }); + }); + }); + + describe('DirectoryReader interface', function() { + describe("readEntries", function() { + it("file.spec.37 should read contents of existing directory", function() { + var reader, + win = jasmine.createSpy().andCallFake(function(entries) { + expect(entries).toBeDefined(); + expect(entries instanceof Array).toBe(true); + }), + fail = createFail('DirectoryReader'); + + // create reader for root directory + reader = root.createReader(); + // read entries + runs(function() { + reader.readEntries(win, fail); + }); + + waitsFor(function() { return win.wasCalled; }, "win never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.38 should read contents of directory that has been removed", function() { + var dirName = "de.createReader.notfound", + dirPath = root.fullPath + '/' + dirName, + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + // read entries + var readEntries = jasmine.createSpy().andCallFake(function() { + var reader = directory.createReader(); + + runs(function() { + reader.readEntries(win, itReader); + }); + + waitsFor(function() { return itReader.wasCalled; }, "itReader never called", Tests.TEST_TIMEOUT); + }); + // delete directory + runs(function() { + directory.removeRecursively(readEntries, fail); + }); + + waitsFor(function() { return readEntries.wasCalled; }, "readEntries never called", Tests.TEST_TIMEOUT); + }), + itReader = jasmine.createSpy().andCallFake(function(error) { + var itDirectoryExists = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + }); + + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + + runs(function() { + root.getDirectory(dirName, {create:false}, win, itDirectoryExists); + }); + + waitsFor(function() { return itDirectoryExists.wasCalled; }, "itDirectoryExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itDirectoryExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }), + fail = createFail('DirectoryReader'), + win = createWin('DirectoryReader'); + + // create a new directory entry to kick off it + runs(function() { + root.getDirectory(dirName, {create:true}, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + }); + }); + + describe('File', function() { + it("file.spec.39 constructor should be defined", function() { + expect(File).toBeDefined(); + expect(typeof File).toBe('function'); + }); + it("file.spec.40 should be define File attributes", function() { + var file = new File(); + expect(file.name).toBeDefined(); + expect(file.fullPath).toBeDefined(); + expect(file.type).toBeDefined(); + expect(file.lastModifiedDate).toBeDefined(); + expect(file.size).toBeDefined(); + }); + }); + + describe('FileEntry', function() { + it("file.spec.41 should be define FileEntry methods", function() { + var fileName = "fe.methods", + itFileEntry = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(typeof fileEntry.createWriter).toBe('function'); + expect(typeof fileEntry.file).toBe('function'); + + // cleanup + fileEntry.remove(null, fail); + }), + fail = createFail('FileEntry'); + + // create a new file entry to kick off it + runs(function() { + root.getFile(fileName, {create:true}, itFileEntry, fail); + }); + + waitsFor(function() { return itFileEntry.wasCalled; }, "itFileEntry never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileEntry).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.42 createWriter should return a FileWriter object", function() { + var fileName = "fe.createWriter", + itFile, + entryCallback = jasmine.createSpy().andCallFake(function(fileEntry) { + itFile = fileEntry; + + runs(function() { + fileEntry.createWriter(itWriter, fail); + }); + + waitsFor(function() { return itWriter.wasCalled; }, "itWriter", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itWriter).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itWriter = jasmine.createSpy().andCallFake(function(writer) { + expect(writer).toBeDefined(); + expect(writer instanceof FileWriter).toBe(true); + + // cleanup + itFile.remove(null, fail); + }), + fail = createFail('FileEntry'); + + // create a new file entry to kick off it + runs(function() { + root.getFile(fileName, {create:true}, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.43 file should return a File object", function() { + var fileName = "fe.file", + newFile, + entryCallback = jasmine.createSpy().andCallFake(function(fileEntry) { + newFile = fileEntry; + + runs(function() { + fileEntry.file(itFile, fail); + }); + + waitsFor(function() { return itFile.wasCalled; }, "itFile never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFile).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itFile = jasmine.createSpy().andCallFake(function(file) { + expect(file).toBeDefined(); + expect(file instanceof File).toBe(true); + + // cleanup + newFile.remove(null, fail); + }), + fail = createFail('FileEntry'); + + // create a new file entry to kick off it + runs(function() { + root.getFile(fileName, {create:true}, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.44 file: on File that has been removed", function() { + var fileName = "fe.no.file", + entryCallback = jasmine.createSpy().andCallFake(function(fileEntry) { + // create File object + var getFile = jasmine.createSpy().andCallFake(function() { + runs(function() { + fileEntry.file(win, itFile); + }); + + waitsFor(function() { return itFile.wasCalled; }, "itFile never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFile).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }); + // delete file + runs(function() { + fileEntry.remove(getFile, fail); + }); + + waitsFor(function() { return getFile.wasCalled; }, "getFile never called", Tests.TEST_TIMEOUT); + }), + itFile = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + }), + fail = createFail('FileEntry'), + win = createWin('FileEntry'); + + // create a new file entry to kick off it + runs(function() { + root.getFile(fileName, {create:true}, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + }); + describe('Entry', function() { + it("file.spec.45 Entry object", function() { + var fileName = "entry", + fullPath = root.fullPath + '/' + fileName, + fail = createFail('Entry'), + itEntry = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.name).toCanonicallyMatch(fileName); + expect(entry.fullPath).toCanonicallyMatch(fullPath); + expect(typeof entry.getMetadata).toBe('function'); + expect(typeof entry.setMetadata).toBe('function'); + expect(typeof entry.moveTo).toBe('function'); + expect(typeof entry.copyTo).toBe('function'); + expect(typeof entry.toURL).toBe('function'); + expect(typeof entry.remove).toBe('function'); + expect(typeof entry.getParent).toBe('function'); + expect(typeof entry.createWriter).toBe('function'); + expect(typeof entry.file).toBe('function'); + + // cleanup + deleteEntry(fileName); + }); + + // create a new file entry + runs(function() { + createFile(fileName, itEntry, fail); + }); + + waitsFor(function() { return itEntry.wasCalled; }, "itEntry", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itEntry).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.46 Entry.getMetadata on file", function() { + var fileName = "entry.metadata.file", + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + runs(function() { + entry.getMetadata(itMetadata, fail); + }); + + waitsFor(function() { return itMetadata.wasCalled; }, "itMetadata never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itMetadata).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + fail = createFail('Entry'), + itMetadata = jasmine.createSpy().andCallFake(function(metadata) { + expect(metadata).toBeDefined(); + expect(metadata.modificationTime instanceof Date).toBe(true); + + // cleanup + deleteEntry(fileName); + }); + + // create a new file entry + createFile(fileName, entryCallback, fail); + }); + it("file.spec.47 Entry.getMetadata on directory", function() { + var dirName = "entry.metadata.dir", + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + runs(function() { + entry.getMetadata(itMetadata, fail); + }); + + waitsFor(function() { return itMetadata.wasCalled; }, "itMetadata never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itMetadata).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + fail = createFail('Entry'), + itMetadata = jasmine.createSpy().andCallFake(function(metadata) { + expect(metadata).toBeDefined(); + expect(metadata.modificationTime instanceof Date).toBe(true); + + // cleanup + deleteEntry(dirName); + }); + + // create a new directory entry + runs(function() { + createDirectory(dirName, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.48 Entry.getParent on file in root file system", function() { + var fileName = "entry.parent.file", + rootPath = root.fullPath, + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + runs(function() { + entry.getParent(itParent, fail); + }); + + waitsFor(function() { return itParent.wasCalled; }, "itCalled never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itParent).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itParent = jasmine.createSpy().andCallFake(function(parent) { + expect(parent).toBeDefined(); + expect(parent.fullPath).toCanonicallyMatch(rootPath); + + // cleanup + deleteEntry(fileName); + }); + + // create a new file entry + runs(function() { + createFile(fileName, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.49 Entry.getParent on directory in root file system", function() { + var dirName = "entry.parent.dir", + rootPath = root.fullPath, + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + runs(function() { + entry.getParent(itParent, fail); + }); + + waitsFor(function() { return itParent.wasCalled; }, "itParent never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itParent).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itParent = jasmine.createSpy().andCallFake(function(parent) { + expect(parent).toBeDefined(); + expect(parent.fullPath).toCanonicallyMatch(rootPath); + + // cleanup + deleteEntry(dirName); + }); + + // create a new directory entry + runs(function() { + createDirectory(dirName, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.50 Entry.getParent on root file system", function() { + var rootPath = root.fullPath, + itParent = jasmine.createSpy().andCallFake(function(parent) { + expect(parent).toBeDefined(); + expect(parent.fullPath).toCanonicallyMatch(rootPath); + }), + fail = createFail('Entry'); + + // create a new directory entry + runs(function() { + root.getParent(itParent, fail); + }); + + waitsFor(function() { return itParent.wasCalled; }, "itParent never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itParent).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.51 Entry.toURL on file", function() { + var fileName = "entry.uri.file", + rootPath = root.fullPath, + itURI = jasmine.createSpy().andCallFake(function(entry) { + var uri = entry.toURL(); + expect(uri).toBeDefined(); + expect(uri.indexOf(rootPath)).not.toBe(-1); + + // cleanup + deleteEntry(fileName); + }), + fail = createFail('Entry'); + + // create a new file entry + runs(function() { + createFile(fileName, itURI, fail); + }); + + waitsFor(function() { return itURI.wasCalled; }, "itURI never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itURI).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.52 Entry.toURL on directory", function() { + var dirName = "entry.uri.dir", + rootPath = root.fullPath, + itURI = jasmine.createSpy().andCallFake(function(entry) { + var uri = entry.toURL(); + expect(uri).toBeDefined(); + expect(uri.indexOf(rootPath)).not.toBe(-1); + + // cleanup + deleteEntry(dirName); + }), + fail = createFail('Entry'); + + // create a new directory entry + runs(function() { + createDirectory(dirName, itURI, fail); + }); + + waitsFor(function() { return itURI.wasCalled; }, "itURI never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itURI).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.53 Entry.remove on file", function() { + var fileName = "entry.rm.file", + fullPath = root.fullPath + '/' + fileName, + win = createWin('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + var checkRemove = jasmine.createSpy().andCallFake(function() { + runs(function() { + root.getFile(fileName, null, win, itRemove); + }); + + waitsFor(function() { return itRemove.wasCalled; }, "itRemove never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(itRemove).toHaveBeenCalled(); + }); + }); + expect(entry).toBeDefined(); + + runs(function() { + entry.remove(checkRemove, fail); + }); + + waitsFor(function() { return checkRemove.wasCalled; }, "checkRemove never called", Tests.TEST_TIMEOUT); + }), + itRemove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + // cleanup + deleteEntry(fileName); + }), + fail = createFail('Entry'); + + // create a new file entry + runs(function() { + createFile(fileName, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.54 remove on empty directory", function() { + var dirName = "entry.rm.dir", + fullPath = root.fullPath + '/' + dirName, + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + var checkRemove = jasmine.createSpy().andCallFake(function() { + runs(function() { + root.getDirectory(dirName, null, win, itRemove); + }); + + waitsFor(function() { return itRemove.wasCalled; }, "itRemove never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itRemove).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + + expect(entry).toBeDefined(); + + runs(function() { + entry.remove(checkRemove, fail); + }); + + waitsFor(function() { return checkRemove.wasCalled; }, "checkRemove never called", Tests.TEST_TIMEOUT); + }), + itRemove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + // cleanup + deleteEntry(dirName); + }), + win = createWin('Entry'), + fail = createFail('Entry'); + + // create a new directory entry + runs(function() { + createDirectory(dirName, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.55 remove on non-empty directory", function() { + var dirName = "entry.rm.dir.not.empty", + fullPath = root.fullPath + '/' + dirName, + fileName = "remove.txt", + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + var checkFile = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + // verify that dir still exists + runs(function() { + root.getDirectory(dirName, null, itRemove, fail); + }); + + waitsFor(function() { return itRemove.wasCalled; }, "itRemove never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(itRemove).toHaveBeenCalled(); + }); + }); + // delete directory + var deleteDirectory = jasmine.createSpy().andCallFake(function(fileEntry) { + runs(function() { + entry.remove(win, checkFile); + }); + + waitsFor(function() { return checkFile.wasCalled; }, "checkFile never called", Tests.TEST_TIMEOUT); + }); + // create a file within directory, then try to delete directory + runs(function() { + entry.getFile(fileName, {create: true}, deleteDirectory, fail); + }); + + waitsFor(function() { return deleteDirectory.wasCalled; }, "deleteDirectory never called", Tests.TEST_TIMEOUT); + }), + itRemove = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.fullPath).toCanonicallyMatch(fullPath); + // cleanup + deleteEntry(dirName); + }), + win = createWin('Entry'), + fail = createFail('Entry'); + + // create a new directory entry + runs(function() { + createDirectory(dirName, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.56 remove on root file system", function() { + var itRemove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NO_MODIFICATION_ALLOWED_ERR); + }), + win = createWin('Entry'); + + // remove entry that doesn't exist + runs(function() { + root.remove(win, itRemove); + }); + + waitsFor(function() { return itRemove.wasCalled; }, "itRemove never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(itRemove).toHaveBeenCalled(); + }); + }); + it("file.spec.57 copyTo: file", function() { + var file1 = "entry.copy.file1", + file2 = "entry.copy.file2", + fullPath = root.fullPath + '/' + file2, + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + // copy file1 to file2 + runs(function() { + entry.copyTo(root, file2, itCopy, fail); + }); + + waitsFor(function() { return itCopy.wasCalled; }, "itCopy never called", Tests.TEST_TIMEOUT); + }), + itCopy = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.fullPath).toCanonicallyMatch(fullPath); + expect(entry.name).toCanonicallyMatch(file2); + + runs(function() { + root.getFile(file2, {create:false}, itFileExists, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).not.toHaveBeenCalled(); + expect(itFileExists).toHaveBeenCalled(); + }); + }), + itFileExists = jasmine.createSpy().andCallFake(function(entry2) { + // a bit redundant since copy returned this entry already + expect(entry2).toBeDefined(); + expect(entry2.isFile).toBe(true); + expect(entry2.isDirectory).toBe(false); + expect(entry2.fullPath).toCanonicallyMatch(fullPath); + expect(entry2.name).toCanonicallyMatch(file2); + + // cleanup + deleteEntry(file1); + deleteEntry(file2); + }); + + // create a new file entry to kick off it + runs(function() { + createFile(file1, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.58 copyTo: file onto itself", function() { + var file1 = "entry.copy.fos.file1", + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + // copy file1 onto itself + runs(function() { + entry.copyTo(root, null, win, itCopy); + }); + + waitsFor(function() { return itCopy.wasCalled; }, "itCopy never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itCopy).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + }); + }), + fail = createFail('Entry'), + win = createWin('Entry'), + itCopy = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + + // cleanup + deleteEntry(file1); + }); + + // create a new file entry to kick off it + runs(function() { + createFile(file1, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.59 copyTo: directory", function() { + var file1 = "file1", + srcDir = "entry.copy.srcDir", + dstDir = "entry.copy.dstDir", + dstPath = root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + var copyDir = jasmine.createSpy().andCallFake(function(fileEntry) { + // copy srcDir to dstDir + runs(function() { + directory.copyTo(root, dstDir, itCopy, fail); + }); + + waitsFor(function() { return itCopy.wasCalled; }, "itCopy never called", Tests.TEST_TIMEOUT); + }); + + // create a file within new directory + runs(function() { + directory.getFile(file1, {create: true}, copyDir, fail); + }); + + waitsFor(function() { return copyDir.wasCalled; }, "copyDir never called", Tests.TEST_TIMEOUT); + }), + itCopy = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.fullPath).toCanonicallyMatch(dstPath); + expect(directory.name).toCanonicallyMatch(dstDir); + + runs(function() { + root.getDirectory(dstDir, {create:false}, itDirExists, fail); + }); + + waitsFor(function() { return itDirExists.wasCalled; }, "itDirExists never called", Tests.TEST_TIMEOUT); + }), + itDirExists = jasmine.createSpy().andCallFake(function(dirEntry) { + expect(dirEntry).toBeDefined(); + expect(dirEntry.isFile).toBe(false); + expect(dirEntry.isDirectory).toBe(true); + expect(dirEntry.fullPath).toCanonicallyMatch(dstPath); + expect(dirEntry.name).toCanonicallyMatch(dstDir); + + runs(function() { + dirEntry.getFile(file1, {create:false}, itFileExists, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileExists).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.isFile).toBe(true); + expect(fileEntry.isDirectory).toBe(false); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + expect(fileEntry.name).toCanonicallyMatch(file1); + + // cleanup + deleteEntry(srcDir); + deleteEntry(dstDir); + }), + fail = createFail('Entry'); + + // create a new directory entry to kick off it + runs(function() { + createDirectory(srcDir, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.60 copyTo: directory to backup at same root directory", function() { + var file1 = "file1", + srcDir = "entry.copy.srcDirSame", + dstDir = "entry.copy.srcDirSame-backup", + dstPath = root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + fail = createFail('Entry copyTo: directory to backup at same root'), + entryCallback = function(directory) { + var copyDir = function(fileEntry) { + // copy srcDir to dstDir + directory.copyTo(root, dstDir, itCopy, fail); + }; + // create a file within new directory + directory.getFile(file1, {create: true}, copyDir, fail); + }, + itCopy = function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.fullPath).toCanonicallyMatch(dstPath); + expect(directory.name).toCanonicallyMatch(dstDir); + + root.getDirectory(dstDir, {create:false}, itDirExists, fail); + }, + itDirExists = function(dirEntry) { + expect(dirEntry).toBeDefined(); + expect(dirEntry.isFile).toBe(false); + expect(dirEntry.isDirectory).toBe(true); + expect(dirEntry.fullPath).toCanonicallyMatch(dstPath); + expect(dirEntry.name).toCanonicallyMatch(dstDir); + + dirEntry.getFile(file1, {create:false}, itFileExists, fail); + }, + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + var cleanSrc = jasmine.createSpy(); + var cleanDst = jasmine.createSpy(); + runs(function() { + expect(fileEntry).toBeDefined(); + expect(fileEntry.isFile).toBe(true); + expect(fileEntry.isDirectory).toBe(false); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + expect(fileEntry.name).toCanonicallyMatch(file1); + expect(fail).not.toHaveBeenCalled(); + + // cleanup + deleteEntry(srcDir, cleanSrc); + deleteEntry(dstDir, cleanDst); + }); + + waitsFor(function() { return cleanSrc.wasCalled && cleanDst.wasCalled; }, "cleanSrc and cleanDst cleanup methods", Tests.TEST_TIMEOUT); + }); + + // create a new directory entry to kick off it + runs(function() { + createDirectory(srcDir, entryCallback, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists", 10000); + }); + it("file.spec.61 copyTo: directory onto itself", function() { + var file1 = "file1", + srcDir = "entry.copy.dos.srcDir", + srcPath = root.fullPath + '/' + srcDir, + filePath = srcPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry copyTo: directory onto itself'), + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + var copyDir = jasmine.createSpy().andCallFake(function(fileEntry) { + // copy srcDir onto itself + runs(function() { + directory.copyTo(root, null, win, itCopy); + }); + + waitsFor(function() { return itCopy.wasCalled; }, "itCopy never called", Tests.TEST_TIMEOUT); + }); + // create a file within new directory + runs(function() { + directory.getFile(file1, {create: true}, copyDir, fail); + }); + + waitsFor(function() { return copyDir.wasCalled; }, "copyDir never called", Tests.TEST_TIMEOUT); + }), + itCopy = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + + runs(function() { + root.getDirectory(srcDir, {create:false}, itDirectoryExists, fail); + }); + + waitsFor(function() { return itDirectoryExists.wasCalled; }, "itDirectoryExists", Tests.TEST_TIMEOUT); + }), + itDirectoryExists = jasmine.createSpy().andCallFake(function(dirEntry) { + // returning confirms existence so just check fullPath entry + expect(dirEntry).toBeDefined(); + expect(dirEntry.fullPath).toCanonicallyMatch(srcPath); + + runs(function() { + dirEntry.getFile(file1, {create:false}, itFileExists, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(itFileExists).toHaveBeenCalled(); + }); + }), + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // cleanup + deleteEntry(srcDir); + }); + + // create a new directory entry to kick off it + runs(function() { + createDirectory(srcDir, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.62 copyTo: directory into itself", function() { + var srcDir = "entry.copy.dis.srcDir", + dstDir = "entry.copy.dis.dstDir", + fail = createFail('Entry'), + win = createWin('Entry'), + srcPath = root.fullPath + '/' + srcDir, + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + // copy source directory into itself + runs(function() { + directory.copyTo(directory, dstDir, win, itCopy); + }); + + waitsFor(function() { return itCopy.wasCalled; }, "itCopy", Tests.TEST_TIMEOUT); + }), + itCopy = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + + runs(function() { + root.getDirectory(srcDir, {create:false}, itDirectoryExists, fail); + }); + + waitsFor(function() { return itDirectoryExists.wasCalled; }, "itDirectoryExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itDirectoryExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itDirectoryExists = jasmine.createSpy().andCallFake(function(dirEntry) { + // returning confirms existence so just check fullPath entry + expect(dirEntry).toBeDefined(); + expect(dirEntry.fullPath).toCanonicallyMatch(srcPath); + + // cleanup + deleteEntry(srcDir); + }); + + // create a new directory entry to kick off it + runs(function() { + createDirectory(srcDir, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.63 copyTo: directory that does not exist", function() { + var file1 = "entry.copy.dnf.file1", + dstDir = "entry.copy.dnf.dstDir", + filePath = root.fullPath + '/' + file1, + dstPath = root.fullPath + '/' + dstDir, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + // copy file to target directory that does not exist + runs(function() { + directory = new DirectoryEntry(); + directory.fullPath = dstPath; + entry.copyTo(directory, null, win, itCopy); + }); + + waitsFor(function() { return itCopy.wasCalled; }, "itCopy never called", Tests.TEST_TIMEOUT); + }), + itCopy = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + runs(function() { + root.getFile(file1, {create: false}, itFileExists, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // cleanup + deleteEntry(file1); + }); + + // create a new file entry to kick off it + runs(function() { + createFile(file1, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.64 copyTo: invalid target name", function() { + var file1 = "entry.copy.itn.file1", + file2 = "bad:file:name", + filePath = root.fullPath + '/' + file1, + fail = createFail('Entry'), + win = createWin('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + // copy file1 to file2 + runs(function() { + entry.copyTo(root, file2, win, itCopy); + }); + + waitsFor(function() { return itCopy.wasCalled; }, "itCopy never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).not.toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(itCopy).toHaveBeenCalled(); + }); + }), + itCopy = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.ENCODING_ERR); + + // cleanup + deleteEntry(file1); + }); + + // create a new file entry + runs(function() { + createFile(file1, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.65 moveTo: file to same parent", function() { + var file1 = "entry.move.fsp.file1", + file2 = "entry.move.fsp.file2", + srcPath = root.fullPath + '/' + file1, + dstPath = root.fullPath + '/' + file2, + fail = createFail('Entry'), + win = createWin('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + // move file1 to file2 + runs(function() { + entry.moveTo(root, file2, itMove, fail); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + }), + itMove = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.fullPath).toCanonicallyMatch(dstPath); + expect(entry.name).toCanonicallyMatch(file2); + + runs(function() { + root.getFile(file2, {create:false}, itMovedExists, fail); + }); + + waitsFor(function() { return itMovedExists.wasCalled; }, "itMovedExists never called", Tests.TEST_TIMEOUT); + }), + itMovedExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(dstPath); + + runs(function() { + root.getFile(file1, {create:false}, win, itOrig); + }); + + waitsFor(function() { return itOrig.wasCalled; }, "itOrig never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(itOrig).toHaveBeenCalled(); + }); + }), + itOrig = jasmine.createSpy().andCallFake(function(error) { + //expect(navigator.fileMgr.itFileExists(srcPath) === false, "original file should not exist."); + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + + // cleanup + deleteEntry(file1); + deleteEntry(file2); + }); + + // create a new file entry to kick off it + runs(function() { + createFile(file1, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.66 moveTo: file to new parent", function() { + var file1 = "entry.move.fnp.file1", + dir = "entry.move.fnp.dir", + srcPath = root.fullPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + dstPath = root.fullPath + '/' + dir + '/' + file1, + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + // move file1 to new directory + var moveFile = jasmine.createSpy().andCallFake(function(directory) { + var itMove = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.fullPath).toCanonicallyMatch(dstPath); + expect(entry.name).toCanonicallyMatch(file1); + // it the moved file exists + runs(function() { + directory.getFile(file1, {create:false}, itMovedExists, fail); + }); + + waitsFor(function() { return itMovedExists.wasCalled; }, "itMovedExists never called", Tests.TEST_TIMEOUT); + }); + // move the file + runs(function() { + entry.moveTo(directory, null, itMove, fail); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + }); + + // create a parent directory to move file to + runs(function() { + root.getDirectory(dir, {create: true}, moveFile, fail); + }); + + waitsFor(function() { return moveFile.wasCalled; }, "moveFile never called", Tests.TEST_TIMEOUT); + }), + itMovedExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(dstPath); + + runs(function() { + root.getFile(file1, {create:false}, win, itOrig); + }); + + waitsFor(function() { return itOrig.wasCalled; }, "itOrig never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(itOrig).toHaveBeenCalled(); + }); + }), + itOrig = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + + // cleanup + deleteEntry(file1); + deleteEntry(dir); + }); + + // ensure destination directory is cleaned up first + runs(function() { + deleteEntry(dir, function() { + // create a new file entry to kick off it + createFile(file1, entryCallback, fail); + }, fail); + }); + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.67 moveTo: directory to same parent", function() { + var file1 = "file1", + srcDir = "entry.move.dsp.srcDir", + dstDir = "entry.move.dsp.dstDir", + srcPath = root.fullPath + '/' + srcDir, + dstPath = root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + var moveDir = jasmine.createSpy().andCallFake(function(fileEntry) { + // move srcDir to dstDir + runs(function() { + directory.moveTo(root, dstDir, itMove, fail); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + }); + // create a file within directory + runs(function() { + directory.getFile(file1, {create: true}, moveDir, fail); + }); + + waitsFor(function() { return moveDir.wasCalled; }, "moveDir never called", Tests.TEST_TIMEOUT); + }), + itMove = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.fullPath).toCanonicallyMatch(dstPath); + expect(directory.name).toCanonicallyMatch(dstDir); + // it that moved file exists in destination dir + + runs(function() { + directory.getFile(file1, {create:false}, itMovedExists, fail); + }); + + waitsFor(function() { return itMovedExists.wasCalled; }, "itMovedExists never called", Tests.TEST_TIMEOUT); + }), + itMovedExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // check that the moved file no longer exists in original dir + runs(function() { + root.getFile(file1, {create:false}, win, itOrig); + }); + + waitsFor(function() { return itOrig.wasCalled; }, "itOrig never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(itOrig).toHaveBeenCalled(); + }); + }), + itOrig = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + + // cleanup + deleteEntry(srcDir); + deleteEntry(dstDir); + }); + + // ensure destination directory is cleaned up before it + runs(function() { + deleteEntry(dstDir, function() { + // create a new directory entry to kick off it + createDirectory(srcDir, entryCallback, fail); + }, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.68 moveTo: directory to same parent with same name", function() { + var file1 = "file1", + srcDir = "entry.move.dsp.srcDir", + dstDir = "entry.move.dsp.srcDir-backup", + srcPath = root.fullPath + '/' + srcDir, + dstPath = root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + var moveDir = jasmine.createSpy().andCallFake(function(fileEntry) { + // move srcDir to dstDir + runs(function() { + directory.moveTo(root, dstDir, itMove, fail); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + }); + // create a file within directory + runs(function() { + directory.getFile(file1, {create: true}, moveDir, fail); + }); + + waitsFor(function() { return moveDir.wasCalled; }, "moveDir never called", Tests.TEST_TIMEOUT); + }), + itMove = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.fullPath).toCanonicallyMatch(dstPath); + expect(directory.name).toCanonicallyMatch(dstDir); + // check that moved file exists in destination dir + runs(function() { + directory.getFile(file1, {create:false}, itMovedExists, null); + }); + + waitsFor(function() { return itMovedExists.wasCalled; }, "itMovedExists never called", Tests.TEST_TIMEOUT); + }), + itMovedExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + // check that the moved file no longer exists in original dir + runs(function() { + root.getFile(file1, {create:false}, win, itOrig); + }); + + waitsFor(function() { return itOrig.wasCalled; }, "itOrig never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(itOrig).toHaveBeenCalled(); + }); + }), + itOrig = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + + // cleanup + deleteEntry(srcDir); + deleteEntry(dstDir); + }); + + // ensure destination directory is cleaned up before it + runs(function() { + deleteEntry(dstDir, function() { + // create a new directory entry to kick off it + createDirectory(srcDir, entryCallback, fail); + }, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.69 moveTo: directory to new parent", function() { + var file1 = "file1", + srcDir = "entry.move.dnp.srcDir", + dstDir = "entry.move.dnp.dstDir", + srcPath = root.fullPath + '/' + srcDir, + dstPath = root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + var moveDir = jasmine.createSpy().andCallFake(function(fileEntry) { + // move srcDir to dstDir + runs(function() { + directory.moveTo(root, dstDir, itMove, fail); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + }); + // create a file within directory + runs(function() { + directory.getFile(file1, {create: true}, moveDir, fail); + }); + + waitsFor(function() { return moveDir.wasCalled; }, "moveDir never called", Tests.TEST_TIMEOUT); + }), + itMove = jasmine.createSpy().andCallFake(function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.fullPath).toCanonicallyMatch(dstPath); + expect(directory.name).toCanonicallyMatch(dstDir); + // it that moved file exists in destination dir + runs(function() { + directory.getFile(file1, {create:false}, itMovedExists, fail); + }); + + waitsFor(function() { return itMovedExists.wasCalled; }, "itMovedExists never called", Tests.TEST_TIMEOUT); + }), + itMovedExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + // it that the moved file no longer exists in original dir + runs(function() { + root.getFile(file1, {create:false}, win, itOrig); + }); + + waitsFor(function() { return itOrig.wasCalled; }, "itOrig never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + expect(itOrig).toHaveBeenCalled(); + }); + }), + itOrig = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + + // cleanup + deleteEntry(srcDir); + deleteEntry(dstDir); + }); + + // ensure destination directory is cleaned up before it + runs(function() { + deleteEntry(dstDir, function() { + // create a new directory entry to kick off it + createDirectory(srcDir, entryCallback, fail); + }, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.70 moveTo: directory onto itself", function() { + var file1 = "file1", + srcDir = "entry.move.dos.srcDir", + srcPath = root.fullPath + '/' + srcDir, + filePath = srcPath + '/' + file1, + fail = createFail('Entry'), + win = createWin('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + var moveDir = jasmine.createSpy().andCallFake(function(fileEntry) { + // move srcDir onto itself + runs(function() { + directory.moveTo(root, null, win, itMove); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + }); + // create a file within new directory + runs(function() { + directory.getFile(file1, {create: true}, moveDir, fail); + }); + + waitsFor(function() { return moveDir.wasCalled; }, "moveDir never called", Tests.TEST_TIMEOUT); + }), + itMove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + + // it that original dir still exists + runs(function() { + root.getDirectory(srcDir, {create:false}, itDirectoryExists, fail); + }); + + waitsFor(function() { return itDirectoryExists.wasCalled; }, "itDirectoryExists", Tests.TEST_TIMEOUT); + }), + itDirectoryExists = jasmine.createSpy().andCallFake(function(dirEntry) { + // returning confirms existence so just check fullPath entry + expect(dirEntry).toBeDefined(); + expect(dirEntry.fullPath).toCanonicallyMatch(srcPath); + + runs(function() { + dirEntry.getFile(file1, {create:false}, itFileExists, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // cleanup + deleteEntry(srcDir); + }); + + // create a new directory entry to kick off it + runs(function() { + createDirectory(srcDir, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.71 moveTo: directory into itself", function() { + var srcDir = "entry.move.dis.srcDir", + dstDir = "entry.move.dis.dstDir", + srcPath = root.fullPath + '/' + srcDir, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(directory) { + // move source directory into itself + runs(function() { + directory.moveTo(directory, dstDir, win, itMove); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + }), + itMove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + // make sure original directory still exists + runs(function() { + root.getDirectory(srcDir, {create:false}, itDirectoryExists, fail); + }); + + waitsFor(function() { return itDirectoryExists.wasCalled; }, "itDirectoryExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).not.toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(itDirectoryExists).toHaveBeenCalled(); + }); + }), + itDirectoryExists = jasmine.createSpy().andCallFake(function(entry) { + expect(entry).toBeDefined(); + expect(entry.fullPath).toCanonicallyMatch(srcPath); + + // cleanup + deleteEntry(srcDir); + }); + + // create a new directory entry to kick off it + runs(function() { + createDirectory(srcDir, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.72 moveTo: file onto itself", function() { + var file1 = "entry.move.fos.file1", + filePath = root.fullPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = jasmine.createSpy().andCallFake(function(entry) { + // move file1 onto itself + runs(function() { + entry.moveTo(root, null, win, itMove); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + }), + itMove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + + //it that original file still exists + runs(function() { + root.getFile(file1, {create:false}, itFileExists, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }), + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // cleanup + deleteEntry(file1); + }); + + // create a new file entry to kick off it + runs(function() { + createFile(file1, entryCallback, fail); + }); + + waitsFor(function() { return entryCallback.wasCalled; }, "entryCallback never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.73 moveTo: file onto existing directory", function() { + var file1 = "entry.move.fod.file1", + dstDir = "entry.move.fod.dstDir", + subDir = "subDir", + dirPath = root.fullPath + '/' + dstDir + '/' + subDir, + filePath = root.fullPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = function(entry) { + var createSubDirectory = function(directory) { + var moveFile = function(subDirectory) { + var itMove = function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + // check that original dir still exists + directory.getDirectory(subDir, {create:false}, itDirectoryExists, fail); + }; + // move file1 onto sub-directory + entry.moveTo(directory, subDir, win, itMove); + }; + // create sub-directory + directory.getDirectory(subDir, {create: true}, moveFile, fail); + }; + // create top level directory + root.getDirectory(dstDir, {create: true}, createSubDirectory, fail); + }, + itDirectoryExists = function(dirEntry) { + expect(dirEntry).toBeDefined(); + expect(dirEntry.fullPath).toCanonicallyMatch(dirPath); + // check that original file still exists + root.getFile(file1, {create:false},itFileExists, fail); + }, + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // cleanup + deleteEntry(file1); + deleteEntry(dstDir); + }); + + // ensure destination directory is cleaned up before it + runs(function() { + deleteEntry(dstDir, function() { + // create a new file entry to kick off it + createFile(file1, entryCallback, fail); + }, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.74 moveTo: directory onto existing file", function() { + var file1 = "entry.move.dof.file1", + srcDir = "entry.move.dof.srcDir", + dirPath = root.fullPath + '/' + srcDir, + filePath = root.fullPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = function(entry) { + var moveDir = function(fileEntry) { + // move directory onto file + entry.moveTo(root, file1, win, itMove); + }; + // create file + root.getFile(file1, {create: true}, moveDir, fail); + }, + itMove = function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + // it that original directory exists + root.getDirectory(srcDir, {create:false}, itDirectoryExists, fail); + }, + itDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + expect(dirEntry).toBeDefined(); + expect(dirEntry.fullPath).toCanonicallyMatch(dirPath); + // it that original file exists + root.getFile(file1, {create:false}, itFileExists, fail); + }, + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // cleanup + deleteEntry(file1); + deleteEntry(srcDir); + }); + + // create a new directory entry to kick off it + runs(function() { + createDirectory(srcDir, entryCallback, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.75 copyTo: directory onto existing file", function() { + var file1 = "entry.copy.dof.file1", + srcDir = "entry.copy.dof.srcDir", + dirPath = root.fullPath + '/' + srcDir, + filePath = root.fullPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = function(entry) { + var copyDir = function(fileEntry) { + // move directory onto file + entry.copyTo(root, file1, win, itMove); + }; + // create file + root.getFile(file1, {create: true}, copyDir, fail); + }, + itMove = function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + //check that original dir still exists + root.getDirectory(srcDir, {create:false}, itDirectoryExists, fail); + }, + itDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + expect(dirEntry).toBeDefined(); + expect(dirEntry.fullPath).toCanonicallyMatch(dirPath); + // it that original file still exists + root.getFile(file1, {create:false}, itFileExists, fail); + }, + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // cleanup + deleteEntry(file1); + deleteEntry(srcDir); + }); + + // create a new directory entry to kick off it + runs(function() { + createDirectory(srcDir, entryCallback, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.76 moveTo: directory onto directory that is not empty", function() { + var srcDir = "entry.move.dod.srcDir", + dstDir = "entry.move.dod.dstDir", + subDir = "subDir", + srcPath = root.fullPath + '/' + srcDir, + dstPath = root.fullPath + '/' + dstDir + '/' + subDir, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = function(entry) { + var createSubDirectory = function(directory) { + var moveDir = function(subDirectory) { + // move srcDir onto dstDir (not empty) + entry.moveTo(root, dstDir, win, itMove); + }; + var itMove = function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.INVALID_MODIFICATION_ERR); + + // it that destination directory still exists + directory.getDirectory(subDir, {create:false}, itDirectoryExists, fail); + }; + // create sub-directory + directory.getDirectory(subDir, {create: true}, moveDir, fail); + }; + // create top level directory + root.getDirectory(dstDir, {create: true}, createSubDirectory, fail); + }, + itDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + expect(dirEntry).toBeDefined(); + expect(dirEntry.fullPath).toCanonicallyMatch(dstPath); + // it that source directory exists + root.getDirectory(srcDir,{create:false}, itSrcDirectoryExists, fail); + }, + itSrcDirectoryExists = jasmine.createSpy().andCallFake(function(srcEntry){ + expect(srcEntry).toBeDefined(); + expect(srcEntry.fullPath).toCanonicallyMatch(srcPath); + // cleanup + deleteEntry(srcDir); + deleteEntry(dstDir); + }); + + // ensure destination directory is cleaned up before it + runs(function() { + deleteEntry(dstDir, function() { + // create a new file entry to kick off it + createDirectory(srcDir, entryCallback, fail); + }, fail); + }); + + waitsFor(function() { return itSrcDirectoryExists.wasCalled; }, "itSrcDirectoryExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itSrcDirectoryExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.77 moveTo: file replace existing file", function() { + var file1 = "entry.move.frf.file1", + file2 = "entry.move.frf.file2", + file1Path = root.fullPath + '/' + file1, + file2Path = root.fullPath + '/' + file2, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = function(entry) { + var moveFile = function(fileEntry) { + // replace file2 with file1 + entry.moveTo(root, file2, itMove, fail); + }; + // create file + root.getFile(file2, {create: true}, moveFile,fail); + }, + itMove = function(entry) { + expect(entry).toBeDefined(); + expect(entry.isFile).toBe(true); + expect(entry.isDirectory).toBe(false); + expect(entry.fullPath).toCanonicallyMatch(file2Path); + expect(entry.name).toCanonicallyMatch(file2); + + // it that old file does not exists + root.getFile(file1, {create:false}, win, itFileMoved); + }, + itFileMoved = function(error){ + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + // it that new file exists + root.getFile(file2, {create:false}, itFileExists, fail); + }, + itFileExists = jasmine.createSpy().andCallFake(function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(file2Path); + + // cleanup + deleteEntry(file1); + deleteEntry(file2); + }); + + // create a new directory entry to kick off it + runs(function() { + createFile(file1, entryCallback, fail); + }); + + waitsFor(function() { return itFileExists.wasCalled; }, "itFileExists never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itFileExists).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.78 moveTo: directory replace empty directory", function() { + var file1 = "file1", + srcDir = "entry.move.drd.srcDir", + dstDir = "entry.move.drd.dstDir", + srcPath = root.fullPath + '/' + srcDir, + dstPath = root.fullPath + '/' + dstDir, + win = createWin('Entry'), + fail = createFail('Entry'), + filePath = dstPath + '/' + file1, + entryCallback = function(directory) { + var mkdir = function(fileEntry) { + // create destination directory + root.getDirectory(dstDir, {create: true}, moveDir, fail); + }; + var moveDir = function(fileEntry) { + // move srcDir to dstDir + directory.moveTo(root, dstDir, itMove, fail); + }; + // create a file within source directory + directory.getFile(file1, {create: true}, mkdir, fail); + }, + itMove = function(directory) { + expect(directory).toBeDefined(); + expect(directory.isFile).toBe(false); + expect(directory.isDirectory).toBe(true); + expect(directory.fullPath).toCanonicallyMatch(dstPath); + expect(directory.name).toCanonicallyMatch(dstDir); + // check that old directory contents have been moved + directory.getFile(file1, {create:false}, itFileExists, fail); + }, + itFileExists = function(fileEntry) { + expect(fileEntry).toBeDefined(); + expect(fileEntry.fullPath).toCanonicallyMatch(filePath); + + // check that old directory no longer exists + root.getDirectory(srcDir, {create:false}, win, itRemoved); + }, + itRemoved = jasmine.createSpy().andCallFake(function(error){ + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + + // cleanup + deleteEntry(srcDir); + deleteEntry(dstDir); + }); + + // ensure destination directory is cleaned up before it + runs(function() { + deleteEntry(dstDir, function() { + // create a new directory entry to kick off it + createDirectory(srcDir, entryCallback, fail); + }, fail); + }); + + waitsFor(function() { return itRemoved.wasCalled; }, "itRemoved never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itRemoved).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.79 moveTo: directory that does not exist", function() { + var file1 = "entry.move.dnf.file1", + dstDir = "entry.move.dnf.dstDir", + filePath = root.fullPath + '/' + file1, + dstPath = root.fullPath + '/' + dstDir, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = function(entry) { + // move file to directory that does not exist + directory = new DirectoryEntry(); + directory.fullPath = dstPath; + entry.moveTo(directory, null, win, itMove); + }, + itMove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.NOT_FOUND_ERR); + + // cleanup + deleteEntry(file1); + }); + + // create a new file entry to kick off it + runs(function() { + createFile(file1, entryCallback, fail); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itMove).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.80 moveTo: invalid target name", function() { + var file1 = "entry.move.itn.file1", + file2 = "bad:file:name", + filePath = root.fullPath + '/' + file1, + win = createWin('Entry'), + fail = createFail('Entry'), + entryCallback = function(entry) { + // move file1 to file2 + entry.moveTo(root, file2, win, itMove); + }, + itMove = jasmine.createSpy().andCallFake(function(error) { + expect(error).toBeDefined(); + expect(error).toBeFileError(FileError.ENCODING_ERR); + + // cleanup + deleteEntry(file1); + }); + + // create a new file entry to kick off it + runs(function() { + createFile(file1,entryCallback, fail); + }); + + waitsFor(function() { return itMove.wasCalled; }, "itMove never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(itMove).toHaveBeenCalled(); + expect(win).not.toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + }); + + describe('FileReader', function() { + it("file.spec.81 should have correct methods", function() { + var reader = new FileReader(); + expect(reader).toBeDefined(); + expect(typeof reader.readAsBinaryString).toBe('function'); + expect(typeof reader.readAsDataURL).toBe('function'); + expect(typeof reader.readAsText).toBe('function'); + expect(typeof reader.readAsArrayBuffer).toBe('function'); + expect(typeof reader.abort).toBe('function'); + }); + }); + + describe('read method', function(){ + it("file.spec.82 should error out on non-existent file", function() { + var reader = new FileReader(); + var verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(evt).toBeDefined(); + expect(evt.target.error).toBeFileError(FileError.NOT_FOUND_ERR); + }); + reader.onerror = verifier; + var myFile = new File(); + myFile.fullPath = root.fullPath + '/' + "doesnotexist.err"; + + reader.readAsText(myFile); + + waitsFor(function() { return verifier.wasCalled; }, "verifier never called", Tests.TEST_TIMEOUT); + }); + it("file.spec.83 should be able to read native blob objects", function() { + // Skip test if blobs are not supported (e.g.: Android 2.3). + if (typeof window.Blob == 'undefined' || typeof window.Uint8Array == 'undefined') { + return; + } + var contents = 'asdf'; + var uint8Array = new Uint8Array(contents.length); + for (var i = 0; i < contents.length; ++i) { + uint8Array[i] = contents.charCodeAt(i); + } + var Builder = window.BlobBuilder || window.WebKitBlobBuilder; + var blob; + if (Builder) { + var builder = new Builder(); + builder.append(uint8Array.buffer); + builder.append(contents); + blob = builder.getBlob("text/plain"); + } else { + try { + // iOS 6 does not support Views, so pass in the buffer. + blob = new Blob([uint8Array.buffer, contents]); + } catch (e) { + // Skip the test if we can't create a blob (e.g.: iOS 5). + if (e instanceof TypeError) { + return; + } + throw e; + } + } + var verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(evt).toBeDefined(); + expect(evt.target.result).toBe('asdfasdf'); + }); + var reader = new FileReader(); + reader.onloadend = verifier; + reader.readAsText(blob); + + waitsFor(function() { return verifier.wasCalled; }, "verifier never called", 300); + }); + + function writeDummyFile(writeBinary, callback) { + var fileName = "dummy.txt", + fileEntry = null, + writerFail = createFail('createWriter'), + getFileFail = createFail('getFile'), + fileFail = createFail('file'), + callback = jasmine.createSpy().andCallFake(callback), + fileData = '\u20AC\xEB - There is an exception to every rule. Except this one.', + fileDataAsBinaryString = '\xe2\x82\xac\xc3\xab - There is an exception to every rule. Except this one.', + createWriter = function(fe) { + fileEntry = fe; + fileEntry.createWriter(writeFile, writerFail); + }, + // writes file and reads it back in + writeFile = function(writer) { + writer.onwriteend = function() { + fileEntry.file(function(f) { + callback(fileEntry, f, fileData, fileDataAsBinaryString); + }, fileFail); + }; + writer.write(fileData); + }; + fileData += writeBinary ? 'bin:\x01\x00' : ''; + fileDataAsBinaryString += writeBinary ? 'bin:\x01\x00' : ''; + // create a file, write to it, and read it in again + root.getFile(fileName, {create: true}, createWriter, getFileFail); + waitsForAny(getFileFail, writerFail, fileFail, callback); + } + + function runReaderTest(funcName, writeBinary, verifierFunc, sliceStart, sliceEnd) { + writeDummyFile(writeBinary, function(fileEntry, file, fileData, fileDataAsBinaryString) { + var readWin = jasmine.createSpy().andCallFake(function(evt) { + expect(evt).toBeDefined(); + verifierFunc(evt, fileData, fileDataAsBinaryString); + }); + + var reader = new FileReader(); + var readFail = createFail(funcName); + reader.onload = readWin; + reader.onerror = readFail; + if (sliceEnd !== undefined) { + file = file.slice(sliceStart, sliceEnd); + } else if (sliceStart !== undefined) { + file = file.slice(sliceStart); + } + reader[funcName](file); + + waitsForAny(readWin, readFail); + }); + } + + function arrayBufferEqualsString(buf, str) { + var buf = new Uint8Array(ab); + var match = buf.length == str.length; + + for (var i = 0; match && i < buf.length; i++) { + match = buf[i] == str.charCodeAt(i); + } + return match; + } + + it("file.spec.84 should read file properly, readAsText", function() { + runReaderTest('readAsText', false, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result).toBe(fileData); + }); + }); + it("file.spec.85 should read file properly, Data URI", function() { + runReaderTest('readAsDataURL', true, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result.substr(0,23)).toBe("data:text/plain;base64,"); + expect(evt.target.result.slice(23)).toBe(atob(fileData)); + }); + }); + it("file.spec.86 should read file properly, readAsBinaryString", function() { + runReaderTest('readAsBinaryString', true, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result).toBe(fileDataAsBinaryString); + }); + }); + it("file.spec.87 should read file properly, readAsArrayBuffer", function() { + // Skip test if ArrayBuffers are not supported (e.g.: Android 2.3). + if (typeof window.ArrayBuffer == 'undefined') { + return; + } + runReaderTest('readAsArrayBuffer', true, function(evt, fileData, fileDataAsBinaryString) { + expect(arrayBufferEqualsString(evt.target.result, fileDataAsBinaryString)).toBe(true); + }); + }); + it("file.spec.88 should read sliced file: readAsText", function() { + runReaderTest('readAsText', false, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result).toBe(fileDataAsBinaryString.slice(10, 40)); + }, 10, 40); + }); + it("file.spec.89 should read sliced file: slice past eof", function() { + runReaderTest('readAsText', false, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result).toBe(fileData.slice(-5, 9999)); + }, -5, 9999); + }); + it("file.spec.90 should read sliced file: slice to eof", function() { + runReaderTest('readAsText', false, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result).toBe(fileData.slice(-5)); + }, -5); + }); + it("file.spec.91 should read empty slice", function() { + runReaderTest('readAsText', false, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result).toBe(''); + }, 0, 0); + }); + it("file.spec.92 should read sliced file properly, readAsDataURL", function() { + runReaderTest('readAsDataURL', true, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result.slice(0, 23)).toBe("data:text/plain;base64,"); + expect(evt.target.result.slice(23)).toBe(atob(fileDataAsBinaryString.slice( 10, -3))); + }, 10, -3); + }); + it("file.spec.93 should read sliced file properly, readAsBinaryString", function() { + runReaderTest('readAsBinaryString', true, function(evt, fileData, fileDataAsBinaryString) { + expect(evt.target.result).toBe(fileDataAsBinaryString.slice(-10, -5)); + }, -10, -5); + }); + it("file.spec.94 should read sliced file properly, readAsArrayBuffer", function() { + // Skip test if ArrayBuffers are not supported (e.g.: Android 2.3). + if (typeof window.ArrayBuffer == 'undefined') { + return; + } + runReaderTest('readAsArrayBuffer', true, function(evt, fileData, fileDataAsBinaryString) { + expect(arrayBufferEqualsString(evt.target.result, fileDataAsBinaryString.slice(0, -1))).toBe(true); + }, 0, -1); + }); + }); + + describe('FileWriter', function(){ + it("file.spec.81 should have correct methods", function() { + // retrieve a FileWriter object + var fileName = "writer.methods", + fail = createFail('FileWriter'), + verifier = jasmine.createSpy().andCallFake(function(writer) { + expect(writer).toBeDefined(); + expect(typeof writer.write).toBe('function'); + expect(typeof writer.seek).toBe('function'); + expect(typeof writer.truncate).toBe('function'); + expect(typeof writer.abort).toBe('function'); + + // cleanup + deleteFile(fileName); + }), + it_writer = function(fileEntry) { + fileEntry.createWriter(verifier, fail); + }; + + // it FileWriter + runs(function() { + root.getFile(fileName, {create: true}, it_writer, fail); + }); + + waitsFor(function() { return verifier.wasCalled; }, "verifier never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).not.toHaveBeenCalled(); + expect(verifier).toHaveBeenCalled(); + }); + }); + it("file.spec.96 should be able to write and append to file, createWriter", function() { + var fileName = "writer.append", + theWriter, + filePath = root.fullPath + '/' + fileName, + // file content + rule = "There is an exception to every rule.", + // for checkin file length + length = rule.length, + fail = createFail('FileWriter'), + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // append some more stuff + var exception = " Except this one."; + theWriter.onwriteend = anotherVerifier; + length += exception.length; + theWriter.seek(theWriter.length); + theWriter.write(exception); + }), + anotherVerifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // cleanup + deleteFile(fileName); + }), + // writes initial file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + writer.onwriteend = verifier; + writer.write(rule); + }, fail); + }; + + // create file, then write and append to it + runs(function() { + createFile(fileName, write_file); + }); + + waitsFor(function() { return anotherVerifier.wasCalled; }, "verifier never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(fail).not.toHaveBeenCalled(); + expect(verifier).toHaveBeenCalled(); + expect(anotherVerifier).toHaveBeenCalled(); + }); + }); + it("file.spec.97 should be able to write and append to file, File object", function() { + var fileName = "writer.append", + theWriter, + filePath = root.fullPath + '/' + fileName, + // file content + rule = "There is an exception to every rule.", + // for checking file length + length = rule.length, + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // append some more stuff + var exception = " Except this one."; + theWriter.onwriteend = anotherVerifier; + length += exception.length; + theWriter.seek(theWriter.length); + theWriter.write(exception); + }), + anotherVerifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // cleanup + deleteFile(fileName); + }), + // writes initial file content + write_file = function(file) { + theWriter = new FileWriter(file); + theWriter.onwriteend = verifier; + theWriter.write(rule); + }; + + // create file, then write and append to it + runs(function() { + var file = new File(); + file.fullPath = filePath; + write_file(file); + }); + + waitsFor(function() { return anotherVerifier.wasCalled; }, "verifier", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(anotherVerifier).toHaveBeenCalled(); + }); + }); + it("file.spec.98 should be able to seek to the middle of the file and write more data than file.length", function() { + var fileName = "writer.seek.write", + filePath = root.fullPath + '/' + fileName, + theWriter, + // file content + rule = "This is our sentence.", + // for iting file length + length = rule.length, + fail = createFail('FileWriter'), + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // append some more stuff + var exception = "newer sentence."; + theWriter.onwriteend = anotherVerifier; + length = 12 + exception.length; + theWriter.seek(12); + theWriter.write(exception); + }), + anotherVerifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // cleanup + deleteFile(fileName); + }), + // writes initial file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + theWriter.onwriteend = verifier; + theWriter.write(rule); + }, fail); + }; + + // create file, then write and append to it + runs(function() { + createFile(fileName, write_file); + }); + + waitsFor(function() { return anotherVerifier.wasCalled; }, "verifier never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(anotherVerifier).toHaveBeenCalled(); + }); + }); + it("file.spec.99 should be able to seek to the middle of the file and write less data than file.length", function() { + var fileName = "writer.seek.write2", + filePath = root.fullPath + '/' + fileName, + // file content + rule = "This is our sentence.", + theWriter, + fail = createFail('FileWriter'), + // for iting file length + length = rule.length, + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // append some more stuff + var exception = "new."; + theWriter.onwriteend = anotherVerifier; + length = 8 + exception.length; + theWriter.seek(8); + theWriter.write(exception); + }), + anotherVerifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // cleanup + deleteFile(fileName); + }), + // writes initial file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + theWriter.onwriteend = verifier; + theWriter.write(rule); + }, fail); + }; + + // create file, then write and append to it + runs(function() { + createFile(fileName, write_file); + }); + + waitsFor(function() { return anotherVerifier.wasCalled; }, "verifier never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(anotherVerifier).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.100 should be able to write XML data", function() { + var fileName = "writer.xml", + filePath = root.fullPath + '/' + fileName, + fail = createFail('FileWriter'), + theWriter, + // file content + rule = '<?xml version="1.0" encoding="UTF-8"?>\n<it prop="ack">\nData\n</it>\n', + // for iting file length + length = rule.length, + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // cleanup + deleteFile(fileName); + }), + // writes file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + theWriter.onwriteend = verifier; + theWriter.write(rule); + }, fail); + }; + + // creates file, then write XML data + runs(function() { + createFile(fileName, write_file); + }); + + waitsFor(function() { return verifier.wasCalled; }, "verifier", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.101 should be able to write JSON data", function() { + var fileName = "writer.json", + filePath = root.fullPath + '/' + fileName, + theWriter, + // file content + rule = '{ "name": "Guy Incognito", "email": "here@there.com" }', + fail = createFail('FileWriter'), + // for iting file length + length = rule.length, + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // cleanup + deleteFile(fileName); + }), + // writes file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + theWriter.onwriteend = verifier; + theWriter.write(rule); + }, fail); + }; + + // creates file, then write JSON content + runs(function() { + createFile(fileName, write_file); + }); + + waitsFor(function() { return verifier.wasCalled; }, "verifier", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.102 should be able to seek", function() { + var fileName = "writer.seek", + // file content + rule = "There is an exception to every rule. Except this one.", + theWriter, + // for iting file length + length = rule.length, + fail = createFail('FileWriter'), + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.position).toBe(length); + theWriter.seek(-5); + expect(theWriter.position).toBe(length-5); + theWriter.seek(length + 100); + expect(theWriter.position).toBe(length); + theWriter.seek(10); + expect(theWriter.position).toBe(10); + + // cleanup + deleteFile(fileName); + }), + // writes file content and its writer.seek + seek_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + theWriter.onwriteend = verifier; + theWriter.seek(-100); + expect(theWriter.position).toBe(0); + theWriter.write(rule); + }, fail); + }; + + // creates file, then write JSON content + runs(function() { + createFile(fileName, seek_file); + }); + + waitsFor(function() { return verifier.wasCalled; }, "verifier never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.103 should be able to truncate", function() { + var fileName = "writer.truncate", + rule = "There is an exception to every rule. Except this one.", + fail = createFail('FileWriter'), + theWriter, + // writes file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + theWriter.onwriteend = function(evt) { + truncate_file(theWriter); + }; + theWriter.write(rule); + }, fail); + }, + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(36); + expect(theWriter.position).toBe(36); + + // cleanup + deleteFile(fileName); + }), + // and its writer.truncate + truncate_file = function(writer) { + writer.onwriteend = verifier; + writer.truncate(36); + }; + + // creates file, writes to it, then truncates it + runs(function() { + createFile(fileName, write_file); + }); + + waitsFor(function() { return verifier.wasCalled; }, "verifier never called", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.104 should be able to write binary data from an ArrayBuffer", function() { + // Skip test if ArrayBuffers are not supported (e.g.: Android 2.3). + if (typeof window.ArrayBuffer == 'undefined') { + return; + } + var fileName = "bufferwriter.bin", + filePath = root.fullPath + '/' + fileName, + theWriter, + // file content + data = new ArrayBuffer(32), + dataView = new Int8Array(data), + fail = createFail('FileWriter'), + // for verifying file length + length = 32, + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // cleanup + deleteFile(fileName); + }), + // writes file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + theWriter.onwriteend = verifier; + theWriter.write(data); + }, fail); + }; + + for (i=0; i < dataView.length; i++) { + dataView[i] = i; + } + + // creates file, then write content + runs(function() { + createFile(fileName, write_file); + }); + + waitsFor(function() { return verifier.wasCalled; }, "verifier", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + it("file.spec.105 should be able to write binary data from a Blob", function() { + // Skip test if Blobs are not supported (e.g.: Android 2.3). + if (typeof window.Blob == 'undefined' || typeof window.ArrayBuffer == 'undefined') { + return; + } + var fileName = "blobwriter.bin", + filePath = root.fullPath + '/' + fileName, + theWriter, + fail = createFail('FileWriter'), + // file content + data = new ArrayBuffer(32), + dataView = new Int8Array(data), + blob, + // for verifying file length + length = 32, + verifier = jasmine.createSpy().andCallFake(function(evt) { + expect(theWriter.length).toBe(length); + expect(theWriter.position).toBe(length); + + // cleanup + deleteFile(fileName); + }), + // writes file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + theWriter = writer; + theWriter.onwriteend = verifier; + theWriter.write(blob); + }, fail); + }; + for (i=0; i < dataView.length; i++) { + dataView[i] = i; + } + try { + // Mobile Safari: Use Blob constructor + blob = new Blob([data], {"type": "application/octet-stream"}) + } catch(e) { + if (window.WebKitBlobBuilder) { + // Android Browser: Use deprecated BlobBuilder + var builder = new WebKitBlobBuilder() + builder.append(data) + blob = builder.getBlob('application/octet-stream'); + } else { + // We have no way defined to create a Blob, so fail + fail(); + } + } + + // creates file, then write content + runs(function() { + createFile(fileName, write_file); + }); + + waitsFor(function() { return verifier.wasCalled; }, "verifier", Tests.TEST_TIMEOUT); + + runs(function() { + expect(verifier).toHaveBeenCalled(); + expect(fail).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/plugins/org.apache.cordova.file/test/cordova-incl.js b/plugins/org.apache.cordova.file/test/cordova-incl.js new file mode 100644 index 00000000..7609effd --- /dev/null +++ b/plugins/org.apache.cordova.file/test/cordova-incl.js @@ -0,0 +1,85 @@ +/* + * + * 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 PLAT; +(function getPlatform() { + var platforms = { + android: /Android/, + ios: /(iPad)|(iPhone)|(iPod)/, + blackberry10: /(BB10)/, + blackberry: /(PlayBook)|(BlackBerry)/, + windows8: /MSAppHost/, + windowsphone: /Windows Phone/ + }; + for (var key in platforms) { + if (platforms[key].exec(navigator.userAgent)) { + PLAT = key; + break; + } + } +})(); + +var scripts = document.getElementsByTagName('script'); +var currentPath = scripts[scripts.length - 1].src; +var platformCordovaPath = currentPath.replace("cordova-incl.js", "cordova." + PLAT + ".js"); +var normalCordovaPath = currentPath.replace("cordova-incl.js", "cordova.js"); +var cordovaPath = normalCordovaPath; + +if (PLAT) { + // XHR to local file is an error on some platforms, windowsphone for one + try { + var xhr = new XMLHttpRequest(); + xhr.open("GET", platformCordovaPath, false); + xhr.onreadystatechange = function() { + + if (this.readyState == this.DONE && this.responseText.length > 0) { + if(parseInt(this.status) >= 400){ + cordovaPath = normalCordovaPath; + }else{ + cordovaPath = platformCordovaPath; + } + } + }; + xhr.send(null); + } + catch(e){ + cordovaPath = normalCordovaPath; + } // access denied! +} + +if (!window._doNotWriteCordovaScript) { + if (PLAT != "windows8") { + document.write('<script type="text/javascript" charset="utf-8" src="' + cordovaPath + '"></script>'); + } else { + var s = document.createElement('script'); + s.src = cordovaPath; + document.head.appendChild(s); + } +} + +function backHome() { + if (window.device && device.platform && device.platform.toLowerCase() == 'android') { + navigator.app.backHistory(); + } + else { + window.history.go(-1); + } +} diff --git a/plugins/org.apache.cordova.file/test/index.html b/plugins/org.apache.cordova.file/test/index.html new file mode 100644 index 00000000..5714d144 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/index.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<!-- + + 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. + +--> + + +<html> + <head> + <meta name="viewport" content="width=device-width,height=device-height,user-scalable=no,initial-scale=1.0" /> + <meta http-equiv="Content-type" content="text/html; charset=utf-8"> + <title>Cordova Mobile Spec</title> + <link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8"> + <script type="text/javascript" charset="utf-8" src="cordova-incl.js"></script> + <script type="text/javascript" charset="utf-8" src="main.js"></script> + + </head> + <body onload="init();" id="stage" class="theme"> + <h1>Apache Cordova Tests</h1> + <div id="info"> + <h4>Platform: <span id="platform"> </span></h4> + <h4>Version: <span id="version"> </span></h4> + <h4>UUID: <span id="uuid"> </span></h4> + <h4>Model: <span id="model"> </span></h4> + <h4>Width: <span id="width"> </span>, Height: <span id="height"> + </span>, Color Depth: <span id="colorDepth"></span></h4> + <h4>User-Agent: <span id="user-agent"> </span></h4> + </div> + <a href="autotest/index.html" class="btn large">Automatic Test</a> + <a href="accelerometer/index.html" class="btn large">Accelerometer</a> + <a href="audio/index.html" class="btn large">Audio Play/Record</a> + <a href="battery/index.html" class="btn large">Battery</a> + <a href="camera/index.html" class="btn large">Camera</a> + <a href="compass/index.html" class="btn large">Compass</a> + <a href="contacts/index.html" class="btn large">Contacts</a> + <a href="events/index.html" class="btn large">Events</a> + <a href="location/index.html" class="btn large">Location</a> + <a href="lazyloadjs/index.html" class="btn large">Lazy Loading of cordova-incl.js</a> + <a href="misc/index.html" class="btn large">Misc Content</a> + <a href="network/index.html" class="btn large">Network</a> + <a href="notification/index.html" class="btn large">Notification</a> + <a href="splashscreen/index.html" class="btn large">Splashscreen</a> + <a href="sql/index.html" class="btn large">Web SQL</a> + <a href="storage/index.html" class="btn large">Local Storage</a> + <a href="benchmarks/index.html" class="btn large">Benchmarks</a> + <a href="inappbrowser/index.html" class="btn large">In App Browser</a> + </body> +</html> diff --git a/plugins/org.apache.cordova.file/test/main.js b/plugins/org.apache.cordova.file/test/main.js new file mode 100644 index 00000000..5f071c47 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/main.js @@ -0,0 +1,162 @@ +/* + * + * 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 deviceInfo = function() { + document.getElementById("platform").innerHTML = device.platform; + document.getElementById("version").innerHTML = device.version; + document.getElementById("uuid").innerHTML = device.uuid; + document.getElementById("model").innerHTML = device.model; + document.getElementById("width").innerHTML = screen.width; + document.getElementById("height").innerHTML = screen.height; + document.getElementById("colorDepth").innerHTML = screen.colorDepth; +}; + +var getLocation = function() { + var suc = function(p) { + alert(p.coords.latitude + " " + p.coords.longitude); + }; + var locFail = function() { + }; + navigator.geolocation.getCurrentPosition(suc, locFail); +}; + +var beep = function() { + navigator.notification.beep(2); +}; + +var vibrate = function() { + navigator.notification.vibrate(0); +}; + +function roundNumber(num) { + var dec = 3; + var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec); + return result; +} + +var accelerationWatch = null; + +function updateAcceleration(a) { + document.getElementById('x').innerHTML = roundNumber(a.x); + document.getElementById('y').innerHTML = roundNumber(a.y); + document.getElementById('z').innerHTML = roundNumber(a.z); +} + +var toggleAccel = function() { + if (accelerationWatch !== null) { + navigator.accelerometer.clearWatch(accelerationWatch); + updateAcceleration({ + x : "", + y : "", + z : "" + }); + accelerationWatch = null; + } else { + var options = {}; + options.frequency = 1000; + accelerationWatch = navigator.accelerometer.watchAcceleration( + updateAcceleration, function(ex) { + alert("accel fail (" + ex.name + ": " + ex.message + ")"); + }, options); + } +}; + +var preventBehavior = function(e) { + e.preventDefault(); +}; + +function dump_pic(data) { + var viewport = document.getElementById('viewport'); + console.log(data); + viewport.style.display = ""; + viewport.style.position = "absolute"; + viewport.style.top = "10px"; + viewport.style.left = "10px"; + document.getElementById("test_img").src = "data:image/jpeg;base64," + data; +} + +function fail(msg) { + alert(msg); +} + +function show_pic() { + navigator.camera.getPicture(dump_pic, fail, { + quality : 50 + }); +} + +function close() { + var viewport = document.getElementById('viewport'); + viewport.style.position = "relative"; + viewport.style.display = "none"; +} + +// This is just to do this. +function readFile() { + navigator.file.read('/sdcard/cordova.txt', fail, fail); +} + +function writeFile() { + navigator.file.write('foo.txt', "This is a test of writing to a file", + fail, fail); +} + +function contacts_success(contacts) { + alert(contacts.length + + ' contacts returned.' + + (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted) + : '')); +} + +function get_contacts() { + var obj = new ContactFindOptions(); + obj.filter = ""; + obj.multiple = true; + obj.limit = 5; + navigator.service.contacts.find( + [ "displayName", "name" ], contacts_success, + fail, obj); +} + +var networkReachableCallback = function(reachability) { + // There is no consistency on the format of reachability + var networkState = reachability.code || reachability; + + var currentState = {}; + currentState[NetworkStatus.NOT_REACHABLE] = 'No network connection'; + currentState[NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK] = 'Carrier data connection'; + currentState[NetworkStatus.REACHABLE_VIA_WIFI_NETWORK] = 'WiFi connection'; + + confirm("Connection type:\n" + currentState[networkState]); +}; + +function check_network() { + navigator.network.isReachable("www.mobiledevelopersolutions.com", + networkReachableCallback, {}); +} + +function init() { + // the next line makes it impossible to see Contacts on the HTC Evo since it + // doesn't have a scroll button + // document.addEventListener("touchmove", preventBehavior, false); + document.addEventListener("deviceready", deviceInfo, true); + document.getElementById("user-agent").textContent = navigator.userAgent; +} diff --git a/plugins/org.apache.cordova.file/test/master.css b/plugins/org.apache.cordova.file/test/master.css new file mode 100644 index 00000000..dcdfeff3 --- /dev/null +++ b/plugins/org.apache.cordova.file/test/master.css @@ -0,0 +1,164 @@ +/* + * + * 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. + * +*/ + + body { + background:#222 none repeat scroll 0 0; + color:#666; + font-family:Helvetica; + font-size:72%; + line-height:1.5em; + margin:0; + border-top:1px solid #393939; + } + + #info{ + background:#ffa; + border: 1px solid #ffd324; + -webkit-border-radius: 5px; + border-radius: 5px; + clear:both; + margin:15px 6px 0; + min-width:295px; + max-width:97%; + padding:4px 0px 2px 10px; + word-wrap:break-word; + margin-bottom:10px; + display:inline-block; + min-height: 160px; + max-height: 300px; + overflow: auto; + -webkit-overflow-scrolling: touch; + } + + #info > h4{ + font-size:.95em; + margin:5px 0; + } + + #stage.theme{ + padding-top:3px; + } + + /* Definition List */ + #stage.theme > dl{ + padding-top:10px; + clear:both; + margin:0; + list-style-type:none; + padding-left:10px; + overflow:auto; + } + + #stage.theme > dl > dt{ + font-weight:bold; + float:left; + margin-left:5px; + } + + #stage.theme > dl > dd{ + width:45px; + float:left; + color:#a87; + font-weight:bold; + } + + /* Content Styling */ + #stage.theme > h1, #stage.theme > h2, #stage.theme > p{ + margin:1em 0 .5em 13px; + } + + #stage.theme > h1{ + color:#eee; + font-size:1.6em; + text-align:center; + margin:0; + margin-top:15px; + padding:0; + } + + #stage.theme > h2{ + clear:both; + margin:0; + padding:3px; + font-size:1em; + text-align:center; + } + + /* Stage Buttons */ + #stage.theme .btn{ + border: 1px solid #555; + -webkit-border-radius: 5px; + border-radius: 5px; + text-align:center; + display:inline-block; + background:#444; + width:150px; + color:#9ab; + font-size:1.1em; + text-decoration:none; + padding:1.2em 0; + margin:3px 0px 3px 5px; + } + + #stage.theme .large{ + width:308px; + padding:1.2em 0; + } + + #stage.theme .wide{ + width:100%; + padding:1.2em 0; + } + + #stage.theme .backBtn{ + border: 1px solid #555; + -webkit-border-radius: 5px; + border-radius: 5px; + text-align:center; + display:block; + float:right; + background:#666; + width:75px; + color:#9ab; + font-size:1.1em; + text-decoration:none; + padding:1.2em 0; + margin:3px 5px 3px 5px; + } + + #stage.theme .input{ + border: 1px solid #555; + -webkit-border-radius: 5px; + border-radius: 5px; + text-align:center; + display:block; + float:light; + background:#888; + color:#9cd; + font-size:1.1em; + text-decoration:none; + padding:1.2em 0; + margin:3px 0px 3px 5px; + } + + #stage.theme .numeric{ + width:100%; + } diff --git a/plugins/org.apache.cordova.file/www/DirectoryEntry.js b/plugins/org.apache.cordova.file/www/DirectoryEntry.js new file mode 100644 index 00000000..053ef9a3 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/DirectoryEntry.js @@ -0,0 +1,108 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('./Entry'), + FileError = require('./FileError'), + DirectoryReader = require('./DirectoryReader'); + +/** + * An interface representing a directory on the file system. + * + * {boolean} isFile always false (readonly) + * {boolean} isDirectory always true (readonly) + * {DOMString} name of the directory, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the directory (readonly) + * {FileSystem} filesystem on which the directory resides (readonly) + */ +var DirectoryEntry = function(name, fullPath, fileSystem) { + DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem); +}; + +utils.extend(DirectoryEntry, Entry); + +/** + * Creates a new DirectoryReader to read entries from this directory + */ +DirectoryEntry.prototype.createReader = function() { + return new DirectoryReader(this.fullPath); +}; + +/** + * Creates or looks up a directory + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory + * @param {Flags} options to create or exclusively create the directory + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { + argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments); + var fs = this.filesystem; + var win = successCallback && function(result) { + var entry = new DirectoryEntry(result.name, result.fullPath, fs); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getDirectory", [this.fullPath, path, options]); +}; + +/** + * Deletes a directory and all of it's contents + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "removeRecursively", [this.fullPath]); +}; + +/** + * Creates or looks up a file + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file + * @param {Flags} options to create or exclusively create the file + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { + argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments); + var fs = this.filesystem; + var win = successCallback && function(result) { + var FileEntry = require('./FileEntry'); + var entry = new FileEntry(result.name, result.fullPath, fs); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFile", [this.fullPath, path, options]); +}; + +module.exports = DirectoryEntry; diff --git a/plugins/org.apache.cordova.file/www/DirectoryReader.js b/plugins/org.apache.cordova.file/www/DirectoryReader.js new file mode 100644 index 00000000..07e7bf6b --- /dev/null +++ b/plugins/org.apache.cordova.file/www/DirectoryReader.js @@ -0,0 +1,71 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var exec = require('cordova/exec'), + FileError = require('./FileError') ; + +/** + * An interface that lists the files and directories in a directory. + */ +function DirectoryReader(path) { + this.path = path || null; + this.hasReadEntries = false; +} + +/** + * Returns a list of entries from a directory. + * + * @param {Function} successCallback is called with a list of entries + * @param {Function} errorCallback is called with a FileError + */ +DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { + // If we've already read and passed on this directory's entries, return an empty list. + if (this.hasReadEntries) { + successCallback([]); + return; + } + var reader = this; + var win = typeof successCallback !== 'function' ? null : function(result) { + var retVal = []; + for (var i=0; i<result.length; i++) { + var entry = null; + if (result[i].isDirectory) { + entry = new (require('./DirectoryEntry'))(); + } + else if (result[i].isFile) { + entry = new (require('./FileEntry'))(); + } + entry.isDirectory = result[i].isDirectory; + entry.isFile = result[i].isFile; + entry.name = result[i].name; + entry.fullPath = result[i].fullPath; + retVal.push(entry); + } + reader.hasReadEntries = true; + successCallback(retVal); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "readEntries", [this.path]); +}; + +module.exports = DirectoryReader; diff --git a/plugins/org.apache.cordova.file/www/Entry.js b/plugins/org.apache.cordova.file/www/Entry.js new file mode 100644 index 00000000..09afb6fa --- /dev/null +++ b/plugins/org.apache.cordova.file/www/Entry.js @@ -0,0 +1,224 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + FileError = require('./FileError'), + Metadata = require('./Metadata'); + +/** + * Represents a file or directory on the local file system. + * + * @param isFile + * {boolean} true if Entry is a file (readonly) + * @param isDirectory + * {boolean} true if Entry is a directory (readonly) + * @param name + * {DOMString} name of the file or directory, excluding the path + * leading to it (readonly) + * @param fullPath + * {DOMString} the absolute full path to the file or directory + * (readonly) + * @param fileSystem + * {FileSystem} the filesystem on which this entry resides + * (readonly) + */ +function Entry(isFile, isDirectory, name, fullPath, fileSystem) { + this.isFile = !!isFile; + this.isDirectory = !!isDirectory; + this.name = name || ''; + this.fullPath = fullPath || ''; + this.filesystem = fileSystem || null; +} + +/** + * Look up the metadata of the entry. + * + * @param successCallback + * {Function} is called with a Metadata object + * @param errorCallback + * {Function} is called with a FileError + */ +Entry.prototype.getMetadata = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.getMetadata', arguments); + var success = successCallback && function(lastModified) { + var metadata = new Metadata(lastModified); + successCallback(metadata); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + + exec(success, fail, "File", "getMetadata", [this.fullPath]); +}; + +/** + * Set the metadata of the entry. + * + * @param successCallback + * {Function} is called with a Metadata object + * @param errorCallback + * {Function} is called with a FileError + * @param metadataObject + * {Object} keys and values to set + */ +Entry.prototype.setMetadata = function(successCallback, errorCallback, metadataObject) { + argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments); + exec(successCallback, errorCallback, "File", "setMetadata", [this.fullPath, metadataObject]); +}; + +/** + * Move a file or directory to a new location. + * + * @param parent + * {DirectoryEntry} the directory to which to move this entry + * @param newName + * {DOMString} new name of the entry, defaults to the current name + * @param successCallback + * {Function} called with the new DirectoryEntry object + * @param errorCallback + * {Function} called with a FileError + */ +Entry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + // source path + var srcPath = this.fullPath, + // entry name + name = newName || this.name, + success = function(entry) { + if (entry) { + if (successCallback) { + // create appropriate Entry object + var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, entry.filesystem) : new (require('org.apache.cordova.file.FileEntry'))(entry.name, entry.fullPath, entry.filesystem); + successCallback(result); + } + } + else { + // no Entry object returned + fail && fail(FileError.NOT_FOUND_ERR); + } + }; + + // copy + exec(success, fail, "File", "moveTo", [srcPath, parent.fullPath, name]); +}; + +/** + * Copy a directory to a different location. + * + * @param parent + * {DirectoryEntry} the directory to which to copy the entry + * @param newName + * {DOMString} new name of the entry, defaults to the current name + * @param successCallback + * {Function} called with the new Entry object + * @param errorCallback + * {Function} called with a FileError + */ +Entry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + + // source path + var srcPath = this.fullPath, + // entry name + name = newName || this.name, + // success callback + success = function(entry) { + if (entry) { + if (successCallback) { + // create appropriate Entry object + var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, entry.filesystem) : new (require('org.apache.cordova.file.FileEntry'))(entry.name, entry.fullPath, entry.filesystem); + successCallback(result); + } + } + else { + // no Entry object returned + fail && fail(FileError.NOT_FOUND_ERR); + } + }; + + // copy + exec(success, fail, "File", "copyTo", [srcPath, parent.fullPath, name]); +}; + +/** + * Return a URL that can be used to identify this entry. + */ +Entry.prototype.toURL = function() { + // fullPath attribute contains the full URL + return this.fullPath; +}; + +/** + * Returns a URI that can be used to identify this entry. + * + * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. + * @return uri + */ +Entry.prototype.toURI = function(mimeType) { + console.log("DEPRECATED: Update your code to use 'toURL'"); + // fullPath attribute contains the full URI + return this.toURL(); +}; + +/** + * Remove 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 a + * root directory of a file system. + * + * @param successCallback {Function} called with no parameters + * @param errorCallback {Function} called with a FileError + */ +Entry.prototype.remove = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.remove', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "remove", [this.fullPath]); +}; + +/** + * Look up the parent DirectoryEntry of this entry. + * + * @param successCallback {Function} called with the parent DirectoryEntry object + * @param errorCallback {Function} called with a FileError + */ +Entry.prototype.getParent = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.getParent', arguments); + var fs = this.filesystem; + var win = successCallback && function(result) { + var DirectoryEntry = require('./DirectoryEntry'); + var entry = new DirectoryEntry(result.name, result.fullPath, fs); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getParent", [this.fullPath]); +}; + +module.exports = Entry; diff --git a/plugins/org.apache.cordova.file/www/File.js b/plugins/org.apache.cordova.file/www/File.js new file mode 100644 index 00000000..103468ea --- /dev/null +++ b/plugins/org.apache.cordova.file/www/File.js @@ -0,0 +1,77 @@ +/* + * + * 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. + * +*/ + +/** + * Constructor. + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ + +var File = function(name, fullPath, type, lastModifiedDate, size){ + this.name = name || ''; + this.fullPath = fullPath || null; + this.type = type || null; + this.lastModifiedDate = lastModifiedDate || null; + this.size = size || 0; + + // These store the absolute start and end for slicing the file. + this.start = 0; + this.end = this.size; +}; + +/** + * Returns a "slice" of the file. Since Cordova Files don't contain the actual + * content, this really returns a File with adjusted start and end. + * Slices of slices are supported. + * start {Number} The index at which to start the slice (inclusive). + * end {Number} The index at which to end the slice (exclusive). + */ +File.prototype.slice = function(start, end) { + var size = this.end - this.start; + var newStart = 0; + var newEnd = size; + if (arguments.length) { + if (start < 0) { + newStart = Math.max(size + start, 0); + } else { + newStart = Math.min(size, start); + } + } + + if (arguments.length >= 2) { + if (end < 0) { + newEnd = Math.max(size + end, 0); + } else { + newEnd = Math.min(end, size); + } + } + + var newFile = new File(this.name, this.fullPath, this.type, this.lastModifiedData, this.size); + newFile.start = this.start + newStart; + newFile.end = this.start + newEnd; + return newFile; +}; + + +module.exports = File; diff --git a/plugins/org.apache.cordova.file/www/FileEntry.js b/plugins/org.apache.cordova.file/www/FileEntry.js new file mode 100644 index 00000000..dc9352db --- /dev/null +++ b/plugins/org.apache.cordova.file/www/FileEntry.js @@ -0,0 +1,80 @@ +/* + * + * 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 utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('./Entry'), + FileWriter = require('./FileWriter'), + File = require('./File'), + FileError = require('./FileError'); + +/** + * An interface representing a file on the file system. + * + * {boolean} isFile always true (readonly) + * {boolean} isDirectory always false (readonly) + * {DOMString} name of the file, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the file (readonly) + * {FileSystem} filesystem on which the file resides (readonly) + */ +var FileEntry = function(name, fullPath, fileSystem) { + FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem]); +}; + +utils.extend(FileEntry, Entry); + +/** + * Creates a new FileWriter associated with the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new FileWriter + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.createWriter = function(successCallback, errorCallback) { + this.file(function(filePointer) { + var writer = new FileWriter(filePointer); + + if (writer.fileName === null || writer.fileName === "") { + errorCallback && errorCallback(new FileError(FileError.INVALID_STATE_ERR)); + } else { + successCallback && successCallback(writer); + } + }, errorCallback); +}; + +/** + * Returns a File that represents the current state of the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new File object + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.file = function(successCallback, errorCallback) { + var win = successCallback && function(f) { + var file = new File(f.name, f.fullPath, f.type, f.lastModifiedDate, f.size); + successCallback(file); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFileMetadata", [this.fullPath]); +}; + + +module.exports = FileEntry; diff --git a/plugins/org.apache.cordova.file/www/FileError.js b/plugins/org.apache.cordova.file/www/FileError.js new file mode 100644 index 00000000..6507921f --- /dev/null +++ b/plugins/org.apache.cordova.file/www/FileError.js @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/** + * FileError + */ +function FileError(error) { + this.code = error || null; +} + +// File error codes +// Found in DOMException +FileError.NOT_FOUND_ERR = 1; +FileError.SECURITY_ERR = 2; +FileError.ABORT_ERR = 3; + +// Added by File API specification +FileError.NOT_READABLE_ERR = 4; +FileError.ENCODING_ERR = 5; +FileError.NO_MODIFICATION_ALLOWED_ERR = 6; +FileError.INVALID_STATE_ERR = 7; +FileError.SYNTAX_ERR = 8; +FileError.INVALID_MODIFICATION_ERR = 9; +FileError.QUOTA_EXCEEDED_ERR = 10; +FileError.TYPE_MISMATCH_ERR = 11; +FileError.PATH_EXISTS_ERR = 12; + +module.exports = FileError; diff --git a/plugins/org.apache.cordova.file/www/FileReader.js b/plugins/org.apache.cordova.file/www/FileReader.js new file mode 100644 index 00000000..3cc75b5c --- /dev/null +++ b/plugins/org.apache.cordova.file/www/FileReader.js @@ -0,0 +1,387 @@ +/* + * + * 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'), + modulemapper = require('cordova/modulemapper'), + utils = require('cordova/utils'), + File = require('./File'), + FileError = require('./FileError'), + ProgressEvent = require('./ProgressEvent'), + origFileReader = modulemapper.getOriginalSymbol(this, 'FileReader'); + +/** + * This class reads the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To read from the SD card, the file name is "sdcard/my_file.txt" + * @constructor + */ +var FileReader = function() { + this._readyState = 0; + this._error = null; + this._result = null; + this._fileName = ''; + this._realReader = origFileReader ? new origFileReader() : {}; +}; + +// States +FileReader.EMPTY = 0; +FileReader.LOADING = 1; +FileReader.DONE = 2; + +utils.defineGetter(FileReader.prototype, 'readyState', function() { + return this._fileName ? this._readyState : this._realReader.readyState; +}); + +utils.defineGetter(FileReader.prototype, 'error', function() { + return this._fileName ? this._error: this._realReader.error; +}); + +utils.defineGetter(FileReader.prototype, 'result', function() { + return this._fileName ? this._result: this._realReader.result; +}); + +function defineEvent(eventName) { + utils.defineGetterSetter(FileReader.prototype, eventName, function() { + return this._realReader[eventName] || null; + }, function(value) { + this._realReader[eventName] = value; + }); +} +defineEvent('onloadstart'); // When the read starts. +defineEvent('onprogress'); // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total) +defineEvent('onload'); // When the read has successfully completed. +defineEvent('onerror'); // When the read has failed (see errors). +defineEvent('onloadend'); // When the request has completed (either in success or failure). +defineEvent('onabort'); // When the read has been aborted. For instance, by invoking the abort() method. + +function initRead(reader, file) { + // Already loading something + if (reader.readyState == FileReader.LOADING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + reader._result = null; + reader._error = null; + reader._readyState = FileReader.LOADING; + + if (typeof file.fullPath == 'string') { + reader._fileName = file.fullPath; + } else { + reader._fileName = ''; + return true; + } + + reader.onloadstart && reader.onloadstart(new ProgressEvent("loadstart", {target:reader})); +} + +/** + * Abort reading file. + */ +FileReader.prototype.abort = function() { + if (origFileReader && !this._fileName) { + return this._realReader.abort(); + } + this._result = null; + + if (this._readyState == FileReader.DONE || this._readyState == FileReader.EMPTY) { + return; + } + + this._readyState = FileReader.DONE; + + // If abort callback + if (typeof this.onabort === 'function') { + this.onabort(new ProgressEvent('abort', {target:this})); + } + // If load end callback + if (typeof this.onloadend === 'function') { + this.onloadend(new ProgressEvent('loadend', {target:this})); + } +}; + +/** + * Read text file. + * + * @param file {File} File object containing file properties + * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) + */ +FileReader.prototype.readAsText = function(file, encoding) { + if (initRead(this, file)) { + return this._realReader.readAsText(file, encoding); + } + + // Default encoding is UTF-8 + var enc = encoding ? encoding : "UTF-8"; + var me = this; + var execArgs = [this._fileName, enc, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // Save result + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // DONE state + me._readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + // null result + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsText", execArgs); +}; + + +/** + * Read file and return data as a base64 encoded data url. + * A data url is of the form: + * data:[<mediatype>][;base64],<data> + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsDataURL = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsDataURL(file); + } + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + // Save result + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsDataURL", execArgs); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsBinaryString = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsBinaryString(file); + } + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsBinaryString", execArgs); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsArrayBuffer(file); + } + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsArrayBuffer", execArgs); +}; + +module.exports = FileReader; diff --git a/plugins/org.apache.cordova.file/www/FileSystem.js b/plugins/org.apache.cordova.file/www/FileSystem.js new file mode 100644 index 00000000..76e3f89d --- /dev/null +++ b/plugins/org.apache.cordova.file/www/FileSystem.js @@ -0,0 +1,38 @@ +/* + * + * 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 DirectoryEntry = require('./DirectoryEntry'); + +/** + * An interface representing a file system + * + * @constructor + * {DOMString} name the unique name of the file system (readonly) + * {DirectoryEntry} root directory of the file system (readonly) + */ +var FileSystem = function(name, root) { + this.name = name || null; + if (root) { + this.root = new DirectoryEntry(root.name, root.fullPath, this); + } +}; + +module.exports = FileSystem; diff --git a/plugins/org.apache.cordova.file/www/FileUploadOptions.js b/plugins/org.apache.cordova.file/www/FileUploadOptions.js new file mode 100644 index 00000000..b2977de7 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/FileUploadOptions.js @@ -0,0 +1,41 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/** + * Options to customize the HTTP request used to upload files. + * @constructor + * @param fileKey {String} Name of file request parameter. + * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. + * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. + * @param params {Object} Object with key: value params to send to the server. + * @param headers {Object} Keys are header names, values are header values. Multiple + * headers of the same name are not supported. + */ +var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) { + this.fileKey = fileKey || null; + this.fileName = fileName || null; + this.mimeType = mimeType || null; + this.params = params || null; + this.headers = headers || null; + this.httpMethod = httpMethod || null; +}; + +module.exports = FileUploadOptions; diff --git a/plugins/org.apache.cordova.file/www/FileUploadResult.js b/plugins/org.apache.cordova.file/www/FileUploadResult.js new file mode 100644 index 00000000..294995f0 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/FileUploadResult.js @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/** + * FileUploadResult + * @constructor + */ +var FileUploadResult = function() { + this.bytesSent = 0; + this.responseCode = null; + this.response = null; +}; + +module.exports = FileUploadResult; diff --git a/plugins/org.apache.cordova.file/www/FileWriter.js b/plugins/org.apache.cordova.file/www/FileWriter.js new file mode 100644 index 00000000..44d7a322 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/FileWriter.js @@ -0,0 +1,297 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var exec = require('cordova/exec'), + FileError = require('./FileError'), + ProgressEvent = require('./ProgressEvent'); + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @constructor + * @param file {File} File object containing file properties + * @param append if true write to the end of the file, otherwise overwrite the file + */ +var FileWriter = function(file) { + this.fileName = ""; + this.length = 0; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; + } + // default is to write at the beginning of the file + this.position = 0; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // set error + this.error = new FileError(FileError.ABORT_ERR); + + this.readyState = FileWriter.DONE; + + // If abort callback + if (typeof this.onabort === "function") { + this.onabort(new ProgressEvent("abort", {"target":this})); + } + + // If write end callback + if (typeof this.onwriteend === "function") { + this.onwriteend(new ProgressEvent("writeend", {"target":this})); + } +}; + +/** + * Writes data to the file + * + * @param data text or blob to be written + */ +FileWriter.prototype.write = function(data) { + + var that=this; + var supportsBinary = (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined'); + var isBinary; + + // Check to see if the incoming data is a blob + if (data instanceof File || (supportsBinary && data instanceof Blob)) { + var fileReader = new FileReader(); + fileReader.onload = function() { + // Call this method again, with the arraybuffer as argument + FileWriter.prototype.write.call(that, this.result); + }; + if (supportsBinary) { + fileReader.readAsArrayBuffer(data); + } else { + fileReader.readAsText(data); + } + return; + } + + // Mark data type for safer transport over the binary bridge + isBinary = supportsBinary && (data instanceof ArrayBuffer); + + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":me})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // position always increases by bytes written because file would be extended + me.position += r; + // The length of the file is now where we are done writing. + + me.length = me.position; + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "write", [this.fileName, data, this.position, isBinary]); +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + if (!offset && offset !== 0) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger than file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":this})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r); + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "truncate", [this.fileName, size]); +}; + +module.exports = FileWriter; diff --git a/plugins/org.apache.cordova.file/www/Flags.js b/plugins/org.apache.cordova.file/www/Flags.js new file mode 100644 index 00000000..a9ae6da3 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/Flags.js @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/** + * Supplies arguments to methods that lookup or create files and directories. + * + * @param create + * {boolean} file or directory if it doesn't exist + * @param exclusive + * {boolean} used with create; if true the command will fail if + * target path exists + */ +function Flags(create, exclusive) { + this.create = create || false; + this.exclusive = exclusive || false; +} + +module.exports = Flags; diff --git a/plugins/org.apache.cordova.file/www/LocalFileSystem.js b/plugins/org.apache.cordova.file/www/LocalFileSystem.js new file mode 100644 index 00000000..1e8f2eeb --- /dev/null +++ b/plugins/org.apache.cordova.file/www/LocalFileSystem.js @@ -0,0 +1,23 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +exports.TEMPORARY = 0; +exports.PERSISTENT = 1; diff --git a/plugins/org.apache.cordova.file/www/Metadata.js b/plugins/org.apache.cordova.file/www/Metadata.js new file mode 100644 index 00000000..bee26bda --- /dev/null +++ b/plugins/org.apache.cordova.file/www/Metadata.js @@ -0,0 +1,31 @@ +/* + * + * 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. + * +*/ + +/** + * Information about the state of the file or directory + * + * {Date} modificationTime (readonly) + */ +var Metadata = function(time) { + this.modificationTime = (typeof time != 'undefined'?new Date(time):null); +}; + +module.exports = Metadata; diff --git a/plugins/org.apache.cordova.file/www/ProgressEvent.js b/plugins/org.apache.cordova.file/www/ProgressEvent.js new file mode 100644 index 00000000..f176f808 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/ProgressEvent.js @@ -0,0 +1,67 @@ +/* + * + * 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. + * +*/ + +// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill +// Feature test: See if we can instantiate a native ProgressEvent; +// if so, use that approach, +// otherwise fill-in with our own implementation. +// +// 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. +var ProgressEvent = (function() { + /* + var createEvent = function(data) { + var event = document.createEvent('Events'); + event.initEvent('ProgressEvent', false, false); + if (data) { + for (var i in data) { + if (data.hasOwnProperty(i)) { + event[i] = data[i]; + } + } + if (data.target) { + // TODO: cannot call <some_custom_object>.dispatchEvent + // need to first figure out how to implement EventTarget + } + } + return event; + }; + try { + var ev = createEvent({type:"abort",target:document}); + return function ProgressEvent(type, data) { + data.type = type; + return createEvent(data); + }; + } catch(e){ + */ + return function ProgressEvent(type, dict) { + this.type = type; + this.bubbles = false; + this.cancelBubble = false; + this.cancelable = false; + this.lengthComputable = false; + this.loaded = dict && dict.loaded ? dict.loaded : 0; + this.total = dict && dict.total ? dict.total : 0; + this.target = dict && dict.target ? dict.target : null; + }; + //} +})(); + +module.exports = ProgressEvent; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/DirectoryEntry.js b/plugins/org.apache.cordova.file/www/blackberry10/DirectoryEntry.js new file mode 100644 index 00000000..cb6628d5 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/DirectoryEntry.js @@ -0,0 +1,112 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + Entry = require('./BB10Entry'), + FileError = require('./FileError'), + DirectoryReader = require('./BB10DirectoryReader'), + fileUtils = require('./BB10Utils'), + DirectoryEntry = function (name, fullPath, fileSystem) { + DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem); + }; + +utils.extend(DirectoryEntry, Entry); + +function err(sandboxState, errorCallback) { + return function (e) { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [sandboxState]); + errorCallback(e); + } +}; + +DirectoryEntry.prototype.createReader = function () { + return new DirectoryReader(this.fullPath); +}; + +DirectoryEntry.prototype.getDirectory = function (path, options, successCallback, errorCallback) { + var sandboxState, + currentPath = this.nativeEntry.fullPath; + + cordova.exec(function (sandboxed) { + sandboxState = sandboxed; + }, function (e) { + console.log("[ERROR]: Could not retrieve sandbox state ", e); + }, "org.apache.cordova.file", "isSandboxed"); + + argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments); + + if (fileUtils.isOutsideSandbox(path)) { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [false]); + window.webkitRequestFileSystem(window.PERSISTENT, this.filesystem._size, function (fs) { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [sandboxState]); + fs.root.getDirectory(currentPath + '/' + path, options, function (entry) { + successCallback(fileUtils.createEntry(entry)); + }, err(sandboxState, errorCallback)); + }, err(sandboxState, errorCallback)); + } else { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [true]); + window.webkitRequestFileSystem(fileUtils.getFileSystemName(this.filesystem) === "persistent" ? window.PERSISTENT : window.TEMPORARY, this.filesystem._size, function (fs) { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [sandboxState]); + fs.root.getDirectory(currentPath + '/' + path, options, function (entry) { + successCallback(fileUtils.createEntry(entry)); + }, err(sandboxState, errorCallback)); + }, err(sandboxState, errorCallback)); + } +}; + +DirectoryEntry.prototype.removeRecursively = function (successCallback, errorCallback) { + argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments); + this.nativeEntry.removeRecursively(successCallback, errorCallback); +}; + +DirectoryEntry.prototype.getFile = function (path, options, successCallback, errorCallback) { + var sandboxState, + currentPath = this.nativeEntry.fullPath; + + cordova.exec(function (sandboxed) { + sandboxState = sandboxed; + }, function (e) { + console.log("[ERROR]: Could not retrieve sandbox state ", e); + }, "org.apache.cordova.file", "isSandboxed"); + + argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments); + + if (fileUtils.isOutsideSandbox(path)) { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [false]); + window.webkitRequestFileSystem(window.PERSISTENT, this.filesystem._size, function (fs) { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [sandboxState]); + fs.root.getFile(currentPath + '/' + path, options, function (entry) { + successCallback(fileUtils.createEntry(entry)); + }, err(sandboxState, errorCallback)); + }, err(sandboxState, errorCallback)); + } else { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [true]); + window.webkitRequestFileSystem(fileUtils.getFileSystemName(this.filesystem) === "persistent" ? window.PERSISTENT: window.TEMPORARY, this.filesystem._size, function (fs) { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [sandboxState]); + fs.root.getFile(currentPath + '/' + path, options, function (entry) { + successCallback(fileUtils.createEntry(entry)); + }, err(sandboxState, errorCallback)); + }, err(sandboxState, errorCallback)); + } +}; + +module.exports = DirectoryEntry; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/DirectoryReader.js b/plugins/org.apache.cordova.file/www/blackberry10/DirectoryReader.js new file mode 100644 index 00000000..6aa3d730 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/DirectoryReader.js @@ -0,0 +1,54 @@ +/* + * + * 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 FileError = require('./FileError'), + fileUtils = require('./BB10Utils'); + +function DirectoryReader(path) { + this.path = path; + this.nativeReader = null; +} + +DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { + var self = this, + win = typeof successCallback !== 'function' ? null : function(result) { + var retVal = []; + for (var i=0; i<result.length; i++) { + retVal.push(fileUtils.createEntry(result[i])); + } + successCallback(retVal); + }, + fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + if (this.nativeReader) { + this.nativeReader.readEntries(win, fail); + } else { + resolveLocalFileSystemURI("filesystem:local:///persistent/" + this.path, function (entry) { + self.nativeReader = entry.nativeEntry.createReader() + self.nativeReader.readEntries(win, fail); + }, function () { + fail(FileError.NOT_FOUND_ERR); + }); + } +}; + +module.exports = DirectoryReader; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/Entry.js b/plugins/org.apache.cordova.file/www/blackberry10/Entry.js new file mode 100644 index 00000000..d432bfde --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/Entry.js @@ -0,0 +1,120 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var argscheck = require('cordova/argscheck'), + FileError = require('./FileError'), + Metadata = require('./Metadata'), + fileUtils = require('./BB10Utils'); + +function Entry(isFile, isDirectory, name, fullPath, fileSystem) { + var strippedPath; + if (fullPath && fullPath.charAt(fullPath.length - 1) === '/') { + strippedPath = fullPath.slice(0, -1); + } + this.isFile = !!isFile; + this.isDirectory = !!isDirectory; + this.name = name || ''; + this.fullPath = typeof strippedPath !== "undefined" ? strippedPath : (fullPath || ''); + this.filesystem = fileSystem || null; +} + +Entry.prototype.getMetadata = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.getMetadata', arguments); + var success = function(lastModified) { + var metadata = new Metadata(lastModified); + if (typeof successCallback === 'function') { + successCallback(metadata); + } + }; + this.nativeEntry.getMetadata(success, errorCallback); +}; + +Entry.prototype.setMetadata = function(successCallback, errorCallback, metadataObject) { + argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments); + errorCallback("Not supported by platform"); +}; + +Entry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments); + var srcPath = this.fullPath, + name = newName || this.name, + success = function(entry) { + if (entry) { + if (typeof successCallback === 'function') { + successCallback(fileUtils.createEntry(entry)); + } + } + else { + if (typeof errorCallback === 'function') { + errorCallback(new FileError(FileError.NOT_FOUND_ERR)); + } + } + }; + this.nativeEntry.moveTo(parent.nativeEntry, newName, success, errorCallback); +}; + + +Entry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments); + var srcPath = this.fullPath, + name = newName || this.name, + success = function(entry) { + if (entry) { + if (typeof successCallback === 'function') { + successCallback(fileUtils.createEntry(entry)); + } + } + else { + if (typeof errorCallback === 'function') { + errorCallback(new FileError(FileError.NOT_FOUND_ERR)); + } + } + }; + this.nativeEntry.copyTo(parent.nativeEntry, newName, success, errorCallback); +}; + +Entry.prototype.toURL = function() { + var nativeURI = this.nativeEntry.toURL(); + if (nativeURI.charAt(nativeURI.length - 1) === '/') { + return nativeURI.slice(0, -1); + } + return nativeURI; +}; + +Entry.prototype.toURI = function(mimeType) { + console.log("DEPRECATED: Update your code to use 'toURL'"); + return this.toURL(); +}; + +Entry.prototype.remove = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.remove', arguments); + this.nativeEntry.remove(successCallback, errorCallback); +}; + +Entry.prototype.getParent = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.getParent', arguments); + var win = successCallback && function(result) { + successCallback(fileUtils.createEntry(result)); + }; + this.nativeEntry.getParent(win, errorCallback); +}; + +module.exports = Entry; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/File.js b/plugins/org.apache.cordova.file/www/blackberry10/File.js new file mode 100644 index 00000000..7ba9734e --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/File.js @@ -0,0 +1,56 @@ +/* + * + * 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 fileUtils = require('./BB10Utils'); + +/** + * Constructor. + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ + +var File = function(name, fullPath, type, lastModifiedDate, size){ + this.name = name || ''; + this.fullPath = fullPath || null; + this.type = type || null; + this.lastModifiedDate = lastModifiedDate || null; + this.size = size || 0; + + // These store the absolute start and end for slicing the file. + this.start = 0; + this.end = this.size; +}; + +/** + * Returns a "slice" of the file. + * Slices of slices are supported. + * start {Number} The index at which to start the slice (inclusive). + * end {Number} The index at which to end the slice (exclusive). + */ +File.prototype.slice = function(start, end) { + return fileUtils.createFile(this.nativeFile.slice(start, end)); +}; + + +module.exports = File; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/FileEntry.js b/plugins/org.apache.cordova.file/www/blackberry10/FileEntry.js new file mode 100644 index 00000000..b358d71a --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/FileEntry.js @@ -0,0 +1,49 @@ +/* + * + * 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 utils = require('cordova/utils'), + Entry = require('./BB10Entry'), + FileWriter = require('./BB10FileWriter'), + File = require('./BB10File'), + fileUtils = require('./BB10Utils'), + FileError = require('./FileError'), + FileEntry = function (name, fullPath, fileSystem) { + FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem]); + }; + +utils.extend(FileEntry, Entry); + +FileEntry.prototype.createWriter = function(successCallback, errorCallback) { + this.file(function (file) { + successCallback(new FileWriter(file)); + }, errorCallback); +}; + +FileEntry.prototype.file = function(successCallback, errorCallback) { + var fullPath = this.fullPath, + success = function (file) { + file.fullPath = fullPath; + successCallback(fileUtils.createFile(file)); + }; + this.nativeEntry.file(success, errorCallback); +}; + +module.exports = FileEntry; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/FileReader.js b/plugins/org.apache.cordova.file/www/blackberry10/FileReader.js new file mode 100644 index 00000000..6e3a10c6 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/FileReader.js @@ -0,0 +1,90 @@ +/* + * + * 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 origFileReader = window.FileReader, + fileUtils = require('./BB10Utils'), + utils = require('cordova/utils'); + +var FileReader = function() { + this.nativeReader = new origFileReader(); +}; + +utils.defineGetter(FileReader.prototype, 'readyState', function() { + return this.nativeReader.readyState; +}); + +utils.defineGetter(FileReader.prototype, 'error', function() { + return this.nativeReader.error; +}); + +utils.defineGetter(FileReader.prototype, 'result', function() { + return this.nativeReader.result; +}); + +function defineEvent(eventName) { + utils.defineGetterSetter(FileReader.prototype, eventName, function() { + return this.nativeReader[eventName] || null; + }, function(value) { + this.nativeReader[eventName] = value; + }); +} + +defineEvent('onabort'); +defineEvent('onerror'); +defineEvent('onload'); +defineEvent('onloadend'); +defineEvent('onloadstart'); +defineEvent('onprogress'); + +FileReader.prototype.abort = function() { + return this.nativeReader.abort(); +}; + +function read(method, context, file, encoding) { + if (file.fullPath) { + resolveLocalFileSystemURI("filesystem:local:///persistent/" + file.fullPath, function (entry) { + entry.nativeEntry.file(function (nativeFile) { + context.nativeReader[method].call(context.nativeReader, nativeFile, encoding); + }, context.onerror); + }, context.onerror); + } else { + context.nativeReader[method](file, encoding); + } +} + +FileReader.prototype.readAsText = function(file, encoding) { + read("readAsText", this, file, encoding); +}; + +FileReader.prototype.readAsDataURL = function(file) { + read("readAsDataURL", this, file); +}; + +FileReader.prototype.readAsBinaryString = function(file) { + read("readAsBinaryString", this, file); +}; + +FileReader.prototype.readAsArrayBuffer = function(file) { + read("readAsArrayBuffer", this, file); +}; + +window.FileReader = FileReader; +module.exports = FileReader; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/FileSystem.js b/plugins/org.apache.cordova.file/www/blackberry10/FileSystem.js new file mode 100644 index 00000000..d1432d6d --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/FileSystem.js @@ -0,0 +1,27 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +module.exports = function(name, root) { + this.name = name || null; + if (root) { + this.root = root; + } +}; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/FileWriter.js b/plugins/org.apache.cordova.file/www/blackberry10/FileWriter.js new file mode 100644 index 00000000..8992943e --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/FileWriter.js @@ -0,0 +1,120 @@ +/* + * + * 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 FileError = require('./FileError'), + ProgressEvent = require('./ProgressEvent'), + fileUtils = require('./BB10Utils'), + utils = require('cordova/utils'); + +function FileWriter (file) { + var that = this; + this.file = file; + this.events = {}; + this.pending = []; + resolveLocalFileSystemURI("filesystem:local:///persistent/" + file.fullPath, function (entry) { + entry.nativeEntry.createWriter(function (writer) { + var i, + event; + that.nativeWriter = writer; + for (event in that.events) { + if (that.events.hasOwnProperty(event)) { + that.nativeWriter[event] = that.events[event]; + } + } + for (i = 0; i < that.pending.length; i++) { + that.pending[i](); + } + }); + }); + this.events = {}; + this.pending = []; +} + +utils.defineGetter(FileWriter.prototype, 'error', function() { + return this.nativeWriter ? this.nativeWriter.error : null; +}); + +utils.defineGetter(FileWriter.prototype, 'fileName', function() { + return this.nativeWriter ? this.nativeWriter.fileName : this.file.name; +}); + +utils.defineGetter(FileWriter.prototype, 'length', function() { + return this.nativeWriter ? this.nativeWriter.length : this.file.size; +}); + +utils.defineGetter(FileWriter.prototype, 'position', function() { + return this.nativeWriter ? this.nativeWriter.position : 0; +}); + +utils.defineGetter(FileWriter.prototype, 'readyState', function() { + return this.nativeWriter ? this.nativeWriter.readyState : 0; +}); + +function defineEvent(eventName) { + utils.defineGetterSetter(FileWriter.prototype, eventName, function() { + return this.nativeWriter ? this.nativeWriter[eventName] || null : this.events[eventName] || null; + }, function(value) { + if (this.nativeWriter) { + this.nativeWriter[eventName] = value; + } + else { + this.events[eventName] = value; + } + }); +} + +defineEvent('onabort'); +defineEvent('onerror'); +defineEvent('onprogress'); +defineEvent('onwrite'); +defineEvent('onwriteend'); +defineEvent('onwritestart'); + +FileWriter.prototype.abort = function() { + this.nativeWriter.abort(); +}; + +FileWriter.prototype.write = function(text) { + var that = this, + op = function () { + that.nativeWriter.write(new Blob([text])); + }; + this.nativeWriter ? op() : this.pending.push(op); + +}; + +FileWriter.prototype.seek = function(offset) { + var that = this, + op = function () { + that.nativeWriter.seek(offset); + }; + this.nativeWriter ? op() : this.pending.push(op); +}; + +FileWriter.prototype.truncate = function(size) { + var that = this, + op = function () { + that.nativeWriter.truncate(size); + }; + this.nativeWriter ? op() : this.pending.push(op); +}; + +module.exports = FileWriter; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/fileUtils.js b/plugins/org.apache.cordova.file/www/blackberry10/fileUtils.js new file mode 100644 index 00000000..6f803b73 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/fileUtils.js @@ -0,0 +1,52 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +function convertPath(url) { + return decodeURI(url).substring(11).replace(/\/$/, ''); +} + +module.exports = { + createEntry: function (entry) { + var cordovaEntry; + if (entry.isFile) { + cordovaEntry = new window.FileEntry(entry.name, entry.fullPath, entry.filesystem); + } else { + cordovaEntry = new window.DirectoryEntry(entry.name, entry.fullPath, entry.filesystem); + } + cordovaEntry.nativeEntry = entry; + return cordovaEntry; + }, + + createFile: function (file) { + var cordovaFile = new File(file.name, file.fullPath, file.type, file.lastModifiedDate, file.size); + cordovaFile.nativeFile = file; + cordovaFile.fullPath = file.name; + return cordovaFile; + }, + + getFileSystemName: function (fs) { + return ((fs.name.indexOf('Persistent') != -1) || (fs.name === "persistent")) ? 'persistent' : 'temporary'; + }, + + isOutsideSandbox: function (path) { + return (path.indexOf("accounts/1000/") === 0 || path.indexOf("/accounts/1000/") === 0); + } +}; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/requestFileSystem.js b/plugins/org.apache.cordova.file/www/blackberry10/requestFileSystem.js new file mode 100644 index 00000000..bd07d089 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/requestFileSystem.js @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var fileUtils = require('./BB10Utils'), + FileError = require('./FileError'), + FileSystem = require('./BB10FileSystem'); + +module.exports = function (type, size, success, fail) { + var cordovaFs, + cordovaFsRoot; + if (size >= 1000000000000000) { + fail(new FileError(FileError.QUOTA_EXCEEDED_ERR)); + } else if (type !== 1 && type !== 0) { + fail(new FileError(FileError.SYNTAX_ERR)); + } else { + window.webkitRequestFileSystem(type, size, function (fs) { + cordovaFsRoot = fileUtils.createEntry(fs.root); + cordovaFs = new FileSystem(fileUtils.getFileSystemName(fs), cordovaFsRoot); + cordovaFsRoot.filesystem = cordovaFs; + cordovaFs._size = size; + success(cordovaFs); + }, function (error) { + fail(new FileError(error)); + }); + } +}; diff --git a/plugins/org.apache.cordova.file/www/blackberry10/resolveLocalFileSystemURI.js b/plugins/org.apache.cordova.file/www/blackberry10/resolveLocalFileSystemURI.js new file mode 100644 index 00000000..4d5af613 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/blackberry10/resolveLocalFileSystemURI.js @@ -0,0 +1,55 @@ +/* + * + * 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 fileUtils = require('./BB10Utils'), + FileError = require('./FileError'); + +function stripURI(uri) { + var rmFsLocal = uri.substring("filesystem:local:///".length); + return rmFsLocal.substring(rmFsLocal.indexOf('/') + 1); +} + +module.exports = function (uri, success, fail) { + var sandboxState, + decodedURI = decodeURI(uri); + + cordova.exec(function (sandboxed) { + sandboxState = sandboxed; + }, function (e) { + console.log("[ERROR]: Could not retrieve sandbox state ", e); + }, "org.apache.cordova.file", "isSandboxed"); + + if (fileUtils.isOutsideSandbox(stripURI(decodedURI))) { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [false]); + } else { + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [true]); + } + window.webkitResolveLocalFileSystemURL(decodedURI, function (entry) { + success(fileUtils.createEntry(entry)); + }, function (e) { + window.webkitResolveLocalFileSystemURL(decodedURI + '/', function (entry) { + success(fileUtils.createEntry(entry)); + }, function (e) { + fail(e); + }); + }); + cordova.exec(null, null, "org.apache.cordova.file", "setSandbox", [sandboxState]); +}; diff --git a/plugins/org.apache.cordova.file/www/ios/Entry.js b/plugins/org.apache.cordova.file/www/ios/Entry.js new file mode 100644 index 00000000..bb8d43c7 --- /dev/null +++ b/plugins/org.apache.cordova.file/www/ios/Entry.js @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +module.exports = { + toURL:function() { + // TODO: refactor path in a cross-platform way so we can eliminate + // these kinds of platform-specific hacks. + return "file://localhost" + this.fullPath; + }, + toURI: function() { + console.log("DEPRECATED: Update your code to use 'toURL'"); + return "file://localhost" + this.fullPath; + } +}; diff --git a/plugins/org.apache.cordova.file/www/requestFileSystem.js b/plugins/org.apache.cordova.file/www/requestFileSystem.js new file mode 100644 index 00000000..bad3291e --- /dev/null +++ b/plugins/org.apache.cordova.file/www/requestFileSystem.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 argscheck = require('cordova/argscheck'), + FileError = require('./FileError'), + FileSystem = require('./FileSystem'), + exec = require('cordova/exec'); + +/** + * Request a file system in which to store application data. + * @param type local file system type + * @param size indicates how much storage space, in bytes, the application expects to need + * @param successCallback invoked with a FileSystem object + * @param errorCallback invoked if error occurs retrieving file system + */ +var requestFileSystem = function(type, size, successCallback, errorCallback) { + argscheck.checkArgs('nnFF', 'requestFileSystem', arguments); + var fail = function(code) { + errorCallback && errorCallback(new FileError(code)); + }; + + if (type < 0 || type > 3) { + fail(FileError.SYNTAX_ERR); + } else { + // if successful, return a FileSystem object + var success = function(file_system) { + if (file_system) { + if (successCallback) { + // grab the name and root from the file system object + var result = new FileSystem(file_system.name, file_system.root); + successCallback(result); + } + } + else { + // no FileSystem object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + exec(success, fail, "File", "requestFileSystem", [type, size]); + } +}; + +module.exports = requestFileSystem; diff --git a/plugins/org.apache.cordova.file/www/resolveLocalFileSystemURI.js b/plugins/org.apache.cordova.file/www/resolveLocalFileSystemURI.js new file mode 100644 index 00000000..b5a0debb --- /dev/null +++ b/plugins/org.apache.cordova.file/www/resolveLocalFileSystemURI.js @@ -0,0 +1,64 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var argscheck = require('cordova/argscheck'), + DirectoryEntry = require('./DirectoryEntry'), + FileEntry = require('./FileEntry'), + FileError = require('./FileError'), + exec = require('cordova/exec'); + +/** + * Look up file system Entry referred to by local URI. + * @param {DOMString} uri URI referring to a local file or directory + * @param successCallback invoked with Entry object corresponding to URI + * @param errorCallback invoked if error occurs retrieving file system entry + */ +module.exports = function(uri, successCallback, errorCallback) { + argscheck.checkArgs('sFF', 'resolveLocalFileSystemURI', arguments); + // error callback + var fail = function(error) { + errorCallback && errorCallback(new FileError(error)); + }; + // sanity check for 'not:valid:filename' + if(!uri || uri.split(":").length > 2) { + setTimeout( function() { + fail(FileError.ENCODING_ERR); + },0); + return; + } + // if successful, return either a file or directory entry + var success = function(entry) { + var result; + if (entry) { + if (successCallback) { + // create appropriate Entry object + result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath); + successCallback(result); + } + } + else { + // no Entry object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + + exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]); +}; diff --git a/plugins/org.apache.cordova.file/www/wp/FileUploadOptions.js b/plugins/org.apache.cordova.file/www/wp/FileUploadOptions.js new file mode 100644 index 00000000..696573ab --- /dev/null +++ b/plugins/org.apache.cordova.file/www/wp/FileUploadOptions.js @@ -0,0 +1,49 @@ +/* + * + * 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. + * +*/ + +/** + * Options to customize the HTTP request used to upload files. + * @constructor + * @param fileKey {String} Name of file request parameter. + * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. + * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. + * @param params {Object} Object with key: value params to send to the server. + */ +var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) { + this.fileKey = fileKey || null; + this.fileName = fileName || null; + this.mimeType = mimeType || null; + this.headers = headers || null; + this.httpMethod = httpMethod || null; + + if(params && typeof params != typeof "") { + var arrParams = []; + for(var v in params) { + arrParams.push(v + "=" + params[v]); + } + this.params = encodeURIComponent(arrParams.join("&")); + } + else { + this.params = params || null; + } +}; + +module.exports = FileUploadOptions; |
