From 02004207ed83c3996a5fdf7a765ba9f8264010d6 Mon Sep 17 00:00:00 2001 From: Bananeweizen Date: Sat, 17 May 2014 13:04:29 +0200 Subject: fix #3823: detect import file type by content --- main/src/cgeo/geocaching/files/FileType.java | 8 ++ .../cgeo/geocaching/files/FileTypeDetector.java | 77 ++++++++++++++++ main/src/cgeo/geocaching/files/GPXImporter.java | 100 ++++++++++++++------- .../geocaching/files/FileTypeDetectorTest.java | 51 +++++++++++ .../AbstractResourceInstrumentationTestCase.java | 7 ++ 5 files changed, 209 insertions(+), 34 deletions(-) create mode 100644 main/src/cgeo/geocaching/files/FileType.java create mode 100644 main/src/cgeo/geocaching/files/FileTypeDetector.java create mode 100644 tests/src/cgeo/geocaching/files/FileTypeDetectorTest.java diff --git a/main/src/cgeo/geocaching/files/FileType.java b/main/src/cgeo/geocaching/files/FileType.java new file mode 100644 index 0000000..ef62351 --- /dev/null +++ b/main/src/cgeo/geocaching/files/FileType.java @@ -0,0 +1,8 @@ +package cgeo.geocaching.files; + +public enum FileType { + UNKNOWN, + LOC, + GPX, + ZIP +} diff --git a/main/src/cgeo/geocaching/files/FileTypeDetector.java b/main/src/cgeo/geocaching/files/FileTypeDetector.java new file mode 100644 index 0000000..389b83a --- /dev/null +++ b/main/src/cgeo/geocaching/files/FileTypeDetector.java @@ -0,0 +1,77 @@ +package cgeo.geocaching.files; + +import cgeo.geocaching.utils.Log; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; + +import android.content.ContentResolver; +import android.net.Uri; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class FileTypeDetector { + + private final ContentResolver contentResolver; + private final Uri uri; + + public FileTypeDetector(Uri uri, ContentResolver contentResolver) { + this.uri = uri; + this.contentResolver = contentResolver; + } + + public @NonNull FileType getFileType() { + InputStream is = null; + BufferedReader reader = null; + FileType type = FileType.UNKNOWN; + try { + is = contentResolver.openInputStream(uri); + if (is == null) { + return FileType.UNKNOWN; + } + reader = new BufferedReader(new InputStreamReader(is)); + type = detectHeader(reader); + reader.close(); + } catch (FileNotFoundException e) { + Log.e("FileTypeDetector", e); + } catch (IOException e) { + Log.e("FileTypeDetector", e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(is); + } + return type; + } + + private static FileType detectHeader(BufferedReader reader) + throws IOException { + String line = reader.readLine(); + if (isZip(line)) { + return FileType.ZIP; + } + // scan at most 5 lines of a GPX file + for (int i = 0; i < 5; i++) { + line = StringUtils.trim(line); + if (StringUtils.contains(line, "= 4 + && StringUtils.startsWith(line, "PK") && line.charAt(2) == 3 + && line.charAt(3) == 4; + } + +} diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java index cd2f445..f87e6b9 100644 --- a/main/src/cgeo/geocaching/files/GPXImporter.java +++ b/main/src/cgeo/geocaching/files/GPXImporter.java @@ -14,6 +14,7 @@ import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import android.app.Activity; @@ -93,46 +94,77 @@ public class GPXImporter { * * @param uri * URI of the file to import - * @param knownMimeType - * @param knownPathName + * @param mimeType + * @param pathName */ - public void importGPX(final Uri uri, final @Nullable String knownMimeType, final @Nullable String knownPathName) { + public void importGPX(final Uri uri, final @Nullable String mimeType, final @Nullable String pathName) { final ContentResolver contentResolver = fromActivity.getContentResolver(); - String mimeType = knownMimeType; - final String pathName = knownPathName != null ? knownPathName : uri.getPath(); - - // if mimetype can't be determined (e.g. for emulators email app), derive it from uri file extension - // contentResolver.getType(uri) doesn't help but throws exception for emulators email app - // Permission Denial: reading com.android.email.provider.EmailProvider uri - // Google search says: there is no solution for this problem - // Gmail doesn't work at all, see #967 - if (mimeType == null) { - if (StringUtils.endsWithIgnoreCase(pathName, GPX_FILE_EXTENSION) || StringUtils.endsWithIgnoreCase(pathName, LOC_FILE_EXTENSION)) { - mimeType = "application/xml"; - } else { - // if we can't determine a better type, default to zip import - // emulator email sends e.g. content://com.android.email.attachmentprovider/1/1/RAW, mimetype=null - mimeType = "application/zip"; - } - } Log.i("importGPX: " + uri + ", mimetype=" + mimeType); - if (GPX_MIME_TYPES.contains(mimeType)) { - if (StringUtils.endsWithIgnoreCase(pathName, LOC_FILE_EXTENSION)) { - new ImportLocAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); - } else { - new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); - } - } else if (ZIP_MIME_TYPES.contains(mimeType)) { - new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); - } else { - importFinished(); + @NonNull + FileType fileType = new FileTypeDetector(uri, contentResolver) + .getFileType(); + + if (fileType == FileType.UNKNOWN) { + fileType = getFileTypeFromPathName(pathName); + } + if (fileType == FileType.UNKNOWN) { + fileType = getFileTypeFromMimeType(mimeType); + } + + ImportThread importer = getImporterFromFileType(uri, contentResolver, + fileType); + + if (importer != null) { + importer.start(); + } else { + importFinished(); + } + } + + private static @NonNull FileType getFileTypeFromPathName( + final String pathName) { + if (StringUtils.endsWithIgnoreCase(pathName, GPX_FILE_EXTENSION)) { + return FileType.GPX; } - } - /** - * Import GPX provided via intent of activity that instantiated this GPXImporter. - */ + if (StringUtils.endsWithIgnoreCase(pathName, LOC_FILE_EXTENSION)) { + return FileType.LOC; + } + return FileType.UNKNOWN; + } + + private static @NonNull FileType getFileTypeFromMimeType( + final String mimeType) { + if (GPX_MIME_TYPES.contains(mimeType)) { + return FileType.GPX; + } else if (ZIP_MIME_TYPES.contains(mimeType)) { + return FileType.ZIP; + } + return FileType.UNKNOWN; + } + + private ImportThread getImporterFromFileType(Uri uri, + ContentResolver contentResolver, FileType fileType) { + switch (fileType) { + case ZIP: + return new ImportGpxZipAttachmentThread(uri, contentResolver, + listId, importStepHandler, progressHandler); + case GPX: + return new ImportGpxAttachmentThread(uri, contentResolver, listId, + importStepHandler, progressHandler); + case LOC: + return new ImportLocAttachmentThread(uri, contentResolver, listId, + importStepHandler, progressHandler); + default: + return null; + } + } + + /** + * Import GPX provided via intent of activity that instantiated this + * GPXImporter. + */ public void importGPX() { final Intent intent = fromActivity.getIntent(); final Uri uri = intent.getData(); diff --git a/tests/src/cgeo/geocaching/files/FileTypeDetectorTest.java b/tests/src/cgeo/geocaching/files/FileTypeDetectorTest.java new file mode 100644 index 0000000..ec7b3a8 --- /dev/null +++ b/tests/src/cgeo/geocaching/files/FileTypeDetectorTest.java @@ -0,0 +1,51 @@ +package cgeo.geocaching.files; + +import static org.assertj.core.api.Assertions.assertThat; + +import cgeo.geocaching.test.AbstractResourceInstrumentationTestCase; +import cgeo.geocaching.test.R; + +import org.eclipse.jdt.annotation.NonNull; + +import android.content.ContentResolver; +import android.net.Uri; + +public class FileTypeDetectorTest extends AbstractResourceInstrumentationTestCase { + + private static class FileContentProvider extends ContentResolver { + + public FileContentProvider() { + super(null); + } + + } + + public void testUnknown() throws Exception { + assertFileType(R.raw.gc2cjpf_html, FileType.UNKNOWN); + assertFileType(R.raw.map1, FileType.UNKNOWN); + } + + public void testLoc() throws Exception { + assertFileType(R.raw.gc1bkp3_loc, FileType.LOC); + assertFileType(R.raw.oc5952_loc, FileType.LOC); + assertFileType(R.raw.waymarking_loc, FileType.LOC); + } + + public void testGpx() throws Exception { + assertFileType(R.raw.gc1bkp3_gpx100, FileType.GPX); + assertFileType(R.raw.gc1bkp3_gpx101, FileType.GPX); + assertFileType(R.raw.oc5952_gpx, FileType.GPX); + assertFileType(R.raw.renamed_waypoints_wpts, FileType.GPX); + assertFileType(R.raw.waymarking_gpx, FileType.GPX); + } + + public void testZip() throws Exception { + assertFileType(R.raw.pq_error, FileType.ZIP); + assertFileType(R.raw.pq7545915, FileType.ZIP); + } + + private void assertFileType(final int resourceId, final @NonNull FileType fileType) { + final Uri resourceURI = getResourceURI(resourceId); + assertThat(new FileTypeDetector(resourceURI, new FileContentProvider()).getFileType()).isEqualTo(fileType); + } +} diff --git a/tests/src/cgeo/geocaching/test/AbstractResourceInstrumentationTestCase.java b/tests/src/cgeo/geocaching/test/AbstractResourceInstrumentationTestCase.java index df8dc1f..cbad794 100644 --- a/tests/src/cgeo/geocaching/test/AbstractResourceInstrumentationTestCase.java +++ b/tests/src/cgeo/geocaching/test/AbstractResourceInstrumentationTestCase.java @@ -12,7 +12,9 @@ import cgeo.geocaching.files.GPX10Parser; import cgeo.geocaching.files.ParserException; import cgeo.geocaching.list.StoredList; +import android.content.ContentResolver; import android.content.res.Resources; +import android.net.Uri; import android.test.InstrumentationTestCase; import java.io.File; @@ -102,4 +104,9 @@ public abstract class AbstractResourceInstrumentationTestCase extends Instrument instream.close(); } } + + protected Uri getResourceURI(int resId) { + Resources resources = getInstrumentation().getContext().getResources(); + return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + resources.getResourcePackageName(resId) + '/' + resources.getResourceTypeName(resId) + '/' + resources.getResourceEntryName(resId)); + } } -- cgit v1.1