Commit a8d8f610 by olly Committed by Oliver Woodman

Add SimpleCache deletion functionality

This is needed now that index data may be stored outside
of the cache directory.

PiperOrigin-RevId: 237051112
parent ea42bfbf
...@@ -61,6 +61,34 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -61,6 +61,34 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@MonotonicNonNull private String tableName; @MonotonicNonNull private String tableName;
/**
* Deletes index data for the specified cache.
*
* @param databaseProvider Provides the database in which the index is stored.
* @param uid The cache UID.
* @throws DatabaseIOException If an error occurs deleting the index data.
*/
public static void delete(DatabaseProvider databaseProvider, long uid)
throws DatabaseIOException {
String hexUid = Long.toHexString(uid);
try {
String tableName = getTableName(hexUid);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction();
try {
VersionTable.removeVersion(
writableDatabase, VersionTable.FEATURE_CACHE_FILE_METADATA, hexUid);
dropTable(writableDatabase, tableName);
writableDatabase.setTransactionSuccessful();
} finally {
writableDatabase.endTransaction();
}
} catch (SQLException e) {
throw new DatabaseIOException(e);
}
}
/** @param databaseProvider Provides the database in which the index is stored. */
public CacheFileMetadataIndex(DatabaseProvider databaseProvider) { public CacheFileMetadataIndex(DatabaseProvider databaseProvider) {
this.databaseProvider = databaseProvider; this.databaseProvider = databaseProvider;
} }
...@@ -68,12 +96,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -68,12 +96,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Initializes the index for the given cache UID. * Initializes the index for the given cache UID.
* *
* @param uid The cache UID.
* @throws DatabaseIOException If an error occurs initializing the index. * @throws DatabaseIOException If an error occurs initializing the index.
*/ */
public void initialize(long uid) throws DatabaseIOException { public void initialize(long uid) throws DatabaseIOException {
try { try {
String hexUid = Long.toHexString(uid); String hexUid = Long.toHexString(uid);
tableName = TABLE_PREFIX + hexUid; tableName = getTableName(hexUid);
SQLiteDatabase readableDatabase = databaseProvider.getReadableDatabase(); SQLiteDatabase readableDatabase = databaseProvider.getReadableDatabase();
int version = int version =
VersionTable.getVersion( VersionTable.getVersion(
...@@ -84,7 +113,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -84,7 +113,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
try { try {
VersionTable.setVersion( VersionTable.setVersion(
writableDatabase, VersionTable.FEATURE_CACHE_FILE_METADATA, hexUid, TABLE_VERSION); writableDatabase, VersionTable.FEATURE_CACHE_FILE_METADATA, hexUid, TABLE_VERSION);
writableDatabase.execSQL("DROP TABLE IF EXISTS " + tableName); dropTable(writableDatabase, tableName);
writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA); writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA);
writableDatabase.setTransactionSuccessful(); writableDatabase.setTransactionSuccessful();
} finally { } finally {
...@@ -196,4 +225,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -196,4 +225,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* having= */ null, /* having= */ null,
/* orderBy= */ null); /* orderBy= */ null);
} }
private static void dropTable(SQLiteDatabase writableDatabase, String tableName) {
writableDatabase.execSQL("DROP TABLE IF EXISTS " + tableName);
}
private static String getTableName(String hexUid) {
return TABLE_PREFIX + hexUid;
}
} }
...@@ -104,6 +104,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -104,6 +104,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
/** /**
* Deletes index data for the specified cache.
*
* @param databaseProvider Provides the database in which the index is stored.
* @param uid The cache UID.
* @throws DatabaseIOException If an error occurs deleting the index data.
*/
public static void delete(DatabaseProvider databaseProvider, long uid)
throws DatabaseIOException {
DatabaseStorage.delete(databaseProvider, uid);
}
/**
* Creates an instance supporting database storage only. * Creates an instance supporting database storage only.
* *
* @param databaseProvider Provides the database in which the index is stored. * @param databaseProvider Provides the database in which the index is stored.
...@@ -717,6 +729,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -717,6 +729,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private String hexUid; private String hexUid;
private String tableName; private String tableName;
public static void delete(DatabaseProvider databaseProvider, long uid)
throws DatabaseIOException {
delete(databaseProvider, Long.toHexString(uid));
}
public DatabaseStorage(DatabaseProvider databaseProvider) { public DatabaseStorage(DatabaseProvider databaseProvider) {
this.databaseProvider = databaseProvider; this.databaseProvider = databaseProvider;
pendingUpdates = new SparseArray<>(); pendingUpdates = new SparseArray<>();
...@@ -725,7 +742,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -725,7 +742,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override @Override
public void initialize(long uid) { public void initialize(long uid) {
hexUid = Long.toHexString(uid); hexUid = Long.toHexString(uid);
tableName = TABLE_PREFIX + hexUid; tableName = getTableName(hexUid);
} }
@Override @Override
...@@ -739,20 +756,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -739,20 +756,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override @Override
public void delete() throws DatabaseIOException { public void delete() throws DatabaseIOException {
try { delete(databaseProvider, hexUid);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction();
try {
VersionTable.removeVersion(
writableDatabase, VersionTable.FEATURE_CACHE_CONTENT_METADATA, hexUid);
dropTable(writableDatabase);
writableDatabase.setTransactionSuccessful();
} finally {
writableDatabase.endTransaction();
}
} catch (SQLException e) {
throw new DatabaseIOException(e);
}
} }
@Override @Override
...@@ -875,14 +879,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -875,14 +879,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private void initializeTable(SQLiteDatabase writableDatabase) throws DatabaseIOException { private void initializeTable(SQLiteDatabase writableDatabase) throws DatabaseIOException {
VersionTable.setVersion( VersionTable.setVersion(
writableDatabase, VersionTable.FEATURE_CACHE_CONTENT_METADATA, hexUid, TABLE_VERSION); writableDatabase, VersionTable.FEATURE_CACHE_CONTENT_METADATA, hexUid, TABLE_VERSION);
dropTable(writableDatabase); dropTable(writableDatabase, tableName);
writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA); writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA);
} }
private void dropTable(SQLiteDatabase writableDatabase) {
writableDatabase.execSQL("DROP TABLE IF EXISTS " + tableName);
}
private void deleteRow(SQLiteDatabase writableDatabase, int key) { private void deleteRow(SQLiteDatabase writableDatabase, int key) {
writableDatabase.delete(tableName, WHERE_ID_EQUALS, new String[] {Integer.toString(key)}); writableDatabase.delete(tableName, WHERE_ID_EQUALS, new String[] {Integer.toString(key)});
} }
...@@ -899,5 +899,32 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -899,5 +899,32 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
values.put(COLUMN_METADATA, data); values.put(COLUMN_METADATA, data);
writableDatabase.replaceOrThrow(tableName, /* nullColumnHack= */ null, values); writableDatabase.replaceOrThrow(tableName, /* nullColumnHack= */ null, values);
} }
private static void delete(DatabaseProvider databaseProvider, String hexUid)
throws DatabaseIOException {
try {
String tableName = getTableName(hexUid);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransaction();
try {
VersionTable.removeVersion(
writableDatabase, VersionTable.FEATURE_CACHE_CONTENT_METADATA, hexUid);
dropTable(writableDatabase, tableName);
writableDatabase.setTransactionSuccessful();
} finally {
writableDatabase.endTransaction();
}
} catch (SQLException e) {
throw new DatabaseIOException(e);
}
}
private static void dropTable(SQLiteDatabase writableDatabase, String tableName) {
writableDatabase.execSQL("DROP TABLE IF EXISTS " + tableName);
}
private static String getTableName(String hexUid) {
return TABLE_PREFIX + hexUid;
}
} }
} }
...@@ -19,9 +19,11 @@ import android.os.ConditionVariable; ...@@ -19,9 +19,11 @@ import android.os.ConditionVariable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.database.DatabaseIOException;
import com.google.android.exoplayer2.database.DatabaseProvider; import com.google.android.exoplayer2.database.DatabaseProvider;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
...@@ -36,8 +38,13 @@ import java.util.TreeSet; ...@@ -36,8 +38,13 @@ import java.util.TreeSet;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* A {@link Cache} implementation that maintains an in-memory representation. Note, only one * A {@link Cache} implementation that maintains an in-memory representation.
* instance of SimpleCache is allowed for a given directory at a given time. *
* <p>Only one instance of SimpleCache is allowed for a given directory at a given time.
*
* <p>To delete a SimpleCache, use {@link #delete(File, DatabaseProvider)} rather than deleting the
* directory and its contents directly. This is necessary to ensure that associated index data is
* also removed.
*/ */
public final class SimpleCache implements Cache { public final class SimpleCache implements Cache {
...@@ -94,6 +101,45 @@ public final class SimpleCache implements Cache { ...@@ -94,6 +101,45 @@ public final class SimpleCache implements Cache {
} }
/** /**
* Deletes all content belonging to a cache instance.
*
* @param cacheDir The cache directory.
* @param databaseProvider The database in which index data is stored, or {@code null} if the
* cache used a legacy index.
*/
public static void delete(File cacheDir, @Nullable DatabaseProvider databaseProvider) {
if (!cacheDir.exists()) {
return;
}
File[] files = cacheDir.listFiles();
if (files == null) {
cacheDir.delete();
return;
}
if (databaseProvider != null) {
// Make a best effort to read the cache UID and delete associated index data before deleting
// cache directory itself.
long uid = loadUid(files);
if (uid != -1) {
try {
CacheFileMetadataIndex.delete(databaseProvider, uid);
} catch (DatabaseIOException e) {
Log.w(TAG, "Failed to delete file metadata: " + uid);
}
try {
CachedContentIndex.delete(databaseProvider, uid);
} catch (DatabaseIOException e) {
Log.w(TAG, "Failed to delete file metadata: " + uid);
}
}
}
Util.recursiveDelete(cacheDir);
}
/**
* Constructs the cache. The cache will delete any unrecognized files from the directory. Hence * Constructs the cache. The cache will delete any unrecognized files from the directory. Hence
* the directory cannot be used to store other files. * the directory cannot be used to store other files.
* *
...@@ -524,12 +570,15 @@ public final class SimpleCache implements Cache { ...@@ -524,12 +570,15 @@ public final class SimpleCache implements Cache {
return; return;
} }
uid = loadUid(files);
if (uid == -1) {
try { try {
uid = loadUid(cacheDir, files); uid = createUid(cacheDir);
} catch (IOException e) { } catch (IOException e) {
initializationException = new CacheException("Failed to load cache UID: " + cacheDir); initializationException = new CacheException("Failed to create cache UID: " + cacheDir);
return; return;
} }
}
try { try {
contentIndex.initialize(uid); contentIndex.initialize(uid);
...@@ -686,14 +735,13 @@ public final class SimpleCache implements Cache { ...@@ -686,14 +735,13 @@ public final class SimpleCache implements Cache {
} }
/** /**
* Loads the cache UID from the files belonging to the root directory, generating one if needed. * Loads the cache UID from the files belonging to the root directory.
* *
* @param directory The root directory.
* @param files The files belonging to the root directory. * @param files The files belonging to the root directory.
* @return The cache loaded UID. * @return The loaded UID, or -1 if a UID has not yet been created.
* @throws IOException If there is an error loading or generating the UID. * @throws IOException If there is an error loading or generating the UID.
*/ */
private static long loadUid(File directory, File[] files) throws IOException { private static long loadUid(File[] files) {
for (File file : files) { for (File file : files) {
String fileName = file.getName(); String fileName = file.getName();
if (fileName.endsWith(UID_FILE_SUFFIX)) { if (fileName.endsWith(UID_FILE_SUFFIX)) {
...@@ -706,7 +754,7 @@ public final class SimpleCache implements Cache { ...@@ -706,7 +754,7 @@ public final class SimpleCache implements Cache {
} }
} }
} }
return createUid(directory); return -1;
} }
@SuppressWarnings("TrulyRandom") @SuppressWarnings("TrulyRandom")
......
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