Commit a6c1dbe1 by olly Committed by Oliver Woodman

Implement CacheFileMetadataIndex using SQLite

PiperOrigin-RevId: 232670039
parent 7eed60c9
...@@ -35,8 +35,10 @@ public final class VersionTable { ...@@ -35,8 +35,10 @@ public final class VersionTable {
public static final int VERSION_UNSET = -1; public static final int VERSION_UNSET = -1;
/** Version of tables used for offline functionality. */ /** Version of tables used for offline functionality. */
public static final int FEATURE_OFFLINE = 0; public static final int FEATURE_OFFLINE = 0;
/** Version of tables used for cache functionality. */ /** Version of tables used for cache content metadata. */
public static final int FEATURE_CACHE = 1; public static final int FEATURE_CACHE_CONTENT_METADATA = 1;
/** Version of tables used for cache file metadata. */
public static final int FEATURE_CACHE_FILE_METADATA = 2;
private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "Versions"; private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "Versions";
...@@ -54,7 +56,7 @@ public final class VersionTable { ...@@ -54,7 +56,7 @@ public final class VersionTable {
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({FEATURE_OFFLINE, FEATURE_CACHE}) @IntDef({FEATURE_OFFLINE, FEATURE_CACHE_CONTENT_METADATA, FEATURE_CACHE_FILE_METADATA})
private @interface Feature {} private @interface Feature {}
private VersionTable() {} private VersionTable() {}
......
...@@ -15,19 +15,72 @@ ...@@ -15,19 +15,72 @@
*/ */
package com.google.android.exoplayer2.upstream.cache; package com.google.android.exoplayer2.upstream.cache;
import java.util.Collections; import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.google.android.exoplayer2.database.DatabaseProvider;
import com.google.android.exoplayer2.database.VersionTable;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
/** Maintains an index of cache file metadata. */ /** Maintains an index of cache file metadata. */
/* package */ class CacheFileMetadataIndex { /* package */ final class CacheFileMetadataIndex {
private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "CacheFileMetadata";
private static final int TABLE_VERSION = 1;
private static final String COLUMN_NAME = "name";
private static final String COLUMN_LENGTH = "length";
private static final String COLUMN_LAST_ACCESS_TIMESTAMP = "last_access_timestamp";
private static final int COLUMN_INDEX_NAME = 0;
private static final int COLUMN_INDEX_LENGTH = 1;
private static final int COLUMN_INDEX_LAST_ACCESS_TIMESTAMP = 2;
private static final String WHERE_NAME_EQUALS = COLUMN_INDEX_NAME + " = ?";
private static final String[] COLUMNS =
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
+ " ("
+ COLUMN_NAME
+ " TEXT PRIMARY KEY NOT NULL,"
+ COLUMN_LENGTH
+ " INTEGER NOT NULL,"
+ COLUMN_LAST_ACCESS_TIMESTAMP
+ " INTEGER NOT NULL)";
private final DatabaseProvider databaseProvider;
private boolean initialized;
public CacheFileMetadataIndex(DatabaseProvider databaseProvider) {
this.databaseProvider = databaseProvider;
}
/** /**
* Returns all file metadata keyed by file name. The returned map is mutable and may be modified * Returns all file metadata keyed by file name. The returned map is mutable and may be modified
* by the caller. * by the caller.
*/ */
public Map<String, CacheFileMetadata> getAll() { public Map<String, CacheFileMetadata> getAll() {
return Collections.emptyMap(); ensureInitialized();
try (Cursor cursor = getCursor()) {
Map<String, CacheFileMetadata> fileMetadata = new HashMap<>(cursor.getCount());
while (cursor.moveToNext()) {
String name = cursor.getString(COLUMN_INDEX_NAME);
long length = cursor.getLong(COLUMN_INDEX_LENGTH);
long lastAccessTimestamp = cursor.getLong(COLUMN_INDEX_LAST_ACCESS_TIMESTAMP);
fileMetadata.put(name, new CacheFileMetadata(length, lastAccessTimestamp));
}
return fileMetadata;
}
} }
/** /**
...@@ -36,11 +89,15 @@ import java.util.Set; ...@@ -36,11 +89,15 @@ import java.util.Set;
* @param name The name of the file. * @param name The name of the file.
* @param length The file length. * @param length The file length.
* @param lastAccessTimestamp The file last access timestamp. * @param lastAccessTimestamp The file last access timestamp.
* @return Whether the index was updated successfully.
*/ */
public boolean set(String name, long length, long lastAccessTimestamp) { public void set(String name, long length, long lastAccessTimestamp) {
// TODO. ensureInitialized();
return false; 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);
} }
/** /**
...@@ -49,7 +106,9 @@ import java.util.Set; ...@@ -49,7 +106,9 @@ import java.util.Set;
* @param name The name of the file whose metadata is to be removed. * @param name The name of the file whose metadata is to be removed.
*/ */
public void remove(String name) { public void remove(String name) {
// TODO. ensureInitialized();
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.delete(TABLE_NAME, WHERE_NAME_EQUALS, new String[] {name});
} }
/** /**
...@@ -58,6 +117,55 @@ import java.util.Set; ...@@ -58,6 +117,55 @@ import java.util.Set;
* @param names The names of the files whose metadata is to be removed. * @param names The names of the files whose metadata is to be removed.
*/ */
public void removeAll(Set<String> names) { public void removeAll(Set<String> names) {
// TODO. ensureInitialized();
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction();
try {
for (String name : names) {
writableDatabase.delete(TABLE_NAME, 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() {
return databaseProvider
.getReadableDatabase()
.query(
TABLE_NAME,
COLUMNS,
/* selection */ null,
/* selectionArgs= */ null,
/* groupBy= */ null,
/* having= */ null,
/* orderBy= */ null);
} }
} }
...@@ -678,7 +678,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -678,7 +678,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// the entire table. Currently this implementation only encrypts new and updated entries. // the entire table. Currently this implementation only encrypts new and updated entries.
private static final class SQLiteStorage implements Storage { private static final class SQLiteStorage implements Storage {
private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "Cache"; private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "CacheContentMetadata";
private static final int TABLE_VERSION = 1; private static final int TABLE_VERSION = 1;
private static final String COLUMN_ID = "id"; private static final String COLUMN_ID = "id";
...@@ -736,7 +736,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -736,7 +736,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
public boolean exists() { public boolean exists() {
return file.exists() return file.exists()
&& VersionTable.getVersion( && VersionTable.getVersion(
databaseProvider.getReadableDatabase(), VersionTable.FEATURE_CACHE) databaseProvider.getReadableDatabase(),
VersionTable.FEATURE_CACHE_CONTENT_METADATA)
!= VersionTable.VERSION_UNSET; != VersionTable.VERSION_UNSET;
} }
...@@ -755,7 +756,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -755,7 +756,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
try { try {
int version = int version =
VersionTable.getVersion( VersionTable.getVersion(
databaseProvider.getReadableDatabase(), VersionTable.FEATURE_CACHE); databaseProvider.getReadableDatabase(),
VersionTable.FEATURE_CACHE_CONTENT_METADATA);
if (version == VersionTable.VERSION_UNSET || version > TABLE_VERSION) { if (version == VersionTable.VERSION_UNSET || version > TABLE_VERSION) {
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase(); SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction(); writableDatabase.beginTransaction();
...@@ -870,7 +872,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -870,7 +872,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
private void initializeTable(SQLiteDatabase writableDatabase) { private void initializeTable(SQLiteDatabase writableDatabase) {
VersionTable.setVersion(writableDatabase, VersionTable.FEATURE_CACHE, TABLE_VERSION); VersionTable.setVersion(
writableDatabase, VersionTable.FEATURE_CACHE_CONTENT_METADATA, TABLE_VERSION);
writableDatabase.execSQL(SQL_DROP_TABLE_IF_EXISTS); writableDatabase.execSQL(SQL_DROP_TABLE_IF_EXISTS);
writableDatabase.execSQL(SQL_CREATE_TABLE); writableDatabase.execSQL(SQL_CREATE_TABLE);
} }
......
...@@ -253,13 +253,12 @@ public final class SimpleCache implements Cache { ...@@ -253,13 +253,12 @@ public final class SimpleCache implements Cache {
String fileName = span.file.getName(); String fileName = span.file.getName();
long length = span.length; long length = span.length;
long lastAccessTimestamp = System.currentTimeMillis(); long lastAccessTimestamp = System.currentTimeMillis();
// Updating the file itself to incorporate the new last access timestamp is much slower than boolean updateFile = false;
// updating the file index. Hence we only update the file if we don't have a file index, or if
// updating the file index failed.
boolean updateFile;
if (fileIndex != null) { if (fileIndex != null) {
updateFile = !fileIndex.set(fileName, length, lastAccessTimestamp); fileIndex.set(fileName, length, lastAccessTimestamp);
} else { } else {
// Updating the file itself to incorporate the new last access timestamp is much slower than
// updating the file index. Hence we only update the file if we don't have a file index.
updateFile = true; updateFile = true;
} }
SimpleCacheSpan newSpan = SimpleCacheSpan newSpan =
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer2.database; package com.google.android.exoplayer2.database;
import static com.google.android.exoplayer2.database.VersionTable.FEATURE_CACHE; import static com.google.android.exoplayer2.database.VersionTable.FEATURE_CACHE_CONTENT_METADATA;
import static com.google.android.exoplayer2.database.VersionTable.FEATURE_OFFLINE; import static com.google.android.exoplayer2.database.VersionTable.FEATURE_OFFLINE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
...@@ -61,8 +61,9 @@ public class VersionTableTest { ...@@ -61,8 +61,9 @@ public class VersionTableTest {
VersionTable.setVersion(writableDatabase, FEATURE_OFFLINE, 10); VersionTable.setVersion(writableDatabase, FEATURE_OFFLINE, 10);
assertThat(VersionTable.getVersion(readableDatabase, FEATURE_OFFLINE)).isEqualTo(10); assertThat(VersionTable.getVersion(readableDatabase, FEATURE_OFFLINE)).isEqualTo(10);
VersionTable.setVersion(writableDatabase, FEATURE_CACHE, 5); VersionTable.setVersion(writableDatabase, FEATURE_CACHE_CONTENT_METADATA, 5);
assertThat(VersionTable.getVersion(readableDatabase, FEATURE_CACHE)).isEqualTo(5); assertThat(VersionTable.getVersion(readableDatabase, FEATURE_CACHE_CONTENT_METADATA))
.isEqualTo(5);
assertThat(VersionTable.getVersion(readableDatabase, FEATURE_OFFLINE)).isEqualTo(10); assertThat(VersionTable.getVersion(readableDatabase, FEATURE_OFFLINE)).isEqualTo(10);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment