kopia lustrzana https://github.com/ryukoposting/Signal-Android
Move the KeyValueDatabase to a separate physical database.
rodzic
46d412a6c3
commit
c466dba8c4
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
|
@ -24,6 +25,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver}
|
||||
|
@ -42,8 +44,12 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||
try {
|
||||
Field databaseHelperField = DatabaseFactory.class.getDeclaredField("databaseHelper");
|
||||
databaseHelperField.setAccessible(true);
|
||||
SQLCipherOpenHelper sqlCipherOpenHelper = (SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext()));
|
||||
return Collections.singletonList(new Descriptor(sqlCipherOpenHelper));
|
||||
|
||||
SignalDatabase mainOpenHelper = Objects.requireNonNull((SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext())));
|
||||
SignalDatabase keyValueOpenHelper = KeyValueDatabase.getInstance((Application) getContext());
|
||||
|
||||
return Arrays.asList(new Descriptor(mainOpenHelper), new Descriptor(keyValueOpenHelper));
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "Unable to use reflection to access raw database.", e);
|
||||
}
|
||||
|
@ -235,9 +241,9 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||
}
|
||||
|
||||
static class Descriptor implements DatabaseDescriptor {
|
||||
private final SQLCipherOpenHelper sqlCipherOpenHelper;
|
||||
private final SignalDatabase sqlCipherOpenHelper;
|
||||
|
||||
Descriptor(@NonNull SQLCipherOpenHelper sqlCipherOpenHelper) {
|
||||
Descriptor(@NonNull SignalDatabase sqlCipherOpenHelper) {
|
||||
this.sqlCipherOpenHelper = sqlCipherOpenHelper;
|
||||
}
|
||||
|
||||
|
@ -247,11 +253,11 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||
}
|
||||
|
||||
public @NonNull SQLiteDatabase getReadable() {
|
||||
return sqlCipherOpenHelper.getReadableDatabase().getSqlCipherDatabase();
|
||||
return sqlCipherOpenHelper.getSqlCipherDatabase();
|
||||
}
|
||||
|
||||
public @NonNull SQLiteDatabase getWritable() {
|
||||
return sqlCipherOpenHelper.getWritableDatabase().getSqlCipherDatabase();
|
||||
return sqlCipherOpenHelper.getSqlCipherDatabase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,8 +77,7 @@ public class FullBackupExporter extends FullBackupBase {
|
|||
SearchDatabase.MMS_FTS_TABLE_NAME,
|
||||
JobDatabase.JOBS_TABLE_NAME,
|
||||
JobDatabase.CONSTRAINTS_TABLE_NAME,
|
||||
JobDatabase.DEPENDENCIES_TABLE_NAME,
|
||||
KeyValueDatabase.TABLE_NAME
|
||||
JobDatabase.DEPENDENCIES_TABLE_NAME
|
||||
);
|
||||
|
||||
public static void export(@NonNull Context context,
|
||||
|
|
|
@ -11,18 +11,30 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class DatabaseSecretProvider {
|
||||
/**
|
||||
* It can be rather expensive to read from the keystore, so this class caches the key in memory
|
||||
* after it is created.
|
||||
*/
|
||||
public final class DatabaseSecretProvider {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = DatabaseSecretProvider.class.getSimpleName();
|
||||
private static volatile DatabaseSecret instance;
|
||||
|
||||
private final Context context;
|
||||
public static DatabaseSecret getOrCreateDatabaseSecret(@NonNull Context context) {
|
||||
if (instance == null) {
|
||||
synchronized (DatabaseSecretProvider.class) {
|
||||
if (instance == null) {
|
||||
instance = getOrCreate(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DatabaseSecretProvider(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public DatabaseSecret getOrCreateDatabaseSecret() {
|
||||
private DatabaseSecretProvider() {
|
||||
}
|
||||
|
||||
private static @NonNull DatabaseSecret getOrCreate(@NonNull Context context) {
|
||||
String unencryptedSecret = TextSecurePreferences.getDatabaseUnencryptedSecret(context);
|
||||
String encryptedSecret = TextSecurePreferences.getDatabaseEncryptedSecret(context);
|
||||
|
||||
|
@ -31,12 +43,12 @@ public class DatabaseSecretProvider {
|
|||
else return createAndStoreDatabaseSecret(context);
|
||||
}
|
||||
|
||||
private DatabaseSecret getUnencryptedDatabaseSecret(@NonNull Context context, @NonNull String unencryptedSecret)
|
||||
private static @NonNull DatabaseSecret getUnencryptedDatabaseSecret(@NonNull Context context, @NonNull String unencryptedSecret)
|
||||
{
|
||||
try {
|
||||
DatabaseSecret databaseSecret = new DatabaseSecret(unencryptedSecret);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
return databaseSecret;
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
|
||||
|
@ -51,8 +63,8 @@ public class DatabaseSecretProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private DatabaseSecret getEncryptedDatabaseSecret(@NonNull String serializedEncryptedSecret) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
private static @NonNull DatabaseSecret getEncryptedDatabaseSecret(@NonNull String serializedEncryptedSecret) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
|
||||
|
@ -60,14 +72,14 @@ public class DatabaseSecretProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
|
||||
private static @NonNull DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
|
||||
DatabaseSecret databaseSecret = new DatabaseSecret(secret);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
|
||||
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
|
||||
} else {
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper;
|
|||
import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class DatabaseFactory {
|
||||
|
@ -61,7 +62,6 @@ public class DatabaseFactory {
|
|||
private final JobDatabase jobDatabase;
|
||||
private final StickerDatabase stickerDatabase;
|
||||
private final StorageKeyDatabase storageKeyDatabase;
|
||||
private final KeyValueDatabase keyValueDatabase;
|
||||
private final MegaphoneDatabase megaphoneDatabase;
|
||||
private final RemappedRecordsDatabase remappedRecordsDatabase;
|
||||
private final MentionDatabase mentionDatabase;
|
||||
|
@ -157,10 +157,6 @@ public class DatabaseFactory {
|
|||
return getInstance(context).storageKeyDatabase;
|
||||
}
|
||||
|
||||
public static KeyValueDatabase getKeyValueDatabase(Context context) {
|
||||
return getInstance(context).keyValueDatabase;
|
||||
}
|
||||
|
||||
public static MegaphoneDatabase getMegaphoneDatabase(Context context) {
|
||||
return getInstance(context).megaphoneDatabase;
|
||||
}
|
||||
|
@ -182,6 +178,7 @@ public class DatabaseFactory {
|
|||
getInstance(context).databaseHelper.onUpgrade(database, database.getVersion(), -1);
|
||||
getInstance(context).databaseHelper.markCurrent(database);
|
||||
getInstance(context).mms.trimEntriesForExpiredMessages();
|
||||
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS key_value");
|
||||
|
||||
instance.databaseHelper.close();
|
||||
instance = null;
|
||||
|
@ -195,7 +192,7 @@ public class DatabaseFactory {
|
|||
private DatabaseFactory(@NonNull Context context) {
|
||||
SQLiteDatabase.loadLibs(context);
|
||||
|
||||
DatabaseSecret databaseSecret = new DatabaseSecretProvider(context).getOrCreateDatabaseSecret();
|
||||
DatabaseSecret databaseSecret = DatabaseSecretProvider.getOrCreateDatabaseSecret(context);
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
|
||||
this.databaseHelper = new SQLCipherOpenHelper(context, databaseSecret);
|
||||
|
@ -219,7 +216,6 @@ public class DatabaseFactory {
|
|||
this.jobDatabase = new JobDatabase(context, databaseHelper);
|
||||
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
|
||||
this.storageKeyDatabase = new StorageKeyDatabase(context, databaseHelper);
|
||||
this.keyValueDatabase = new KeyValueDatabase(context, databaseHelper);
|
||||
this.megaphoneDatabase = new MegaphoneDatabase(context, databaseHelper);
|
||||
this.remappedRecordsDatabase = new RemappedRecordsDatabase(context, databaseHelper);
|
||||
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
|
||||
|
@ -252,4 +248,12 @@ public class DatabaseFactory {
|
|||
public void triggerDatabaseAccess() {
|
||||
databaseHelper.getWritableDatabase();
|
||||
}
|
||||
|
||||
public SQLiteDatabase getRawDatabase() {
|
||||
return databaseHelper.getWritableDatabase().getSqlCipherDatabase();
|
||||
}
|
||||
|
||||
public boolean hasTable(String table) {
|
||||
return SqlUtil.tableExists(databaseHelper.getReadableDatabase().getSqlCipherDatabase(), table);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,118 @@
|
|||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class KeyValueDatabase extends Database {
|
||||
/**
|
||||
* Persists data for the {@link org.thoughtcrime.securesms.keyvalue.KeyValueStore}.
|
||||
*
|
||||
* This is it's own separate physical database, so it cannot do joins or queries with any other
|
||||
* tables.
|
||||
*/
|
||||
public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase {
|
||||
|
||||
public static final String TABLE_NAME = "key_value";
|
||||
private static final String TAG = Log.tag(KeyValueDatabase.class);
|
||||
|
||||
private static final String ID = "_id";
|
||||
private static final String KEY = "key";
|
||||
private static final String VALUE = "value";
|
||||
private static final String TYPE = "type";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
private static final String DATABASE_NAME = "signal-key-value.db";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
KEY + " TEXT UNIQUE, " +
|
||||
VALUE + " TEXT, " +
|
||||
TYPE + " INTEGER)";
|
||||
private static final String TABLE_NAME = "key_value";
|
||||
private static final String ID = "_id";
|
||||
private static final String KEY = "key";
|
||||
private static final String VALUE = "value";
|
||||
private static final String TYPE = "type";
|
||||
|
||||
KeyValueDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
KEY + " TEXT UNIQUE, " +
|
||||
VALUE + " TEXT, " +
|
||||
TYPE + " INTEGER)";
|
||||
|
||||
private static volatile KeyValueDatabase instance;
|
||||
|
||||
private final Application application;
|
||||
private final DatabaseSecret databaseSecret;
|
||||
|
||||
public static @NonNull KeyValueDatabase getInstance(@NonNull Application context) {
|
||||
if (instance == null) {
|
||||
synchronized (KeyValueDatabase.class) {
|
||||
if (instance == null) {
|
||||
instance = new KeyValueDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
||||
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
|
||||
@Override
|
||||
public void preKey(SQLiteDatabase db) {
|
||||
db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;");
|
||||
db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postKey(SQLiteDatabase db) {
|
||||
db.rawExecSQL("PRAGMA kdf_iter = '1';");
|
||||
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
|
||||
}
|
||||
});
|
||||
|
||||
this.application = application;
|
||||
this.databaseSecret = databaseSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onCreate()");
|
||||
|
||||
db.execSQL(CREATE_TABLE);
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("key_value")) {
|
||||
Log.i(TAG, "Found old key_value table. Migrating data.");
|
||||
migrateDataFromPreviousDatabase(DatabaseFactory.getInstance(application).getRawDatabase(), db);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.i(TAG, "onUpgrade(" + oldVersion + ", " + newVersion + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onOpen()");
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("key_value")) {
|
||||
Log.i(TAG, "Dropping original key_value table from the main database.");
|
||||
DatabaseFactory.getInstance(application).getRawDatabase().rawExecSQL("DROP TABLE key_value");
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull KeyValueDataSet getDataSet() {
|
||||
KeyValueDataSet dataSet = new KeyValueDataSet();
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)){
|
||||
try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)){
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
Type type = Type.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)));
|
||||
String key = cursor.getString(cursor.getColumnIndexOrThrow(KEY));
|
||||
Type type = Type.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)));
|
||||
String key = cursor.getString(cursor.getColumnIndexOrThrow(KEY));
|
||||
|
||||
switch (type) {
|
||||
case BLOB:
|
||||
|
@ -65,7 +141,7 @@ public class KeyValueDatabase extends Database {
|
|||
}
|
||||
|
||||
public void writeDataSet(@NonNull KeyValueDataSet dataSet, @NonNull Collection<String> removes) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
|
@ -113,6 +189,53 @@ public class KeyValueDatabase extends Database {
|
|||
}
|
||||
}
|
||||
|
||||
private @NonNull SQLiteDatabase getReadableDatabase() {
|
||||
return getReadableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
private @NonNull SQLiteDatabase getWritableDatabase() {
|
||||
return getWritableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SQLiteDatabase getSqlCipherDatabase() {
|
||||
return getWritableDatabase();
|
||||
}
|
||||
|
||||
private static void migrateDataFromPreviousDatabase(@NonNull SQLiteDatabase oldDb, @NonNull SQLiteDatabase newDb) {
|
||||
try (Cursor cursor = oldDb.rawQuery("SELECT * FROM key_value", null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
int type = CursorUtil.requireInt(cursor, "type");
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY, CursorUtil.requireString(cursor, "key"));
|
||||
values.put(TYPE, type);
|
||||
|
||||
switch (type) {
|
||||
case 0:
|
||||
values.put(VALUE, CursorUtil.requireBlob(cursor, "value"));
|
||||
break;
|
||||
case 1:
|
||||
values.put(VALUE, CursorUtil.requireBoolean(cursor, "value"));
|
||||
break;
|
||||
case 2:
|
||||
values.put(VALUE, CursorUtil.requireFloat(cursor, "value"));
|
||||
break;
|
||||
case 3:
|
||||
values.put(VALUE, CursorUtil.requireInt(cursor, "value"));
|
||||
break;
|
||||
case 4:
|
||||
values.put(VALUE, CursorUtil.requireLong(cursor, "value"));
|
||||
break;
|
||||
case 5:
|
||||
values.put(VALUE, CursorUtil.requireString(cursor, "value"));
|
||||
break;
|
||||
}
|
||||
|
||||
newDb.insert(TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum Type {
|
||||
BLOB(0), BOOLEAN(1), FLOAT(2), INTEGER(3), LONG(4), STRING(5);
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
/**
|
||||
* Simple interface for common methods across our various
|
||||
* {@link net.sqlcipher.database.SQLiteOpenHelper}s.
|
||||
*/
|
||||
public interface SignalDatabase {
|
||||
SQLiteDatabase getSqlCipherDatabase();
|
||||
String getDatabaseName();
|
||||
}
|
|
@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase;
|
|||
import org.thoughtcrime.securesms.database.RemappedRecordsDatabase;
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
|
@ -76,7 +77,7 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatabase {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
|
||||
|
@ -208,7 +209,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||
db.execSQL(SessionDatabase.CREATE_TABLE);
|
||||
db.execSQL(StickerDatabase.CREATE_TABLE);
|
||||
db.execSQL(StorageKeyDatabase.CREATE_TABLE);
|
||||
db.execSQL(KeyValueDatabase.CREATE_TABLE);
|
||||
db.execSQL(MegaphoneDatabase.CREATE_TABLE);
|
||||
db.execSQL(MentionDatabase.CREATE_TABLE);
|
||||
executeStatements(db, SearchDatabase.CREATE_TABLE);
|
||||
|
@ -1264,6 +1264,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||
return new org.thoughtcrime.securesms.database.SQLiteDatabase(getWritableDatabase(databaseSecret.asString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SQLiteDatabase getSqlCipherDatabase() {
|
||||
return getWritableDatabase().getSqlCipherDatabase();
|
||||
}
|
||||
|
||||
public void markCurrent(SQLiteDatabase db) {
|
||||
db.setVersion(DATABASE_VERSION);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
|
@ -38,9 +39,9 @@ public final class KeyValueStore implements KeyValueReader {
|
|||
|
||||
private KeyValueDataSet dataSet;
|
||||
|
||||
public KeyValueStore(@NonNull Context context) {
|
||||
public KeyValueStore(@NonNull Application application) {
|
||||
this.executor = SignalExecutors.newCachedSingleThreadExecutor("signal-KeyValueStore");
|
||||
this.database = DatabaseFactory.getKeyValueDatabase(context);
|
||||
this.database = KeyValueDatabase.getInstance(application);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
|
|
|
@ -18,6 +18,10 @@ public final class CursorUtil {
|
|||
return cursor.getInt(cursor.getColumnIndexOrThrow(column));
|
||||
}
|
||||
|
||||
public static float requireFloat(@NonNull Cursor cursor, @NonNull String column) {
|
||||
return cursor.getFloat(cursor.getColumnIndexOrThrow(column));
|
||||
}
|
||||
|
||||
public static long requireLong(@NonNull Cursor cursor, @NonNull String column) {
|
||||
return cursor.getLong(cursor.getColumnIndexOrThrow(column));
|
||||
}
|
||||
|
|
|
@ -27,6 +27,16 @@ public final class SqlUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isEmpty(@NonNull SQLiteDatabase db, @NonNull String table) {
|
||||
try (Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM " + table, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getInt(0) == 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean columnExists(@NonNull SQLiteDatabase db, @NonNull String table, @NonNull String column) {
|
||||
try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + table + ")", null)) {
|
||||
int nameColumnIndex = cursor.getColumnIndexOrThrow("name");
|
||||
|
|
Ładowanie…
Reference in New Issue