package cn.rongcloud.im.stetho;
import android.annotation.TargetApi;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.support.annotation.NonNull;
import com.facebook.stetho.common.Util;
import com.facebook.stetho.inspector.database.DatabaseConnectionProvider;
import com.facebook.stetho.inspector.database.DatabaseFilesProvider;
import com.facebook.stetho.inspector.database.DefaultDatabaseConnectionProvider;
import com.facebook.stetho.inspector.database.DefaultDatabaseFilesProvider;
import com.facebook.stetho.inspector.protocol.module.Database;
import com.facebook.stetho.inspector.protocol.module.DatabaseConstants;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.concurrent.ThreadSafe;
/**
* Created by jiangecho on 2016/12/21.
*/
@ThreadSafe
public class RongDatabaseDriver extends Database.DatabaseDriver {
private static final String[] UNINTERESTING_FILENAME_SUFFIXES = new String[]{
"-journal",
"-shm",
"-uid",
"-wal"
};
private final DatabaseFilesProvider mDatabaseFilesProvider;
private final DatabaseConnectionProvider mDatabaseConnectionProvider;
private List<String> mDatabases;
/**
* Constructs the object with a {@link DatabaseFilesProvider} that supplies the database files
* from {@link Context#databaseList()}.
*
* @param context the context
* @deprecated use {@link com.facebook.stetho.inspector.database.SqliteDatabaseDriver#SqliteDatabaseDriver(Context, DatabaseFilesProvider, DatabaseConnectionProvider)}
*/
@Deprecated
public RongDatabaseDriver(Context context) {
this(
context,
new DefaultDatabaseFilesProvider(context),
new DefaultDatabaseConnectionProvider());
}
/**
* @param context the context
* @param databaseFilesProvider a database file name provider
* @param databaseConnectionProvider a database connection provider
*/
public RongDatabaseDriver(
Context context,
DatabaseFilesProvider databaseFilesProvider,
DatabaseConnectionProvider databaseConnectionProvider) {
super(context);
mDatabaseFilesProvider = databaseFilesProvider;
mDatabaseConnectionProvider = databaseConnectionProvider;
}
@Override
public List<String> getDatabaseNames() {
if (mDatabases == null) {
mDatabases = new ArrayList<>();
List<File> potentialDatabaseFiles = mDatabaseFilesProvider.getDatabaseFiles();
Collections.sort(potentialDatabaseFiles);
Iterable<File> tidiedList = tidyDatabaseList(potentialDatabaseFiles);
for (File database : tidiedList) {
mDatabases.add(buildDatabaseDisplayName(database));
}
}
return mDatabases;
}
private String buildDatabaseDisplayName(@NonNull File databaseFile) {
String fileName = databaseFile.getName();
String path = databaseFile.getAbsolutePath();
if (path.contains("databases")) { // db files in databases dir
return fileName;
} else {
// rong database
String parent = databaseFile.getParent();
return parent.substring(parent.lastIndexOf('/') + 1) + " " + fileName;
}
}
/**
* Attempt to smartly eliminate uninteresting shadow databases such as -journal and -uid. Note
* that this only removes the database if it is true that it shadows another database lacking
* the uninteresting suffix.
*
* @param databaseFiles Raw list of database files.
* @return Tidied list with shadow databases removed.
*/
// @VisibleForTesting
static List<File> tidyDatabaseList(List<File> databaseFiles) {
Set<File> originalAsSet = new HashSet<File>(databaseFiles);
List<File> tidiedList = new ArrayList<File>();
for (File databaseFile : databaseFiles) {
String databaseFilename = databaseFile.getPath();
String sansSuffix = removeSuffix(databaseFilename, UNINTERESTING_FILENAME_SUFFIXES);
if (sansSuffix.equals(databaseFilename) || !originalAsSet.contains(new File(sansSuffix))) {
tidiedList.add(databaseFile);
}
}
return tidiedList;
}
private static String removeSuffix(String str, String[] suffixesToRemove) {
for (String suffix : suffixesToRemove) {
if (str.endsWith(suffix)) {
return str.substring(0, str.length() - suffix.length());
}
}
return str;
}
public List<String> getTableNames(String databaseName)
throws SQLiteException {
SQLiteDatabase database = openDatabase(databaseName);
try {
Cursor cursor = database.rawQuery("SELECT name FROM sqlite_master WHERE type IN (?, ?)",
new String[]{"table", "view"});
try {
List<String> tableNames = new ArrayList<String>();
while (cursor.moveToNext()) {
tableNames.add(cursor.getString(0));
}
return tableNames;
} finally {
cursor.close();
}
} finally {
database.close();
}
}
public Database.ExecuteSQLResponse executeSQL(String databaseName, String query, ExecuteResultHandler<Database.ExecuteSQLResponse> handler)
throws SQLiteException {
Util.throwIfNull(query);
Util.throwIfNull(handler);
SQLiteDatabase database = openDatabase(databaseName);
try {
String firstWordUpperCase = getFirstWord(query).toUpperCase();
switch (firstWordUpperCase) {
case "UPDATE":
case "DELETE":
return executeUpdateDelete(database, query, handler);
case "INSERT":
return executeInsert(database, query, handler);
case "SELECT":
case "PRAGMA":
case "EXPLAIN":
return executeSelect(database, query, handler);
default:
return executeRawQuery(database, query, handler);
}
} finally {
database.close();
}
}
private static String getFirstWord(String s) {
s = s.trim();
int firstSpace = s.indexOf(' ');
return firstSpace >= 0 ? s.substring(0, firstSpace) : s;
}
@TargetApi(DatabaseConstants.MIN_API_LEVEL)
private <T> T executeUpdateDelete(
SQLiteDatabase database,
String query,
ExecuteResultHandler<T> handler) {
SQLiteStatement statement = database.compileStatement(query);
int count = statement.executeUpdateDelete();
return handler.handleUpdateDelete(count);
}
private <T> T executeInsert(
SQLiteDatabase database,
String query,
ExecuteResultHandler<T> handler) {
SQLiteStatement statement = database.compileStatement(query);
long count = statement.executeInsert();
return handler.handleInsert(count);
}
private <T> T executeSelect(
SQLiteDatabase database,
String query,
ExecuteResultHandler<T> handler) {
Cursor cursor = database.rawQuery(query, null);
try {
return handler.handleSelect(cursor);
} finally {
cursor.close();
}
}
private <T> T executeRawQuery(
SQLiteDatabase database,
String query,
ExecuteResultHandler<T> handler) {
database.execSQL(query);
return handler.handleRawQuery();
}
private SQLiteDatabase openDatabase(String databaseName) throws SQLiteException {
Util.throwIfNull(databaseName);
return mDatabaseConnectionProvider.openDatabase(findDatabaseFile(databaseName));
}
private File findDatabaseFile(String databaseName) {
String[] tmp;
for (File providedDatabaseFile : mDatabaseFilesProvider.getDatabaseFiles()) {
if (providedDatabaseFile.getAbsolutePath().contains("databases")) {
if (providedDatabaseFile.getName().equals(databaseName)) {
return providedDatabaseFile;
}
} else {
tmp = databaseName.split(" ");
if (tmp.length == 2) {
if (providedDatabaseFile.getAbsolutePath().contains(tmp[0]) && providedDatabaseFile.getName().equals(tmp[1])) {
return providedDatabaseFile;
}
}
}
if (providedDatabaseFile.getName().equals(databaseName)) {
return providedDatabaseFile;
}
}
return mContext.getDatabasePath(databaseName);
}
}