forked from upstream-mirrors/OpenTracks
Generate UUID for each track (necessary for synchronization).
Fixes #294.
This commit is contained in:
+18
-12
@@ -37,6 +37,7 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -47,6 +48,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -171,22 +173,26 @@ public class CustomContentProviderUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTrack() {
|
||||
int startColumnIndex = 1;
|
||||
int columnIndex = startColumnIndex;
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex++);
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex++);
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
// Name
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
long trackId = System.currentTimeMillis();
|
||||
columnIndex = startColumnIndex;
|
||||
|
||||
int columnIndex = 1;
|
||||
// Id
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getLong(columnIndex)).thenReturn(trackId);
|
||||
|
||||
//Uuid
|
||||
columnIndex++;
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.UUID)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getBlob(columnIndex)).thenReturn(UUIDUtils.toBytes(UUID.randomUUID()));
|
||||
|
||||
// Name
|
||||
columnIndex++;
|
||||
String name = NAME_PREFIX + trackId;
|
||||
when(cursorMock.getString(columnIndex++)).thenReturn(name);
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getString(columnIndex)).thenReturn(name);
|
||||
|
||||
Track track = contentProviderUtils.createTrack(cursorMock);
|
||||
Assert.assertEquals(trackId, track.getId());
|
||||
|
||||
+27
-22
@@ -5,6 +5,7 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
@@ -36,7 +37,8 @@ public class CustomSQLiteOpenHelperTest {
|
||||
*
|
||||
* @return Map(TableName, SQL)
|
||||
*/
|
||||
private static Map<String, String> getTableSQL(SQLiteDatabase db) {
|
||||
@VisibleForTesting
|
||||
public static Map<String, String> getTableSQL(SQLiteDatabase db) {
|
||||
HashMap<String, String> tableSQL = new HashMap<>();
|
||||
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + WaypointsColumns.TABLE_NAME + "')", null, null, null, "name")) {
|
||||
if (cursor != null) {
|
||||
@@ -53,6 +55,7 @@ public class CustomSQLiteOpenHelperTest {
|
||||
*
|
||||
* @return Map(tableName, SQL)
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
|
||||
HashMap<String, String> indexSQL = new HashMap<>();
|
||||
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
|
||||
@@ -104,24 +107,7 @@ public class CustomSQLiteOpenHelperTest {
|
||||
|
||||
@Test
|
||||
public void onUpgrade_FromVersion23() {
|
||||
// Manually create database schema with version 23 (base version)
|
||||
{
|
||||
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
}.getWritableDatabase();
|
||||
|
||||
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
|
||||
|
||||
dbBase.close();
|
||||
}
|
||||
createVersion23();
|
||||
|
||||
// Open database with SQL upgrade
|
||||
Map<String, String> tableByUpgrade;
|
||||
@@ -130,10 +116,9 @@ public class CustomSQLiteOpenHelperTest {
|
||||
tableByUpgrade = getTableSQL(dbUpgraded);
|
||||
indicesByUpgrade = getIndexSQL(dbUpgraded);
|
||||
}
|
||||
|
||||
// Open database via
|
||||
context.deleteDatabase(DATABASE_NAME);
|
||||
|
||||
// Open database via creation script
|
||||
Map<String, String> tablesByCreate;
|
||||
Map<String, String> indicesByCreate;
|
||||
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
|
||||
@@ -150,7 +135,8 @@ public class CustomSQLiteOpenHelperTest {
|
||||
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
|
||||
|
||||
// then - verify custom indices
|
||||
Assert.assertEquals(2, indicesByCreate.size());
|
||||
Assert.assertEquals(3, indicesByCreate.size());
|
||||
Assert.assertEquals(indicesByUpgrade.get(TracksColumns.TABLE_NAME), indicesByCreate.get(TracksColumns.TABLE_NAME));
|
||||
Assert.assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
|
||||
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
|
||||
}
|
||||
@@ -176,4 +162,23 @@ public class CustomSQLiteOpenHelperTest {
|
||||
// then - verify custom indices
|
||||
Assert.assertEquals(0, indicesByDowngrade.size());
|
||||
}
|
||||
|
||||
private void createVersion23() {
|
||||
// Manually create database schema with version 23 (base version)
|
||||
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
}.getWritableDatabase();
|
||||
|
||||
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
|
||||
|
||||
dbBase.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class UUIDUtilsTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
byte[] bytes = UUIDUtils.toBytes(uuid);
|
||||
UUID output = UUIDUtils.fromBytes(bytes);
|
||||
|
||||
Assert.assertEquals(uuid, output);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
@@ -27,6 +29,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
public class Track {
|
||||
|
||||
private long id = -1L;
|
||||
private UUID uuid = UUID.randomUUID();
|
||||
|
||||
private String name = "";
|
||||
private String description = "";
|
||||
private String category = "";
|
||||
@@ -46,6 +50,14 @@ public class Track {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public interface TracksColumns extends BaseColumns {
|
||||
String DEFAULT_SORT_ORDER = "_id";
|
||||
|
||||
// Columns
|
||||
String UUID = "uuid";
|
||||
String NAME = "name"; // track name
|
||||
String DESCRIPTION = "description"; // track description
|
||||
String CATEGORY = "category"; // track activity type
|
||||
@@ -71,5 +72,9 @@ public interface TracksColumns extends BaseColumns {
|
||||
+ MINELEVATION + " FLOAT, " // min elevation
|
||||
+ MAXELEVATION + " FLOAT, " // max elevation
|
||||
+ ELEVATIONGAIN + " FLOAT, " // elevation gain
|
||||
+ ICON + " TEXT)"; // icon
|
||||
+ ICON + " TEXT, " // icon
|
||||
+ UUID + " BLOB)"; // UUID
|
||||
|
||||
String CREATE_TABLE_INDEX = "CREATE UNIQUE INDEX " + TABLE_NAME + "_" + UUID + "_index ON " + TABLE_NAME + "(" + UUID + ")";
|
||||
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
|
||||
/**
|
||||
* {@link ContentProviderUtils} implementation.
|
||||
@@ -99,6 +100,7 @@ public class ContentProviderUtils {
|
||||
*/
|
||||
public Track createTrack(Cursor cursor) {
|
||||
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||
int uuidIndex = cursor.getColumnIndexOrThrow(TracksColumns.UUID);
|
||||
int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
||||
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
||||
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
||||
@@ -118,6 +120,9 @@ public class ContentProviderUtils {
|
||||
if (!cursor.isNull(idIndex)) {
|
||||
track.setId(cursor.getLong(idIndex));
|
||||
}
|
||||
if (!cursor.isNull(uuidIndex)) {
|
||||
track.setUuid(UUIDUtils.fromBytes(cursor.getBlob(uuidIndex)));
|
||||
}
|
||||
if (!cursor.isNull(nameIndex)) {
|
||||
track.setName(cursor.getString(nameIndex));
|
||||
}
|
||||
@@ -287,6 +292,7 @@ public class ContentProviderUtils {
|
||||
if (track.getId() >= 0) {
|
||||
values.put(TracksColumns._ID, track.getId());
|
||||
}
|
||||
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
|
||||
values.put(TracksColumns.NAME, track.getName());
|
||||
values.put(TracksColumns.DESCRIPTION, track.getDescription());
|
||||
values.put(TracksColumns.CATEGORY, track.getCategory());
|
||||
|
||||
+51
-5
@@ -1,15 +1,20 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
|
||||
/**
|
||||
* Database helper for creating and upgrading the database.
|
||||
@@ -17,13 +22,11 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
@VisibleForTesting
|
||||
class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
@VisibleForTesting
|
||||
private static final int DATABASE_VERSION = 25;
|
||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||
|
||||
@VisibleForTesting
|
||||
private static final String DATABASE_NAME = "database.db";
|
||||
private static final int DATABASE_VERSION = 26;
|
||||
|
||||
private static final String DATABASE_NAME = "database.db";
|
||||
|
||||
public CustomSQLiteOpenHelper(Context context) {
|
||||
this(context, DATABASE_NAME);
|
||||
@@ -45,6 +48,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(TrackPointsColumns.CREATE_TABLE_INDEX);
|
||||
|
||||
db.execSQL(TracksColumns.CREATE_TABLE);
|
||||
db.execSQL(TracksColumns.CREATE_TABLE_INDEX);
|
||||
|
||||
db.execSQL(WaypointsColumns.CREATE_TABLE);
|
||||
db.execSQL(WaypointsColumns.CREATE_TABLE_INDEX);
|
||||
@@ -61,6 +65,9 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 25:
|
||||
upgradeFrom24to25(db);
|
||||
break;
|
||||
case 26:
|
||||
upgradeFrom25to26(db);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||
@@ -79,6 +86,9 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 24:
|
||||
downgradeFrom25to24(db);
|
||||
break;
|
||||
case 25:
|
||||
downgradeFrom26to25(db);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||
@@ -107,7 +117,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
private void downgradeFrom24to23(SQLiteDatabase db) {
|
||||
//Not needed as the delete columns did not contain any data
|
||||
//Not needed as the deleted columns did not contain any data
|
||||
db.beginTransaction();
|
||||
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)");
|
||||
@@ -142,4 +152,40 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void upgradeFrom25to26(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("ALTER TABLE tracks ADD COLUMN uuid BLOB");
|
||||
try (Cursor cursor = db.query("tracks", new String[]{"_id"}, null, null, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
int trackIdIndex = cursor.getColumnIndexOrThrow("_id");
|
||||
do {
|
||||
long trackId = cursor.getLong(trackIdIndex);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("uuid", UUIDUtils.toBytes(UUID.randomUUID()));
|
||||
db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId)});
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
db.execSQL("CREATE UNIQUE INDEX tracks_uuid_index ON tracks(uuid)");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void downgradeFrom26to25(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("DROP INDEX tracks_uuid_index");
|
||||
|
||||
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)");
|
||||
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, 0, 0, icon FROM tracks_old");
|
||||
db.execSQL("DROP TABLE tracks_old");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDUtils {
|
||||
|
||||
public static UUID fromBytes(byte[] bytes) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
||||
long mostSignificant = byteBuffer.getLong();
|
||||
long lestSignificant = byteBuffer.getLong();
|
||||
return new UUID(mostSignificant, lestSignificant);
|
||||
}
|
||||
|
||||
public static byte[] toBytes(@NonNull UUID uuid) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
|
||||
byteBuffer.putLong(uuid.getMostSignificantBits());
|
||||
byteBuffer.putLong(uuid.getLeastSignificantBits());
|
||||
return byteBuffer.array();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user