Skip to content

Commit

Permalink
Support multiple instances of database features
Browse files Browse the repository at this point in the history
- Use Cache UID for CacheContentIndex and CacheFileMetadataIndex. This
  enables SD card swapping for a single device.
- I'm hopeful of finding a way to get the Cache UID to DefaultDownloadIndex
  so we can do the same there.

PiperOrigin-RevId: 234662753
  • Loading branch information
ojw28 authored and andrewlewis committed Feb 20, 2019
1 parent 5b891a4 commit 2685b8b
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
*/
public final class VersionTable {

/** Returned by {@link #getVersion(SQLiteDatabase, int)} if the version is unset. */
/** Returned by {@link #getVersion(SQLiteDatabase, int, String)} if the version is unset. */
public static final int VERSION_UNSET = -1;
/** Version of tables used for offline functionality. */
public static final int FEATURE_OFFLINE = 0;
Expand All @@ -43,18 +43,26 @@ public final class VersionTable {
private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "Versions";

private static final String COLUMN_FEATURE = "feature";
private static final String COLUMN_INSTANCE_UID = "instance_uid";
private static final String COLUMN_VERSION = "version";

private static final String WHERE_FEATURE_EQUALS = COLUMN_FEATURE + " = ?";
private static final String WHERE_FEATURE_AND_INSTANCE_UID_EQUALS =
COLUMN_FEATURE + " = ? AND " + COLUMN_INSTANCE_UID + " = ?";

private static final String PRIMARY_KEY =
"PRIMARY KEY (" + COLUMN_FEATURE + ", " + COLUMN_INSTANCE_UID + ")";
private static final String SQL_CREATE_TABLE_IF_NOT_EXISTS =
"CREATE TABLE IF NOT EXISTS "
+ TABLE_NAME
+ " ("
+ COLUMN_FEATURE
+ " INTEGER PRIMARY KEY NOT NULL,"
+ " INTEGER NOT NULL,"
+ COLUMN_INSTANCE_UID
+ " TEXT NOT NULL,"
+ COLUMN_VERSION
+ " INTEGER NOT NULL)";
+ " INTEGER NOT NULL,"
+ PRIMARY_KEY
+ ")";

@Documented
@Retention(RetentionPolicy.SOURCE)
Expand All @@ -64,51 +72,60 @@ public final class VersionTable {
private VersionTable() {}

/**
* Sets the version of the specified feature.
* Sets the version of a specified instance of a specified feature.
*
* @param writableDatabase The database to update.
* @param feature The feature.
* @param instanceUid The unique identifier of the instance of the feature.
* @param version The version.
*/
public static void setVersion(
SQLiteDatabase writableDatabase, @Feature int feature, int version) {
SQLiteDatabase writableDatabase, @Feature int feature, String instanceUid, int version) {
writableDatabase.execSQL(SQL_CREATE_TABLE_IF_NOT_EXISTS);
ContentValues values = new ContentValues();
values.put(COLUMN_FEATURE, feature);
values.put(COLUMN_INSTANCE_UID, instanceUid);
values.put(COLUMN_VERSION, version);
writableDatabase.replace(TABLE_NAME, /* nullColumnHack= */ null, values);
}

/**
* Removes the version of the specified feature.
* Removes the version of a specified instance of a feature.
*
* @param writableDatabase The database to update.
* @param feature The feature.
* @param instanceUid The unique identifier of the instance of the feature.
*/
public static void removeVersion(SQLiteDatabase writableDatabase, @Feature int feature) {
public static void removeVersion(
SQLiteDatabase writableDatabase, @Feature int feature, String instanceUid) {
if (!tableExists(writableDatabase, TABLE_NAME)) {
return;
}
writableDatabase.delete(TABLE_NAME, WHERE_FEATURE_EQUALS, featureArgument(feature));
writableDatabase.delete(
TABLE_NAME,
WHERE_FEATURE_AND_INSTANCE_UID_EQUALS,
featureAndInstanceUidArguments(feature, instanceUid));
}

/**
* Returns the version of the specified feature, or {@link #VERSION_UNSET} if no version
* information is available.
* Returns the version of a specified instance of a feature, or {@link #VERSION_UNSET} if no
* version is set.
*
* @param database The database to query.
* @param feature The feature.
* @param instanceUid The unique identifier of the instance of the feature.
* @return The version, or {@link #VERSION_UNSET} if no version is set.
*/
public static int getVersion(SQLiteDatabase database, @Feature int feature) {
public static int getVersion(SQLiteDatabase database, @Feature int feature, String instanceUid) {
if (!tableExists(database, TABLE_NAME)) {
return VERSION_UNSET;
}
try (Cursor cursor =
database.query(
TABLE_NAME,
new String[] {COLUMN_VERSION},
WHERE_FEATURE_EQUALS,
featureArgument(feature),
WHERE_FEATURE_AND_INSTANCE_UID_EQUALS,
featureAndInstanceUidArguments(feature, instanceUid),
/* groupBy= */ null,
/* having= */ null,
/* orderBy= */ null)) {
Expand All @@ -128,7 +145,7 @@ public static int getVersion(SQLiteDatabase database, @Feature int feature) {
return count > 0;
}

private static String[] featureArgument(int feature) {
return new String[] {Integer.toString(feature)};
private static String[] featureAndInstanceUidArguments(int feature, String instance) {
return new String[] {Integer.toString(feature), instance};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public final class DefaultDownloadIndex implements DownloadIndex {
@VisibleForTesting
/* package */ static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "Downloads";

// TODO: Support multiple instances. Probably using the underlying cache UID.
@VisibleForTesting /* package */ static final String INSTANCE_UID = "singleton";
@VisibleForTesting /* package */ static final int TABLE_VERSION = 1;

private static final String COLUMN_ID = "id";
Expand Down Expand Up @@ -218,12 +220,14 @@ private void ensureInitialized() {
return;
}
SQLiteDatabase readableDatabase = databaseProvider.getReadableDatabase();
int version = VersionTable.getVersion(readableDatabase, VersionTable.FEATURE_OFFLINE);
int version =
VersionTable.getVersion(readableDatabase, VersionTable.FEATURE_OFFLINE, INSTANCE_UID);
if (version == VersionTable.VERSION_UNSET || version > TABLE_VERSION) {
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction();
try {
VersionTable.setVersion(writableDatabase, VersionTable.FEATURE_OFFLINE, TABLE_VERSION);
VersionTable.setVersion(
writableDatabase, VersionTable.FEATURE_OFFLINE, INSTANCE_UID, TABLE_VERSION);
writableDatabase.execSQL(SQL_DROP_TABLE_IF_EXISTS);
writableDatabase.execSQL(SQL_CREATE_TABLE);
writableDatabase.setTransactionSuccessful();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@
import android.database.sqlite.SQLiteDatabase;
import com.google.android.exoplayer2.database.DatabaseProvider;
import com.google.android.exoplayer2.database.VersionTable;
import com.google.android.exoplayer2.util.Assertions;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/** Maintains an index of cache file metadata. */
/* package */ final class CacheFileMetadataIndex {

private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "CacheFileMetadata";
private static final String TABLE_PREFIX = DatabaseProvider.TABLE_PREFIX + "CacheFileMetadata";
private static final int TABLE_VERSION = 1;

private static final String COLUMN_NAME = "name";
Expand All @@ -44,12 +46,8 @@
new String[] {
COLUMN_NAME, COLUMN_LENGTH, COLUMN_LAST_ACCESS_TIMESTAMP,
};

private static final String SQL_DROP_TABLE_IF_EXISTS = "DROP TABLE IF EXISTS " + TABLE_NAME;
private static final String SQL_CREATE_TABLE =
"CREATE TABLE "
+ TABLE_NAME
+ " ("
private static final String TABLE_SCHEMA =
"("
+ COLUMN_NAME
+ " TEXT PRIMARY KEY NOT NULL,"
+ COLUMN_LENGTH
Expand All @@ -59,18 +57,42 @@

private final DatabaseProvider databaseProvider;

private boolean initialized;
@MonotonicNonNull private String tableName;

public CacheFileMetadataIndex(DatabaseProvider databaseProvider) {
this.databaseProvider = databaseProvider;
}

/** Initializes the index for the given cache UID. */
public void initialize(long uid) {
String hexUid = Long.toHexString(uid);
tableName = TABLE_PREFIX + hexUid;
SQLiteDatabase readableDatabase = databaseProvider.getReadableDatabase();
int version =
VersionTable.getVersion(readableDatabase, VersionTable.FEATURE_CACHE_FILE_METADATA, hexUid);
if (version == VersionTable.VERSION_UNSET || version > TABLE_VERSION) {
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction();
try {
VersionTable.setVersion(
writableDatabase, VersionTable.FEATURE_CACHE_FILE_METADATA, hexUid, TABLE_VERSION);
writableDatabase.execSQL("DROP TABLE IF EXISTS " + tableName);
writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA);
writableDatabase.setTransactionSuccessful();
} finally {
writableDatabase.endTransaction();
}
} else if (version < TABLE_VERSION) {
// There is no previous version currently.
throw new IllegalStateException();
}
}

/**
* Returns all file metadata keyed by file name. The returned map is mutable and may be modified
* by the caller.
*/
public Map<String, CacheFileMetadata> getAll() {
ensureInitialized();
try (Cursor cursor = getCursor()) {
Map<String, CacheFileMetadata> fileMetadata = new HashMap<>(cursor.getCount());
while (cursor.moveToNext()) {
Expand All @@ -91,13 +113,13 @@ public Map<String, CacheFileMetadata> getAll() {
* @param lastAccessTimestamp The file last access timestamp.
*/
public void set(String name, long length, long lastAccessTimestamp) {
ensureInitialized();
Assertions.checkNotNull(tableName);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_NAME, name);
values.put(COLUMN_LENGTH, length);
values.put(COLUMN_LAST_ACCESS_TIMESTAMP, lastAccessTimestamp);
writableDatabase.replace(TABLE_NAME, /* nullColumnHack= */ null, values);
writableDatabase.replace(tableName, /* nullColumnHack= */ null, values);
}

/**
Expand All @@ -106,9 +128,9 @@ public void set(String name, long length, long lastAccessTimestamp) {
* @param name The name of the file whose metadata is to be removed.
*/
public void remove(String name) {
ensureInitialized();
Assertions.checkNotNull(tableName);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.delete(TABLE_NAME, WHERE_NAME_EQUALS, new String[] {name});
writableDatabase.delete(tableName, WHERE_NAME_EQUALS, new String[] {name});
}

/**
Expand All @@ -117,50 +139,25 @@ public void remove(String name) {
* @param names The names of the files whose metadata is to be removed.
*/
public void removeAll(Set<String> names) {
ensureInitialized();
Assertions.checkNotNull(tableName);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction();
try {
for (String name : names) {
writableDatabase.delete(TABLE_NAME, WHERE_NAME_EQUALS, new String[] {name});
writableDatabase.delete(tableName, WHERE_NAME_EQUALS, new String[] {name});
}
writableDatabase.setTransactionSuccessful();
} finally {
writableDatabase.endTransaction();
}
}

private void ensureInitialized() {
if (initialized) {
return;
}
SQLiteDatabase readableDatabase = databaseProvider.getReadableDatabase();
int version =
VersionTable.getVersion(readableDatabase, VersionTable.FEATURE_CACHE_FILE_METADATA);
if (version == VersionTable.VERSION_UNSET || version > TABLE_VERSION) {
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction();
try {
VersionTable.setVersion(
writableDatabase, VersionTable.FEATURE_CACHE_FILE_METADATA, TABLE_VERSION);
writableDatabase.execSQL(SQL_DROP_TABLE_IF_EXISTS);
writableDatabase.execSQL(SQL_CREATE_TABLE);
writableDatabase.setTransactionSuccessful();
} finally {
writableDatabase.endTransaction();
}
} else if (version < TABLE_VERSION) {
// There is no previous version currently.
throw new IllegalStateException();
}
initialized = true;
}

private Cursor getCursor() {
Assertions.checkNotNull(tableName);
return databaseProvider
.getReadableDatabase()
.query(
TABLE_NAME,
tableName,
COLUMNS,
/* selection */ null,
/* selectionArgs= */ null,
Expand Down
Loading

0 comments on commit 2685b8b

Please sign in to comment.