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.CordovaInterface; import org.apache.cordova.CordovaResourceApi; import org.apache.cordova.CordovaWebView; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore; import android.provider.OpenableColumns; public class ContentFilesystem extends Filesystem { private CordovaInterface cordova; private CordovaResourceApi resourceApi; public ContentFilesystem(String name, CordovaInterface cordova, CordovaWebView webView) { this.name = name; this.cordova = cordova; this.resourceApi = new CordovaResourceApi(webView.getContext(), webView.pluginManager); } @Override public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException { // Get the cursor to validate that the file exists Cursor cursor = openCursorForURL(inputURL); try { if (cursor == null || !cursor.moveToFirst()) { throw new FileNotFoundException(); } } finally { if (cursor != null) cursor.close(); } try { return makeEntryForPath(inputURL.fullPath, inputURL.filesystemName, false /*fp.isDirectory()*/, inputURL.URL.toString()); } catch (JSONException e) { throw new IOException(); } } @Override public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException { if (options != null) { if (options.optBoolean("create")) { throw new IOException("Cannot create content url"); } } LocalFilesystemURL requestedURL = new LocalFilesystemURL(Uri.withAppendedPath(inputURL.URL, fileName)); File fp = new File(this.filesystemPathForURL(requestedURL)); 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 makeEntryForPath(requestedURL.fullPath, requestedURL.filesystemName, directory, Uri.fromFile(fp).toString()); } @Override public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws NoModificationAllowedException { String filePath = filesystemPathForURL(inputURL); File file = new File(filePath); try { this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStore.Images.Media.DATA + " = ?", new String[] { filePath }); } 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. } return file.delete(); } @Override public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws NoModificationAllowedException { throw new NoModificationAllowedException("Cannot remove content url"); } @Override public JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException { // TODO Auto-generated method stub return null; } @Override public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException { Integer size = null; Integer lastModified = null; Cursor cursor = openCursorForURL(inputURL); try { if (cursor != null && cursor.moveToFirst()) { size = resourceSizeForCursor(cursor); lastModified = lastModifiedDateForCursor(cursor); } else { throw new FileNotFoundException(); } } finally { if (cursor != null) cursor.close(); } JSONObject metadata = new JSONObject(); try { metadata.put("size", size); metadata.put("type", resourceApi.getMimeType(inputURL.URL)); metadata.put("name", inputURL.filesystemName); metadata.put("fullPath", inputURL.fullPath); metadata.put("lastModifiedDate", lastModified); } catch (JSONException e) { return null; } return metadata; } @Override public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName, Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException { if (LocalFilesystem.class.isInstance(srcFs)) { /* Same FS, we can shortcut with CordovaResourceApi operations */ // Figure out where we should be copying to final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL); OutputStream os = resourceApi.openOutputStream(destURL.URL); CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcURL.URL); if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) { throw new NoModificationAllowedException("Cannot move file at source URL"); } try { resourceApi.copyResource(ofrr, os); } catch (IOException e) { throw new IOException("Cannot read file at source URL"); } if (move) { srcFs.removeFileAtLocalURL(srcURL); } return makeEntryForURL(destinationURL, false, destinationURL.URL.toString()); } else { // Need to copy the hard way return super.copyFileToURL(destURL, newName, srcFs, srcURL, move); } } @Override public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end, ReadFileCallback readFileCallback) throws IOException { CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(inputURL.URL); if (end < 0) { end = ofrr.length; } long numBytesToRead = end - start; try { if (start > 0) { ofrr.inputStream.skip(start); } LimitedInputStream inputStream = new LimitedInputStream(ofrr.inputStream, numBytesToRead); readFileCallback.handleData(inputStream, ofrr.mimeType); } finally { ofrr.inputStream.close(); } } @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(LocalFilesystemURL url) { ContentResolver contentResolver = this.cordova.getActivity().getContentResolver(); Cursor cursor = contentResolver.query(url.URL, null, null, null, null); return cursor; } protected String filesystemPathForCursor(Cursor cursor) { final String[] LOCAL_FILE_PROJECTION = { MediaStore.Images.Media.DATA }; int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]); if (columnIndex != -1) { return cursor.getString(columnIndex); } return null; } protected Integer resourceSizeForCursor(Cursor cursor) { int columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE); if (columnIndex != -1) { String sizeStr = cursor.getString(columnIndex); if (sizeStr != null) { return Integer.parseInt(sizeStr,10); } } return null; } protected Integer 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 Integer.parseInt(dateStr,10); } } return null; } @Override public String filesystemPathForURL(LocalFilesystemURL url) { Cursor cursor = openCursorForURL(url); try { if (cursor != null && cursor.moveToFirst()) { return filesystemPathForCursor(cursor); } } finally { if (cursor != null) cursor.close(); } return null; } @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) { String path = filesystemPathForURL(inputURL); File file = new File(path); return file.exists(); } @Override OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException { OutputStream os = resourceApi.openOutputStream(inputURL.URL); return os; } }