Skip to content

Commit

Permalink
Split out shared database components for reuse in caching
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 229946997
  • Loading branch information
ojw28 committed Jan 21, 2019
1 parent 02dc937 commit 82da627
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 338 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.database;

import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;

/**
* Provides {@link SQLiteDatabase} instances to ExoPlayer components, which may read and write
* tables prefixed with {@link #TABLE_PREFIX}.
*/
public interface DatabaseProvider {

/** Prefix for tables that can be read and written by ExoPlayer components. */
String TABLE_PREFIX = "ExoPlayer";

/**
* Creates and/or opens a database that will be used for reading and writing.
*
* <p>Once opened successfully, the database is cached, so you can call this method every time you
* need to write to the database. Errors such as bad permissions or a full disk may cause this
* method to fail, but future attempts may succeed if the problem is fixed.
*
* @throws SQLiteException If the database cannot be opened for writing.
* @return A read/write database object.
*/
SQLiteDatabase getWritableDatabase();

/**
* Creates and/or opens a database. This will be the same object returned by {@link
* #getWritableDatabase()} unless some problem, such as a full disk, requires the database to be
* opened read-only. In that case, a read-only database object will be returned. If the problem is
* fixed, a future call to {@link #getWritableDatabase()} may succeed, in which case the read-only
* database object will be closed and the read/write object will be returned in the future.
*
* <p>Once opened successfully, the database is cached, so you can call this method every time you
* need to read from the database.
*
* @throws SQLiteException If the database cannot be opened.
* @return A database object valid until {@link #getWritableDatabase()} is called.
*/
SQLiteDatabase getReadableDatabase();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.database;

import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/** A {@link DatabaseProvider} that provides instances obtained from a {@link SQLiteOpenHelper}. */
public final class DefaultDatabaseProvider implements DatabaseProvider {

private final SQLiteOpenHelper sqliteOpenHelper;

/**
* @param sqliteOpenHelper An {@link SQLiteOpenHelper} from which to obtain database instances.
*/
public DefaultDatabaseProvider(SQLiteOpenHelper sqliteOpenHelper) {
this.sqliteOpenHelper = sqliteOpenHelper;
}

@Override
public SQLiteDatabase getWritableDatabase() {
return sqliteOpenHelper.getWritableDatabase();
}

@Override
public SQLiteDatabase getReadableDatabase() {
return sqliteOpenHelper.getReadableDatabase();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.database;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
* An {@link SQLiteOpenHelper} that provides instances of a standalone ExoPlayer database.
*
* <p>Suitable for use by applications that do not already have their own database, or which would
* prefer to keep ExoPlayer tables isolated in their own database. Other applications should prefer
* to use {@link DefaultDatabaseProvider} with their own {@link SQLiteOpenHelper}.
*/
public final class ExoDatabaseProvider extends SQLiteOpenHelper implements DatabaseProvider {

/** The file name used for the standalone ExoPlayer database. */
public static final String DATABASE_NAME = "exoplayer_internal.db";

private static final int VERSION = 1;

public ExoDatabaseProvider(Context context) {
super(context.getApplicationContext(), DATABASE_NAME, /* factory= */ null, VERSION);
}

@Override
public void onCreate(SQLiteDatabase db) {
// Features create their own tables.
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Features handle their own upgrades.
}

@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO: Wipe the database.
super.onDowngrade(db, oldVersion, newVersion);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.database;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.IntDef;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* A table that holds version information about other ExoPlayer tables. This allows ExoPlayer tables
* to be versioned independently to the version of the containing database.
*/
public final class VersionTable {

/** Returned by {@link #getVersion(int)} 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;
/** Version of tables used for cache functionality. */
public static final int FEATURE_CACHE = 1;

private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "Versions";

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

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

@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({FEATURE_OFFLINE, FEATURE_CACHE})
private @interface Feature {}

private final DatabaseProvider databaseProvider;

public VersionTable(DatabaseProvider databaseProvider) {
this.databaseProvider = databaseProvider;
// Check whether the table exists to avoid getting a writable database if we don't need one.
if (!doesTableExist(databaseProvider, TABLE_NAME)) {
databaseProvider.getWritableDatabase().execSQL(SQL_CREATE_TABLE_IF_NOT_EXISTS);
}
}

/**
* Sets the version of tables belonging to the specified feature.
*
* @param feature The feature.
* @param version The version.
*/
public void setVersion(@Feature int feature, int version) {
ContentValues values = new ContentValues();
values.put(COLUMN_FEATURE, feature);
values.put(COLUMN_VERSION, version);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.replace(TABLE_NAME, /* nullColumnHack= */ null, values);
}

/**
* Returns the version of tables belonging to the specified feature, or {@link #VERSION_UNSET} if
* no version information is available.
*/
public int getVersion(@Feature int feature) {
String selection = COLUMN_FEATURE + " = ?";
String[] selectionArgs = {Integer.toString(feature)};
try (Cursor cursor =
databaseProvider
.getReadableDatabase()
.query(
TABLE_NAME,
new String[] {COLUMN_VERSION},
selection,
selectionArgs,
/* groupBy= */ null,
/* having= */ null,
/* orderBy= */ null)) {
if (cursor.getCount() == 0) {
return VERSION_UNSET;
}
cursor.moveToNext();
return cursor.getInt(/* COLUMN_VERSION index */ 0);
}
}

/* package */ static boolean doesTableExist(DatabaseProvider databaseProvider, String tableName) {
SQLiteDatabase readableDatabase = databaseProvider.getReadableDatabase();
long count =
DatabaseUtils.queryNumEntries(
readableDatabase, "sqlite_master", "tbl_name = ?", new String[] {tableName});
return count > 0;
}
}
Loading

0 comments on commit 82da627

Please sign in to comment.