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; 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 @SuppressWarnings("deprecation") public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException { File fp = null; Cursor cursor = this.cordova.getActivity().managedQuery(inputURL.URL, new String[] { MediaStore.Images.Media.DATA }, null, null, null); // Note: MediaStore.Images/Audio/Video.Media.DATA is always "_data" int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); fp = new File(cursor.getString(column_index)); if (!fp.exists()) { throw new FileNotFoundException(); } if (!fp.canRead()) { throw new IOException(); } try { return makeEntryForPath(inputURL.fullPath, inputURL.filesystemName, fp.isDirectory()); } 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); } @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 { String path = filesystemPathForURL(inputURL); if (path == null) { throw new FileNotFoundException(); } File file = new File(path); JSONObject metadata = new JSONObject(); try { metadata.put("size", file.length()); metadata.put("type", resourceApi.getMimeType(inputURL.URL)); metadata.put("name", file.getName()); metadata.put("fullPath", inputURL.fullPath); metadata.put("lastModifiedDate", file.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); } 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"); } @Override public String filesystemPathForURL(LocalFilesystemURL url) { final String[] LOCAL_FILE_PROJECTION = { MediaStore.Images.Media.DATA }; ContentResolver contentResolver = this.cordova.getActivity().getContentResolver(); Cursor cursor = contentResolver.query(url.URL, LOCAL_FILE_PROJECTION, null, null, null); if (cursor != null) { try { int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]); if (columnIndex != -1 && cursor.getCount() > 0) { cursor.moveToFirst(); String path = cursor.getString(columnIndex); return path; } } finally { 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; } }