Cordova plugin file und file-transfer geadded
This commit is contained in:
283
plugins/cordova-plugin-file/src/android/AssetFilesystem.java
Normal file
283
plugins/cordova-plugin-file/src/android/AssetFilesystem.java
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
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.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AssetFilesystem extends Filesystem {
|
||||
|
||||
private final AssetManager assetManager;
|
||||
|
||||
// A custom gradle hook creates the cdvasset.manifest file, which speeds up asset listing a tonne.
|
||||
// See: http://stackoverflow.com/questions/16911558/android-assetmanager-list-incredibly-slow
|
||||
private static Object listCacheLock = new Object();
|
||||
private static boolean listCacheFromFile;
|
||||
private static Map<String, String[]> listCache;
|
||||
private static Map<String, Long> lengthCache;
|
||||
|
||||
private void lazyInitCaches() {
|
||||
synchronized (listCacheLock) {
|
||||
if (listCache == null) {
|
||||
ObjectInputStream ois = null;
|
||||
try {
|
||||
ois = new ObjectInputStream(assetManager.open("cdvasset.manifest"));
|
||||
listCache = (Map<String, String[]>) ois.readObject();
|
||||
lengthCache = (Map<String, Long>) ois.readObject();
|
||||
listCacheFromFile = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// Asset manifest won't exist if the gradle hook isn't set up correctly.
|
||||
} finally {
|
||||
if (ois != null) {
|
||||
try {
|
||||
ois.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (listCache == null) {
|
||||
Log.w("AssetFilesystem", "Asset manifest not found. Recursive copies and directory listing will be slow.");
|
||||
listCache = new HashMap<String, String[]>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String[] listAssets(String assetPath) throws IOException {
|
||||
if (assetPath.startsWith("/")) {
|
||||
assetPath = assetPath.substring(1);
|
||||
}
|
||||
lazyInitCaches();
|
||||
String[] ret = listCache.get(assetPath);
|
||||
if (ret == null) {
|
||||
if (listCacheFromFile) {
|
||||
ret = new String[0];
|
||||
} else {
|
||||
ret = assetManager.list(assetPath);
|
||||
listCache.put(assetPath, ret);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private long getAssetSize(String assetPath) throws FileNotFoundException {
|
||||
if (assetPath.startsWith("/")) {
|
||||
assetPath = assetPath.substring(1);
|
||||
}
|
||||
lazyInitCaches();
|
||||
if (lengthCache != null) {
|
||||
Long ret = lengthCache.get(assetPath);
|
||||
if (ret == null) {
|
||||
throw new FileNotFoundException("Asset not found: " + assetPath);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
CordovaResourceApi.OpenForReadResult offr = null;
|
||||
try {
|
||||
offr = resourceApi.openForRead(nativeUriForFullPath(assetPath));
|
||||
long length = offr.length;
|
||||
if (length < 0) {
|
||||
// available() doesn't always yield the file size, but for assets it does.
|
||||
length = offr.inputStream.available();
|
||||
}
|
||||
return length;
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("File not found: " + assetPath);
|
||||
} finally {
|
||||
if (offr != null) {
|
||||
try {
|
||||
offr.inputStream.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AssetFilesystem(AssetManager assetManager, CordovaResourceApi resourceApi) {
|
||||
super(Uri.parse("file:///android_asset/"), "assets", resourceApi);
|
||||
this.assetManager = assetManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri toNativeUri(LocalFilesystemURL inputURL) {
|
||||
return nativeUriForFullPath(inputURL.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFilesystemURL toLocalUri(Uri inputURL) {
|
||||
if (!"file".equals(inputURL.getScheme())) {
|
||||
return null;
|
||||
}
|
||||
File f = new File(inputURL.getPath());
|
||||
// Removes and duplicate /s (e.g. file:///a//b/c)
|
||||
Uri resolvedUri = Uri.fromFile(f);
|
||||
String rootUriNoTrailingSlash = rootUri.getEncodedPath();
|
||||
rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
|
||||
if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
|
||||
return null;
|
||||
}
|
||||
String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
|
||||
// Strip leading slash
|
||||
if (!subPath.isEmpty()) {
|
||||
subPath = subPath.substring(1);
|
||||
}
|
||||
Uri.Builder b = new Uri.Builder()
|
||||
.scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
|
||||
.authority("localhost")
|
||||
.path(name);
|
||||
if (!subPath.isEmpty()) {
|
||||
b.appendEncodedPath(subPath);
|
||||
}
|
||||
if (isDirectory(subPath) || inputURL.getPath().endsWith("/")) {
|
||||
// Add trailing / for directories.
|
||||
b.appendEncodedPath("");
|
||||
}
|
||||
return LocalFilesystemURL.parse(b.build());
|
||||
}
|
||||
|
||||
private boolean isDirectory(String assetPath) {
|
||||
try {
|
||||
return listAssets(assetPath).length != 0;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
|
||||
String pathNoSlashes = inputURL.path.substring(1);
|
||||
if (pathNoSlashes.endsWith("/")) {
|
||||
pathNoSlashes = pathNoSlashes.substring(0, pathNoSlashes.length() - 1);
|
||||
}
|
||||
|
||||
String[] files;
|
||||
try {
|
||||
files = listAssets(pathNoSlashes);
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
|
||||
for (int i = 0; i < files.length; ++i) {
|
||||
entries[i] = localUrlforFullPath(new File(inputURL.path, files[i]).getPath());
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
|
||||
String path, JSONObject options, boolean directory)
|
||||
throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
|
||||
if (options != null && options.optBoolean("create")) {
|
||||
throw new UnsupportedOperationException("Assets are read-only");
|
||||
}
|
||||
|
||||
// Check whether the supplied path is absolute or relative
|
||||
if (directory && !path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
|
||||
LocalFilesystemURL requestedURL;
|
||||
if (path.startsWith("/")) {
|
||||
requestedURL = localUrlforFullPath(normalizePath(path));
|
||||
} else {
|
||||
requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
|
||||
}
|
||||
|
||||
// Throws a FileNotFoundException if it doesn't exist.
|
||||
getFileMetadataForLocalURL(requestedURL);
|
||||
|
||||
boolean isDir = isDirectory(requestedURL.path);
|
||||
if (directory && !isDir) {
|
||||
throw new TypeMismatchException("path doesn't exist or is file");
|
||||
} else if (!directory && isDir) {
|
||||
throw new TypeMismatchException("path doesn't exist or is directory");
|
||||
}
|
||||
|
||||
// Return the directory
|
||||
return makeEntryForURL(requestedURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
|
||||
JSONObject metadata = new JSONObject();
|
||||
long size = inputURL.isDirectory ? 0 : getAssetSize(inputURL.path);
|
||||
try {
|
||||
metadata.put("size", size);
|
||||
metadata.put("type", inputURL.isDirectory ? "text/directory" : resourceApi.getMimeType(toNativeUri(inputURL)));
|
||||
metadata.put("name", new File(inputURL.path).getName());
|
||||
metadata.put("fullPath", inputURL.path);
|
||||
metadata.put("lastModifiedDate", 0);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset, boolean isBinary) throws NoModificationAllowedException, IOException {
|
||||
throw new NoModificationAllowedException("Assets are read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException, NoModificationAllowedException {
|
||||
throw new NoModificationAllowedException("Assets are read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
String filesystemPathForURL(LocalFilesystemURL url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
LocalFilesystemURL URLforFilesystemPath(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException {
|
||||
throw new NoModificationAllowedException("Assets are read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws NoModificationAllowedException {
|
||||
throw new NoModificationAllowedException("Assets are read-only");
|
||||
}
|
||||
|
||||
}
|
||||
215
plugins/cordova-plugin-file/src/android/ContentFilesystem.java
Normal file
215
plugins/cordova-plugin-file/src/android/ContentFilesystem.java
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
|
||||
public class ContentFilesystem extends Filesystem {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public ContentFilesystem(Context context, CordovaResourceApi resourceApi) {
|
||||
super(Uri.parse("content://"), "content", resourceApi);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri toNativeUri(LocalFilesystemURL inputURL) {
|
||||
String authorityAndPath = inputURL.uri.getEncodedPath().substring(this.name.length() + 2);
|
||||
if (authorityAndPath.length() < 2) {
|
||||
return null;
|
||||
}
|
||||
String ret = "content://" + authorityAndPath;
|
||||
String query = inputURL.uri.getEncodedQuery();
|
||||
if (query != null) {
|
||||
ret += '?' + query;
|
||||
}
|
||||
String frag = inputURL.uri.getEncodedFragment();
|
||||
if (frag != null) {
|
||||
ret += '#' + frag;
|
||||
}
|
||||
return Uri.parse(ret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFilesystemURL toLocalUri(Uri inputURL) {
|
||||
if (!"content".equals(inputURL.getScheme())) {
|
||||
return null;
|
||||
}
|
||||
String subPath = inputURL.getEncodedPath();
|
||||
if (subPath.length() > 0) {
|
||||
subPath = subPath.substring(1);
|
||||
}
|
||||
Uri.Builder b = new Uri.Builder()
|
||||
.scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
|
||||
.authority("localhost")
|
||||
.path(name)
|
||||
.appendPath(inputURL.getAuthority());
|
||||
if (subPath.length() > 0) {
|
||||
b.appendEncodedPath(subPath);
|
||||
}
|
||||
Uri localUri = b.encodedQuery(inputURL.getEncodedQuery())
|
||||
.encodedFragment(inputURL.getEncodedFragment())
|
||||
.build();
|
||||
return LocalFilesystemURL.parse(localUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
|
||||
String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
|
||||
throw new UnsupportedOperationException("getFile() not supported for content:. Use resolveLocalFileSystemURL instead.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL)
|
||||
throws NoModificationAllowedException {
|
||||
Uri contentUri = toNativeUri(inputURL);
|
||||
try {
|
||||
context.getContentResolver().delete(contentUri, null, null);
|
||||
} 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.
|
||||
throw new NoModificationAllowedException("Deleting not supported for content uri: " + contentUri);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL)
|
||||
throws NoModificationAllowedException {
|
||||
throw new NoModificationAllowedException("Cannot remove content url");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
|
||||
throw new UnsupportedOperationException("readEntriesAtLocalURL() not supported for content:. Use resolveLocalFileSystemURL instead.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
|
||||
long size = -1;
|
||||
long lastModified = 0;
|
||||
Uri nativeUri = toNativeUri(inputURL);
|
||||
String mimeType = resourceApi.getMimeType(nativeUri);
|
||||
Cursor cursor = openCursorForURL(nativeUri);
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
size = resourceSizeForCursor(cursor);
|
||||
lastModified = lastModifiedDateForCursor(cursor);
|
||||
} else {
|
||||
// Some content providers don't support cursors at all!
|
||||
CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(nativeUri);
|
||||
size = offr.length;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException();
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
JSONObject metadata = new JSONObject();
|
||||
try {
|
||||
metadata.put("size", size);
|
||||
metadata.put("type", mimeType);
|
||||
metadata.put("name", name);
|
||||
metadata.put("fullPath", inputURL.path);
|
||||
metadata.put("lastModifiedDate", lastModified);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
|
||||
int offset, boolean isBinary) throws NoModificationAllowedException {
|
||||
throw new NoModificationAllowedException("Couldn't write to file given its content URI");
|
||||
}
|
||||
@Override
|
||||
public long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
|
||||
throws NoModificationAllowedException {
|
||||
throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
|
||||
}
|
||||
|
||||
protected Cursor openCursorForURL(Uri nativeUri) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
try {
|
||||
return contentResolver.query(nativeUri, null, null, null, null);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Long resourceSizeForCursor(Cursor cursor) {
|
||||
int columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
|
||||
if (columnIndex != -1) {
|
||||
String sizeStr = cursor.getString(columnIndex);
|
||||
if (sizeStr != null) {
|
||||
return Long.parseLong(sizeStr);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Long lastModifiedDateForCursor(Cursor cursor) {
|
||||
final String[] LOCAL_FILE_PROJECTION = { MediaStore.MediaColumns.DATE_MODIFIED };
|
||||
int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
|
||||
if (columnIndex != -1) {
|
||||
String dateStr = cursor.getString(columnIndex);
|
||||
if (dateStr != null) {
|
||||
return Long.parseLong(dateStr);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filesystemPathForURL(LocalFilesystemURL url) {
|
||||
File f = resourceApi.mapUriToFile(toNativeUri(url));
|
||||
return f == null ? null : f.getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFilesystemURL URLforFilesystemPath(String path) {
|
||||
// Returns null as we don't support reverse mapping back to content:// URLs
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
133
plugins/cordova-plugin-file/src/android/DirectoryManager.java
Normal file
133
plugins/cordova-plugin-file/src/android/DirectoryManager.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
1022
plugins/cordova-plugin-file/src/android/FileUtils.java
Normal file
1022
plugins/cordova-plugin-file/src/android/FileUtils.java
Normal file
File diff suppressed because it is too large
Load Diff
325
plugins/cordova-plugin-file/src/android/Filesystem.java
Normal file
325
plugins/cordova-plugin-file/src/android/Filesystem.java
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova.file;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public abstract class Filesystem {
|
||||
|
||||
protected final Uri rootUri;
|
||||
protected final CordovaResourceApi resourceApi;
|
||||
public final String name;
|
||||
private JSONObject rootEntry;
|
||||
|
||||
public Filesystem(Uri rootUri, String name, CordovaResourceApi resourceApi) {
|
||||
this.rootUri = rootUri;
|
||||
this.name = name;
|
||||
this.resourceApi = resourceApi;
|
||||
}
|
||||
|
||||
public interface ReadFileCallback {
|
||||
public void handleData(InputStream inputStream, String contentType) throws IOException;
|
||||
}
|
||||
|
||||
public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Uri nativeURL) {
|
||||
try {
|
||||
String path = inputURL.path;
|
||||
int end = path.endsWith("/") ? 1 : 0;
|
||||
String[] parts = path.substring(0, path.length() - end).split("/+");
|
||||
String fileName = parts[parts.length - 1];
|
||||
|
||||
JSONObject entry = new JSONObject();
|
||||
entry.put("isFile", !inputURL.isDirectory);
|
||||
entry.put("isDirectory", inputURL.isDirectory);
|
||||
entry.put("name", fileName);
|
||||
entry.put("fullPath", path);
|
||||
// The file system can't be specified, as it would lead to an infinite loop,
|
||||
// but the filesystem name can be.
|
||||
entry.put("filesystemName", inputURL.fsName);
|
||||
// Backwards compatibility
|
||||
entry.put("filesystem", "temporary".equals(inputURL.fsName) ? 0 : 1);
|
||||
|
||||
String nativeUrlStr = nativeURL.toString();
|
||||
if (inputURL.isDirectory && !nativeUrlStr.endsWith("/")) {
|
||||
nativeUrlStr += "/";
|
||||
}
|
||||
entry.put("nativeURL", nativeUrlStr);
|
||||
return entry;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject makeEntryForURL(LocalFilesystemURL inputURL) {
|
||||
Uri nativeUri = toNativeUri(inputURL);
|
||||
return nativeUri == null ? null : makeEntryForURL(inputURL, nativeUri);
|
||||
}
|
||||
|
||||
public JSONObject makeEntryForNativeUri(Uri nativeUri) {
|
||||
LocalFilesystemURL inputUrl = toLocalUri(nativeUri);
|
||||
return inputUrl == null ? null : makeEntryForURL(inputUrl, nativeUri);
|
||||
}
|
||||
|
||||
public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
|
||||
return makeEntryForURL(inputURL);
|
||||
}
|
||||
|
||||
public JSONObject makeEntryForFile(File file) {
|
||||
return makeEntryForNativeUri(Uri.fromFile(file));
|
||||
}
|
||||
|
||||
abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
|
||||
JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
|
||||
|
||||
abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
|
||||
|
||||
abstract boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException, NoModificationAllowedException;
|
||||
|
||||
abstract LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException;
|
||||
|
||||
public final JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
|
||||
LocalFilesystemURL[] children = listChildren(inputURL);
|
||||
JSONArray entries = new JSONArray();
|
||||
if (children != null) {
|
||||
for (LocalFilesystemURL url : children) {
|
||||
entries.put(makeEntryForURL(url));
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
abstract JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
|
||||
|
||||
public Uri getRootUri() {
|
||||
return rootUri;
|
||||
}
|
||||
|
||||
public boolean exists(LocalFilesystemURL inputURL) {
|
||||
try {
|
||||
getFileMetadataForLocalURL(inputURL);
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Uri nativeUriForFullPath(String fullPath) {
|
||||
Uri ret = null;
|
||||
if (fullPath != null) {
|
||||
String encodedPath = Uri.fromFile(new File(fullPath)).getEncodedPath();
|
||||
if (encodedPath.startsWith("/")) {
|
||||
encodedPath = encodedPath.substring(1);
|
||||
}
|
||||
ret = rootUri.buildUpon().appendEncodedPath(encodedPath).build();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public LocalFilesystemURL localUrlforFullPath(String fullPath) {
|
||||
Uri nativeUri = nativeUriForFullPath(fullPath);
|
||||
if (nativeUri != null) {
|
||||
return toLocalUri(nativeUri);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes multiple repeated //s, and collapses processes ../s.
|
||||
*/
|
||||
protected static String normalizePath(String rawPath) {
|
||||
// If this is an absolute path, trim the leading "/" and replace it later
|
||||
boolean isAbsolutePath = rawPath.startsWith("/");
|
||||
if (isAbsolutePath) {
|
||||
rawPath = rawPath.replaceFirst("/+", "");
|
||||
}
|
||||
ArrayList<String> components = new ArrayList<String>(Arrays.asList(rawPath.split("/+")));
|
||||
for (int index = 0; index < components.size(); ++index) {
|
||||
if (components.get(index).equals("..")) {
|
||||
components.remove(index);
|
||||
if (index > 0) {
|
||||
components.remove(index-1);
|
||||
--index;
|
||||
}
|
||||
}
|
||||
}
|
||||
StringBuilder normalizedPath = new StringBuilder();
|
||||
for(String component: components) {
|
||||
normalizedPath.append("/");
|
||||
normalizedPath.append(component);
|
||||
}
|
||||
if (isAbsolutePath) {
|
||||
return normalizedPath.toString();
|
||||
} else {
|
||||
return normalizedPath.toString().substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public abstract Uri toNativeUri(LocalFilesystemURL inputURL);
|
||||
public abstract LocalFilesystemURL toLocalUri(Uri inputURL);
|
||||
|
||||
public JSONObject getRootEntry() {
|
||||
if (rootEntry == null) {
|
||||
rootEntry = makeEntryForNativeUri(rootUri);
|
||||
}
|
||||
return rootEntry;
|
||||
}
|
||||
|
||||
public JSONObject getParentForLocalURL(LocalFilesystemURL inputURL) throws IOException {
|
||||
Uri parentUri = inputURL.uri;
|
||||
String parentPath = new File(inputURL.uri.getPath()).getParent();
|
||||
if (!"/".equals(parentPath)) {
|
||||
parentUri = inputURL.uri.buildUpon().path(parentPath + '/').build();
|
||||
}
|
||||
return getEntryForLocalURL(LocalFilesystemURL.parse(parentUri));
|
||||
}
|
||||
|
||||
protected LocalFilesystemURL makeDestinationURL(String newName, LocalFilesystemURL srcURL, LocalFilesystemURL destURL, boolean isDirectory) {
|
||||
// I know this looks weird but it is to work around a JSON bug.
|
||||
if ("null".equals(newName) || "".equals(newName)) {
|
||||
newName = srcURL.uri.getLastPathSegment();;
|
||||
}
|
||||
|
||||
String newDest = destURL.uri.toString();
|
||||
if (newDest.endsWith("/")) {
|
||||
newDest = newDest + newName;
|
||||
} else {
|
||||
newDest = newDest + "/" + newName;
|
||||
}
|
||||
if (isDirectory) {
|
||||
newDest += '/';
|
||||
}
|
||||
return LocalFilesystemURL.parse(newDest);
|
||||
}
|
||||
|
||||
/* Read a source URL (possibly from a different filesystem, srcFs,) and copy it to
|
||||
* the destination URL on this filesystem, optionally with a new filename.
|
||||
* If move is true, then this method should either perform an atomic move operation
|
||||
* or remove the source file when finished.
|
||||
*/
|
||||
public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
|
||||
Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
|
||||
// First, check to see that we can do it
|
||||
if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
|
||||
throw new NoModificationAllowedException("Cannot move file at source URL");
|
||||
}
|
||||
final LocalFilesystemURL destination = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
|
||||
|
||||
Uri srcNativeUri = srcFs.toNativeUri(srcURL);
|
||||
|
||||
CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcNativeUri);
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = getOutputStreamForURL(destination);
|
||||
} catch (IOException e) {
|
||||
ofrr.inputStream.close();
|
||||
throw e;
|
||||
}
|
||||
// Closes streams.
|
||||
resourceApi.copyResource(ofrr, os);
|
||||
|
||||
if (move) {
|
||||
srcFs.removeFileAtLocalURL(srcURL);
|
||||
}
|
||||
return getEntryForLocalURL(destination);
|
||||
}
|
||||
|
||||
public OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException {
|
||||
return resourceApi.openOutputStream(toNativeUri(inputURL));
|
||||
}
|
||||
|
||||
public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
|
||||
ReadFileCallback readFileCallback) throws IOException {
|
||||
CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(toNativeUri(inputURL));
|
||||
if (end < 0) {
|
||||
end = ofrr.length;
|
||||
}
|
||||
long numBytesToRead = end - start;
|
||||
try {
|
||||
if (start > 0) {
|
||||
ofrr.inputStream.skip(start);
|
||||
}
|
||||
InputStream inputStream = ofrr.inputStream;
|
||||
if (end < ofrr.length) {
|
||||
inputStream = new LimitedInputStream(inputStream, numBytesToRead);
|
||||
}
|
||||
readFileCallback.handleData(inputStream, ofrr.mimeType);
|
||||
} finally {
|
||||
ofrr.inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
|
||||
boolean isBinary) throws NoModificationAllowedException, IOException;
|
||||
|
||||
abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
|
||||
throws IOException, NoModificationAllowedException;
|
||||
|
||||
// This method should return null if filesystem urls cannot be mapped to paths
|
||||
abstract String filesystemPathForURL(LocalFilesystemURL url);
|
||||
|
||||
abstract LocalFilesystemURL URLforFilesystemPath(String path);
|
||||
|
||||
abstract boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL);
|
||||
|
||||
protected class LimitedInputStream extends FilterInputStream {
|
||||
long numBytesToRead;
|
||||
public LimitedInputStream(InputStream in, long numBytesToRead) {
|
||||
super(in);
|
||||
this.numBytesToRead = numBytesToRead;
|
||||
}
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (numBytesToRead <= 0) {
|
||||
return -1;
|
||||
}
|
||||
numBytesToRead--;
|
||||
return in.read();
|
||||
}
|
||||
@Override
|
||||
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
||||
if (numBytesToRead <= 0) {
|
||||
return -1;
|
||||
}
|
||||
int bytesToRead = byteCount;
|
||||
if (byteCount > numBytesToRead) {
|
||||
bytesToRead = (int)numBytesToRead; // Cast okay; long is less than int here.
|
||||
}
|
||||
int numBytesRead = in.read(buffer, byteOffset, bytesToRead);
|
||||
numBytesToRead -= numBytesRead;
|
||||
return numBytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
505
plugins/cordova-plugin-file/src/android/LocalFilesystem.java
Normal file
505
plugins/cordova-plugin-file/src/android/LocalFilesystem.java
Normal file
@@ -0,0 +1,505 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova.file;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.Base64;
|
||||
import android.net.Uri;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class LocalFilesystem extends Filesystem {
|
||||
private final Context context;
|
||||
|
||||
public LocalFilesystem(String name, Context context, CordovaResourceApi resourceApi, File fsRoot) {
|
||||
super(Uri.fromFile(fsRoot).buildUpon().appendEncodedPath("").build(), name, resourceApi);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public String filesystemPathForFullPath(String fullPath) {
|
||||
return new File(rootUri.getPath(), fullPath).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filesystemPathForURL(LocalFilesystemURL url) {
|
||||
return filesystemPathForFullPath(url.path);
|
||||
}
|
||||
|
||||
private String fullPathForFilesystemPath(String absolutePath) {
|
||||
if (absolutePath != null && absolutePath.startsWith(rootUri.getPath())) {
|
||||
return absolutePath.substring(rootUri.getPath().length() - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri toNativeUri(LocalFilesystemURL inputURL) {
|
||||
return nativeUriForFullPath(inputURL.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFilesystemURL toLocalUri(Uri inputURL) {
|
||||
if (!"file".equals(inputURL.getScheme())) {
|
||||
return null;
|
||||
}
|
||||
File f = new File(inputURL.getPath());
|
||||
// Removes and duplicate /s (e.g. file:///a//b/c)
|
||||
Uri resolvedUri = Uri.fromFile(f);
|
||||
String rootUriNoTrailingSlash = rootUri.getEncodedPath();
|
||||
rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
|
||||
if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
|
||||
return null;
|
||||
}
|
||||
String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
|
||||
// Strip leading slash
|
||||
if (!subPath.isEmpty()) {
|
||||
subPath = subPath.substring(1);
|
||||
}
|
||||
Uri.Builder b = new Uri.Builder()
|
||||
.scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
|
||||
.authority("localhost")
|
||||
.path(name);
|
||||
if (!subPath.isEmpty()) {
|
||||
b.appendEncodedPath(subPath);
|
||||
}
|
||||
if (f.isDirectory() || inputURL.getPath().endsWith("/")) {
|
||||
// Add trailing / for directories.
|
||||
b.appendEncodedPath("");
|
||||
}
|
||||
return LocalFilesystemURL.parse(b.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFilesystemURL URLforFilesystemPath(String path) {
|
||||
return localUrlforFullPath(fullPathForFilesystemPath(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
|
||||
String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
|
||||
boolean create = false;
|
||||
boolean exclusive = false;
|
||||
|
||||
if (options != null) {
|
||||
create = options.optBoolean("create");
|
||||
if (create) {
|
||||
exclusive = options.optBoolean("exclusive");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a ":" character in the file to line up with BB and iOS
|
||||
if (path.contains(":")) {
|
||||
throw new EncodingException("This path has an invalid \":\" in it.");
|
||||
}
|
||||
|
||||
LocalFilesystemURL requestedURL;
|
||||
|
||||
// Check whether the supplied path is absolute or relative
|
||||
if (directory && !path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
if (path.startsWith("/")) {
|
||||
requestedURL = localUrlforFullPath(normalizePath(path));
|
||||
} else {
|
||||
requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
|
||||
}
|
||||
|
||||
File fp = new File(this.filesystemPathForURL(requestedURL));
|
||||
|
||||
if (create) {
|
||||
if (exclusive && fp.exists()) {
|
||||
throw new FileExistsException("create/exclusive fails");
|
||||
}
|
||||
if (directory) {
|
||||
fp.mkdir();
|
||||
} else {
|
||||
fp.createNewFile();
|
||||
}
|
||||
if (!fp.exists()) {
|
||||
throw new FileExistsException("create fails");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!fp.exists()) {
|
||||
throw new FileNotFoundException("path does not exist");
|
||||
}
|
||||
if (directory) {
|
||||
if (fp.isFile()) {
|
||||
throw new TypeMismatchException("path doesn't exist or is file");
|
||||
}
|
||||
} else {
|
||||
if (fp.isDirectory()) {
|
||||
throw new TypeMismatchException("path doesn't exist or is directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the directory
|
||||
return makeEntryForURL(requestedURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException {
|
||||
|
||||
File fp = new File(filesystemPathForURL(inputURL));
|
||||
|
||||
// You can't delete a directory that is not empty
|
||||
if (fp.isDirectory() && fp.list().length > 0) {
|
||||
throw new InvalidModificationException("You can't delete a directory that is not empty.");
|
||||
}
|
||||
|
||||
return fp.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(LocalFilesystemURL inputURL) {
|
||||
File fp = new File(filesystemPathForURL(inputURL));
|
||||
return fp.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException {
|
||||
File directory = new File(filesystemPathForURL(inputURL));
|
||||
return removeDirRecursively(directory);
|
||||
}
|
||||
|
||||
protected boolean removeDirRecursively(File directory) throws FileExistsException {
|
||||
if (directory.isDirectory()) {
|
||||
for (File file : directory.listFiles()) {
|
||||
removeDirRecursively(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!directory.delete()) {
|
||||
throw new FileExistsException("could not delete: " + directory.getName());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
|
||||
File fp = new File(filesystemPathForURL(inputURL));
|
||||
|
||||
if (!fp.exists()) {
|
||||
// The directory we are listing doesn't exist so we should fail.
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
File[] files = fp.listFiles();
|
||||
if (files == null) {
|
||||
// inputURL is a directory
|
||||
return null;
|
||||
}
|
||||
LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
entries[i] = URLforFilesystemPath(files[i].getPath());
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
|
||||
File file = new File(filesystemPathForURL(inputURL));
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
|
||||
}
|
||||
|
||||
JSONObject metadata = new JSONObject();
|
||||
try {
|
||||
// Ensure that directories report a size of 0
|
||||
metadata.put("size", file.isDirectory() ? 0 : file.length());
|
||||
metadata.put("type", resourceApi.getMimeType(Uri.fromFile(file)));
|
||||
metadata.put("name", file.getName());
|
||||
metadata.put("fullPath", inputURL.path);
|
||||
metadata.put("lastModifiedDate", file.lastModified());
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private void copyFile(Filesystem srcFs, LocalFilesystemURL srcURL, File destFile, boolean move) throws IOException, InvalidModificationException, NoModificationAllowedException {
|
||||
if (move) {
|
||||
String realSrcPath = srcFs.filesystemPathForURL(srcURL);
|
||||
if (realSrcPath != null) {
|
||||
File srcFile = new File(realSrcPath);
|
||||
if (srcFile.renameTo(destFile)) {
|
||||
return;
|
||||
}
|
||||
// Trying to rename the file failed. Possibly because we moved across file system on the device.
|
||||
}
|
||||
}
|
||||
|
||||
CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(srcFs.toNativeUri(srcURL));
|
||||
copyResource(offr, new FileOutputStream(destFile));
|
||||
|
||||
if (move) {
|
||||
srcFs.removeFileAtLocalURL(srcURL);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyDirectory(Filesystem srcFs, LocalFilesystemURL srcURL, File dstDir, boolean move) throws IOException, NoModificationAllowedException, InvalidModificationException, FileExistsException {
|
||||
if (move) {
|
||||
String realSrcPath = srcFs.filesystemPathForURL(srcURL);
|
||||
if (realSrcPath != null) {
|
||||
File srcDir = new File(realSrcPath);
|
||||
// If the destination directory already exists and is empty then delete it. This is according to spec.
|
||||
if (dstDir.exists()) {
|
||||
if (dstDir.list().length > 0) {
|
||||
throw new InvalidModificationException("directory is not empty");
|
||||
}
|
||||
dstDir.delete();
|
||||
}
|
||||
// Try to rename the directory
|
||||
if (srcDir.renameTo(dstDir)) {
|
||||
return;
|
||||
}
|
||||
// Trying to rename the file failed. Possibly because we moved across file system on the device.
|
||||
}
|
||||
}
|
||||
|
||||
if (dstDir.exists()) {
|
||||
if (dstDir.list().length > 0) {
|
||||
throw new InvalidModificationException("directory is not empty");
|
||||
}
|
||||
} else {
|
||||
if (!dstDir.mkdir()) {
|
||||
// If we can't create the directory then fail
|
||||
throw new NoModificationAllowedException("Couldn't create the destination directory");
|
||||
}
|
||||
}
|
||||
|
||||
LocalFilesystemURL[] children = srcFs.listChildren(srcURL);
|
||||
for (LocalFilesystemURL childLocalUrl : children) {
|
||||
File target = new File(dstDir, new File(childLocalUrl.path).getName());
|
||||
if (childLocalUrl.isDirectory) {
|
||||
copyDirectory(srcFs, childLocalUrl, target, false);
|
||||
} else {
|
||||
copyFile(srcFs, childLocalUrl, target, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (move) {
|
||||
srcFs.recursiveRemoveFileAtLocalURL(srcURL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
|
||||
Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
|
||||
|
||||
// Check to see if the destination directory exists
|
||||
String newParent = this.filesystemPathForURL(destURL);
|
||||
File destinationDir = new File(newParent);
|
||||
if (!destinationDir.exists()) {
|
||||
// The destination does not exist so we should fail.
|
||||
throw new FileNotFoundException("The source does not exist");
|
||||
}
|
||||
|
||||
// Figure out where we should be copying to
|
||||
final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
|
||||
|
||||
Uri dstNativeUri = toNativeUri(destinationURL);
|
||||
Uri srcNativeUri = srcFs.toNativeUri(srcURL);
|
||||
// Check to see if source and destination are the same file
|
||||
if (dstNativeUri.equals(srcNativeUri)) {
|
||||
throw new InvalidModificationException("Can't copy onto itself");
|
||||
}
|
||||
|
||||
if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
|
||||
throw new InvalidModificationException("Source URL is read-only (cannot move)");
|
||||
}
|
||||
|
||||
File destFile = new File(dstNativeUri.getPath());
|
||||
if (destFile.exists()) {
|
||||
if (!srcURL.isDirectory && destFile.isDirectory()) {
|
||||
throw new InvalidModificationException("Can't copy/move a file to an existing directory");
|
||||
} else if (srcURL.isDirectory && destFile.isFile()) {
|
||||
throw new InvalidModificationException("Can't copy/move a directory to an existing file");
|
||||
}
|
||||
}
|
||||
|
||||
if (srcURL.isDirectory) {
|
||||
// E.g. Copy /sdcard/myDir to /sdcard/myDir/backup
|
||||
if (dstNativeUri.toString().startsWith(srcNativeUri.toString() + '/')) {
|
||||
throw new InvalidModificationException("Can't copy directory into itself");
|
||||
}
|
||||
copyDirectory(srcFs, srcURL, destFile, move);
|
||||
} else {
|
||||
copyFile(srcFs, srcURL, destFile, move);
|
||||
}
|
||||
return makeEntryForURL(destinationURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
|
||||
int offset, boolean isBinary) throws IOException, NoModificationAllowedException {
|
||||
|
||||
boolean append = false;
|
||||
if (offset > 0) {
|
||||
this.truncateFileAtURL(inputURL, offset);
|
||||
append = true;
|
||||
}
|
||||
|
||||
byte[] rawData;
|
||||
if (isBinary) {
|
||||
rawData = Base64.decode(data, Base64.DEFAULT);
|
||||
} else {
|
||||
rawData = data.getBytes();
|
||||
}
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(rawData);
|
||||
try
|
||||
{
|
||||
byte buff[] = new byte[rawData.length];
|
||||
String absolutePath = filesystemPathForURL(inputURL);
|
||||
FileOutputStream out = new FileOutputStream(absolutePath, append);
|
||||
try {
|
||||
in.read(buff, 0, buff.length);
|
||||
out.write(buff, 0, rawData.length);
|
||||
out.flush();
|
||||
} finally {
|
||||
// Always close the output
|
||||
out.close();
|
||||
}
|
||||
if (isPublicDirectory(absolutePath)) {
|
||||
broadcastNewFile(Uri.fromFile(new File(absolutePath)));
|
||||
}
|
||||
}
|
||||
catch (NullPointerException e)
|
||||
{
|
||||
// This is a bug in the Android implementation of the Java Stack
|
||||
NoModificationAllowedException realException = new NoModificationAllowedException(inputURL.toString());
|
||||
throw realException;
|
||||
}
|
||||
|
||||
return rawData.length;
|
||||
}
|
||||
|
||||
private boolean isPublicDirectory(String absolutePath) {
|
||||
// TODO: should expose a way to scan app's private files (maybe via a flag).
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Lollipop has a bug where SD cards are null.
|
||||
for (File f : context.getExternalMediaDirs()) {
|
||||
if(f != null && absolutePath.startsWith(f.getAbsolutePath())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String extPath = Environment.getExternalStorageDirectory().getAbsolutePath();
|
||||
return absolutePath.startsWith(extPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send broadcast of new file so files appear over MTP
|
||||
*/
|
||||
private void broadcastNewFile(Uri nativeUri) {
|
||||
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, nativeUri);
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException {
|
||||
File file = new File(filesystemPathForURL(inputURL));
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
|
||||
}
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(filesystemPathForURL(inputURL), "rw");
|
||||
try {
|
||||
if (raf.length() >= size) {
|
||||
FileChannel channel = raf.getChannel();
|
||||
channel.truncate(size);
|
||||
return size;
|
||||
}
|
||||
|
||||
return raf.length();
|
||||
} finally {
|
||||
raf.close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
|
||||
String path = filesystemPathForURL(inputURL);
|
||||
File file = new File(path);
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
// This is a copy & paste from CordovaResource API that is required since CordovaResourceApi
|
||||
// has a bug pre-4.0.0.
|
||||
// TODO: Once cordova-android@4.0.0 is released, delete this copy and make the plugin depend on
|
||||
// 4.0.0 with an engine tag.
|
||||
private static void copyResource(CordovaResourceApi.OpenForReadResult input, OutputStream outputStream) throws IOException {
|
||||
try {
|
||||
InputStream inputStream = input.inputStream;
|
||||
if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
|
||||
FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
|
||||
FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
|
||||
long offset = 0;
|
||||
long length = input.length;
|
||||
if (input.assetFd != null) {
|
||||
offset = input.assetFd.getStartOffset();
|
||||
}
|
||||
// transferFrom()'s 2nd arg is a relative position. Need to set the absolute
|
||||
// position first.
|
||||
inChannel.position(offset);
|
||||
outChannel.transferFrom(inChannel, 0, length);
|
||||
} else {
|
||||
final int BUFFER_SIZE = 8192;
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
for (;;) {
|
||||
int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
|
||||
|
||||
if (bytesRead <= 0) {
|
||||
break;
|
||||
}
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
input.inputStream.close();
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
package org.apache.cordova.file;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
public class LocalFilesystemURL {
|
||||
|
||||
public static final String FILESYSTEM_PROTOCOL = "cdvfile";
|
||||
|
||||
public final Uri uri;
|
||||
public final String fsName;
|
||||
public final String path;
|
||||
public final boolean isDirectory;
|
||||
|
||||
private LocalFilesystemURL(Uri uri, String fsName, String fsPath, boolean isDirectory) {
|
||||
this.uri = uri;
|
||||
this.fsName = fsName;
|
||||
this.path = fsPath;
|
||||
this.isDirectory = isDirectory;
|
||||
}
|
||||
|
||||
public static LocalFilesystemURL parse(Uri uri) {
|
||||
if (!FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
|
||||
return null;
|
||||
}
|
||||
String path = uri.getPath();
|
||||
if (path.length() < 1) {
|
||||
return null;
|
||||
}
|
||||
int firstSlashIdx = path.indexOf('/', 1);
|
||||
if (firstSlashIdx < 0) {
|
||||
return null;
|
||||
}
|
||||
String fsName = path.substring(1, firstSlashIdx);
|
||||
path = path.substring(firstSlashIdx);
|
||||
boolean isDirectory = path.charAt(path.length() - 1) == '/';
|
||||
return new LocalFilesystemURL(uri, fsName, path, isDirectory);
|
||||
}
|
||||
|
||||
public static LocalFilesystemURL parse(String uri) {
|
||||
return parse(Uri.parse(uri));
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return uri.toString();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
47
plugins/cordova-plugin-file/src/android/build-extras.gradle
Normal file
47
plugins/cordova-plugin-file/src/android/build-extras.gradle
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
ext.postBuildExtras = {
|
||||
def inAssetsDir = file("assets")
|
||||
def outAssetsDir = inAssetsDir
|
||||
def outFile = new File(outAssetsDir, "cdvasset.manifest")
|
||||
|
||||
def newTask = task("cdvCreateAssetManifest") << {
|
||||
def contents = new HashMap()
|
||||
def sizes = new HashMap()
|
||||
contents[""] = inAssetsDir.list()
|
||||
def tree = fileTree(dir: inAssetsDir)
|
||||
tree.visit { fileDetails ->
|
||||
if (fileDetails.isDirectory()) {
|
||||
contents[fileDetails.relativePath.toString()] = fileDetails.file.list()
|
||||
} else {
|
||||
sizes[fileDetails.relativePath.toString()] = fileDetails.file.length()
|
||||
}
|
||||
}
|
||||
|
||||
outAssetsDir.mkdirs()
|
||||
outFile.withObjectOutputStream { oos ->
|
||||
oos.writeObject(contents)
|
||||
oos.writeObject(sizes)
|
||||
}
|
||||
}
|
||||
newTask.inputs.dir inAssetsDir
|
||||
newTask.outputs.file outFile
|
||||
def preBuildTask = tasks["preBuild"]
|
||||
preBuildTask.dependsOn(newTask)
|
||||
}
|
||||
44
plugins/cordova-plugin-file/src/blackberry10/index.js
vendored
Normal file
44
plugins/cordova-plugin-file/src/blackberry10/index.js
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
module.exports = {
|
||||
setSandbox : function (success, fail, args, env) {
|
||||
require("lib/webview").setSandbox(JSON.parse(decodeURIComponent(args[0])));
|
||||
new PluginResult(args, env).ok();
|
||||
},
|
||||
|
||||
getHomePath: function (success, fail, args, env) {
|
||||
var homeDir = window.qnx.webplatform.getApplication().getEnv("HOME");
|
||||
new PluginResult(args, env).ok(homeDir);
|
||||
},
|
||||
|
||||
requestAllPaths: function (success, fail, args, env) {
|
||||
var homeDir = 'file://' + window.qnx.webplatform.getApplication().getEnv("HOME").replace('/data', ''),
|
||||
paths = {
|
||||
applicationDirectory: homeDir + '/app/native/',
|
||||
applicationStorageDirectory: homeDir + '/',
|
||||
dataDirectory: homeDir + '/data/webviews/webfs/persistent/local__0/',
|
||||
cacheDirectory: homeDir + '/data/webviews/webfs/temporary/local__0/',
|
||||
externalRootDirectory: 'file:///accounts/1000/removable/sdcard/',
|
||||
sharedDirectory: homeDir + '/shared/'
|
||||
};
|
||||
success(paths);
|
||||
}
|
||||
};
|
||||
964
plugins/cordova-plugin-file/src/browser/FileProxy.js
vendored
Normal file
964
plugins/cordova-plugin-file/src/browser/FileProxy.js
vendored
Normal file
@@ -0,0 +1,964 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/*global require, exports, module*/
|
||||
/*global FILESYSTEM_PREFIX*/
|
||||
/*global IDBKeyRange*/
|
||||
|
||||
/* Heavily based on https://github.com/ebidel/idb.filesystem.js */
|
||||
|
||||
// window.webkitRequestFileSystem and window.webkitResolveLocalFileSystemURL
|
||||
// are available only in Chrome and possible a good flag to indicate
|
||||
// that we're running in Chrome
|
||||
var isChrome = window.webkitRequestFileSystem && window.webkitResolveLocalFileSystemURL;
|
||||
|
||||
// For chrome we don't need to implement proxy methods
|
||||
// All functionality can be accessed natively.
|
||||
if (isChrome) {
|
||||
var pathsPrefix = {
|
||||
// Read-only directory where the application is installed.
|
||||
applicationDirectory: location.origin + "/",
|
||||
// Where to put app-specific data files.
|
||||
dataDirectory: 'filesystem:file:///persistent/',
|
||||
// Cached files that should survive app restarts.
|
||||
// Apps should not rely on the OS to delete files in here.
|
||||
cacheDirectory: 'filesystem:file:///temporary/',
|
||||
};
|
||||
|
||||
exports.requestAllPaths = function(successCallback) {
|
||||
successCallback(pathsPrefix);
|
||||
};
|
||||
|
||||
require("cordova/exec/proxy").add("File", module.exports);
|
||||
return;
|
||||
}
|
||||
|
||||
var LocalFileSystem = require('./LocalFileSystem'),
|
||||
FileSystem = require('./FileSystem'),
|
||||
FileEntry = require('./FileEntry'),
|
||||
FileError = require('./FileError'),
|
||||
DirectoryEntry = require('./DirectoryEntry'),
|
||||
File = require('./File');
|
||||
|
||||
(function(exports, global) {
|
||||
var indexedDB = global.indexedDB || global.mozIndexedDB;
|
||||
if (!indexedDB) {
|
||||
throw "Firefox OS File plugin: indexedDB not supported";
|
||||
}
|
||||
|
||||
var fs_ = null;
|
||||
|
||||
var idb_ = {};
|
||||
idb_.db = null;
|
||||
var FILE_STORE_ = 'entries';
|
||||
|
||||
var DIR_SEPARATOR = '/';
|
||||
|
||||
var pathsPrefix = {
|
||||
// Read-only directory where the application is installed.
|
||||
applicationDirectory: location.origin + "/",
|
||||
// Where to put app-specific data files.
|
||||
dataDirectory: 'file:///persistent/',
|
||||
// Cached files that should survive app restarts.
|
||||
// Apps should not rely on the OS to delete files in here.
|
||||
cacheDirectory: 'file:///temporary/',
|
||||
};
|
||||
|
||||
var unicodeLastChar = 65535;
|
||||
|
||||
/*** Exported functionality ***/
|
||||
|
||||
exports.requestFileSystem = function(successCallback, errorCallback, args) {
|
||||
var type = args[0];
|
||||
// Size is ignored since IDB filesystem size depends
|
||||
// on browser implementation and can't be set up by user
|
||||
var size = args[1]; // jshint ignore: line
|
||||
|
||||
if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent';
|
||||
var storageName = (location.protocol + location.host).replace(/:/g, '_');
|
||||
|
||||
var root = new DirectoryEntry('', DIR_SEPARATOR);
|
||||
fs_ = new FileSystem(name, root);
|
||||
|
||||
idb_.open(storageName, function() {
|
||||
successCallback(fs_);
|
||||
}, errorCallback);
|
||||
};
|
||||
|
||||
// Overridden by Android, BlackBerry 10 and iOS to populate fsMap
|
||||
require('./fileSystems').getFs = function(name, callback) {
|
||||
callback(new FileSystem(name, fs_.root));
|
||||
};
|
||||
|
||||
// list a directory's contents (files and folders).
|
||||
exports.readEntries = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
|
||||
if (typeof successCallback !== 'function') {
|
||||
throw Error('Expected successCallback argument.');
|
||||
}
|
||||
|
||||
var path = resolveToFullPath_(fullPath);
|
||||
|
||||
exports.getDirectory(function() {
|
||||
idb_.getAllEntries(path.fullPath + DIR_SEPARATOR, path.storagePath, function(entries) {
|
||||
successCallback(entries);
|
||||
}, errorCallback);
|
||||
}, function() {
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.NOT_FOUND_ERR);
|
||||
}
|
||||
}, [path.storagePath, path.fullPath, {create: false}]);
|
||||
};
|
||||
|
||||
exports.getFile = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
var path = args[1];
|
||||
var options = args[2] || {};
|
||||
|
||||
// Create an absolute path if we were handed a relative one.
|
||||
path = resolveToFullPath_(fullPath, path);
|
||||
|
||||
idb_.get(path.storagePath, function(fileEntry) {
|
||||
if (options.create === true && options.exclusive === true && fileEntry) {
|
||||
// If create and exclusive are both true, and the path already exists,
|
||||
// getFile must fail.
|
||||
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.PATH_EXISTS_ERR);
|
||||
}
|
||||
} else if (options.create === true && !fileEntry) {
|
||||
// If create is true, the path doesn't exist, and no other error occurs,
|
||||
// getFile must create it as a zero-length file and return a corresponding
|
||||
// FileEntry.
|
||||
var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
|
||||
|
||||
newFileEntry.file_ = new MyFile({
|
||||
size: 0,
|
||||
name: newFileEntry.name,
|
||||
lastModifiedDate: new Date(),
|
||||
storagePath: path.storagePath
|
||||
});
|
||||
|
||||
idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
|
||||
} else if (options.create === true && fileEntry) {
|
||||
if (fileEntry.isFile) {
|
||||
// Overwrite file, delete then create new.
|
||||
idb_['delete'](path.storagePath, function() {
|
||||
var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
|
||||
|
||||
newFileEntry.file_ = new MyFile({
|
||||
size: 0,
|
||||
name: newFileEntry.name,
|
||||
lastModifiedDate: new Date(),
|
||||
storagePath: path.storagePath
|
||||
});
|
||||
|
||||
idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
|
||||
}, errorCallback);
|
||||
} else {
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
}
|
||||
}
|
||||
} else if ((!options.create || options.create === false) && !fileEntry) {
|
||||
// If create is not true and the path doesn't exist, getFile must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.NOT_FOUND_ERR);
|
||||
}
|
||||
} else if ((!options.create || options.create === false) && fileEntry &&
|
||||
fileEntry.isDirectory) {
|
||||
// If create is not true and the path exists, but is a directory, getFile
|
||||
// must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.TYPE_MISMATCH_ERR);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, if no other error occurs, getFile must return a FileEntry
|
||||
// corresponding to path.
|
||||
|
||||
successCallback(fileEntryFromIdbEntry(fileEntry));
|
||||
}
|
||||
}, errorCallback);
|
||||
};
|
||||
|
||||
exports.getFileMetadata = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
|
||||
exports.getFile(function(fileEntry) {
|
||||
successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate,
|
||||
fileEntry.file_.size));
|
||||
}, errorCallback, [fullPath, null]);
|
||||
};
|
||||
|
||||
exports.getMetadata = function(successCallback, errorCallback, args) {
|
||||
exports.getFile(function (fileEntry) {
|
||||
successCallback(
|
||||
{
|
||||
modificationTime: fileEntry.file_.lastModifiedDate,
|
||||
size: fileEntry.file_.lastModifiedDate
|
||||
});
|
||||
}, errorCallback, args);
|
||||
};
|
||||
|
||||
exports.setMetadata = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
var metadataObject = args[1];
|
||||
|
||||
exports.getFile(function (fileEntry) {
|
||||
fileEntry.file_.lastModifiedDate = metadataObject.modificationTime;
|
||||
idb_.put(fileEntry, fileEntry.file_.storagePath, successCallback, errorCallback);
|
||||
}, errorCallback, [fullPath, null]);
|
||||
};
|
||||
|
||||
exports.write = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
data = args[1],
|
||||
position = args[2],
|
||||
isBinary = args[3]; // jshint ignore: line
|
||||
|
||||
if (!data) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof data === 'string' || data instanceof String) {
|
||||
data = new Blob([data]);
|
||||
}
|
||||
|
||||
exports.getFile(function(fileEntry) {
|
||||
var blob_ = fileEntry.file_.blob_;
|
||||
|
||||
if (!blob_) {
|
||||
blob_ = new Blob([data], {type: data.type});
|
||||
} else {
|
||||
// Calc the head and tail fragments
|
||||
var head = blob_.slice(0, position);
|
||||
var tail = blob_.slice(position + (data.size || data.byteLength));
|
||||
|
||||
// Calc the padding
|
||||
var padding = position - head.size;
|
||||
if (padding < 0) {
|
||||
padding = 0;
|
||||
}
|
||||
|
||||
// Do the "write". In fact, a full overwrite of the Blob.
|
||||
blob_ = new Blob([head, new Uint8Array(padding), data, tail],
|
||||
{type: data.type});
|
||||
}
|
||||
|
||||
// Set the blob we're writing on this file entry so we can recall it later.
|
||||
fileEntry.file_.blob_ = blob_;
|
||||
fileEntry.file_.lastModifiedDate = new Date() || null;
|
||||
fileEntry.file_.size = blob_.size;
|
||||
fileEntry.file_.name = blob_.name;
|
||||
fileEntry.file_.type = blob_.type;
|
||||
|
||||
idb_.put(fileEntry, fileEntry.file_.storagePath, function() {
|
||||
successCallback(data.size || data.byteLength);
|
||||
}, errorCallback);
|
||||
}, errorCallback, [fileName, null]);
|
||||
};
|
||||
|
||||
exports.readAsText = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
enc = args[1],
|
||||
startPos = args[2],
|
||||
endPos = args[3];
|
||||
|
||||
readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
exports.readAsDataURL = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
startPos = args[1],
|
||||
endPos = args[2];
|
||||
|
||||
readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
exports.readAsBinaryString = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
startPos = args[1],
|
||||
endPos = args[2];
|
||||
|
||||
readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
exports.readAsArrayBuffer = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
startPos = args[1],
|
||||
endPos = args[2];
|
||||
|
||||
readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
exports.removeRecursively = exports.remove = function(successCallback, errorCallback, args) {
|
||||
if (typeof successCallback !== 'function') {
|
||||
throw Error('Expected successCallback argument.');
|
||||
}
|
||||
|
||||
var fullPath = resolveToFullPath_(args[0]).storagePath;
|
||||
if (fullPath === pathsPrefix.cacheDirectory || fullPath === pathsPrefix.dataDirectory) {
|
||||
errorCallback(FileError.NO_MODIFICATION_ALLOWED_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
function deleteEntry(isDirectory) {
|
||||
// TODO: This doesn't protect against directories that have content in it.
|
||||
// Should throw an error instead if the dirEntry is not empty.
|
||||
idb_['delete'](fullPath, function() {
|
||||
successCallback();
|
||||
}, function() {
|
||||
if (errorCallback) { errorCallback(); }
|
||||
}, isDirectory);
|
||||
}
|
||||
|
||||
// We need to to understand what we are deleting:
|
||||
exports.getDirectory(function(entry) {
|
||||
deleteEntry(entry.isDirectory);
|
||||
}, function(){
|
||||
//DirectoryEntry was already deleted or entry is FileEntry
|
||||
deleteEntry(false);
|
||||
}, [fullPath, null, {create: false}]);
|
||||
};
|
||||
|
||||
exports.getDirectory = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
var path = args[1];
|
||||
var options = args[2];
|
||||
|
||||
// Create an absolute path if we were handed a relative one.
|
||||
path = resolveToFullPath_(fullPath, path);
|
||||
|
||||
idb_.get(path.storagePath, function(folderEntry) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (options.create === true && options.exclusive === true && folderEntry) {
|
||||
// If create and exclusive are both true, and the path already exists,
|
||||
// getDirectory must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.PATH_EXISTS_ERR);
|
||||
}
|
||||
// There is a strange bug in mobilespec + FF, which results in coming to multiple else-if's
|
||||
// so we are shielding from it with returns.
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.create === true && !folderEntry) {
|
||||
// If create is true, the path doesn't exist, and no other error occurs,
|
||||
// getDirectory must create it as a zero-length file and return a corresponding
|
||||
// MyDirectoryEntry.
|
||||
var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
|
||||
|
||||
idb_.put(dirEntry, path.storagePath, successCallback, errorCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.create === true && folderEntry) {
|
||||
|
||||
if (folderEntry.isDirectory) {
|
||||
// IDB won't save methods, so we need re-create the MyDirectoryEntry.
|
||||
successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem));
|
||||
} else {
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!options.create || options.create === false) && !folderEntry) {
|
||||
// Handle root special. It should always exist.
|
||||
if (path.fullPath === DIR_SEPARATOR) {
|
||||
successCallback(fs_.root);
|
||||
return;
|
||||
}
|
||||
|
||||
// If create is not true and the path doesn't exist, getDirectory must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.NOT_FOUND_ERR);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if ((!options.create || options.create === false) && folderEntry && folderEntry.isFile) {
|
||||
// If create is not true and the path exists, but is a file, getDirectory
|
||||
// must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.TYPE_MISMATCH_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, if no other error occurs, getDirectory must return a
|
||||
// MyDirectoryEntry corresponding to path.
|
||||
|
||||
// IDB won't' save methods, so we need re-create MyDirectoryEntry.
|
||||
successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem));
|
||||
}, errorCallback);
|
||||
};
|
||||
|
||||
exports.getParent = function(successCallback, errorCallback, args) {
|
||||
if (typeof successCallback !== 'function') {
|
||||
throw Error('Expected successCallback argument.');
|
||||
}
|
||||
|
||||
var fullPath = args[0];
|
||||
//fullPath is like this:
|
||||
//file:///persistent/path/to/file or
|
||||
//file:///persistent/path/to/directory/
|
||||
|
||||
if (fullPath === DIR_SEPARATOR || fullPath === pathsPrefix.cacheDirectory ||
|
||||
fullPath === pathsPrefix.dataDirectory) {
|
||||
successCallback(fs_.root);
|
||||
return;
|
||||
}
|
||||
|
||||
//To delete all slashes at the end
|
||||
while (fullPath[fullPath.length - 1] === '/') {
|
||||
fullPath = fullPath.substr(0, fullPath.length - 1);
|
||||
}
|
||||
|
||||
var pathArr = fullPath.split(DIR_SEPARATOR);
|
||||
pathArr.pop();
|
||||
var parentName = pathArr.pop();
|
||||
var path = pathArr.join(DIR_SEPARATOR) + DIR_SEPARATOR;
|
||||
|
||||
//To get parent of root files
|
||||
var joined = path + parentName + DIR_SEPARATOR;//is like this: file:///persistent/
|
||||
if (joined === pathsPrefix.cacheDirectory || joined === pathsPrefix.dataDirectory) {
|
||||
exports.getDirectory(successCallback, errorCallback, [joined, DIR_SEPARATOR, {create: false}]);
|
||||
return;
|
||||
}
|
||||
|
||||
exports.getDirectory(successCallback, errorCallback, [path, parentName, {create: false}]);
|
||||
};
|
||||
|
||||
exports.copyTo = function(successCallback, errorCallback, args) {
|
||||
var srcPath = args[0];
|
||||
var parentFullPath = args[1];
|
||||
var name = args[2];
|
||||
|
||||
if (name.indexOf('/') !== -1 || srcPath === parentFullPath + name) {
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Read src file
|
||||
exports.getFile(function(srcFileEntry) {
|
||||
|
||||
var path = resolveToFullPath_(parentFullPath);
|
||||
//Check directory
|
||||
exports.getDirectory(function() {
|
||||
|
||||
// Create dest file
|
||||
exports.getFile(function(dstFileEntry) {
|
||||
|
||||
exports.write(function() {
|
||||
successCallback(dstFileEntry);
|
||||
}, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]);
|
||||
|
||||
}, errorCallback, [parentFullPath, name, {create: true}]);
|
||||
|
||||
}, function() { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); }},
|
||||
[path.storagePath, null, {create:false}]);
|
||||
|
||||
}, errorCallback, [srcPath, null]);
|
||||
};
|
||||
|
||||
exports.moveTo = function(successCallback, errorCallback, args) {
|
||||
var srcPath = args[0];
|
||||
// parentFullPath and name parameters is ignored because
|
||||
// args is being passed downstream to exports.copyTo method
|
||||
var parentFullPath = args[1]; // jshint ignore: line
|
||||
var name = args[2]; // jshint ignore: line
|
||||
|
||||
exports.copyTo(function (fileEntry) {
|
||||
|
||||
exports.remove(function () {
|
||||
successCallback(fileEntry);
|
||||
}, errorCallback, [srcPath]);
|
||||
|
||||
}, errorCallback, args);
|
||||
};
|
||||
|
||||
exports.resolveLocalFileSystemURI = function(successCallback, errorCallback, args) {
|
||||
var path = args[0];
|
||||
|
||||
// Ignore parameters
|
||||
if (path.indexOf('?') !== -1) {
|
||||
path = String(path).split("?")[0];
|
||||
}
|
||||
|
||||
// support for encodeURI
|
||||
if (/\%5/g.test(path) || /\%20/g.test(path)) {
|
||||
path = decodeURI(path);
|
||||
}
|
||||
|
||||
if (path.trim()[0] === '/') {
|
||||
errorCallback && errorCallback(FileError.ENCODING_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
//support for cdvfile
|
||||
if (path.trim().substr(0,7) === "cdvfile") {
|
||||
if (path.indexOf("cdvfile://localhost") === -1) {
|
||||
errorCallback && errorCallback(FileError.ENCODING_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var indexPersistent = path.indexOf("persistent");
|
||||
var indexTemporary = path.indexOf("temporary");
|
||||
|
||||
//cdvfile://localhost/persistent/path/to/file
|
||||
if (indexPersistent !== -1) {
|
||||
path = "file:///persistent" + path.substr(indexPersistent + 10);
|
||||
} else if (indexTemporary !== -1) {
|
||||
path = "file:///temporary" + path.substr(indexTemporary + 9);
|
||||
} else {
|
||||
errorCallback && errorCallback(FileError.ENCODING_ERR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// to avoid path form of '///path/to/file'
|
||||
function handlePathSlashes(path) {
|
||||
var cutIndex = 0;
|
||||
for (var i = 0; i < path.length - 1; i++) {
|
||||
if (path[i] === DIR_SEPARATOR && path[i + 1] === DIR_SEPARATOR) {
|
||||
cutIndex = i + 1;
|
||||
} else break;
|
||||
}
|
||||
|
||||
return path.substr(cutIndex);
|
||||
}
|
||||
|
||||
// Handle localhost containing paths (see specs )
|
||||
if (path.indexOf('file://localhost/') === 0) {
|
||||
path = path.replace('file://localhost/', 'file:///');
|
||||
}
|
||||
|
||||
if (path.indexOf(pathsPrefix.dataDirectory) === 0) {
|
||||
path = path.substring(pathsPrefix.dataDirectory.length - 1);
|
||||
path = handlePathSlashes(path);
|
||||
|
||||
exports.requestFileSystem(function() {
|
||||
exports.getFile(successCallback, function() {
|
||||
exports.getDirectory(successCallback, errorCallback, [pathsPrefix.dataDirectory, path,
|
||||
{create: false}]);
|
||||
}, [pathsPrefix.dataDirectory, path, {create: false}]);
|
||||
}, errorCallback, [LocalFileSystem.PERSISTENT]);
|
||||
} else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) {
|
||||
path = path.substring(pathsPrefix.cacheDirectory.length - 1);
|
||||
path = handlePathSlashes(path);
|
||||
|
||||
exports.requestFileSystem(function() {
|
||||
exports.getFile(successCallback, function() {
|
||||
exports.getDirectory(successCallback, errorCallback, [pathsPrefix.cacheDirectory, path,
|
||||
{create: false}]);
|
||||
}, [pathsPrefix.cacheDirectory, path, {create: false}]);
|
||||
}, errorCallback, [LocalFileSystem.TEMPORARY]);
|
||||
} else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) {
|
||||
path = path.substring(pathsPrefix.applicationDirectory.length);
|
||||
//TODO: need to cut out redundant slashes?
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", path, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.status === 200 && xhr.readyState === 4) {
|
||||
exports.requestFileSystem(function(fs) {
|
||||
fs.name = location.hostname;
|
||||
|
||||
//TODO: need to call exports.getFile(...) to handle errors correct
|
||||
fs.root.getFile(path, {create: true}, writeFile, errorCallback);
|
||||
}, errorCallback, [LocalFileSystem.PERSISTENT]);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
errorCallback && errorCallback(FileError.NOT_READABLE_ERR);
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
} else {
|
||||
errorCallback && errorCallback(FileError.NOT_FOUND_ERR);
|
||||
}
|
||||
|
||||
function writeFile(entry) {
|
||||
entry.createWriter(function (fileWriter) {
|
||||
fileWriter.onwriteend = function (evt) {
|
||||
if (!evt.target.error) {
|
||||
entry.filesystemName = location.hostname;
|
||||
successCallback(entry);
|
||||
}
|
||||
};
|
||||
fileWriter.onerror = function () {
|
||||
errorCallback && errorCallback(FileError.NOT_READABLE_ERR);
|
||||
};
|
||||
fileWriter.write(new Blob([xhr.response]));
|
||||
}, errorCallback);
|
||||
}
|
||||
};
|
||||
|
||||
exports.requestAllPaths = function(successCallback) {
|
||||
successCallback(pathsPrefix);
|
||||
};
|
||||
|
||||
/*** Helpers ***/
|
||||
|
||||
/**
|
||||
* Interface to wrap the native File interface.
|
||||
*
|
||||
* This interface is necessary for creating zero-length (empty) files,
|
||||
* something the Filesystem API allows you to do. Unfortunately, File's
|
||||
* constructor cannot be called directly, making it impossible to instantiate
|
||||
* an empty File in JS.
|
||||
*
|
||||
* @param {Object} opts Initial values.
|
||||
* @constructor
|
||||
*/
|
||||
function MyFile(opts) {
|
||||
var blob_ = new Blob();
|
||||
|
||||
this.size = opts.size || 0;
|
||||
this.name = opts.name || '';
|
||||
this.type = opts.type || '';
|
||||
this.lastModifiedDate = opts.lastModifiedDate || null;
|
||||
this.storagePath = opts.storagePath || '';
|
||||
|
||||
// Need some black magic to correct the object's size/name/type based on the
|
||||
// blob that is saved.
|
||||
Object.defineProperty(this, 'blob_', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return blob_;
|
||||
},
|
||||
set: function(val) {
|
||||
blob_ = val;
|
||||
this.size = blob_.size;
|
||||
this.name = blob_.name;
|
||||
this.type = blob_.type;
|
||||
this.lastModifiedDate = blob_.lastModifiedDate;
|
||||
}.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
MyFile.prototype.constructor = MyFile;
|
||||
|
||||
// When saving an entry, the fullPath should always lead with a slash and never
|
||||
// end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute
|
||||
// one. This method ensures path is legit!
|
||||
function resolveToFullPath_(cwdFullPath, path) {
|
||||
path = path || '';
|
||||
var fullPath = path;
|
||||
var prefix = '';
|
||||
|
||||
cwdFullPath = cwdFullPath || DIR_SEPARATOR;
|
||||
if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) {
|
||||
prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
|
||||
cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
|
||||
}
|
||||
|
||||
var relativePath = path[0] !== DIR_SEPARATOR;
|
||||
if (relativePath) {
|
||||
fullPath = cwdFullPath;
|
||||
if (cwdFullPath !== DIR_SEPARATOR) {
|
||||
fullPath += DIR_SEPARATOR + path;
|
||||
} else {
|
||||
fullPath += path;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove doubled separator substrings
|
||||
var re = new RegExp(DIR_SEPARATOR + DIR_SEPARATOR, 'g');
|
||||
fullPath = fullPath.replace(re, DIR_SEPARATOR);
|
||||
|
||||
// Adjust '..'s by removing parent directories when '..' flows in path.
|
||||
var parts = fullPath.split(DIR_SEPARATOR);
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
var part = parts[i];
|
||||
if (part === '..') {
|
||||
parts[i - 1] = '';
|
||||
parts[i] = '';
|
||||
}
|
||||
}
|
||||
fullPath = parts.filter(function(el) {
|
||||
return el;
|
||||
}).join(DIR_SEPARATOR);
|
||||
|
||||
// Add back in leading slash.
|
||||
if (fullPath[0] !== DIR_SEPARATOR) {
|
||||
fullPath = DIR_SEPARATOR + fullPath;
|
||||
}
|
||||
|
||||
// Replace './' by current dir. ('./one/./two' -> one/two)
|
||||
fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR);
|
||||
|
||||
// Replace '//' with '/'.
|
||||
fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR);
|
||||
|
||||
// Replace '/.' with '/'.
|
||||
fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR);
|
||||
|
||||
// Remove '/' if it appears on the end.
|
||||
if (fullPath[fullPath.length - 1] === DIR_SEPARATOR &&
|
||||
fullPath !== DIR_SEPARATOR) {
|
||||
fullPath = fullPath.substring(0, fullPath.length - 1);
|
||||
}
|
||||
|
||||
var storagePath = prefix + fullPath;
|
||||
storagePath = decodeURI(storagePath);
|
||||
fullPath = decodeURI(fullPath);
|
||||
|
||||
return {
|
||||
storagePath: storagePath,
|
||||
fullPath: fullPath,
|
||||
fileName: fullPath.split(DIR_SEPARATOR).pop(),
|
||||
fsName: prefix.split(DIR_SEPARATOR).pop()
|
||||
};
|
||||
}
|
||||
|
||||
function fileEntryFromIdbEntry(fileEntry) {
|
||||
// IDB won't save methods, so we need re-create the FileEntry.
|
||||
var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.filesystem);
|
||||
clonedFileEntry.file_ = fileEntry.file_;
|
||||
|
||||
return clonedFileEntry;
|
||||
}
|
||||
|
||||
function readAs(what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) {
|
||||
exports.getFile(function(fileEntry) {
|
||||
var fileReader = new FileReader(),
|
||||
blob = fileEntry.file_.blob_.slice(startPos, endPos);
|
||||
|
||||
fileReader.onload = function(e) {
|
||||
successCallback(e.target.result);
|
||||
};
|
||||
|
||||
fileReader.onerror = errorCallback;
|
||||
|
||||
switch (what) {
|
||||
case 'text':
|
||||
fileReader.readAsText(blob, encoding);
|
||||
break;
|
||||
case 'dataURL':
|
||||
fileReader.readAsDataURL(blob);
|
||||
break;
|
||||
case 'arrayBuffer':
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
break;
|
||||
case 'binaryString':
|
||||
fileReader.readAsBinaryString(blob);
|
||||
break;
|
||||
}
|
||||
|
||||
}, errorCallback, [fullPath, null]);
|
||||
}
|
||||
|
||||
/*** Core logic to handle IDB operations ***/
|
||||
|
||||
idb_.open = function(dbName, successCallback, errorCallback) {
|
||||
var self = this;
|
||||
|
||||
// TODO: FF 12.0a1 isn't liking a db name with : in it.
|
||||
var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version*/);
|
||||
|
||||
request.onerror = errorCallback || onError;
|
||||
|
||||
request.onupgradeneeded = function(e) {
|
||||
// First open was called or higher db version was used.
|
||||
|
||||
// console.log('onupgradeneeded: oldVersion:' + e.oldVersion,
|
||||
// 'newVersion:' + e.newVersion);
|
||||
|
||||
self.db = e.target.result;
|
||||
self.db.onerror = onError;
|
||||
|
||||
if (!self.db.objectStoreNames.contains(FILE_STORE_)) {
|
||||
self.db.createObjectStore(FILE_STORE_/*,{keyPath: 'id', autoIncrement: true}*/);
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = function(e) {
|
||||
self.db = e.target.result;
|
||||
self.db.onerror = onError;
|
||||
successCallback(e);
|
||||
};
|
||||
|
||||
request.onblocked = errorCallback || onError;
|
||||
};
|
||||
|
||||
idb_.close = function() {
|
||||
this.db.close();
|
||||
this.db = null;
|
||||
};
|
||||
|
||||
idb_.get = function(fullPath, successCallback, errorCallback) {
|
||||
if (!this.db) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = this.db.transaction([FILE_STORE_], 'readonly');
|
||||
|
||||
var request = tx.objectStore(FILE_STORE_).get(fullPath);
|
||||
|
||||
tx.onabort = errorCallback || onError;
|
||||
tx.oncomplete = function() {
|
||||
successCallback(request.result);
|
||||
};
|
||||
};
|
||||
|
||||
idb_.getAllEntries = function(fullPath, storagePath, successCallback, errorCallback) {
|
||||
if (!this.db) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var results = [];
|
||||
|
||||
if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) {
|
||||
storagePath = storagePath.substring(0, storagePath.length - 1);
|
||||
}
|
||||
|
||||
var range = IDBKeyRange.bound(storagePath + DIR_SEPARATOR + ' ',
|
||||
storagePath + DIR_SEPARATOR + String.fromCharCode(unicodeLastChar));
|
||||
|
||||
var tx = this.db.transaction([FILE_STORE_], 'readonly');
|
||||
tx.onabort = errorCallback || onError;
|
||||
tx.oncomplete = function() {
|
||||
results = results.filter(function(val) {
|
||||
var pathWithoutSlash = val.fullPath;
|
||||
|
||||
if (val.fullPath[val.fullPath.length - 1] === DIR_SEPARATOR) {
|
||||
pathWithoutSlash = pathWithoutSlash.substr(0, pathWithoutSlash.length - 1);
|
||||
}
|
||||
|
||||
var valPartsLen = pathWithoutSlash.split(DIR_SEPARATOR).length;
|
||||
var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
|
||||
|
||||
/* Input fullPath parameter equals '//' for root folder */
|
||||
/* Entries in root folder has valPartsLen equals 2 (see below) */
|
||||
if (fullPath[fullPath.length -1] === DIR_SEPARATOR && fullPath.trim().length === 2) {
|
||||
fullPathPartsLen = 1;
|
||||
} else if (fullPath[fullPath.length -1] === DIR_SEPARATOR) {
|
||||
fullPathPartsLen = fullPath.substr(0, fullPath.length - 1).split(DIR_SEPARATOR).length;
|
||||
} else {
|
||||
fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
|
||||
}
|
||||
|
||||
if (valPartsLen === fullPathPartsLen + 1) {
|
||||
// If this a subfolder and entry is a direct child, include it in
|
||||
// the results. Otherwise, it's not an entry of this folder.
|
||||
return val;
|
||||
} else return false;
|
||||
});
|
||||
|
||||
successCallback(results);
|
||||
};
|
||||
|
||||
var request = tx.objectStore(FILE_STORE_).openCursor(range);
|
||||
|
||||
request.onsuccess = function(e) {
|
||||
var cursor = e.target.result;
|
||||
if (cursor) {
|
||||
var val = cursor.value;
|
||||
|
||||
results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.filesystem));
|
||||
cursor['continue']();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
idb_['delete'] = function(fullPath, successCallback, errorCallback, isDirectory) {
|
||||
if (!idb_.db) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = this.db.transaction([FILE_STORE_], 'readwrite');
|
||||
tx.oncomplete = successCallback;
|
||||
tx.onabort = errorCallback || onError;
|
||||
tx.oncomplete = function() {
|
||||
if (isDirectory) {
|
||||
//We delete nested files and folders after deleting parent folder
|
||||
//We use ranges: https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange
|
||||
fullPath = fullPath + DIR_SEPARATOR;
|
||||
|
||||
//Range contains all entries in the form fullPath<symbol> where
|
||||
//symbol in the range from ' ' to symbol which has code `unicodeLastChar`
|
||||
var range = IDBKeyRange.bound(fullPath + ' ', fullPath + String.fromCharCode(unicodeLastChar));
|
||||
|
||||
var newTx = this.db.transaction([FILE_STORE_], 'readwrite');
|
||||
newTx.oncomplete = successCallback;
|
||||
newTx.onabort = errorCallback || onError;
|
||||
newTx.objectStore(FILE_STORE_)['delete'](range);
|
||||
} else {
|
||||
successCallback();
|
||||
}
|
||||
};
|
||||
tx.objectStore(FILE_STORE_)['delete'](fullPath);
|
||||
};
|
||||
|
||||
idb_.put = function(entry, storagePath, successCallback, errorCallback) {
|
||||
if (!this.db) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = this.db.transaction([FILE_STORE_], 'readwrite');
|
||||
tx.onabort = errorCallback || onError;
|
||||
tx.oncomplete = function() {
|
||||
// TODO: Error is thrown if we pass the request event back instead.
|
||||
successCallback(entry);
|
||||
};
|
||||
|
||||
tx.objectStore(FILE_STORE_).put(entry, storagePath);
|
||||
};
|
||||
|
||||
// Global error handler. Errors bubble from request, to transaction, to db.
|
||||
function onError(e) {
|
||||
switch (e.target.errorCode) {
|
||||
case 12:
|
||||
console.log('Error - Attempt to open db with a lower version than the ' +
|
||||
'current one.');
|
||||
break;
|
||||
default:
|
||||
console.log('errorCode: ' + e.target.errorCode);
|
||||
}
|
||||
|
||||
console.log(e, e.code, e.message);
|
||||
}
|
||||
|
||||
})(module.exports, window);
|
||||
|
||||
require("cordova/exec/proxy").add("File", module.exports);
|
||||
785
plugins/cordova-plugin-file/src/firefoxos/FileProxy.js
vendored
Normal file
785
plugins/cordova-plugin-file/src/firefoxos/FileProxy.js
vendored
Normal file
@@ -0,0 +1,785 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
var LocalFileSystem = require('./LocalFileSystem'),
|
||||
FileSystem = require('./FileSystem'),
|
||||
FileEntry = require('./FileEntry'),
|
||||
FileError = require('./FileError'),
|
||||
DirectoryEntry = require('./DirectoryEntry'),
|
||||
File = require('./File');
|
||||
|
||||
/*
|
||||
QUIRKS:
|
||||
Does not fail when removing non-empty directories
|
||||
Does not support metadata for directories
|
||||
Does not support requestAllFileSystems
|
||||
Does not support resolveLocalFileSystemURI
|
||||
Methods copyTo and moveTo do not support directories
|
||||
|
||||
Heavily based on https://github.com/ebidel/idb.filesystem.js
|
||||
*/
|
||||
|
||||
|
||||
(function(exports, global) {
|
||||
var indexedDB = global.indexedDB || global.mozIndexedDB;
|
||||
if (!indexedDB) {
|
||||
throw "Firefox OS File plugin: indexedDB not supported";
|
||||
}
|
||||
|
||||
var fs_ = null;
|
||||
|
||||
var idb_ = {};
|
||||
idb_.db = null;
|
||||
var FILE_STORE_ = 'entries';
|
||||
|
||||
var DIR_SEPARATOR = '/';
|
||||
var DIR_OPEN_BOUND = String.fromCharCode(DIR_SEPARATOR.charCodeAt(0) + 1);
|
||||
|
||||
var pathsPrefix = {
|
||||
// Read-only directory where the application is installed.
|
||||
applicationDirectory: location.origin + "/",
|
||||
// Where to put app-specific data files.
|
||||
dataDirectory: 'file:///persistent/',
|
||||
// Cached files that should survive app restarts.
|
||||
// Apps should not rely on the OS to delete files in here.
|
||||
cacheDirectory: 'file:///temporary/',
|
||||
};
|
||||
|
||||
/*** Exported functionality ***/
|
||||
|
||||
exports.requestFileSystem = function(successCallback, errorCallback, args) {
|
||||
var type = args[0];
|
||||
var size = args[1];
|
||||
|
||||
if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent';
|
||||
var storageName = (location.protocol + location.host).replace(/:/g, '_');
|
||||
|
||||
var root = new DirectoryEntry('', DIR_SEPARATOR);
|
||||
fs_ = new FileSystem(name, root);
|
||||
|
||||
idb_.open(storageName, function() {
|
||||
successCallback(fs_);
|
||||
}, errorCallback);
|
||||
};
|
||||
|
||||
require('./fileSystems').getFs = function(name, callback) {
|
||||
callback(new FileSystem(name, fs_.root));
|
||||
};
|
||||
|
||||
// list a directory's contents (files and folders).
|
||||
exports.readEntries = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
|
||||
if (!successCallback) {
|
||||
throw Error('Expected successCallback argument.');
|
||||
}
|
||||
|
||||
var path = resolveToFullPath_(fullPath);
|
||||
|
||||
idb_.getAllEntries(path.fullPath, path.storagePath, function(entries) {
|
||||
successCallback(entries);
|
||||
}, errorCallback);
|
||||
};
|
||||
|
||||
exports.getFile = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
var path = args[1];
|
||||
var options = args[2] || {};
|
||||
|
||||
// Create an absolute path if we were handed a relative one.
|
||||
path = resolveToFullPath_(fullPath, path);
|
||||
|
||||
idb_.get(path.storagePath, function(fileEntry) {
|
||||
if (options.create === true && options.exclusive === true && fileEntry) {
|
||||
// If create and exclusive are both true, and the path already exists,
|
||||
// getFile must fail.
|
||||
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.PATH_EXISTS_ERR);
|
||||
}
|
||||
} else if (options.create === true && !fileEntry) {
|
||||
// If create is true, the path doesn't exist, and no other error occurs,
|
||||
// getFile must create it as a zero-length file and return a corresponding
|
||||
// FileEntry.
|
||||
var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
|
||||
|
||||
newFileEntry.file_ = new MyFile({
|
||||
size: 0,
|
||||
name: newFileEntry.name,
|
||||
lastModifiedDate: new Date(),
|
||||
storagePath: path.storagePath
|
||||
});
|
||||
|
||||
idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
|
||||
} else if (options.create === true && fileEntry) {
|
||||
if (fileEntry.isFile) {
|
||||
// Overwrite file, delete then create new.
|
||||
idb_['delete'](path.storagePath, function() {
|
||||
var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
|
||||
|
||||
newFileEntry.file_ = new MyFile({
|
||||
size: 0,
|
||||
name: newFileEntry.name,
|
||||
lastModifiedDate: new Date(),
|
||||
storagePath: path.storagePath
|
||||
});
|
||||
|
||||
idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
|
||||
}, errorCallback);
|
||||
} else {
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
}
|
||||
}
|
||||
} else if ((!options.create || options.create === false) && !fileEntry) {
|
||||
// If create is not true and the path doesn't exist, getFile must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.NOT_FOUND_ERR);
|
||||
}
|
||||
} else if ((!options.create || options.create === false) && fileEntry &&
|
||||
fileEntry.isDirectory) {
|
||||
// If create is not true and the path exists, but is a directory, getFile
|
||||
// must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, if no other error occurs, getFile must return a FileEntry
|
||||
// corresponding to path.
|
||||
|
||||
successCallback(fileEntryFromIdbEntry(fileEntry));
|
||||
}
|
||||
}, errorCallback);
|
||||
};
|
||||
|
||||
exports.getFileMetadata = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
|
||||
exports.getFile(function(fileEntry) {
|
||||
successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate,
|
||||
fileEntry.file_.size));
|
||||
}, errorCallback, [fullPath, null]);
|
||||
};
|
||||
|
||||
exports.getMetadata = function(successCallback, errorCallback, args) {
|
||||
exports.getFile(function (fileEntry) {
|
||||
successCallback(
|
||||
{
|
||||
modificationTime: fileEntry.file_.lastModifiedDate,
|
||||
size: fileEntry.file_.lastModifiedDate
|
||||
});
|
||||
}, errorCallback, args);
|
||||
};
|
||||
|
||||
exports.setMetadata = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
var metadataObject = args[1];
|
||||
|
||||
exports.getFile(function (fileEntry) {
|
||||
fileEntry.file_.lastModifiedDate = metadataObject.modificationTime;
|
||||
}, errorCallback, [fullPath, null]);
|
||||
};
|
||||
|
||||
exports.write = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
data = args[1],
|
||||
position = args[2],
|
||||
isBinary = args[3];
|
||||
|
||||
if (!data) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
exports.getFile(function(fileEntry) {
|
||||
var blob_ = fileEntry.file_.blob_;
|
||||
|
||||
if (!blob_) {
|
||||
blob_ = new Blob([data], {type: data.type});
|
||||
} else {
|
||||
// Calc the head and tail fragments
|
||||
var head = blob_.slice(0, position);
|
||||
var tail = blob_.slice(position + data.byteLength);
|
||||
|
||||
// Calc the padding
|
||||
var padding = position - head.size;
|
||||
if (padding < 0) {
|
||||
padding = 0;
|
||||
}
|
||||
|
||||
// Do the "write". In fact, a full overwrite of the Blob.
|
||||
blob_ = new Blob([head, new Uint8Array(padding), data, tail],
|
||||
{type: data.type});
|
||||
}
|
||||
|
||||
// Set the blob we're writing on this file entry so we can recall it later.
|
||||
fileEntry.file_.blob_ = blob_;
|
||||
fileEntry.file_.lastModifiedDate = data.lastModifiedDate || null;
|
||||
fileEntry.file_.size = blob_.size;
|
||||
fileEntry.file_.name = blob_.name;
|
||||
fileEntry.file_.type = blob_.type;
|
||||
|
||||
idb_.put(fileEntry, fileEntry.file_.storagePath, function() {
|
||||
successCallback(data.byteLength);
|
||||
}, errorCallback);
|
||||
}, errorCallback, [fileName, null]);
|
||||
};
|
||||
|
||||
exports.readAsText = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
enc = args[1],
|
||||
startPos = args[2],
|
||||
endPos = args[3];
|
||||
|
||||
readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
exports.readAsDataURL = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
startPos = args[1],
|
||||
endPos = args[2];
|
||||
|
||||
readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
exports.readAsBinaryString = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
startPos = args[1],
|
||||
endPos = args[2];
|
||||
|
||||
readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
exports.readAsArrayBuffer = function(successCallback, errorCallback, args) {
|
||||
var fileName = args[0],
|
||||
startPos = args[1],
|
||||
endPos = args[2];
|
||||
|
||||
readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
exports.removeRecursively = exports.remove = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
|
||||
// TODO: This doesn't protect against directories that have content in it.
|
||||
// Should throw an error instead if the dirEntry is not empty.
|
||||
idb_['delete'](fullPath, function() {
|
||||
successCallback();
|
||||
}, errorCallback);
|
||||
};
|
||||
|
||||
exports.getDirectory = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
var path = args[1];
|
||||
var options = args[2];
|
||||
|
||||
// Create an absolute path if we were handed a relative one.
|
||||
path = resolveToFullPath_(fullPath, path);
|
||||
|
||||
idb_.get(path.storagePath, function(folderEntry) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (options.create === true && options.exclusive === true && folderEntry) {
|
||||
// If create and exclusive are both true, and the path already exists,
|
||||
// getDirectory must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
}
|
||||
} else if (options.create === true && !folderEntry) {
|
||||
// If create is true, the path doesn't exist, and no other error occurs,
|
||||
// getDirectory must create it as a zero-length file and return a corresponding
|
||||
// MyDirectoryEntry.
|
||||
var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
|
||||
|
||||
idb_.put(dirEntry, path.storagePath, successCallback, errorCallback);
|
||||
} else if (options.create === true && folderEntry) {
|
||||
|
||||
if (folderEntry.isDirectory) {
|
||||
// IDB won't save methods, so we need re-create the MyDirectoryEntry.
|
||||
successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.fileSystem));
|
||||
} else {
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
}
|
||||
}
|
||||
} else if ((!options.create || options.create === false) && !folderEntry) {
|
||||
// Handle root special. It should always exist.
|
||||
if (path.fullPath === DIR_SEPARATOR) {
|
||||
successCallback(fs_.root);
|
||||
return;
|
||||
}
|
||||
|
||||
// If create is not true and the path doesn't exist, getDirectory must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.NOT_FOUND_ERR);
|
||||
}
|
||||
} else if ((!options.create || options.create === false) && folderEntry &&
|
||||
folderEntry.isFile) {
|
||||
// If create is not true and the path exists, but is a file, getDirectory
|
||||
// must fail.
|
||||
if (errorCallback) {
|
||||
errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, if no other error occurs, getDirectory must return a
|
||||
// MyDirectoryEntry corresponding to path.
|
||||
|
||||
// IDB won't' save methods, so we need re-create MyDirectoryEntry.
|
||||
successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.fileSystem));
|
||||
}
|
||||
}, errorCallback);
|
||||
};
|
||||
|
||||
exports.getParent = function(successCallback, errorCallback, args) {
|
||||
var fullPath = args[0];
|
||||
|
||||
if (fullPath === DIR_SEPARATOR) {
|
||||
successCallback(fs_.root);
|
||||
return;
|
||||
}
|
||||
|
||||
var pathArr = fullPath.split(DIR_SEPARATOR);
|
||||
pathArr.pop();
|
||||
var namesa = pathArr.pop();
|
||||
var path = pathArr.join(DIR_SEPARATOR);
|
||||
|
||||
exports.getDirectory(successCallback, errorCallback, [path, namesa, {create: false}]);
|
||||
};
|
||||
|
||||
exports.copyTo = function(successCallback, errorCallback, args) {
|
||||
var srcPath = args[0];
|
||||
var parentFullPath = args[1];
|
||||
var name = args[2];
|
||||
|
||||
// Read src file
|
||||
exports.getFile(function(srcFileEntry) {
|
||||
|
||||
// Create dest file
|
||||
exports.getFile(function(dstFileEntry) {
|
||||
|
||||
exports.write(function() {
|
||||
successCallback(dstFileEntry);
|
||||
}, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]);
|
||||
|
||||
}, errorCallback, [parentFullPath, name, {create: true}]);
|
||||
|
||||
}, errorCallback, [srcPath, null]);
|
||||
};
|
||||
|
||||
exports.moveTo = function(successCallback, errorCallback, args) {
|
||||
var srcPath = args[0];
|
||||
var parentFullPath = args[1];
|
||||
var name = args[2];
|
||||
|
||||
exports.copyTo(function (fileEntry) {
|
||||
|
||||
exports.remove(function () {
|
||||
successCallback(fileEntry);
|
||||
}, errorCallback, [srcPath]);
|
||||
|
||||
}, errorCallback, args);
|
||||
};
|
||||
|
||||
exports.resolveLocalFileSystemURI = function(successCallback, errorCallback, args) {
|
||||
var path = args[0];
|
||||
|
||||
// Ignore parameters
|
||||
if (path.indexOf('?') !== -1) {
|
||||
path = String(path).split("?")[0];
|
||||
}
|
||||
|
||||
// support for encodeURI
|
||||
if (/\%5/g.test(path)) {
|
||||
path = decodeURI(path);
|
||||
}
|
||||
|
||||
if (path.indexOf(pathsPrefix.dataDirectory) === 0) {
|
||||
path = path.substring(pathsPrefix.dataDirectory.length - 1);
|
||||
|
||||
exports.requestFileSystem(function(fs) {
|
||||
fs.root.getFile(path, {create: false}, successCallback, function() {
|
||||
fs.root.getDirectory(path, {create: false}, successCallback, errorCallback);
|
||||
});
|
||||
}, errorCallback, [LocalFileSystem.PERSISTENT]);
|
||||
} else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) {
|
||||
path = path.substring(pathsPrefix.cacheDirectory.length - 1);
|
||||
|
||||
exports.requestFileSystem(function(fs) {
|
||||
fs.root.getFile(path, {create: false}, successCallback, function() {
|
||||
fs.root.getDirectory(path, {create: false}, successCallback, errorCallback);
|
||||
});
|
||||
}, errorCallback, [LocalFileSystem.TEMPORARY]);
|
||||
} else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) {
|
||||
path = path.substring(pathsPrefix.applicationDirectory.length);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", path, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.status === 200 && xhr.readyState === 4) {
|
||||
exports.requestFileSystem(function(fs) {
|
||||
fs.name = location.hostname;
|
||||
fs.root.getFile(path, {create: true}, writeFile, errorCallback);
|
||||
}, errorCallback, [LocalFileSystem.PERSISTENT]);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
errorCallback && errorCallback(FileError.NOT_READABLE_ERR);
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
} else {
|
||||
errorCallback && errorCallback(FileError.NOT_FOUND_ERR);
|
||||
}
|
||||
|
||||
function writeFile(entry) {
|
||||
entry.createWriter(function (fileWriter) {
|
||||
fileWriter.onwriteend = function (evt) {
|
||||
if (!evt.target.error) {
|
||||
entry.filesystemName = location.hostname;
|
||||
successCallback(entry);
|
||||
}
|
||||
};
|
||||
fileWriter.onerror = function () {
|
||||
errorCallback && errorCallback(FileError.NOT_READABLE_ERR);
|
||||
};
|
||||
fileWriter.write(new Blob([xhr.response]));
|
||||
}, errorCallback);
|
||||
}
|
||||
};
|
||||
|
||||
exports.requestAllPaths = function(successCallback) {
|
||||
successCallback(pathsPrefix);
|
||||
};
|
||||
|
||||
/*** Helpers ***/
|
||||
|
||||
/**
|
||||
* Interface to wrap the native File interface.
|
||||
*
|
||||
* This interface is necessary for creating zero-length (empty) files,
|
||||
* something the Filesystem API allows you to do. Unfortunately, File's
|
||||
* constructor cannot be called directly, making it impossible to instantiate
|
||||
* an empty File in JS.
|
||||
*
|
||||
* @param {Object} opts Initial values.
|
||||
* @constructor
|
||||
*/
|
||||
function MyFile(opts) {
|
||||
var blob_ = new Blob();
|
||||
|
||||
this.size = opts.size || 0;
|
||||
this.name = opts.name || '';
|
||||
this.type = opts.type || '';
|
||||
this.lastModifiedDate = opts.lastModifiedDate || null;
|
||||
this.storagePath = opts.storagePath || '';
|
||||
|
||||
// Need some black magic to correct the object's size/name/type based on the
|
||||
// blob that is saved.
|
||||
Object.defineProperty(this, 'blob_', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return blob_;
|
||||
},
|
||||
set: function(val) {
|
||||
blob_ = val;
|
||||
this.size = blob_.size;
|
||||
this.name = blob_.name;
|
||||
this.type = blob_.type;
|
||||
this.lastModifiedDate = blob_.lastModifiedDate;
|
||||
}.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
MyFile.prototype.constructor = MyFile;
|
||||
|
||||
// When saving an entry, the fullPath should always lead with a slash and never
|
||||
// end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute
|
||||
// one. This method ensures path is legit!
|
||||
function resolveToFullPath_(cwdFullPath, path) {
|
||||
path = path || '';
|
||||
var fullPath = path;
|
||||
var prefix = '';
|
||||
|
||||
cwdFullPath = cwdFullPath || DIR_SEPARATOR;
|
||||
if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) {
|
||||
prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
|
||||
cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
|
||||
}
|
||||
|
||||
var relativePath = path[0] !== DIR_SEPARATOR;
|
||||
if (relativePath) {
|
||||
fullPath = cwdFullPath;
|
||||
if (cwdFullPath != DIR_SEPARATOR) {
|
||||
fullPath += DIR_SEPARATOR + path;
|
||||
} else {
|
||||
fullPath += path;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust '..'s by removing parent directories when '..' flows in path.
|
||||
var parts = fullPath.split(DIR_SEPARATOR);
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
var part = parts[i];
|
||||
if (part == '..') {
|
||||
parts[i - 1] = '';
|
||||
parts[i] = '';
|
||||
}
|
||||
}
|
||||
fullPath = parts.filter(function(el) {
|
||||
return el;
|
||||
}).join(DIR_SEPARATOR);
|
||||
|
||||
// Add back in leading slash.
|
||||
if (fullPath[0] !== DIR_SEPARATOR) {
|
||||
fullPath = DIR_SEPARATOR + fullPath;
|
||||
}
|
||||
|
||||
// Replace './' by current dir. ('./one/./two' -> one/two)
|
||||
fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR);
|
||||
|
||||
// Replace '//' with '/'.
|
||||
fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR);
|
||||
|
||||
// Replace '/.' with '/'.
|
||||
fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR);
|
||||
|
||||
// Remove '/' if it appears on the end.
|
||||
if (fullPath[fullPath.length - 1] == DIR_SEPARATOR &&
|
||||
fullPath != DIR_SEPARATOR) {
|
||||
fullPath = fullPath.substring(0, fullPath.length - 1);
|
||||
}
|
||||
|
||||
return {
|
||||
storagePath: prefix + fullPath,
|
||||
fullPath: fullPath,
|
||||
fileName: fullPath.split(DIR_SEPARATOR).pop(),
|
||||
fsName: prefix.split(DIR_SEPARATOR).pop()
|
||||
};
|
||||
}
|
||||
|
||||
function fileEntryFromIdbEntry(fileEntry) {
|
||||
// IDB won't save methods, so we need re-create the FileEntry.
|
||||
var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.fileSystem);
|
||||
clonedFileEntry.file_ = fileEntry.file_;
|
||||
|
||||
return clonedFileEntry;
|
||||
}
|
||||
|
||||
function readAs(what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) {
|
||||
exports.getFile(function(fileEntry) {
|
||||
var fileReader = new FileReader(),
|
||||
blob = fileEntry.file_.blob_.slice(startPos, endPos);
|
||||
|
||||
fileReader.onload = function(e) {
|
||||
successCallback(e.target.result);
|
||||
};
|
||||
|
||||
fileReader.onerror = errorCallback;
|
||||
|
||||
switch (what) {
|
||||
case 'text':
|
||||
fileReader.readAsText(blob, encoding);
|
||||
break;
|
||||
case 'dataURL':
|
||||
fileReader.readAsDataURL(blob);
|
||||
break;
|
||||
case 'arrayBuffer':
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
break;
|
||||
case 'binaryString':
|
||||
fileReader.readAsBinaryString(blob);
|
||||
break;
|
||||
}
|
||||
|
||||
}, errorCallback, [fullPath, null]);
|
||||
}
|
||||
|
||||
/*** Core logic to handle IDB operations ***/
|
||||
|
||||
idb_.open = function(dbName, successCallback, errorCallback) {
|
||||
var self = this;
|
||||
|
||||
// TODO: FF 12.0a1 isn't liking a db name with : in it.
|
||||
var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version*/);
|
||||
|
||||
request.onerror = errorCallback || onError;
|
||||
|
||||
request.onupgradeneeded = function(e) {
|
||||
// First open was called or higher db version was used.
|
||||
|
||||
// console.log('onupgradeneeded: oldVersion:' + e.oldVersion,
|
||||
// 'newVersion:' + e.newVersion);
|
||||
|
||||
self.db = e.target.result;
|
||||
self.db.onerror = onError;
|
||||
|
||||
if (!self.db.objectStoreNames.contains(FILE_STORE_)) {
|
||||
var store = self.db.createObjectStore(FILE_STORE_/*,{keyPath: 'id', autoIncrement: true}*/);
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = function(e) {
|
||||
self.db = e.target.result;
|
||||
self.db.onerror = onError;
|
||||
successCallback(e);
|
||||
};
|
||||
|
||||
request.onblocked = errorCallback || onError;
|
||||
};
|
||||
|
||||
idb_.close = function() {
|
||||
this.db.close();
|
||||
this.db = null;
|
||||
};
|
||||
|
||||
idb_.get = function(fullPath, successCallback, errorCallback) {
|
||||
if (!this.db) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = this.db.transaction([FILE_STORE_], 'readonly');
|
||||
|
||||
//var request = tx.objectStore(FILE_STORE_).get(fullPath);
|
||||
var range = IDBKeyRange.bound(fullPath, fullPath + DIR_OPEN_BOUND,
|
||||
false, true);
|
||||
var request = tx.objectStore(FILE_STORE_).get(range);
|
||||
|
||||
tx.onabort = errorCallback || onError;
|
||||
tx.oncomplete = function(e) {
|
||||
successCallback(request.result);
|
||||
};
|
||||
};
|
||||
|
||||
idb_.getAllEntries = function(fullPath, storagePath, successCallback, errorCallback) {
|
||||
if (!this.db) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var results = [];
|
||||
|
||||
if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) {
|
||||
storagePath = storagePath.substring(0, storagePath.length - 1);
|
||||
}
|
||||
|
||||
range = IDBKeyRange.bound(
|
||||
storagePath + DIR_SEPARATOR, storagePath + DIR_OPEN_BOUND, false, true);
|
||||
|
||||
var tx = this.db.transaction([FILE_STORE_], 'readonly');
|
||||
tx.onabort = errorCallback || onError;
|
||||
tx.oncomplete = function(e) {
|
||||
results = results.filter(function(val) {
|
||||
var valPartsLen = val.fullPath.split(DIR_SEPARATOR).length;
|
||||
var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
|
||||
|
||||
if (fullPath === DIR_SEPARATOR && valPartsLen < fullPathPartsLen + 1) {
|
||||
// Hack to filter out entries in the root folder. This is inefficient
|
||||
// because reading the entires of fs.root (e.g. '/') returns ALL
|
||||
// results in the database, then filters out the entries not in '/'.
|
||||
return val;
|
||||
} else if (fullPath !== DIR_SEPARATOR &&
|
||||
valPartsLen === fullPathPartsLen + 1) {
|
||||
// If this a subfolder and entry is a direct child, include it in
|
||||
// the results. Otherwise, it's not an entry of this folder.
|
||||
return val;
|
||||
}
|
||||
});
|
||||
|
||||
successCallback(results);
|
||||
};
|
||||
|
||||
var request = tx.objectStore(FILE_STORE_).openCursor(range);
|
||||
|
||||
request.onsuccess = function(e) {
|
||||
var cursor = e.target.result;
|
||||
if (cursor) {
|
||||
var val = cursor.value;
|
||||
|
||||
results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.fileSystem));
|
||||
cursor['continue']();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
idb_['delete'] = function(fullPath, successCallback, errorCallback) {
|
||||
if (!this.db) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = this.db.transaction([FILE_STORE_], 'readwrite');
|
||||
tx.oncomplete = successCallback;
|
||||
tx.onabort = errorCallback || onError;
|
||||
|
||||
//var request = tx.objectStore(FILE_STORE_).delete(fullPath);
|
||||
var range = IDBKeyRange.bound(
|
||||
fullPath, fullPath + DIR_OPEN_BOUND, false, true);
|
||||
tx.objectStore(FILE_STORE_)['delete'](range);
|
||||
};
|
||||
|
||||
idb_.put = function(entry, storagePath, successCallback, errorCallback) {
|
||||
if (!this.db) {
|
||||
errorCallback && errorCallback(FileError.INVALID_MODIFICATION_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = this.db.transaction([FILE_STORE_], 'readwrite');
|
||||
tx.onabort = errorCallback || onError;
|
||||
tx.oncomplete = function(e) {
|
||||
// TODO: Error is thrown if we pass the request event back instead.
|
||||
successCallback(entry);
|
||||
};
|
||||
|
||||
tx.objectStore(FILE_STORE_).put(entry, storagePath);
|
||||
};
|
||||
|
||||
// Global error handler. Errors bubble from request, to transaction, to db.
|
||||
function onError(e) {
|
||||
switch (e.target.errorCode) {
|
||||
case 12:
|
||||
console.log('Error - Attempt to open db with a lower version than the ' +
|
||||
'current one.');
|
||||
break;
|
||||
default:
|
||||
console.log('errorCode: ' + e.target.errorCode);
|
||||
}
|
||||
|
||||
console.log(e, e.code, e.message);
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
// TODO: Is there a place for this?
|
||||
// global.addEventListener('beforeunload', function(e) {
|
||||
// idb_.db && idb_.db.close();
|
||||
// }, false);
|
||||
|
||||
})(module.exports, window);
|
||||
|
||||
require("cordova/exec/proxy").add("File", module.exports);
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVFile.h"
|
||||
|
||||
extern NSString* const kCDVAssetsLibraryPrefix;
|
||||
extern NSString* const kCDVAssetsLibraryScheme;
|
||||
|
||||
@interface CDVAssetLibraryFilesystem : NSObject<CDVFileSystem> {
|
||||
}
|
||||
|
||||
- (id) initWithName:(NSString *)name;
|
||||
|
||||
@end
|
||||
253
plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m
Normal file
253
plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m
Normal file
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVFile.h"
|
||||
#import "CDVAssetLibraryFilesystem.h"
|
||||
#import <Cordova/CDV.h>
|
||||
#import <AssetsLibrary/ALAsset.h>
|
||||
#import <AssetsLibrary/ALAssetRepresentation.h>
|
||||
#import <AssetsLibrary/ALAssetsLibrary.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
|
||||
NSString* const kCDVAssetsLibraryScheme = @"assets-library";
|
||||
|
||||
@implementation CDVAssetLibraryFilesystem
|
||||
@synthesize name=_name, urlTransformer;
|
||||
|
||||
|
||||
/*
|
||||
The CDVAssetLibraryFilesystem works with resources which are identified
|
||||
by iOS as
|
||||
asset-library://<path>
|
||||
and represents them internally as URLs of the form
|
||||
cdvfile://localhost/assets-library/<path>
|
||||
*/
|
||||
|
||||
- (NSURL *)assetLibraryURLForLocalURL:(CDVFilesystemURL *)url
|
||||
{
|
||||
if ([url.url.scheme isEqualToString:kCDVFilesystemURLPrefix]) {
|
||||
NSString *path = [[url.url absoluteString] substringFromIndex:[@"cdvfile://localhost/assets-library" length]];
|
||||
return [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@", path]];
|
||||
}
|
||||
return url.url;
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
|
||||
{
|
||||
NSDictionary* entry = [self makeEntryForLocalURL:url];
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
|
||||
}
|
||||
|
||||
- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
|
||||
return [self makeEntryForPath:url.fullPath isDirectory:NO];
|
||||
}
|
||||
|
||||
- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
|
||||
{
|
||||
NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
|
||||
NSString* lastPart = [fullPath lastPathComponent];
|
||||
if (isDir && ![fullPath hasSuffix:@"/"]) {
|
||||
fullPath = [fullPath stringByAppendingString:@"/"];
|
||||
}
|
||||
[dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
|
||||
[dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
|
||||
[dirEntry setObject:fullPath forKey:@"fullPath"];
|
||||
[dirEntry setObject:lastPart forKey:@"name"];
|
||||
[dirEntry setObject:self.name forKey: @"filesystemName"];
|
||||
|
||||
NSURL* nativeURL = [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@",fullPath]];
|
||||
if (self.urlTransformer) {
|
||||
nativeURL = self.urlTransformer(nativeURL);
|
||||
}
|
||||
dirEntry[@"nativeURL"] = [nativeURL absoluteString];
|
||||
|
||||
return dirEntry;
|
||||
}
|
||||
|
||||
/* helper function to get the mimeType from the file extension
|
||||
* IN:
|
||||
* NSString* fullPath - filename (may include path)
|
||||
* OUT:
|
||||
* NSString* the mime type as type/subtype. nil if not able to determine
|
||||
*/
|
||||
+ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
|
||||
{
|
||||
NSString* mimeType = nil;
|
||||
|
||||
if (fullPath) {
|
||||
CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
|
||||
if (typeId) {
|
||||
mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
|
||||
if (!mimeType) {
|
||||
// special case for m4a
|
||||
if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
|
||||
mimeType = @"audio/mp4";
|
||||
} else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
|
||||
mimeType = @"audio/wav";
|
||||
} else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
|
||||
mimeType = @"text/css";
|
||||
}
|
||||
}
|
||||
CFRelease(typeId);
|
||||
}
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
- (id)initWithName:(NSString *)name
|
||||
{
|
||||
if (self) {
|
||||
_name = name;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
|
||||
{
|
||||
// return unsupported result for assets-library URLs
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."];
|
||||
}
|
||||
|
||||
- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
|
||||
{
|
||||
// we don't (yet?) support getting the parent of an asset
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR];
|
||||
}
|
||||
|
||||
- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
|
||||
{
|
||||
// setMetadata doesn't make sense for asset library files
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
|
||||
{
|
||||
// return error for assets-library URLs
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
|
||||
{
|
||||
// return error for assets-library URLs
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."];
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
|
||||
{
|
||||
// return unsupported result for assets-library URLs
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."];
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
|
||||
{
|
||||
// assets-library files can't be truncated
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
|
||||
{
|
||||
// text can't be written into assets-library files
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
|
||||
}
|
||||
|
||||
- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
|
||||
{
|
||||
// Copying to an assets library file is not doable, since we can't write it.
|
||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
|
||||
callback(result);
|
||||
}
|
||||
|
||||
- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
|
||||
{
|
||||
NSString *path = nil;
|
||||
if ([[url.url scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
|
||||
path = [url.url path];
|
||||
} else {
|
||||
path = url.fullPath;
|
||||
}
|
||||
if ([path hasSuffix:@"/"]) {
|
||||
path = [path substringToIndex:([path length]-1)];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
|
||||
{
|
||||
ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
|
||||
if (asset) {
|
||||
// We have the asset! Get the data and send it off.
|
||||
ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
|
||||
NSUInteger size = (end > start) ? (end - start) : [assetRepresentation size];
|
||||
Byte* buffer = (Byte*)malloc(size);
|
||||
NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:start length:size error:nil];
|
||||
NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES];
|
||||
NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType);
|
||||
|
||||
callback(data, MIMEType, NO_ERROR);
|
||||
} else {
|
||||
callback(nil, nil, NOT_FOUND_ERR);
|
||||
}
|
||||
};
|
||||
|
||||
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
|
||||
// Retrieving the asset failed for some reason. Send the appropriate error.
|
||||
NSLog(@"Error: %@", error);
|
||||
callback(nil, nil, SECURITY_ERR);
|
||||
};
|
||||
|
||||
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
|
||||
[assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
|
||||
}
|
||||
|
||||
- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
|
||||
{
|
||||
// In this case, we need to use an asynchronous method to retrieve the file.
|
||||
// Because of this, we can't just assign to `result` and send it at the end of the method.
|
||||
// Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
|
||||
ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
|
||||
if (asset) {
|
||||
// We have the asset! Populate the dictionary and send it off.
|
||||
NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
|
||||
ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
|
||||
[fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[assetRepresentation size]] forKey:@"size"];
|
||||
[fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
|
||||
NSString* filename = [assetRepresentation filename];
|
||||
[fileInfo setObject:filename forKey:@"name"];
|
||||
[fileInfo setObject:[CDVAssetLibraryFilesystem getMimeTypeFromPath:filename] forKey:@"type"];
|
||||
NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate];
|
||||
NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000];
|
||||
[fileInfo setObject:msDate forKey:@"lastModifiedDate"];
|
||||
|
||||
callback([CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]);
|
||||
} else {
|
||||
// We couldn't find the asset. Send the appropriate error.
|
||||
callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]);
|
||||
}
|
||||
};
|
||||
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
|
||||
// Retrieving the asset failed for some reason. Send the appropriate error.
|
||||
callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]);
|
||||
};
|
||||
|
||||
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
|
||||
[assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
|
||||
return;
|
||||
}
|
||||
@end
|
||||
157
plugins/cordova-plugin-file/src/ios/CDVFile.h
Normal file
157
plugins/cordova-plugin-file/src/ios/CDVFile.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
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>
|
||||
|
||||
NSString* const kCDVAssetsLibraryPrefix;
|
||||
NSString* const kCDVFilesystemURLPrefix;
|
||||
|
||||
enum CDVFileError {
|
||||
NO_ERROR = 0,
|
||||
NOT_FOUND_ERR = 1,
|
||||
SECURITY_ERR = 2,
|
||||
ABORT_ERR = 3,
|
||||
NOT_READABLE_ERR = 4,
|
||||
ENCODING_ERR = 5,
|
||||
NO_MODIFICATION_ALLOWED_ERR = 6,
|
||||
INVALID_STATE_ERR = 7,
|
||||
SYNTAX_ERR = 8,
|
||||
INVALID_MODIFICATION_ERR = 9,
|
||||
QUOTA_EXCEEDED_ERR = 10,
|
||||
TYPE_MISMATCH_ERR = 11,
|
||||
PATH_EXISTS_ERR = 12
|
||||
};
|
||||
typedef int CDVFileError;
|
||||
|
||||
@interface CDVFilesystemURL : NSObject {
|
||||
NSURL *_url;
|
||||
NSString *_fileSystemName;
|
||||
NSString *_fullPath;
|
||||
}
|
||||
|
||||
- (id) initWithString:(NSString*)strURL;
|
||||
- (id) initWithURL:(NSURL*)URL;
|
||||
+ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL;
|
||||
+ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL;
|
||||
|
||||
- (NSString *)absoluteURL;
|
||||
|
||||
@property (atomic) NSURL *url;
|
||||
@property (atomic) NSString *fileSystemName;
|
||||
@property (atomic) NSString *fullPath;
|
||||
|
||||
@end
|
||||
|
||||
@interface CDVFilesystemURLProtocol : NSURLProtocol
|
||||
@end
|
||||
|
||||
@protocol CDVFileSystem
|
||||
- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url;
|
||||
- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options;
|
||||
- (CDVPluginResult *)getParentForURL:(CDVFilesystemURL *)localURI;
|
||||
- (CDVPluginResult *)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options;
|
||||
- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI;
|
||||
- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI;
|
||||
- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI;
|
||||
- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos;
|
||||
- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend;
|
||||
- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback;
|
||||
- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback;
|
||||
- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback;
|
||||
|
||||
- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url;
|
||||
- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir;
|
||||
|
||||
@property (nonatomic,strong) NSString *name;
|
||||
@property (nonatomic, copy) NSURL*(^urlTransformer)(NSURL*);
|
||||
|
||||
@optional
|
||||
- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURI;
|
||||
- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
|
||||
@interface CDVFile : CDVPlugin {
|
||||
NSString* rootDocsPath;
|
||||
NSString* appDocsPath;
|
||||
NSString* appLibraryPath;
|
||||
NSString* appTempPath;
|
||||
|
||||
NSMutableArray* fileSystems_;
|
||||
BOOL userHasAllowed;
|
||||
}
|
||||
|
||||
- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath;
|
||||
- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir;
|
||||
- (NSDictionary *)makeEntryForURL:(NSURL *)URL;
|
||||
- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath;
|
||||
|
||||
- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL;
|
||||
|
||||
/* Native Registration API */
|
||||
- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs;
|
||||
- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName;
|
||||
|
||||
/* Exec API */
|
||||
- (void)requestFileSystem:(CDVInvokedUrlCommand*)command;
|
||||
- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command;
|
||||
- (void)getDirectory:(CDVInvokedUrlCommand*)command;
|
||||
- (void)getFile:(CDVInvokedUrlCommand*)command;
|
||||
- (void)getParent:(CDVInvokedUrlCommand*)command;
|
||||
- (void)removeRecursively:(CDVInvokedUrlCommand*)command;
|
||||
- (void)remove:(CDVInvokedUrlCommand*)command;
|
||||
- (void)copyTo:(CDVInvokedUrlCommand*)command;
|
||||
- (void)moveTo:(CDVInvokedUrlCommand*)command;
|
||||
- (void)getFileMetadata:(CDVInvokedUrlCommand*)command;
|
||||
- (void)readEntries:(CDVInvokedUrlCommand*)command;
|
||||
- (void)readAsText:(CDVInvokedUrlCommand*)command;
|
||||
- (void)readAsDataURL:(CDVInvokedUrlCommand*)command;
|
||||
- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command;
|
||||
- (void)write:(CDVInvokedUrlCommand*)command;
|
||||
- (void)testFileExists:(CDVInvokedUrlCommand*)command;
|
||||
- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command;
|
||||
- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command;
|
||||
- (void)truncate:(CDVInvokedUrlCommand*)command;
|
||||
- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy;
|
||||
|
||||
/* Compatibilty with older File API */
|
||||
- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
|
||||
- (NSDictionary *)getDirectoryEntry:(NSString *)target isDirectory:(BOOL)bDirRequest;
|
||||
|
||||
/* Conversion between filesystem paths and URLs */
|
||||
- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)URL;
|
||||
|
||||
/* Internal methods for testing */
|
||||
- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command;
|
||||
|
||||
@property (nonatomic, strong) NSString* rootDocsPath;
|
||||
@property (nonatomic, strong) NSString* appDocsPath;
|
||||
@property (nonatomic, strong) NSString* appLibraryPath;
|
||||
@property (nonatomic, strong) NSString* appTempPath;
|
||||
@property (nonatomic, strong) NSString* persistentPath;
|
||||
@property (nonatomic, strong) NSString* temporaryPath;
|
||||
@property (nonatomic, strong) NSMutableArray* fileSystems;
|
||||
|
||||
@property BOOL userHasAllowed;
|
||||
|
||||
@end
|
||||
|
||||
#define kW3FileTemporary @"temporary"
|
||||
#define kW3FilePersistent @"persistent"
|
||||
1106
plugins/cordova-plugin-file/src/ios/CDVFile.m
Normal file
1106
plugins/cordova-plugin-file/src/ios/CDVFile.m
Normal file
File diff suppressed because it is too large
Load Diff
32
plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h
Normal file
32
plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVFile.h"
|
||||
|
||||
@interface CDVLocalFilesystem : NSObject<CDVFileSystem> {
|
||||
NSString *_name;
|
||||
NSString *_fsRoot;
|
||||
}
|
||||
|
||||
- (id) initWithName:(NSString *)name root:(NSString *)fsRoot;
|
||||
+ (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
|
||||
|
||||
@property (nonatomic,strong) NSString *fsRoot;
|
||||
|
||||
@end
|
||||
734
plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m
Normal file
734
plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m
Normal file
@@ -0,0 +1,734 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVFile.h"
|
||||
#import "CDVLocalFilesystem.h"
|
||||
#import <Cordova/CDV.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import <sys/xattr.h>
|
||||
|
||||
@implementation CDVLocalFilesystem
|
||||
@synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
|
||||
|
||||
- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
|
||||
{
|
||||
if (self) {
|
||||
_name = name;
|
||||
_fsRoot = fsRoot;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
* IN
|
||||
* NSString localURI
|
||||
* OUT
|
||||
* CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
|
||||
* URI represents a non-existent path, or is unrecognized or otherwise malformed.
|
||||
*/
|
||||
- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
|
||||
{
|
||||
CDVPluginResult* result = nil;
|
||||
NSDictionary* entry = [self makeEntryForLocalURL:url];
|
||||
if (entry) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
|
||||
} else {
|
||||
// return NOT_FOUND_ERR
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
|
||||
NSString *path = [self filesystemPathForURL:url];
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
BOOL isDir = NO;
|
||||
// see if exists and is file or dir
|
||||
BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
|
||||
if (bExists) {
|
||||
return [self makeEntryForPath:url.fullPath isDirectory:isDir];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
|
||||
{
|
||||
NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
|
||||
NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
|
||||
if (isDir && ![fullPath hasSuffix:@"/"]) {
|
||||
fullPath = [fullPath stringByAppendingString:@"/"];
|
||||
}
|
||||
[dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
|
||||
[dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
|
||||
[dirEntry setObject:fullPath forKey:@"fullPath"];
|
||||
[dirEntry setObject:lastPart forKey:@"name"];
|
||||
[dirEntry setObject:self.name forKey: @"filesystemName"];
|
||||
|
||||
NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]];
|
||||
if (self.urlTransformer) {
|
||||
nativeURL = self.urlTransformer(nativeURL);
|
||||
}
|
||||
|
||||
dirEntry[@"nativeURL"] = [nativeURL absoluteString];
|
||||
|
||||
return dirEntry;
|
||||
}
|
||||
|
||||
- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
|
||||
{
|
||||
NSRange questionMark = [fullPath rangeOfString:@"?"];
|
||||
if (questionMark.location != NSNotFound) {
|
||||
return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
|
||||
}
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
|
||||
{
|
||||
NSString *path = nil;
|
||||
NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
|
||||
path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
|
||||
if ([path length] > 1 && [path hasSuffix:@"/"]) {
|
||||
path = [path substringToIndex:([path length]-1)];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
/*
|
||||
* IN
|
||||
* NSString localURI
|
||||
* OUT
|
||||
* NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
|
||||
* The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
|
||||
* or if the URL is malformed.
|
||||
* The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
|
||||
*/
|
||||
- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
|
||||
{
|
||||
return [self filesystemPathForFullPath:url.fullPath];
|
||||
}
|
||||
|
||||
- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
|
||||
{
|
||||
if (fullPath) {
|
||||
NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
if ([fullPath hasPrefix:@"/"]) {
|
||||
return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
|
||||
}
|
||||
return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
|
||||
{
|
||||
return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)normalizePath:(NSString *)rawPath
|
||||
{
|
||||
// If this is an absolute path, the first path component will be '/'. Skip it if that's the case
|
||||
BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
|
||||
if (isAbsolutePath) {
|
||||
rawPath = [rawPath substringFromIndex:1];
|
||||
}
|
||||
NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
|
||||
for (int index = 0; index < [components count]; ++index) {
|
||||
if ([[components objectAtIndex:index] isEqualToString:@".."]) {
|
||||
[components removeObjectAtIndex:index];
|
||||
if (index > 0) {
|
||||
[components removeObjectAtIndex:index-1];
|
||||
--index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isAbsolutePath) {
|
||||
return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
|
||||
} else {
|
||||
return [components componentsJoinedByString:@"/"];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
|
||||
{
|
||||
BOOL bNumber = NO;
|
||||
NSObject* value = dict[key];
|
||||
if (value) {
|
||||
bNumber = [value isKindOfClass:[NSNumber class]];
|
||||
}
|
||||
return bNumber;
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
|
||||
{
|
||||
CDVPluginResult* result = nil;
|
||||
BOOL bDirRequest = NO;
|
||||
BOOL create = NO;
|
||||
BOOL exclusive = NO;
|
||||
int errorCode = 0; // !!! risky - no error code currently defined for 0
|
||||
|
||||
if ([self valueForKeyIsNumber:options key:@"create"]) {
|
||||
create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
|
||||
}
|
||||
if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
|
||||
exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
|
||||
}
|
||||
if ([self valueForKeyIsNumber:options key:@"getDir"]) {
|
||||
// this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method
|
||||
bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue];
|
||||
}
|
||||
// see if the requested path has invalid characters - should we be checking for more than just ":"?
|
||||
if ([requestedPath rangeOfString:@":"].location != NSNotFound) {
|
||||
errorCode = ENCODING_ERR;
|
||||
} else {
|
||||
// Build new fullPath for the requested resource.
|
||||
// We concatenate the two paths together, and then scan the resulting string to remove
|
||||
// parent ("..") references. Any parent references at the beginning of the string are
|
||||
// silently removed.
|
||||
NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
|
||||
combinedPath = [self normalizePath:combinedPath];
|
||||
CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
|
||||
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
BOOL bIsDir;
|
||||
BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
|
||||
if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
|
||||
// path exists and is not of requested type - return TYPE_MISMATCH_ERR
|
||||
errorCode = TYPE_MISMATCH_ERR;
|
||||
} else if (!bExists && (create == NO)) {
|
||||
// path does not exist and create is false - return NOT_FOUND_ERR
|
||||
errorCode = NOT_FOUND_ERR;
|
||||
} else if (bExists && (create == YES) && (exclusive == YES)) {
|
||||
// file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR
|
||||
errorCode = PATH_EXISTS_ERR;
|
||||
} else {
|
||||
// if bExists and create == YES - just return data
|
||||
// if bExists and create == NO - just return data
|
||||
// if !bExists and create == YES - create and return data
|
||||
BOOL bSuccess = YES;
|
||||
NSError __autoreleasing* pError = nil;
|
||||
if (!bExists && (create == YES)) {
|
||||
if (bDirRequest) {
|
||||
// create the dir
|
||||
bSuccess = [fileMgr createDirectoryAtPath:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
|
||||
} else {
|
||||
// create the empty file
|
||||
bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] contents:nil attributes:nil];
|
||||
}
|
||||
}
|
||||
if (!bSuccess) {
|
||||
errorCode = ABORT_ERR;
|
||||
if (pError) {
|
||||
NSLog(@"error creating directory: %@", [pError localizedDescription]);
|
||||
}
|
||||
} else {
|
||||
// NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]);
|
||||
// file existed or was created
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
|
||||
}
|
||||
} // are all possible conditions met?
|
||||
}
|
||||
|
||||
if (errorCode > 0) {
|
||||
// create error callback
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
|
||||
{
|
||||
CDVPluginResult* result = nil;
|
||||
CDVFilesystemURL *newURI = nil;
|
||||
if ([localURI.fullPath isEqualToString:@""]) {
|
||||
// return self
|
||||
newURI = localURI;
|
||||
} else {
|
||||
newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
|
||||
}
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
BOOL bIsDir;
|
||||
BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
|
||||
if (bExists) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
|
||||
} else {
|
||||
// invalid path or file does not exist
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
|
||||
{
|
||||
BOOL ok = NO;
|
||||
|
||||
NSString* filePath = [self filesystemPathForURL:localURI];
|
||||
// we only care about this iCloud key for now.
|
||||
// set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute)
|
||||
NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup";
|
||||
id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey];
|
||||
|
||||
if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) {
|
||||
if (IsAtLeastiOSVersion(@"5.1")) {
|
||||
NSURL* url = [NSURL fileURLWithPath:filePath];
|
||||
NSError* __autoreleasing error = nil;
|
||||
|
||||
ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error];
|
||||
} else { // below 5.1 (deprecated - only really supported in 5.01)
|
||||
u_int8_t value = [iCloudBackupExtendedAttributeValue intValue];
|
||||
if (value == 0) { // remove the attribute (allow backup, the default)
|
||||
ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0);
|
||||
} else { // set the attribute (skip backup)
|
||||
ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
||||
} else {
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
|
||||
}
|
||||
}
|
||||
|
||||
/* remove the file or directory (recursively)
|
||||
* IN:
|
||||
* NSString* fullPath - the full path to the file or directory to be removed
|
||||
* NSString* callbackId
|
||||
* called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling
|
||||
*/
|
||||
|
||||
- (CDVPluginResult*)doRemove:(NSString*)fullPath
|
||||
{
|
||||
CDVPluginResult* result = nil;
|
||||
BOOL bSuccess = NO;
|
||||
NSError* __autoreleasing pError = nil;
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
|
||||
@try {
|
||||
bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError];
|
||||
if (bSuccess) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
||||
} else {
|
||||
// see if we can give a useful error
|
||||
CDVFileError errorCode = ABORT_ERR;
|
||||
NSLog(@"error removing filesystem entry at %@: %@", fullPath, [pError localizedDescription]);
|
||||
if ([pError code] == NSFileNoSuchFileError) {
|
||||
errorCode = NOT_FOUND_ERR;
|
||||
} else if ([pError code] == NSFileWriteNoPermissionError) {
|
||||
errorCode = NO_MODIFICATION_ALLOWED_ERR;
|
||||
}
|
||||
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
|
||||
}
|
||||
} @catch(NSException* e) { // NSInvalidArgumentException if path is . or ..
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
|
||||
{
|
||||
NSString *fileSystemPath = [self filesystemPathForURL:localURI];
|
||||
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
BOOL bIsDir = NO;
|
||||
BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
|
||||
if (!bExists) {
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
|
||||
}
|
||||
if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
|
||||
// dir is not empty
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
|
||||
}
|
||||
return [self doRemove:fileSystemPath];
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
|
||||
{
|
||||
NSString *fileSystemPath = [self filesystemPathForURL:localURI];
|
||||
return [self doRemove:fileSystemPath];
|
||||
}
|
||||
|
||||
/*
|
||||
* IN
|
||||
* NSString localURI
|
||||
* OUT
|
||||
* NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
|
||||
* The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
|
||||
* or if the URL is malformed.
|
||||
* The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
|
||||
*/
|
||||
- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
|
||||
{
|
||||
if ([fsPath hasPrefix:self.fsRoot]) {
|
||||
return [fsPath substringFromIndex:[self.fsRoot length]];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
|
||||
{
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
NSError* __autoreleasing error = nil;
|
||||
NSString *fileSystemPath = [self filesystemPathForURL:localURI];
|
||||
|
||||
NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath error:&error];
|
||||
|
||||
if (contents) {
|
||||
NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1];
|
||||
if ([contents count] > 0) {
|
||||
// create an Entry (as JSON) for each file/dir
|
||||
for (NSString* name in contents) {
|
||||
// see if is dir or file
|
||||
NSString* entryPath = [fileSystemPath stringByAppendingPathComponent:name];
|
||||
BOOL bIsDir = NO;
|
||||
[fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
|
||||
NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
|
||||
[entries addObject:entryDict];
|
||||
}
|
||||
}
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
|
||||
} else {
|
||||
// assume not found but could check error for more specific error conditions
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
|
||||
}
|
||||
}
|
||||
|
||||
- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos
|
||||
{
|
||||
unsigned long long newPos = 0UL;
|
||||
|
||||
NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath];
|
||||
|
||||
if (file) {
|
||||
[file truncateFileAtOffset:(unsigned long long)pos];
|
||||
newPos = [file offsetInFile];
|
||||
[file synchronizeFile];
|
||||
[file closeFile];
|
||||
}
|
||||
return newPos;
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
|
||||
{
|
||||
unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
|
||||
}
|
||||
|
||||
- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
|
||||
{
|
||||
NSString *filePath = [self filesystemPathForURL:localURL];
|
||||
|
||||
CDVPluginResult* result = nil;
|
||||
CDVFileError errCode = INVALID_MODIFICATION_ERR;
|
||||
int bytesWritten = 0;
|
||||
|
||||
if (filePath) {
|
||||
NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
|
||||
if (fileStream) {
|
||||
NSUInteger len = [encData length];
|
||||
if (len == 0) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
|
||||
} else {
|
||||
[fileStream open];
|
||||
|
||||
bytesWritten = (int)[fileStream write:[encData bytes] maxLength:len];
|
||||
|
||||
[fileStream close];
|
||||
if (bytesWritten > 0) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten];
|
||||
// } else {
|
||||
// can probably get more detailed error info via [fileStream streamError]
|
||||
// errCode already set to INVALID_MODIFICATION_ERR;
|
||||
// bytesWritten = 0; // may be set to -1 on error
|
||||
}
|
||||
}
|
||||
} // else fileStream not created return INVALID_MODIFICATION_ERR
|
||||
} else {
|
||||
// invalid filePath
|
||||
errCode = NOT_FOUND_ERR;
|
||||
}
|
||||
if (!result) {
|
||||
// was an error
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check to see if the user attempted to copy an entry into its parent without changing its name,
|
||||
* or attempted to copy a directory into a directory that it contains directly or indirectly.
|
||||
*
|
||||
* IN:
|
||||
* NSString* srcDir
|
||||
* NSString* destinationDir
|
||||
* OUT:
|
||||
* YES copy/ move is allows
|
||||
* NO move is onto itself
|
||||
*/
|
||||
- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest
|
||||
{
|
||||
// This weird test is to determine if we are copying or moving a directory into itself.
|
||||
// Copy /Documents/myDir to /Documents/myDir-backup is okay but
|
||||
// Copy /Documents/myDir to /Documents/myDir/backup not okay
|
||||
BOOL copyOK = YES;
|
||||
NSRange range = [dest rangeOfString:src];
|
||||
|
||||
if (range.location != NSNotFound) {
|
||||
NSRange testRange = {range.length - 1, ([dest length] - range.length)};
|
||||
NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange];
|
||||
if (resultRange.location != NSNotFound) {
|
||||
copyOK = NO;
|
||||
}
|
||||
}
|
||||
return copyOK;
|
||||
}
|
||||
|
||||
- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
|
||||
{
|
||||
NSFileManager *fileMgr = [[NSFileManager alloc] init];
|
||||
NSString *destRootPath = [self filesystemPathForURL:destURL];
|
||||
BOOL bDestIsDir = NO;
|
||||
BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
|
||||
|
||||
NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
|
||||
NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
|
||||
|
||||
BOOL bNewIsDir = NO;
|
||||
BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
|
||||
|
||||
CDVPluginResult *result = nil;
|
||||
int errCode = 0;
|
||||
|
||||
if (!bDestExists) {
|
||||
// the destination root does not exist
|
||||
errCode = NOT_FOUND_ERR;
|
||||
}
|
||||
|
||||
else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
|
||||
/* Same FS, we can shortcut with NSFileManager operations */
|
||||
NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
|
||||
|
||||
BOOL bSrcIsDir = NO;
|
||||
BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
|
||||
|
||||
if (!bSrcExists) {
|
||||
// the source does not exist
|
||||
errCode = NOT_FOUND_ERR;
|
||||
} else if ([newFileSystemPath isEqualToString:srcFullPath]) {
|
||||
// source and destination can not be the same
|
||||
errCode = INVALID_MODIFICATION_ERR;
|
||||
} else if (bSrcIsDir && (bNewExists && !bNewIsDir)) {
|
||||
// can't copy/move dir to file
|
||||
errCode = INVALID_MODIFICATION_ERR;
|
||||
} else { // no errors yet
|
||||
NSError* __autoreleasing error = nil;
|
||||
BOOL bSuccess = NO;
|
||||
if (bCopy) {
|
||||
if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
|
||||
// can't copy dir into self
|
||||
errCode = INVALID_MODIFICATION_ERR;
|
||||
} else if (bNewExists) {
|
||||
// the full destination should NOT already exist if a copy
|
||||
errCode = PATH_EXISTS_ERR;
|
||||
} else {
|
||||
bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
|
||||
}
|
||||
} else { // move
|
||||
// iOS requires that destination must not exist before calling moveTo
|
||||
// is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents
|
||||
//
|
||||
if (!bSrcIsDir && (bNewExists && bNewIsDir)) {
|
||||
// can't move a file to directory
|
||||
errCode = INVALID_MODIFICATION_ERR;
|
||||
} else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
|
||||
// can't move a dir into itself
|
||||
errCode = INVALID_MODIFICATION_ERR;
|
||||
} else if (bNewExists) {
|
||||
if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
|
||||
// can't move dir to a dir that is not empty
|
||||
errCode = INVALID_MODIFICATION_ERR;
|
||||
newFileSystemPath = nil; // so we won't try to move
|
||||
} else {
|
||||
// remove destination so can perform the moveItemAtPath
|
||||
bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
|
||||
if (!bSuccess) {
|
||||
errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
|
||||
newFileSystemPath = nil;
|
||||
}
|
||||
}
|
||||
} else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
|
||||
// can't move a directory inside itself or to any child at any depth;
|
||||
errCode = INVALID_MODIFICATION_ERR;
|
||||
newFileSystemPath = nil;
|
||||
}
|
||||
|
||||
if (newFileSystemPath != nil) {
|
||||
bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
|
||||
}
|
||||
}
|
||||
if (bSuccess) {
|
||||
// should verify it is there and of the correct type???
|
||||
NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
|
||||
} else {
|
||||
if (error) {
|
||||
if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) {
|
||||
errCode = NOT_READABLE_ERR;
|
||||
} else if ([error code] == NSFileWriteOutOfSpaceError) {
|
||||
errCode = QUOTA_EXCEEDED_ERR;
|
||||
} else if ([error code] == NSFileWriteNoPermissionError) {
|
||||
errCode = NO_MODIFICATION_ALLOWED_ERR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Need to copy the hard way
|
||||
[srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
|
||||
CDVPluginResult* result = nil;
|
||||
if (data != nil) {
|
||||
BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
|
||||
if (bSuccess) {
|
||||
// should verify it is there and of the correct type???
|
||||
NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
|
||||
} else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
|
||||
}
|
||||
} else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
|
||||
}
|
||||
callback(result);
|
||||
}];
|
||||
return; // Async IO; return without callback.
|
||||
}
|
||||
if (result == nil) {
|
||||
if (!errCode) {
|
||||
errCode = INVALID_MODIFICATION_ERR; // Catch-all default
|
||||
}
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
|
||||
}
|
||||
callback(result);
|
||||
}
|
||||
|
||||
/* helper function to get the mimeType from the file extension
|
||||
* IN:
|
||||
* NSString* fullPath - filename (may include path)
|
||||
* OUT:
|
||||
* NSString* the mime type as type/subtype. nil if not able to determine
|
||||
*/
|
||||
+ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
|
||||
{
|
||||
NSString* mimeType = nil;
|
||||
|
||||
if (fullPath) {
|
||||
CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
|
||||
if (typeId) {
|
||||
mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
|
||||
if (!mimeType) {
|
||||
// special case for m4a
|
||||
if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
|
||||
mimeType = @"audio/mp4";
|
||||
} else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
|
||||
mimeType = @"audio/wav";
|
||||
} else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
|
||||
mimeType = @"text/css";
|
||||
}
|
||||
}
|
||||
CFRelease(typeId);
|
||||
}
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
|
||||
{
|
||||
NSString *path = [self filesystemPathForURL:localURL];
|
||||
|
||||
NSString* mimeType = [CDVLocalFilesystem getMimeTypeFromPath:path];
|
||||
if (mimeType == nil) {
|
||||
mimeType = @"*/*";
|
||||
}
|
||||
NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
|
||||
if (start > 0) {
|
||||
[file seekToFileOffset:start];
|
||||
}
|
||||
|
||||
NSData* readData;
|
||||
if (end < 0) {
|
||||
readData = [file readDataToEndOfFile];
|
||||
} else {
|
||||
readData = [file readDataOfLength:(end - start)];
|
||||
}
|
||||
[file closeFile];
|
||||
|
||||
callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR);
|
||||
}
|
||||
|
||||
- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
|
||||
{
|
||||
NSString *path = [self filesystemPathForURL:localURL];
|
||||
CDVPluginResult *result;
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
|
||||
NSError* __autoreleasing error = nil;
|
||||
NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
|
||||
|
||||
if (fileAttrs) {
|
||||
|
||||
// create dictionary of file info
|
||||
NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
|
||||
|
||||
[fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
|
||||
[fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping
|
||||
[fileInfo setObject:[path lastPathComponent] forKey:@"name"];
|
||||
|
||||
// Ensure that directories (and other non-regular files) report size of 0
|
||||
unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
|
||||
[fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
|
||||
|
||||
NSDate* modDate = [fileAttrs fileModificationDate];
|
||||
if (modDate) {
|
||||
[fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
|
||||
}
|
||||
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
|
||||
|
||||
} else {
|
||||
// didn't get fileAttribs
|
||||
CDVFileError errorCode = ABORT_ERR;
|
||||
NSLog(@"error getting metadata: %@", [error localizedDescription]);
|
||||
if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
|
||||
errorCode = NOT_FOUND_ERR;
|
||||
}
|
||||
// log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
|
||||
}
|
||||
|
||||
callback(result);
|
||||
}
|
||||
|
||||
@end
|
||||
912
plugins/cordova-plugin-file/src/ubuntu/file.cpp
Normal file
912
plugins/cordova-plugin-file/src/ubuntu/file.cpp
Normal file
@@ -0,0 +1,912 @@
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "file.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
namespace {
|
||||
class FileError {
|
||||
public:
|
||||
static const QString kEncodingErr;
|
||||
static const QString kTypeMismatchErr;
|
||||
static const QString kNotFoundErr;
|
||||
static const QString kSecurityErr;
|
||||
static const QString kAbortErr;
|
||||
static const QString kNotReadableErr;
|
||||
static const QString kNoModificationAllowedErr;
|
||||
static const QString kInvalidStateErr;
|
||||
static const QString kSyntaxErr;
|
||||
static const QString kInvalidModificationErr;
|
||||
static const QString kQuotaExceededErr;
|
||||
static const QString kPathExistsErr;
|
||||
};
|
||||
|
||||
bool checkFileName(const QString &name) {
|
||||
if (name.contains(":")){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const QString FileError::kEncodingErr("FileError.ENCODING_ERR");
|
||||
const QString FileError::kTypeMismatchErr("FileError.TYPE_MISMATCH_ERR");
|
||||
const QString FileError::kNotFoundErr("FileError.NOT_FOUND_ERR");
|
||||
const QString FileError::kSecurityErr("FileError.SECURITY_ERR");
|
||||
const QString FileError::kAbortErr("FileError.ABORT_ERR");
|
||||
const QString FileError::kNotReadableErr("FileError.NOT_READABLE_ERR");
|
||||
const QString FileError::kNoModificationAllowedErr("FileError.NO_MODIFICATION_ALLOWED_ERR");
|
||||
const QString FileError::kInvalidStateErr("FileError.INVALID_STATE_ERR");
|
||||
const QString FileError::kSyntaxErr("FileError.SYNTAX_ERR");
|
||||
const QString FileError::kInvalidModificationErr("FileError.INVALID_MODIFICATION_ERR");
|
||||
const QString FileError::kQuotaExceededErr("FileError.QUOTA_EXCEEDED_ERR");
|
||||
const QString FileError::kPathExistsErr("FileError.PATH_EXISTS_ERR");
|
||||
|
||||
File::File(Cordova *cordova) :
|
||||
CPlugin(cordova),
|
||||
_persistentDir(QString("%1/.local/share/%2/persistent").arg(QDir::homePath()).arg(QCoreApplication::applicationName())) {
|
||||
QDir::root().mkpath(_persistentDir.absolutePath());
|
||||
}
|
||||
|
||||
QVariantMap File::file2map(const QFileInfo &fileInfo) {
|
||||
QVariantMap res;
|
||||
|
||||
res.insert("name", fileInfo.fileName());
|
||||
QPair<QString, QString> r = GetRelativePath(fileInfo);
|
||||
res.insert("fullPath", QString("/") + r.second);
|
||||
res.insert("filesystemName", r.first);
|
||||
|
||||
res.insert("nativeURL", QString("file://localhost") + fileInfo.absoluteFilePath());
|
||||
res.insert("isDirectory", (int)fileInfo.isDir());
|
||||
res.insert("isFile", (int)fileInfo.isFile());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
QVariantMap File::dir2map(const QDir &dir) {
|
||||
return file2map(QFileInfo(dir.absolutePath()));
|
||||
}
|
||||
|
||||
QPair<QString, QString> File::GetRelativePath(const QFileInfo &fileInfo) {
|
||||
QString fullPath = fileInfo.isDir() ? QDir::cleanPath(fileInfo.absoluteFilePath()) : fileInfo.absoluteFilePath();
|
||||
|
||||
QString relativePath1 = _persistentDir.relativeFilePath(fullPath);
|
||||
QString relativePath2 = QDir::temp().relativeFilePath(fullPath);
|
||||
|
||||
if (!(relativePath1[0] != '.' || relativePath2[0] != '.')) {
|
||||
if (relativePath1.size() > relativePath2.size()) {
|
||||
return QPair<QString, QString>("temporary", relativePath2);
|
||||
} else {
|
||||
return QPair<QString, QString>("persistent", relativePath1);
|
||||
}
|
||||
}
|
||||
|
||||
if (relativePath1[0] != '.')
|
||||
return QPair<QString, QString>("persistent", relativePath1);
|
||||
return QPair<QString, QString>("temporary", relativePath2);
|
||||
}
|
||||
|
||||
void File::requestFileSystem(int scId, int ecId, unsigned short type, unsigned long long size) {
|
||||
QDir dir;
|
||||
|
||||
if (size >= 1000485760){
|
||||
this->callback(ecId, FileError::kQuotaExceededErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == 0)
|
||||
dir = QDir::temp();
|
||||
else
|
||||
dir = _persistentDir;
|
||||
|
||||
if (type > 1) {
|
||||
this->callback(ecId, FileError::kSyntaxErr);
|
||||
return;
|
||||
} else {
|
||||
QVariantMap res;
|
||||
res.insert("root", dir2map(dir));
|
||||
if (type == 0)
|
||||
res.insert("name", "temporary");
|
||||
else
|
||||
res.insert("name", "persistent");
|
||||
|
||||
this->cb(scId, res);
|
||||
}
|
||||
}
|
||||
|
||||
QPair<bool, QFileInfo> File::resolveURI(int ecId, const QString &uri) {
|
||||
QPair<bool, QFileInfo> result;
|
||||
|
||||
result.first = false;
|
||||
|
||||
QUrl url = QUrl::fromUserInput(uri);
|
||||
|
||||
if (url.scheme() == "file" && url.isValid()) {
|
||||
result.first = true;
|
||||
result.second = QFileInfo(url.path());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (url.scheme() != "cdvfile") {
|
||||
if (ecId)
|
||||
this->callback(ecId, FileError::kTypeMismatchErr);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString path = url.path().replace("//", "/");
|
||||
//NOTE: colon is not safe in url, it is not a valid path in Win and Mac, simple disable it here.
|
||||
if (path.contains(":") || !url.isValid()){
|
||||
if (ecId)
|
||||
this->callback(ecId, FileError::kEncodingErr);
|
||||
return result;
|
||||
}
|
||||
if (!path.startsWith("/persistent/") && !path.startsWith("/temporary/")) {
|
||||
if (ecId)
|
||||
this->callback(ecId, FileError::kEncodingErr);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.first = true;
|
||||
if (path.startsWith("/persistent/")) {
|
||||
QString relativePath = path.mid(QString("/persistent/").size());
|
||||
result.second = QFileInfo(_persistentDir.filePath(relativePath));
|
||||
} else {
|
||||
QString relativePath = path.mid(QString("/temporary/").size());
|
||||
result.second = QFileInfo(QDir::temp().filePath(relativePath));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QPair<bool, QFileInfo> File::resolveURI(const QString &uri) {
|
||||
return resolveURI(0, uri);
|
||||
}
|
||||
|
||||
|
||||
void File::_getLocalFilesystemPath(int scId, int ecId, const QString& uri) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
this->cb(scId, f1.second.absoluteFilePath());
|
||||
}
|
||||
|
||||
void File::resolveLocalFileSystemURI(int scId, int ecId, const QString &uri) {
|
||||
if (uri[0] == '/' || uri[0] == '.') {
|
||||
this->callback(ecId, FileError::kEncodingErr);
|
||||
return;
|
||||
}
|
||||
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QFileInfo fileInfo = f1.second;
|
||||
if (!fileInfo.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
this->cb(scId, file2map(fileInfo));
|
||||
}
|
||||
|
||||
void File::getFile(int scId, int ecId, const QString &parentPath, const QString &rpath, const QVariantMap &options) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, parentPath + "/" + rpath);
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
bool create = options.value("create").toBool();
|
||||
bool exclusive = options.value("exclusive").toBool();
|
||||
QFile file(f1.second.absoluteFilePath());
|
||||
|
||||
// if create is false and the path represents a directory, return error
|
||||
QFileInfo fileInfo = f1.second;
|
||||
if ((!create) && fileInfo.isDir()) {
|
||||
this->callback(ecId, FileError::kTypeMismatchErr);
|
||||
return;
|
||||
}
|
||||
|
||||
// if file does exist, and create is true and exclusive is true, return error
|
||||
if (file.exists()) {
|
||||
if (create && exclusive) {
|
||||
this->callback(ecId, FileError::kPathExistsErr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if file does not exist and create is false, return error
|
||||
if (!create) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
file.open(QIODevice::WriteOnly);
|
||||
file.close();
|
||||
|
||||
// Check if creation was successfull
|
||||
if (!file.exists()) {
|
||||
this->callback(ecId, FileError::kNoModificationAllowedErr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->cb(scId, file2map(QFileInfo(file)));
|
||||
}
|
||||
|
||||
void File::getDirectory(int scId, int ecId, const QString &parentPath, const QString &rpath, const QVariantMap &options) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, parentPath + "/" + rpath);
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
bool create = options.value("create").toBool();
|
||||
bool exclusive = options.value("exclusive").toBool();
|
||||
QDir dir(f1.second.absoluteFilePath());
|
||||
|
||||
QFileInfo &fileInfo = f1.second;
|
||||
if ((!create) && fileInfo.isFile()) {
|
||||
this->callback(ecId, FileError::kTypeMismatchErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dir.exists()) {
|
||||
if (create && exclusive) {
|
||||
this->callback(ecId, FileError::kPathExistsErr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!create) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
QString folderName = dir.dirName();
|
||||
dir.cdUp();
|
||||
dir.mkdir(folderName);
|
||||
dir.cd(folderName);
|
||||
|
||||
if (!dir.exists()) {
|
||||
this->callback(ecId, FileError::kNoModificationAllowedErr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->cb(scId, dir2map(dir));
|
||||
}
|
||||
|
||||
void File::removeRecursively(int scId, int ecId, const QString &uri) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QDir dir(f1.second.absoluteFilePath());
|
||||
if (File::rmDir(dir))
|
||||
this->cb(scId);
|
||||
else
|
||||
this->callback(ecId, FileError::kNoModificationAllowedErr);
|
||||
}
|
||||
|
||||
void File::write(int scId, int ecId, const QString &uri, const QString &_data, unsigned long long position, bool binary) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QFile file(f1.second.absoluteFilePath());
|
||||
|
||||
file.open(QIODevice::WriteOnly);
|
||||
file.close();
|
||||
|
||||
if (!file.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo fileInfo(file);
|
||||
if (!file.open(QIODevice::ReadWrite)) {
|
||||
this->callback(ecId, FileError::kNoModificationAllowedErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!binary) {
|
||||
QTextStream textStream(&file);
|
||||
textStream.setCodec("UTF-8");
|
||||
textStream.setAutoDetectUnicode(true);
|
||||
|
||||
if (!textStream.seek(position)) {
|
||||
file.close();
|
||||
fileInfo.refresh();
|
||||
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
textStream << _data;
|
||||
textStream.flush();
|
||||
} else {
|
||||
QByteArray data(_data.toUtf8());
|
||||
if (!file.seek(position)) {
|
||||
file.close();
|
||||
fileInfo.refresh();
|
||||
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
file.write(data.data(), data.length());
|
||||
}
|
||||
|
||||
file.flush();
|
||||
file.close();
|
||||
fileInfo.refresh();
|
||||
|
||||
this->cb(scId, fileInfo.size() - position);
|
||||
}
|
||||
|
||||
void File::truncate(int scId, int ecId, const QString &uri, unsigned long long size) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QFile file(f1.second.absoluteFilePath());
|
||||
|
||||
if (!file.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.resize(size)) {
|
||||
this->callback(ecId, FileError::kNoModificationAllowedErr);
|
||||
return;
|
||||
}
|
||||
|
||||
this->cb(scId, size);
|
||||
}
|
||||
|
||||
void File::getParent(int scId, int ecId, const QString &uri) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
QDir dir(f1.second.absoluteFilePath());
|
||||
|
||||
//can't cdup more than app's root
|
||||
// Try to change into upper directory
|
||||
if (dir != _persistentDir && dir != QDir::temp()){
|
||||
if (!dir.cdUp()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
this->cb(scId, dir2map(dir));
|
||||
}
|
||||
|
||||
void File::remove(int scId, int ecId, const QString &uri) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QFileInfo &fileInfo = f1.second;
|
||||
//TODO: fix
|
||||
if (!fileInfo.exists() || (fileInfo.absoluteFilePath() == _persistentDir.absolutePath()) || (QDir::temp() == fileInfo.absoluteFilePath())) {
|
||||
this->callback(ecId, FileError::kNoModificationAllowedErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileInfo.isDir()) {
|
||||
QDir dir(fileInfo.absoluteFilePath());
|
||||
if (dir.rmdir(dir.absolutePath())) {
|
||||
this->cb(scId);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
QFile file(fileInfo.absoluteFilePath());
|
||||
if (file.remove()) {
|
||||
this->cb(scId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
}
|
||||
|
||||
void File::getFileMetadata(int scId, int ecId, const QString &uri) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
QFileInfo &fileInfo = f1.second;
|
||||
|
||||
if (!fileInfo.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
} else {
|
||||
QMimeType mime = _db.mimeTypeForFile(fileInfo.fileName());
|
||||
|
||||
QString args = QString("{name: %1, fullPath: %2, type: %3, lastModifiedDate: new Date(%4), size: %5}")
|
||||
.arg(CordovaInternal::format(fileInfo.fileName())).arg(CordovaInternal::format(fileInfo.absoluteFilePath()))
|
||||
.arg(CordovaInternal::format(mime.name())).arg(fileInfo.lastModified().toMSecsSinceEpoch())
|
||||
.arg(fileInfo.size());
|
||||
|
||||
this->callback(scId, args);
|
||||
}
|
||||
}
|
||||
|
||||
void File::getMetadata(int scId, int ecId, const QString &uri) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
QFileInfo &fileInfo = f1.second;
|
||||
|
||||
if (!fileInfo.exists())
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
else {
|
||||
QVariantMap obj;
|
||||
obj.insert("modificationTime", fileInfo.lastModified().toMSecsSinceEpoch());
|
||||
obj.insert("size", fileInfo.isDir() ? 0 : fileInfo.size());
|
||||
this->cb(scId, obj);
|
||||
}
|
||||
}
|
||||
|
||||
void File::readEntries(int scId, int ecId, const QString &uri) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
QDir dir(f1.second.absoluteFilePath());
|
||||
QString entriesList;
|
||||
|
||||
if (!dir.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QFileInfo &fileInfo: dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) {
|
||||
entriesList += CordovaInternal::format(file2map(fileInfo)) + ",";
|
||||
}
|
||||
// Remove trailing comma
|
||||
if (entriesList.size() > 0)
|
||||
entriesList.remove(entriesList.size() - 1, 1);
|
||||
|
||||
entriesList = "new Array(" + entriesList + ")";
|
||||
|
||||
this->callback(scId, entriesList);
|
||||
}
|
||||
|
||||
void File::readAsText(int scId, int ecId, const QString &uri, const QString &/*encoding*/, int sliceStart, int sliceEnd) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QFile file(f1.second.absoluteFilePath());
|
||||
|
||||
if (!file.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
this->callback(ecId, FileError::kNotReadableErr);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray content = file.readAll();
|
||||
|
||||
if (sliceEnd == -1)
|
||||
sliceEnd = content.size();
|
||||
if (sliceEnd < 0) {
|
||||
sliceEnd++;
|
||||
sliceEnd = std::max(0, content.size() + sliceEnd);
|
||||
}
|
||||
if (sliceEnd > content.size())
|
||||
sliceEnd = content.size();
|
||||
|
||||
if (sliceStart < 0)
|
||||
sliceStart = std::max(0, content.size() + sliceStart);
|
||||
if (sliceStart > content.size())
|
||||
sliceStart = content.size();
|
||||
|
||||
if (sliceStart > sliceEnd)
|
||||
sliceEnd = sliceStart;
|
||||
|
||||
//FIXME: encoding
|
||||
content = content.mid(sliceStart, sliceEnd - sliceStart);
|
||||
|
||||
this->cb(scId, content);
|
||||
}
|
||||
|
||||
void File::readAsArrayBuffer(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) {
|
||||
const QString str2array("\
|
||||
(function strToArray(str) { \
|
||||
var res = new Uint8Array(str.length); \
|
||||
for (var i = 0; i < str.length; i++) { \
|
||||
res[i] = str.charCodeAt(i); \
|
||||
} \
|
||||
return res; \
|
||||
})(\"%1\")");
|
||||
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QFile file(f1.second.absoluteFilePath());
|
||||
|
||||
if (!file.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
this->callback(ecId, FileError::kNotReadableErr);
|
||||
return;
|
||||
}
|
||||
QString res;
|
||||
QByteArray content = file.readAll();
|
||||
|
||||
if (sliceEnd == -1)
|
||||
sliceEnd = content.size();
|
||||
if (sliceEnd < 0) {
|
||||
sliceEnd++;
|
||||
sliceEnd = std::max(0, content.size() + sliceEnd);
|
||||
}
|
||||
if (sliceEnd > content.size())
|
||||
sliceEnd = content.size();
|
||||
|
||||
if (sliceStart < 0)
|
||||
sliceStart = std::max(0, content.size() + sliceStart);
|
||||
if (sliceStart > content.size())
|
||||
sliceStart = content.size();
|
||||
|
||||
if (sliceStart > sliceEnd)
|
||||
sliceEnd = sliceStart;
|
||||
|
||||
content = content.mid(sliceStart, sliceEnd - sliceStart);
|
||||
|
||||
res.reserve(content.length() * 6);
|
||||
for (uchar c: content) {
|
||||
res += "\\x";
|
||||
res += QString::number(c, 16).rightJustified(2, '0').toUpper();
|
||||
}
|
||||
|
||||
this->callback(scId, str2array.arg(res));
|
||||
}
|
||||
|
||||
void File::readAsBinaryString(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QFile file(f1.second.absoluteFilePath());
|
||||
|
||||
if (!file.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
this->callback(ecId, FileError::kNotReadableErr);
|
||||
return;
|
||||
}
|
||||
QString res;
|
||||
QByteArray content = file.readAll();
|
||||
|
||||
if (sliceEnd == -1)
|
||||
sliceEnd = content.size();
|
||||
if (sliceEnd < 0) {
|
||||
sliceEnd++;
|
||||
sliceEnd = std::max(0, content.size() + sliceEnd);
|
||||
}
|
||||
if (sliceEnd > content.size())
|
||||
sliceEnd = content.size();
|
||||
|
||||
if (sliceStart < 0)
|
||||
sliceStart = std::max(0, content.size() + sliceStart);
|
||||
if (sliceStart > content.size())
|
||||
sliceStart = content.size();
|
||||
|
||||
if (sliceStart > sliceEnd)
|
||||
sliceEnd = sliceStart;
|
||||
|
||||
content = content.mid(sliceStart, sliceEnd - sliceStart);
|
||||
|
||||
res.reserve(content.length() * 6);
|
||||
for (uchar c: content) {
|
||||
res += "\\x";
|
||||
res += QString::number(c, 16).rightJustified(2, '0').toUpper();
|
||||
}
|
||||
this->callback(scId, "\"" + res + "\"");
|
||||
}
|
||||
|
||||
void File::readAsDataURL(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, uri);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
QFile file(f1.second.absoluteFilePath());
|
||||
QFileInfo &fileInfo = f1.second;
|
||||
|
||||
if (!file.exists()) {
|
||||
this->callback(ecId, FileError::kNotReadableErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
this->callback(ecId, FileError::kNotReadableErr);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray content = file.readAll();
|
||||
QString contentType(_db.mimeTypeForFile(fileInfo.fileName()).name());
|
||||
|
||||
if (sliceEnd == -1)
|
||||
sliceEnd = content.size();
|
||||
if (sliceEnd < 0) {
|
||||
sliceEnd++;
|
||||
sliceEnd = std::max(0, content.size() + sliceEnd);
|
||||
}
|
||||
if (sliceEnd > content.size())
|
||||
sliceEnd = content.size();
|
||||
|
||||
if (sliceStart < 0)
|
||||
sliceStart = std::max(0, content.size() + sliceStart);
|
||||
if (sliceStart > content.size())
|
||||
sliceStart = content.size();
|
||||
|
||||
if (sliceStart > sliceEnd)
|
||||
sliceEnd = sliceStart;
|
||||
|
||||
content = content.mid(sliceStart, sliceEnd - sliceStart);
|
||||
|
||||
this->cb(scId, QString("data:%1;base64,").arg(contentType) + content.toBase64());
|
||||
}
|
||||
|
||||
bool File::rmDir(const QDir &dir) {
|
||||
if (dir == _persistentDir || dir == QDir::temp()) {//can't remove root dir
|
||||
return false;
|
||||
}
|
||||
bool result = true;
|
||||
if (dir.exists()) {
|
||||
// Iterate over entries and remove them
|
||||
Q_FOREACH(const QFileInfo &fileInfo, dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) {
|
||||
if (fileInfo.isDir()) {
|
||||
result = rmDir(fileInfo.absoluteFilePath());
|
||||
}
|
||||
else {
|
||||
result = QFile::remove(fileInfo.absoluteFilePath());
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally remove the current dir
|
||||
return dir.rmdir(dir.absolutePath());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool File::copyFile(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) {
|
||||
QPair<bool, QFileInfo> destDir = resolveURI(ecId, destinationUri);
|
||||
QPair<bool, QFileInfo> sourceFile = resolveURI(ecId, sourceUri);
|
||||
|
||||
if (!destDir.first || !sourceFile.first)
|
||||
return false;
|
||||
|
||||
if (!checkFileName(newName)) {
|
||||
this->callback(ecId, FileError::kEncodingErr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (destDir.second.isFile()) {
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!destDir.second.isDir()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo &fileInfo = sourceFile.second;
|
||||
QString fileName((newName.isEmpty()) ? fileInfo.fileName() : newName);
|
||||
QString destinationFile(QDir(destDir.second.absoluteFilePath()).filePath(fileName));
|
||||
if (QFile::copy(fileInfo.absoluteFilePath(), destinationFile)){
|
||||
this->cb(scId, file2map(QFileInfo(destinationFile)));
|
||||
return true;
|
||||
}
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void File::copyDir(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) {
|
||||
QPair<bool, QFileInfo> destDir = resolveURI(ecId, destinationUri);
|
||||
QPair<bool, QFileInfo> sourceDir = resolveURI(ecId, sourceUri);
|
||||
|
||||
if (!destDir.first || !sourceDir.first)
|
||||
return;
|
||||
if (!checkFileName(newName)) {
|
||||
this->callback(ecId, FileError::kEncodingErr);
|
||||
return;
|
||||
}
|
||||
|
||||
QString targetName = ((newName.isEmpty()) ? sourceDir.second.fileName() : newName);
|
||||
QString target(QDir(destDir.second.absoluteFilePath()).filePath(targetName));
|
||||
|
||||
if (QFileInfo(target).isFile()){
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
// check: copy directory into itself
|
||||
if (QDir(sourceDir.second.absoluteFilePath()).relativeFilePath(target)[0] != '.'){
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QDir(target).exists()){
|
||||
QDir(destDir.second.absoluteFilePath()).mkdir(target);;
|
||||
} else{
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (copyFolder(sourceDir.second.absoluteFilePath(), target)){
|
||||
this->cb(scId, dir2map(QDir(target)));
|
||||
return;
|
||||
}
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
void File::copyTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, source);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
if (f1.second.isDir())
|
||||
copyDir(scId, ecId, source, destinationDir, newName);
|
||||
else
|
||||
copyFile(scId, ecId, source, destinationDir, newName);
|
||||
}
|
||||
|
||||
void File::moveFile(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) {
|
||||
QPair<bool, QFileInfo> sourceFile = resolveURI(ecId, sourceUri);
|
||||
QPair<bool, QFileInfo> destDir = resolveURI(ecId, destinationUri);
|
||||
|
||||
if (!destDir.first || !sourceFile.first)
|
||||
return;
|
||||
if (!checkFileName(newName)) {
|
||||
this->callback(ecId, FileError::kEncodingErr);
|
||||
return;
|
||||
}
|
||||
|
||||
QString fileName = ((newName.isEmpty()) ? sourceFile.second.fileName() : newName);
|
||||
QString target = QDir(destDir.second.absoluteFilePath()).filePath(fileName);
|
||||
|
||||
if (sourceFile.second == QFileInfo(target)) {
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!destDir.second.exists()) {
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
if (!destDir.second.isDir()){
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (QFileInfo(target).exists()) {
|
||||
if (!QFile::remove(target)) {
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QFile::rename(sourceFile.second.absoluteFilePath(), target);
|
||||
this->cb(scId, file2map(QFileInfo(target)));
|
||||
}
|
||||
|
||||
void File::moveDir(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName){
|
||||
QPair<bool, QFileInfo> sourceDir = resolveURI(ecId, sourceUri);
|
||||
QPair<bool, QFileInfo> destDir = resolveURI(ecId, destinationUri);
|
||||
|
||||
if (!destDir.first || !sourceDir.first)
|
||||
return;
|
||||
if (!checkFileName(newName)) {
|
||||
this->callback(ecId, FileError::kEncodingErr);
|
||||
return;
|
||||
}
|
||||
|
||||
QString fileName = ((newName.isEmpty()) ? sourceDir.second.fileName() : newName);
|
||||
QString target = QDir(destDir.second.absoluteFilePath()).filePath(fileName);
|
||||
|
||||
if (!destDir.second.exists()){
|
||||
this->callback(ecId, FileError::kNotFoundErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (destDir.second.isFile()){
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
// check: copy directory into itself
|
||||
if (QDir(sourceDir.second.absoluteFilePath()).relativeFilePath(target)[0] != '.'){
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (QFileInfo(target).exists() && !QDir(destDir.second.absoluteFilePath()).rmdir(fileName)) {
|
||||
this->callback(ecId, FileError::kInvalidModificationErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (copyFolder(sourceDir.second.absoluteFilePath(), target)) {
|
||||
rmDir(sourceDir.second.absoluteFilePath());
|
||||
this->cb(scId, file2map(QFileInfo(target)));
|
||||
} else {
|
||||
this->callback(ecId, FileError::kNoModificationAllowedErr);
|
||||
}
|
||||
}
|
||||
|
||||
void File::moveTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName) {
|
||||
QPair<bool, QFileInfo> f1 = resolveURI(ecId, source);
|
||||
|
||||
if (!f1.first)
|
||||
return;
|
||||
|
||||
if (f1.second.isDir())
|
||||
moveDir(scId, ecId, source, destinationDir, newName);
|
||||
else
|
||||
moveFile(scId, ecId, source, destinationDir, newName);
|
||||
}
|
||||
|
||||
bool File::copyFolder(const QString& sourceFolder, const QString& destFolder) {
|
||||
QDir sourceDir(sourceFolder);
|
||||
if (!sourceDir.exists())
|
||||
return false;
|
||||
QDir destDir(destFolder);
|
||||
if (!destDir.exists()){
|
||||
destDir.mkdir(destFolder);
|
||||
}
|
||||
QStringList files = sourceDir.entryList(QDir::Files);
|
||||
for (int i = 0; i< files.count(); i++)
|
||||
{
|
||||
QString srcName = sourceFolder + "/" + files[i];
|
||||
QString destName = destFolder + "/" + files[i];
|
||||
QFile::copy(srcName, destName);
|
||||
}
|
||||
files.clear();
|
||||
files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
|
||||
for (int i = 0; i< files.count(); i++)
|
||||
{
|
||||
QString srcName = sourceFolder + "/" + files[i];
|
||||
QString destName = destFolder + "/" + files[i];
|
||||
copyFolder(srcName, destName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
81
plugins/cordova-plugin-file/src/ubuntu/file.h
Normal file
81
plugins/cordova-plugin-file/src/ubuntu/file.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FILEAPI_H_SDASDASDAS
|
||||
#define FILEAPI_H_SDASDASDAS
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QtCore>
|
||||
|
||||
#include <cplugin.h>
|
||||
#include <cordova.h>
|
||||
|
||||
class File: public CPlugin {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit File(Cordova *cordova);
|
||||
|
||||
virtual const QString fullName() override {
|
||||
return File::fullID();
|
||||
}
|
||||
|
||||
virtual const QString shortName() override {
|
||||
return "File";
|
||||
}
|
||||
|
||||
static const QString fullID() {
|
||||
return "File";
|
||||
}
|
||||
QPair<bool, QFileInfo> resolveURI(const QString &uri);
|
||||
QPair<bool, QFileInfo> resolveURI(int ecId, const QString &uri);
|
||||
QVariantMap file2map(const QFileInfo &dir);
|
||||
|
||||
public slots:
|
||||
void requestFileSystem(int scId, int ecId, unsigned short type, unsigned long long size);
|
||||
void resolveLocalFileSystemURI(int scId, int ecId, const QString&);
|
||||
void getDirectory(int scId, int ecId, const QString&, const QString&, const QVariantMap&);
|
||||
void getFile(int scId, int ecId, const QString &parentPath, const QString &rpath, const QVariantMap &options);
|
||||
void readEntries(int scId, int ecId, const QString &uri);
|
||||
void getParent(int scId, int ecId, const QString &uri);
|
||||
void copyTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName);
|
||||
void moveTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName);
|
||||
void getFileMetadata(int scId, int ecId, const QString &);
|
||||
void getMetadata(int scId, int ecId, const QString &);
|
||||
void remove(int scId, int ecId, const QString &);
|
||||
void removeRecursively(int scId, int ecId, const QString&);
|
||||
void write(int scId, int ecId, const QString&, const QString&, unsigned long long position, bool binary);
|
||||
void readAsText(int scId, int ecId, const QString&, const QString &encoding, int sliceStart, int sliceEnd);
|
||||
void readAsDataURL(int scId, int ecId, const QString&, int sliceStart, int sliceEnd);
|
||||
void readAsArrayBuffer(int scId, int ecId, const QString&, int sliceStart, int sliceEnd);
|
||||
void readAsBinaryString(int scId, int ecId, const QString&, int sliceStart, int sliceEnd);
|
||||
void truncate(int scId, int ecId, const QString&, unsigned long long size);
|
||||
|
||||
void _getLocalFilesystemPath(int scId, int ecId, const QString&);
|
||||
private:
|
||||
void moveFile(int scId, int ecId,const QString&, const QString&, const QString&);
|
||||
void moveDir(int scId, int ecId,const QString&, const QString&, const QString&);
|
||||
bool copyFile(int scId, int ecId, const QString&, const QString&, const QString&);
|
||||
void copyDir(int scId, int ecId, const QString&, const QString&, const QString&);
|
||||
bool rmDir(const QDir &dir);
|
||||
bool copyFolder(const QString&, const QString&);
|
||||
|
||||
QPair<QString, QString> GetRelativePath(const QFileInfo &fileInfo);
|
||||
QVariantMap dir2map(const QDir &dir);
|
||||
|
||||
QMimeDatabase _db;
|
||||
const QDir _persistentDir;
|
||||
QNetworkAccessManager _manager;
|
||||
};
|
||||
|
||||
#endif
|
||||
1186
plugins/cordova-plugin-file/src/windows/FileProxy.js
vendored
Normal file
1186
plugins/cordova-plugin-file/src/windows/FileProxy.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1800
plugins/cordova-plugin-file/src/wp/File.cs
Normal file
1800
plugins/cordova-plugin-file/src/wp/File.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user