forked from upstream-mirrors/OpenTracks
Import/export track UUID (necessary for synchronization).
If a track with a UUID is presented in OpenTracks, it cannot be imported again (enforced by database constraint). Fixes #307.
This commit is contained in:
@@ -205,9 +205,9 @@ public class ImportProgressDialogFragment extends DialogFragment {
|
||||
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new GpxFileTrackImporter(context);
|
||||
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new KmlFileTrackImporter(ImportProgressDialogFragment.this.getContext(), -1L);
|
||||
trackImporter = new KmlFileTrackImporter(ImportProgressDialogFragment.this.getContext());
|
||||
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new KmzTrackImporter(context, -1L, file.getUri());
|
||||
trackImporter = new KmzTrackImporter(context, file.getUri());
|
||||
} else {
|
||||
Log.i(TAG, "Unsupported file format.");
|
||||
return false;
|
||||
|
||||
@@ -33,6 +33,7 @@ import de.dennisguse.opentracks.util.StringUtils;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
//TODO Export waypoints
|
||||
public class GpxTrackWriter implements TrackWriter {
|
||||
|
||||
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
@@ -93,10 +94,15 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\"");
|
||||
printWriter.println("xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\"");
|
||||
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
||||
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
|
||||
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\"");
|
||||
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\"");
|
||||
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
printWriter.println("xsi:schemaLocation=" +
|
||||
"\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
|
||||
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd\">");
|
||||
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
|
||||
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
|
||||
|
||||
printWriter.println("<metadata>");
|
||||
|
||||
Track track = tracks[0];
|
||||
@@ -127,17 +133,15 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
public void writeWaypoint(Waypoint waypoint) {
|
||||
if (printWriter != null) {
|
||||
Location location = waypoint.getLocation();
|
||||
if (location != null) {
|
||||
printWriter.println("<wpt " + formatLocation(location) + ">");
|
||||
if (location.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
|
||||
}
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(waypoint.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(waypoint.getDescription()) + "</desc>");
|
||||
printWriter.println("<type>" + StringUtils.formatCData(waypoint.getCategory()) + "</type>");
|
||||
printWriter.println("</wpt>");
|
||||
printWriter.println("<wpt " + formatLocation(location) + ">");
|
||||
if (location.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
|
||||
}
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(waypoint.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(waypoint.getDescription()) + "</desc>");
|
||||
printWriter.println("<type>" + StringUtils.formatCData(waypoint.getCategory()) + "</type>");
|
||||
printWriter.println("</wpt>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +162,11 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||
printWriter.println("<type>" + StringUtils.formatCData(track.getCategory()) + "</type>");
|
||||
printWriter.println("<extensions><topografix:color>c0c0c0</topografix:color></extensions>");
|
||||
|
||||
printWriter.println("<extensions>");
|
||||
printWriter.println("<topografix:color>c0c0c0</topografix:color>");
|
||||
printWriter.println("<opentracks:trackid>" + track.getUuid() + "</opentracks:trackid>");
|
||||
printWriter.println("</extensions>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,10 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.2\"");
|
||||
printWriter.println("xmlns:gx=\"http://www.google.com/kml/ext/2.2\"");
|
||||
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\">");
|
||||
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
|
||||
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\">");
|
||||
//TODO ADD xsi:schemaLocation here!
|
||||
|
||||
printWriter.println("<Document>");
|
||||
printWriter.println("<open>1</open>");
|
||||
printWriter.println("<visibility>1</visibility>");
|
||||
@@ -204,6 +207,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
|
||||
printWriter.println("<icon>" + StringUtils.formatCData(track.getIcon()) + "</icon>");
|
||||
printWriter.println("<opentracks:trackid>" + track.getUuid() + "</opentracks:trackid>");
|
||||
}
|
||||
|
||||
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
|
||||
@@ -460,7 +464,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
/**
|
||||
* Writes a simple array style.
|
||||
*
|
||||
* @param name the name of the simple array.
|
||||
* @param name the name of the simple array.
|
||||
* @param extendedDataType the extended data display name
|
||||
*/
|
||||
private void writeSimpleArrayStyle(String name, String extendedDataType) {
|
||||
|
||||
@@ -42,6 +42,7 @@ import de.dennisguse.opentracks.util.FileUtils;
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class KmzTrackExporter implements TrackExporter {
|
||||
|
||||
private static final String KMZ_IMAGES_DIR = "images";
|
||||
public static final String KMZ_KML_FILE = "doc.kml";
|
||||
|
||||
@@ -49,7 +50,7 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final TrackExporter fileTrackExporter;
|
||||
private final FileTrackExporter fileTrackExporter;
|
||||
private final Track[] tracks;
|
||||
|
||||
private final boolean exportPhotos;
|
||||
@@ -58,10 +59,10 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
* Constructor.
|
||||
*
|
||||
* @param contentProviderUtils the content provider utils
|
||||
* @param trackExporter the file track exporter
|
||||
* @param trackExporter the file track exporter
|
||||
* @param tracks the tracks to export
|
||||
*/
|
||||
public KmzTrackExporter(ContentProviderUtils contentProviderUtils, TrackExporter trackExporter, Track[] tracks, boolean exportPhotos) {
|
||||
public KmzTrackExporter(ContentProviderUtils contentProviderUtils, FileTrackExporter trackExporter, Track[] tracks, boolean exportPhotos) {
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.fileTrackExporter = trackExporter;
|
||||
this.tracks = tracks;
|
||||
|
||||
+16
-13
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -62,11 +63,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
// The maximum number of buffered locations for bulk-insertion
|
||||
private static final int MAX_BUFFERED_LOCATIONS = 512;
|
||||
|
||||
private final Context context;
|
||||
private final long importTrackId;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final int recordingDistanceInterval;
|
||||
|
||||
private long importTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
private final List<Long> trackIds = new ArrayList<>();
|
||||
private final List<Waypoint> waypoints = new ArrayList<>();
|
||||
|
||||
@@ -96,19 +98,17 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
// The SAX locator to get the current line information
|
||||
private Locator locator;
|
||||
|
||||
/**
|
||||
* @param context the context
|
||||
* @param importTrackId the track id to import to. -1L to import to a new track.
|
||||
*/
|
||||
@Deprecated
|
||||
// Remove importTrackId
|
||||
AbstractFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
|
||||
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
||||
this.context = context;
|
||||
this.importTrackId = importTrackId;
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
}
|
||||
|
||||
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils, long importTrackId) {
|
||||
this(context, contentProviderUtils);
|
||||
this.importTrackId = importTrackId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocumentLocator(Locator locator) {
|
||||
this.locator = locator;
|
||||
@@ -134,12 +134,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
saxParser.parse(inputStream, this);
|
||||
Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms");
|
||||
if (trackIds.size() != 1) {
|
||||
Log.d(TAG, trackIds.size() + " tracks imported");
|
||||
cleanImport();
|
||||
return -1L;
|
||||
throw new MultiTracksImportException();
|
||||
}
|
||||
return trackIds.get(0);
|
||||
} catch (IOException | SAXException | ParserConfigurationException e) {
|
||||
} catch (MultiTracksImportException | IOException | SAXException | SQLiteConstraintException | ParserConfigurationException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
cleanImport();
|
||||
return -1L;
|
||||
@@ -544,4 +542,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
// The number of buffered locations
|
||||
int numBufferedTrackPoints = 0;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO According to the reader AND writer, we support multiple tracks per file; we should implement it here also.
|
||||
private static class MultiTracksImportException extends RuntimeException {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String TAG_TRACK_SEGMENT = "trkseg";
|
||||
private static final String TAG_TYPE = "type";
|
||||
private static final String TAG_WAYPOINT = "wpt";
|
||||
private static final String TAG_ID = "opentracks:trackid";
|
||||
|
||||
private static final String ATTRIBUTE_LAT = "lat";
|
||||
private static final String ATTRIBUTE_LON = "lon";
|
||||
@@ -64,7 +65,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
@VisibleForTesting
|
||||
GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
||||
super(context, -1L, contentProviderUtils);
|
||||
super(context, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,6 +145,10 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
cadence = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ID:
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
|
||||
@@ -57,6 +57,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String TAG_STYLE_URL = "styleUrl";
|
||||
private static final String TAG_VALUE = "value";
|
||||
private static final String TAG_WHEN = "when";
|
||||
private static final String TAG_UUID = "opentracks:trackid";
|
||||
|
||||
private static final String ATTRIBUTE_NAME = "name";
|
||||
|
||||
@@ -68,23 +69,17 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private ArrayList<Float> heartRateList;
|
||||
private ArrayList<Float> powerList;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
@Deprecated //TODO Do not pass importTrackId here; get it from somewhere else
|
||||
public KmlFileTrackImporter(Context context, long importTrackId) {
|
||||
this(context, importTrackId, new ContentProviderUtils(context));
|
||||
public KmlFileTrackImporter(Context context) {
|
||||
this(context, new ContentProviderUtils(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
KmlFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
|
||||
super(context, importTrackId, contentProviderUtils);
|
||||
KmlFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
||||
super(context, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes)
|
||||
throws SAXException {
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||
switch (tag) {
|
||||
case TAG_PLACEMARK:
|
||||
case TAG_PHOTO_OVERLAY:
|
||||
@@ -109,50 +104,70 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||
//TODO Check if order is relevant (uses localname and tag); and convert to switch statement
|
||||
if (tag.equals(TAG_KML)) {
|
||||
onFileEnd();
|
||||
} else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) {
|
||||
// Note that a track is contained in a Placemark, calling onWaypointend is save since waypointType is not set for a track.
|
||||
onWaypointEnd();
|
||||
} else if (localName.equals(TAG_COORDINATES)) {
|
||||
onWaypointLocationEnd();
|
||||
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
|
||||
onTrackEnd();
|
||||
} else if (tag.equals(TAG_GX_TRACK)) {
|
||||
onTrackSegmentEnd();
|
||||
} else if (tag.equals(TAG_GX_COORD)) {
|
||||
onTrackPointEnd();
|
||||
} else if (tag.equals(TAG_GX_VALUE)) {
|
||||
onExtendedDataValueEnd();
|
||||
} else if (tag.equals(TAG_NAME)) {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_DESCRIPTION)) {
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_ICON)) {
|
||||
if (content != null) {
|
||||
icon = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_VALUE)) {
|
||||
if (content != null) {
|
||||
category = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_WHEN)) {
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_STYLE_URL)) {
|
||||
if (content != null) {
|
||||
waypointType = content.trim();
|
||||
}
|
||||
} else if (localName.equals(TAG_HREF)) {
|
||||
if (content != null) {
|
||||
photoUrl = content.trim();
|
||||
}
|
||||
switch (tag) {
|
||||
case TAG_KML:
|
||||
onFileEnd();
|
||||
break;
|
||||
case TAG_PLACEMARK:
|
||||
case TAG_PHOTO_OVERLAY:
|
||||
// Note that a track is contained in a Placemark, calling onWaypointend is save since waypointType is not set for a track.
|
||||
onWaypointEnd();
|
||||
break;
|
||||
case TAG_COORDINATES:
|
||||
onWaypointLocationEnd();
|
||||
break;
|
||||
case TAG_GX_MULTI_TRACK:
|
||||
onTrackEnd();
|
||||
break;
|
||||
case TAG_GX_TRACK:
|
||||
onTrackSegmentEnd();
|
||||
break;
|
||||
case TAG_GX_COORD:
|
||||
onTrackPointEnd();
|
||||
break;
|
||||
case TAG_GX_VALUE:
|
||||
onExtendedDataValueEnd();
|
||||
break;
|
||||
case TAG_NAME:
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_UUID:
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_DESCRIPTION:
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ICON:
|
||||
if (content != null) {
|
||||
icon = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_VALUE:
|
||||
if (content != null) {
|
||||
category = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_WHEN:
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_STYLE_URL:
|
||||
if (content != null) {
|
||||
waypointType = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_HREF:
|
||||
if (content != null) {
|
||||
photoUrl = content.trim();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
|
||||
@@ -52,18 +52,15 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
private final Context context;
|
||||
private final long importTrackId;
|
||||
private long importTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
private final Uri uriKmzFile;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @param context the context
|
||||
* @param uriFile URI of the kmz file.
|
||||
*/
|
||||
@Deprecated //TODO Do not pass importTrackId here; get it from somewhere else
|
||||
public KmzTrackImporter(Context context, long importTrackId, Uri uriFile) {
|
||||
public KmzTrackImporter(Context context, Uri uriFile) {
|
||||
this.context = context;
|
||||
this.importTrackId = importTrackId;
|
||||
this.uriKmzFile = uriFile;
|
||||
}
|
||||
|
||||
@@ -244,7 +241,7 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
* @return the imported track id or -1L
|
||||
*/
|
||||
private long parseKml(ZipInputStream zipInputStream) throws IOException {
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId);
|
||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
|
||||
|
||||
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
|
||||
return kmlFileTrackImporter.importFile(byteArrayInputStream);
|
||||
|
||||
Reference in New Issue
Block a user