From 0f1d8500d1feebde73d043d1ceb291afe180fb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n?= Date: Sun, 12 Apr 2020 17:25:30 +0200 Subject: [PATCH 1/6] KMZ importing: firstly copies images then parses kml (checks kml's href link to image is correct). --- .../importer/AbstractFileTrackImporter.java | 11 ++- .../io/file/importer/ImportAsyncTask.java | 2 +- .../io/file/importer/KmzTrackImporter.java | 76 ++++++++++++++++--- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index 9c056342e..49fb6379b 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -393,10 +393,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track File dir = FileUtils.getPhotoDir(context, importTrackId); File file = new File(dir, filename); - - Uri photoUri = FileUtils.getUriForFile(context, file); - - return "" + photoUri; + if (file.exists()) { + Uri photoUri = FileUtils.getUriForFile(context, file); + return "" + photoUri; + } + else { + return null; + } } /** diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java index 5c24c0ad2..fa8c55c95 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java @@ -186,7 +186,7 @@ class ImportAsyncTask extends AsyncTask { Uri uri = contentProviderUtils.insertTrack(new Track()); long newId = Long.parseLong(uri.getLastPathSegment()); - trackImporter = new KmzTrackImporter(importActivity, newId); + trackImporter = new KmzTrackImporter(importActivity, newId, file.getUri()); } } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java index 3db706bdd..a69059fc8 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java @@ -17,6 +17,7 @@ package de.dennisguse.opentracks.io.file.importer; import android.content.Context; +import android.net.Uri; import android.util.Log; import java.io.ByteArrayInputStream; @@ -46,50 +47,103 @@ public class KmzTrackImporter implements TrackImporter { private final Context context; private final long importTrackId; + private Uri uriKmzFile; /** * Constructor. * * @param context the context * @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported. + * @param uriFile URI of the kmz file. */ - KmzTrackImporter(Context context, long importTrackId) { + KmzTrackImporter(Context context, long importTrackId, Uri uriFile) { this.context = context; this.importTrackId = importTrackId; + this.uriKmzFile = uriFile; } @Override public long importFile(InputStream inputStream) { - long trackId = importTrackId; - try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { + long trackId; + + if (!copyKmzImages()) { + cleanImport(context, importTrackId); + return -1L; + } + + trackId = findAndParseKmlFile(inputStream); + if (trackId == -1L) { + cleanImport(context, importTrackId); + return -1L; + } + + return trackId; + } + + /** + * Copies KMZ images that are inside KmzTrackExporter.KMZ_IMAGES_DIR to OpenTracks external storage. + * + * @return false if there are errors or true otherwise. + */ + private boolean copyKmzImages() { + try (InputStream inputStream = context.getContentResolver().openInputStream(uriKmzFile); + ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { ZipEntry zipEntry; while ((zipEntry = zipInputStream.getNextEntry()) != null) { if (Thread.interrupted()) { Log.d(TAG, "Thread interrupted"); - cleanImport(context, trackId); + return false; + } + + String fileName = zipEntry.getName(); + String prefix = KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar; + if (fileName.startsWith(prefix)) { + readImageFile(zipInputStream, fileName.substring(prefix.length())); + } + + zipInputStream.closeEntry(); + } + + return true; + } catch (IOException e) { + Log.e(TAG, "Unable to import file", e); + return false; + } + } + + /** + * Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it. + * + * @param inputStream kmz input stream. + * @return -1 if error or the id of the track otherwise. + */ + private long findAndParseKmlFile(InputStream inputStream) { + try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { + ZipEntry zipEntry; + long trackId = -1L; + + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + if (Thread.interrupted()) { + Log.d(TAG, "Thread interrupted"); return -1L; } + String fileName = zipEntry.getName(); if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) { trackId = parseKml(zipInputStream); if (trackId == -1L) { Log.d(TAG, "Unable to parse kml in kmz"); - cleanImport(context, trackId); return -1L; } - } else { - String prefix = KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar; - if (fileName.startsWith(prefix)) { - readImageFile(zipInputStream, fileName.substring(prefix.length())); - } } + zipInputStream.closeEntry(); } + return trackId; } catch (IOException e) { Log.e(TAG, "Unable to import file", e); - cleanImport(context, trackId); return -1L; } } From fa9cb3903c7b04279b3e37354668b2ff7e437e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n?= Date: Tue, 14 Apr 2020 14:58:52 +0200 Subject: [PATCH 2/6] KMZ exporting: checks waypoints images exists before parse kml. --- .../io/file/exporter/KmlTrackWriter.java | 4 ++- .../io/file/exporter/KmzTrackExporter.java | 10 ++++--- .../importer/AbstractFileTrackImporter.java | 16 ++-------- .../dennisguse/opentracks/util/FileUtils.java | 30 +++++++++++++++++++ 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java index 393ab19ca..91f8b9c47 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java @@ -32,6 +32,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.Waypoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.sensor.SensorDataSet; +import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.StringUtils; /** @@ -162,7 +163,8 @@ public class KmlTrackWriter implements TrackWriter { @Override public void writeWaypoint(Waypoint waypoint) { if (printWriter != null && exportTrackDetail) { - if (waypoint.hasPhoto() && exportPhotos) { + boolean existsPhoto = FileUtils.getPhotoFileIfExists(context, waypoint.getTrackId(), waypoint.getPhotoURI()) != null; + if (waypoint.hasPhoto() && exportPhotos && existsPhoto) { float heading = getHeading(waypoint.getTrackId(), waypoint.getLocation()); writePhotoOverlay(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), waypoint.getLocation(), waypoint.getPhotoUrl(), heading); } else { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmzTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmzTrackExporter.java index ef24556dc..2f4ccab39 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmzTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmzTrackExporter.java @@ -112,7 +112,11 @@ public class KmzTrackExporter implements TrackExporter { } Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); if (waypoint.hasPhoto()) { - addImage(context, zipOutputStream, waypoint.getPhotoUrl()); + Uri uriPhoto = waypoint.getPhotoURI(); + boolean existsPhoto = FileUtils.getPhotoFileIfExists(context, track.getId(), uriPhoto) != null; + if (existsPhoto) { + addImage(context, zipOutputStream, uriPhoto); + } } cursor.moveToNext(); @@ -122,9 +126,7 @@ public class KmzTrackExporter implements TrackExporter { } } - private void addImage(Context context, ZipOutputStream zipOutputStream, String photoUrl) throws IOException { - Uri uri = Uri.parse(photoUrl); - + private void addImage(Context context, ZipOutputStream zipOutputStream, Uri uri) throws IOException { try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) { ZipEntry zipEntry = new ZipEntry(buildKmzImageFilePath(uri)); zipOutputStream.putNextEntry(zipEntry); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index 49fb6379b..726a6ed00 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -380,20 +380,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track return null; } - Uri externalPhotoUri = Uri.parse(externalPhotoUrl); - if (externalPhotoUri == null) { - Log.w(TAG, "Could not parse external photo url."); - return null; - } - String filename = externalPhotoUri.getLastPathSegment(); - if (filename == null) { - Log.w(TAG, "External photo contains no filename."); - return null; - } - - File dir = FileUtils.getPhotoDir(context, importTrackId); - File file = new File(dir, filename); - if (file.exists()) { + File file = FileUtils.getPhotoFileIfExists(context, importTrackId, Uri.parse(externalPhotoUrl)); + if (file != null) { Uri photoUri = FileUtils.getUriForFile(context, file); return "" + photoUri; } diff --git a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java index 71368a196..9772bad29 100644 --- a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java @@ -250,4 +250,34 @@ public class FileUtils { public static Uri getUriForFile(Context context, File file) { return FileProvider.getUriForFile(context, FileUtils.FILEPROVIDER, file); } + + /** + * Checks that there is a file inside track photo directory whose name is the same that uri file. + * If there is a file inside photo directory whose name is the same that uri then returns File. Otherwise returns null. + * + * @param context the Context. + * @param trackId the id of the Track. + * @param uri the uri to check. + * @return File object or null. + */ + public static File getPhotoFileIfExists(Context context, long trackId, Uri uri) { + if (uri == null) { + Log.w(TAG, "URI object is null."); + return null; + } + + String filename = uri.getLastPathSegment(); + if (filename == null) { + Log.w(TAG, "External photo contains no filename."); + return null; + } + + File dir = FileUtils.getPhotoDir(context, trackId); + File file = new File(dir, filename); + if (!file.exists()) { + return null; + } + + return file; + } } From 54b9c4370b094b462b746167a466f26c6b2ec147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n?= Date: Thu, 16 Apr 2020 18:53:49 +0200 Subject: [PATCH 3/6] KML description: max/min elevation will not show if have invalid values (unknow). --- .../content/DescriptionGeneratorTest.java | 34 +++++++++++++++++++ .../content/DescriptionGenerator.java | 8 +++-- .../importer/AbstractFileTrackImporter.java | 3 ++ .../opentracks/stats/TrackStatistics.java | 8 +++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java index 2daf4007d..232b7f620 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java @@ -88,6 +88,40 @@ public class DescriptionGeneratorTest { Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true)); } + @Test + public void testGenerateTrackDescriptionWithoutMaxMinElevation() { + Track track = new Track(); + TrackStatistics stats = new TrackStatistics(); + stats.setTotalDistance(20000); + stats.setTotalTime(600000); + stats.setMovingTime(300000); + stats.setMaxSpeed(100); + stats.setMaxElevation(Double.POSITIVE_INFINITY); + stats.setMinElevation(Double.NEGATIVE_INFINITY); + stats.setTotalElevationGain(6000); + stats.setStartTime_ms(START_TIME); + track.setTrackStatistics(stats); + track.setCategory("hiking"); + String expected = //"Created by" + "OpenTracks

" + + "Name: -
" + + "Activity type: hiking
" + + "Description: -
" + + "Total distance: 20.00 km (12.4 mi)
" + + "Total time: 10:00
" + + "Moving time: 05:00
" + + "Average speed: 120.00 km/h (74.6 mi/h)
" + + "Average moving speed: 240.00 km/h (149.1 mi/h)
" + + "Max speed: 360.00 km/h (223.7 mi/h)
" + + "Average pace: 0:30 min/km (0:48 min/mi)
" + + "Average moving pace: 0:15 min/km (0:24 min/mi)
" + + "Fastest pace: 0:10 min/km (0:16 min/mi)
" + + "Elevation gain: 6000 m (19685 ft)
" + + "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "
"; + + Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true)); + } + /** * Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}. diff --git a/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java b/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java index e4933e6f2..be850d530 100644 --- a/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java +++ b/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java @@ -126,10 +126,14 @@ public class DescriptionGenerator { writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak); // Max elevation - writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak); + if (stats.hasElevationMax()) { + writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak); + } // Min elevation - writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak); + if (stats.hasElevationMin()) { + writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak); + } // Elevation gain writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index 726a6ed00..3446e5873 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -313,6 +313,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track */ protected TrackPoint getTrackPoint() throws SAXException { TrackPoint trackPoint = createTrackPoint(); + if (trackPoint == null) { + throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint)); + } // Calculate derived attributes from the previous point if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) { diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java index 37cd6b273..fa4fb271d 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java @@ -176,6 +176,10 @@ public class TrackStatistics { this.maxSpeed_mps = maxSpeed; } + public boolean hasElevationMin() { + return !Double.isInfinite(getMinElevation()); + } + /** * Gets the minimum elevation. * This is calculated from the smoothed elevation, so this can actually be more than the current elevation. @@ -193,6 +197,10 @@ public class TrackStatistics { elevationExtremities.setMin(elevation); } + public boolean hasElevationMax() { + return !Double.isInfinite(getMaxElevation()); + } + /** * Gets the maximum elevation. * This is calculated from the smoothed elevation, so this can actually be less than the current elevation. From 653eb8320a1c34d7cde897286f2825120ff71dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n?= Date: Tue, 21 Apr 2020 11:03:02 +0200 Subject: [PATCH 4/6] KMZ importing: look for images anywhere in KMZ file and only images referenced remain in external storage. --- .../importer/AbstractFileTrackImporter.java | 3 +- .../io/file/importer/ImportAsyncTask.java | 2 +- .../io/file/importer/KmzTrackImporter.java | 103 ++++++++++++++++-- .../dennisguse/opentracks/util/FileUtils.java | 6 +- 4 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index 3446e5873..00dd2393b 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -383,7 +383,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track return null; } - File file = FileUtils.getPhotoFileIfExists(context, importTrackId, Uri.parse(externalPhotoUrl)); + String importFileName = KmzTrackImporter.importNameForFilename(externalPhotoUrl); + File file = FileUtils.getPhotoFileIfExists(context, importTrackId, Uri.parse(importFileName)); if (file != null) { Uri photoUri = FileUtils.getUriForFile(context, file); return "" + photoUri; diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java index fa8c55c95..96a701d9b 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java @@ -99,7 +99,7 @@ class ImportAsyncTask extends AsyncTask { for (DocumentFile candidate : file.listFiles()) { if (!candidate.isDirectory()) { String extension = FileUtils.getExtension(candidate.getName()); - if (trackFileFormat.getExtension().equals(extension)) { + if (extension != null && trackFileFormat.getExtension().equals(extension)) { files.add(candidate); } } else { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java index a69059fc8..0763446af 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java @@ -26,9 +26,12 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import de.dennisguse.opentracks.content.data.Waypoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter; import de.dennisguse.opentracks.util.FileUtils; @@ -43,6 +46,13 @@ public class KmzTrackImporter implements TrackImporter { private static final String TAG = KmzTrackImporter.class.getSimpleName(); + public static final List KMZ_IMAGES_EXT = new ArrayList<>(); + static { + KMZ_IMAGES_EXT.add("jpeg"); + KMZ_IMAGES_EXT.add("jpg"); + KMZ_IMAGES_EXT.add("png"); + } + private static final int BUFFER_SIZE = 4096; private final Context context; @@ -77,17 +87,19 @@ public class KmzTrackImporter implements TrackImporter { return -1L; } + deleteOrphanImages(context, trackId); + return trackId; } /** - * Copies KMZ images that are inside KmzTrackExporter.KMZ_IMAGES_DIR to OpenTracks external storage. + * Copies all images that are inside KMZ to OpenTracks external storage. * * @return false if there are errors or true otherwise. */ private boolean copyKmzImages() { try (InputStream inputStream = context.getContentResolver().openInputStream(uriKmzFile); - ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { + ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { ZipEntry zipEntry; while ((zipEntry = zipInputStream.getNextEntry()) != null) { @@ -97,9 +109,8 @@ public class KmzTrackImporter implements TrackImporter { } String fileName = zipEntry.getName(); - String prefix = KmzTrackExporter.KMZ_IMAGES_DIR + File.separatorChar; - if (fileName.startsWith(prefix)) { - readImageFile(zipInputStream, fileName.substring(prefix.length())); + if (hasImageExtension(fileName)) { + readAndSaveImageFile(zipInputStream, importNameForFilename(fileName)); } zipInputStream.closeEntry(); @@ -112,6 +123,50 @@ public class KmzTrackImporter implements TrackImporter { } } + /** + * From path fileName generates an import unique name and returns it. + * The name generator is simple: change the path fileName with '-' instead of File.separatorChar. + * + * @param fileName the file name. + */ + public static String importNameForFilename(String fileName) { + // TODO this tricky code for maintain backward compatibility must be deleted some day. + /* + * In versions before v3.5.0 photo URL in KML files were wrong. + * For compatibility reasons it checks if fileName begins with "content://" or "file://". + * All fileName begins with "content:/" or "file://" are cooked. + * We cannot guess what's the folder name where images are so we use "images" that was the folder name expected in versions before v3.5.0. + */ + if (fileName.startsWith("content://") || fileName.startsWith("file://")) { + fileName = "images/" + fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1); + } + + return fileName.replace(File.separatorChar, '-'); + } + + /** + * Returns true if fileName ends with some of the KMZ_IMAGES_EXT suffixes. + * Otherwise returns false. + */ + private boolean hasImageExtension(String fileName) { + if (fileName == null) { + return false; + } + + String fileExt = FileUtils.getExtension(fileName.toLowerCase()); + if (fileExt == null) { + return false; + } + + for (String ext : KMZ_IMAGES_EXT) { + if (fileExt.equals(ext)) { + return true; + } + } + + return false; + } + /** * Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it. * @@ -148,6 +203,40 @@ public class KmzTrackImporter implements TrackImporter { } } + /** + * Deletes all images that remained in external storage that doesn't have waypoint (marker) associated. + * + * @param context the Context object. + * @param trackId the id of the Track. + */ + private void deleteOrphanImages(Context context, long trackId) { + if (trackId != 1L) { + // 1.- Gets all photo names in the waypoints of the track identified by id. + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); + List waypoints = contentProviderUtils.getWaypoints(trackId); + List photosName = new ArrayList<>(); + for (Waypoint w : waypoints) { + if (w.hasPhoto()) { + String photoUrl = Uri.decode(w.getPhotoUrl()); + photosName.add(photoUrl.substring(photoUrl.lastIndexOf(File.separatorChar) + 1)); + } + } + + // 2.- Deletes all orphan photos from external storage. + File dir = FileUtils.getPhotoDir(context, trackId); + if (dir.exists() && dir.isDirectory()) { + for (File file : dir.listFiles()) { + if (!photosName.contains(file.getName())) { + file.delete(); + } + } + if (dir.listFiles().length == 0) { + dir.delete(); + } + } + } + } + /** * Cleans up import. * @@ -201,12 +290,12 @@ public class KmzTrackImporter implements TrackImporter { } /** - * Reads an image file. + * Reads an image file (zipInputStream) and save it in a file called fileName inside photo folder. * * @param zipInputStream the zip input stream * @param fileName the file name */ - private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException { + private void readAndSaveImageFile(ZipInputStream zipInputStream, String fileName) throws IOException { if (importTrackId == -1L || fileName.equals("")) { return; } diff --git a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java index 9772bad29..c480a8979 100644 --- a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java @@ -95,11 +95,15 @@ public class FileUtils { /** * Gets the extension from a file name. - * Returns null if there is no extension. + * Returns null if there is no extension or fileName is null. * * @param fileName the file name */ public static String getExtension(String fileName) { + if (fileName == null) { + return null; + } + int index = fileName.lastIndexOf('.'); if (index == -1) { return null; From ed0dc053663be3e54cfb577ba473c2b58f18168c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n?= Date: Tue, 21 Apr 2020 13:11:11 +0200 Subject: [PATCH 5/6] KMZ exporting: marker images use waypoint id as a name. --- .../io/file/exporter/KmlTrackWriter.java | 33 +++++++++---------- .../io/file/exporter/KmzTrackExporter.java | 16 +++++---- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java index 91f8b9c47..d3656c1ff 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java @@ -166,7 +166,7 @@ public class KmlTrackWriter implements TrackWriter { boolean existsPhoto = FileUtils.getPhotoFileIfExists(context, waypoint.getTrackId(), waypoint.getPhotoURI()) != null; if (waypoint.hasPhoto() && exportPhotos && existsPhoto) { float heading = getHeading(waypoint.getTrackId(), waypoint.getLocation()); - writePhotoOverlay(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), waypoint.getLocation(), waypoint.getPhotoUrl(), heading); + writePhotoOverlay(waypoint, heading); } else { writePlacemark(waypoint.getName(), waypoint.getCategory(), waypoint.getDescription(), WAYPOINT_STYLE, waypoint.getLocation()); } @@ -324,32 +324,29 @@ public class KmlTrackWriter implements TrackWriter { } /** - * Writes a photo overlay. - * @param name the name - * @param category the category - * @param description the description - * @param location the location - * @param photoUrl the photo url - * @param heading the heading + * Writes a photo overlay from waypoint. + * + * @param waypoint Waypoint object. + * @param heading the heading. */ - private void writePhotoOverlay(String name, String category, String description, Location location, String photoUrl, float heading) { - if (location != null && exportTrackDetail) { + private void writePhotoOverlay(Waypoint waypoint, float heading) { + if (waypoint.getLocation() != null && exportTrackDetail) { printWriter.println(""); - printWriter.println("" + StringUtils.formatCData(name) + ""); - printWriter.println("" + StringUtils.formatCData(description) + ""); + printWriter.println("" + StringUtils.formatCData(waypoint.getName()) + ""); + printWriter.println("" + StringUtils.formatCData(waypoint.getDescription()) + ""); printWriter.print(""); - printWriter.print("" + location.getLongitude() + ""); - printWriter.print("" + location.getLatitude() + ""); + printWriter.print("" + waypoint.getLocation().getLongitude() + ""); + printWriter.print("" + waypoint.getLocation().getLatitude() + ""); printWriter.print("20"); printWriter.print("" + heading + ""); printWriter.print("90"); printWriter.println(""); - printWriter.println("" + getTime(location) + ""); + printWriter.println("" + getTime(waypoint.getLocation()) + ""); printWriter.println("#" + KmlTrackWriter.WAYPOINT_STYLE + ""); - writeCategory(category); + writeCategory(waypoint.getCategory()); if (exportPhotos) { - printWriter.println("" + KmzTrackExporter.buildKmzImageFilePath(Uri.parse(photoUrl)) + ""); + printWriter.println("" + KmzTrackExporter.buildKmzImageFilePath(waypoint) + ""); } printWriter.print(""); @@ -360,7 +357,7 @@ public class KmlTrackWriter implements TrackWriter { printWriter.print("45"); printWriter.println(""); printWriter.println(""); - printWriter.println("" + getCoordinates(location, ",") + ""); + printWriter.println("" + getCoordinates(waypoint.getLocation(), ",") + ""); printWriter.println(""); printWriter.println(""); } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmzTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmzTrackExporter.java index 2f4ccab39..ca4fc59a2 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmzTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmzTrackExporter.java @@ -115,7 +115,7 @@ public class KmzTrackExporter implements TrackExporter { Uri uriPhoto = waypoint.getPhotoURI(); boolean existsPhoto = FileUtils.getPhotoFileIfExists(context, track.getId(), uriPhoto) != null; if (existsPhoto) { - addImage(context, zipOutputStream, uriPhoto); + addImage(context, zipOutputStream, uriPhoto, waypoint); } } @@ -126,9 +126,9 @@ public class KmzTrackExporter implements TrackExporter { } } - private void addImage(Context context, ZipOutputStream zipOutputStream, Uri uri) throws IOException { + private void addImage(Context context, ZipOutputStream zipOutputStream, Uri uri, Waypoint waypoint) throws IOException { try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) { - ZipEntry zipEntry = new ZipEntry(buildKmzImageFilePath(uri)); + ZipEntry zipEntry = new ZipEntry(buildKmzImageFilePath(waypoint)); zipOutputStream.putNextEntry(zipEntry); if (inputStream == null) throw new FileNotFoundException(); @@ -151,11 +151,13 @@ public class KmzTrackExporter implements TrackExporter { } /** - * Builds and returns the path for the file that will be saved inside KMZ_IMAGES_DIR. + * Builds and returns the path for the image that will be saved inside KMZ_IMAGES_DIR for the waypoint. * - * @param uri URI object. + * @param waypoint Waypoint object. */ - public static String buildKmzImageFilePath(Uri uri) { - return KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(uri.getLastPathSegment()); + public static String buildKmzImageFilePath(Waypoint waypoint) { + String ext = FileUtils.getExtension(waypoint.getPhotoUrl()); + ext = ext == null ? "" : "." + ext; + return KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(waypoint.getId() + ext); } } \ No newline at end of file From 74835dcef596f3986816d03056e00a58efcee2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n?= Date: Wed, 22 Apr 2020 13:19:32 +0200 Subject: [PATCH 6/6] KMZ import/export: code improved. --- .../io/file/exporter/KmlTrackWriter.java | 2 +- .../io/file/importer/KmzTrackImporter.java | 14 ++++---------- .../de/dennisguse/opentracks/util/FileUtils.java | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java index d3656c1ff..4ed5314f7 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java @@ -330,7 +330,7 @@ public class KmlTrackWriter implements TrackWriter { * @param heading the heading. */ private void writePhotoOverlay(Waypoint waypoint, float heading) { - if (waypoint.getLocation() != null && exportTrackDetail) { + if (exportTrackDetail) { printWriter.println(""); printWriter.println("" + StringUtils.formatCData(waypoint.getName()) + ""); printWriter.println("" + StringUtils.formatCData(waypoint.getDescription()) + ""); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java index 0763446af..beda05523 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java @@ -27,6 +27,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -46,12 +47,7 @@ public class KmzTrackImporter implements TrackImporter { private static final String TAG = KmzTrackImporter.class.getSimpleName(); - public static final List KMZ_IMAGES_EXT = new ArrayList<>(); - static { - KMZ_IMAGES_EXT.add("jpeg"); - KMZ_IMAGES_EXT.add("jpg"); - KMZ_IMAGES_EXT.add("png"); - } + public static final List KMZ_IMAGES_EXT = Arrays.asList("jpeg", "jpg", "png"); private static final int BUFFER_SIZE = 4096; @@ -158,10 +154,8 @@ public class KmzTrackImporter implements TrackImporter { return false; } - for (String ext : KMZ_IMAGES_EXT) { - if (fileExt.equals(ext)) { - return true; - } + if (KMZ_IMAGES_EXT.contains(fileExt)) { + return true; } return false; diff --git a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java index c480a8979..3a609b247 100644 --- a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java @@ -95,9 +95,9 @@ public class FileUtils { /** * Gets the extension from a file name. - * Returns null if there is no extension or fileName is null. * * @param fileName the file name + * @return null if there is no extension or fileName is null. */ public static String getExtension(String fileName) { if (fileName == null) {