Move the MegaphoneDatabase to a separate physical database.
This commit is contained in:
parent
9da49f9f8a
commit
86542febf9
7 changed files with 130 additions and 57 deletions
|
@ -47,9 +47,11 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
||||||
|
|
||||||
SignalDatabase mainOpenHelper = Objects.requireNonNull((SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext())));
|
SignalDatabase mainOpenHelper = Objects.requireNonNull((SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext())));
|
||||||
SignalDatabase keyValueOpenHelper = KeyValueDatabase.getInstance((Application) getContext());
|
SignalDatabase keyValueOpenHelper = KeyValueDatabase.getInstance((Application) getContext());
|
||||||
|
SignalDatabase megaphoneOpenHelper = MegaphoneDatabase.getInstance((Application) getContext());
|
||||||
|
|
||||||
return Arrays.asList(new Descriptor(mainOpenHelper), new Descriptor(keyValueOpenHelper));
|
return Arrays.asList(new Descriptor(mainOpenHelper),
|
||||||
|
new Descriptor(keyValueOpenHelper),
|
||||||
|
new Descriptor(megaphoneOpenHelper));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.i(TAG, "Unable to use reflection to access raw database.", e);
|
Log.i(TAG, "Unable to use reflection to access raw database.", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,6 @@ public class DatabaseFactory {
|
||||||
private final JobDatabase jobDatabase;
|
private final JobDatabase jobDatabase;
|
||||||
private final StickerDatabase stickerDatabase;
|
private final StickerDatabase stickerDatabase;
|
||||||
private final StorageKeyDatabase storageKeyDatabase;
|
private final StorageKeyDatabase storageKeyDatabase;
|
||||||
private final MegaphoneDatabase megaphoneDatabase;
|
|
||||||
private final RemappedRecordsDatabase remappedRecordsDatabase;
|
private final RemappedRecordsDatabase remappedRecordsDatabase;
|
||||||
private final MentionDatabase mentionDatabase;
|
private final MentionDatabase mentionDatabase;
|
||||||
|
|
||||||
|
@ -157,10 +156,6 @@ public class DatabaseFactory {
|
||||||
return getInstance(context).storageKeyDatabase;
|
return getInstance(context).storageKeyDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MegaphoneDatabase getMegaphoneDatabase(Context context) {
|
|
||||||
return getInstance(context).megaphoneDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
static RemappedRecordsDatabase getRemappedRecordsDatabase(Context context) {
|
static RemappedRecordsDatabase getRemappedRecordsDatabase(Context context) {
|
||||||
return getInstance(context).remappedRecordsDatabase;
|
return getInstance(context).remappedRecordsDatabase;
|
||||||
}
|
}
|
||||||
|
@ -179,6 +174,7 @@ public class DatabaseFactory {
|
||||||
getInstance(context).databaseHelper.markCurrent(database);
|
getInstance(context).databaseHelper.markCurrent(database);
|
||||||
getInstance(context).mms.trimEntriesForExpiredMessages();
|
getInstance(context).mms.trimEntriesForExpiredMessages();
|
||||||
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS key_value");
|
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS key_value");
|
||||||
|
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS megaphone");
|
||||||
|
|
||||||
instance.databaseHelper.close();
|
instance.databaseHelper.close();
|
||||||
instance = null;
|
instance = null;
|
||||||
|
@ -216,7 +212,6 @@ public class DatabaseFactory {
|
||||||
this.jobDatabase = new JobDatabase(context, databaseHelper);
|
this.jobDatabase = new JobDatabase(context, databaseHelper);
|
||||||
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
|
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
|
||||||
this.storageKeyDatabase = new StorageKeyDatabase(context, databaseHelper);
|
this.storageKeyDatabase = new StorageKeyDatabase(context, databaseHelper);
|
||||||
this.megaphoneDatabase = new MegaphoneDatabase(context, databaseHelper);
|
|
||||||
this.remappedRecordsDatabase = new RemappedRecordsDatabase(context, databaseHelper);
|
this.remappedRecordsDatabase = new RemappedRecordsDatabase(context, databaseHelper);
|
||||||
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
|
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,19 +61,7 @@ public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
public KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
||||||
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
|
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
|
||||||
@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.application = application;
|
||||||
this.databaseSecret = databaseSecret;
|
this.databaseSecret = databaseSecret;
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||||
|
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||||
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
|
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
|
||||||
import org.thoughtcrime.securesms.megaphone.Megaphones.Event;
|
import org.thoughtcrime.securesms.megaphone.Megaphones.Event;
|
||||||
|
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -20,12 +25,14 @@ import java.util.Set;
|
||||||
/**
|
/**
|
||||||
* IMPORTANT: Writes should only be made through {@link org.thoughtcrime.securesms.megaphone.MegaphoneRepository}.
|
* IMPORTANT: Writes should only be made through {@link org.thoughtcrime.securesms.megaphone.MegaphoneRepository}.
|
||||||
*/
|
*/
|
||||||
public class MegaphoneDatabase extends Database {
|
public class MegaphoneDatabase extends SQLiteOpenHelper implements SignalDatabase {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(MegaphoneDatabase.class);
|
private static final String TAG = Log.tag(MegaphoneDatabase.class);
|
||||||
|
|
||||||
private static final String TABLE_NAME = "megaphone";
|
private static final int DATABASE_VERSION = 1;
|
||||||
|
private static final String DATABASE_NAME = "signal-megaphone.db";
|
||||||
|
|
||||||
|
private static final String TABLE_NAME = "megaphone";
|
||||||
private static final String ID = "_id";
|
private static final String ID = "_id";
|
||||||
private static final String EVENT = "event";
|
private static final String EVENT = "event";
|
||||||
private static final String SEEN_COUNT = "seen_count";
|
private static final String SEEN_COUNT = "seen_count";
|
||||||
|
@ -40,12 +47,58 @@ public class MegaphoneDatabase extends Database {
|
||||||
FIRST_VISIBLE + " INTEGER, " +
|
FIRST_VISIBLE + " INTEGER, " +
|
||||||
FINISHED + " INTEGER)";
|
FINISHED + " INTEGER)";
|
||||||
|
|
||||||
MegaphoneDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
private static volatile MegaphoneDatabase instance;
|
||||||
super(context, databaseHelper);
|
|
||||||
|
private final Application application;
|
||||||
|
private final DatabaseSecret databaseSecret;
|
||||||
|
|
||||||
|
public static @NonNull MegaphoneDatabase getInstance(@NonNull Application context) {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (MegaphoneDatabase.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new MegaphoneDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MegaphoneDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
||||||
|
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
|
||||||
|
|
||||||
|
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("megaphone")) {
|
||||||
|
Log.i(TAG, "Found old megaphone 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("megaphone")) {
|
||||||
|
Log.i(TAG, "Dropping original megaphone table from the main database.");
|
||||||
|
DatabaseFactory.getInstance(application).getRawDatabase().rawExecSQL("DROP TABLE megaphone");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insert(@NonNull Collection<Event> events) {
|
public void insert(@NonNull Collection<Event> events) {
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
|
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
try {
|
try {
|
||||||
|
@ -63,14 +116,14 @@ public class MegaphoneDatabase extends Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull List<MegaphoneRecord> getAllAndDeleteMissing() {
|
public @NonNull List<MegaphoneRecord> getAllAndDeleteMissing() {
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
List<MegaphoneRecord> records = new ArrayList<>();
|
List<MegaphoneRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
try {
|
try {
|
||||||
Set<String> missingKeys = new HashSet<>();
|
Set<String> missingKeys = new HashSet<>();
|
||||||
|
|
||||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)) {
|
try (Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
String event = cursor.getString(cursor.getColumnIndexOrThrow(EVENT));
|
String event = cursor.getString(cursor.getColumnIndexOrThrow(EVENT));
|
||||||
int seenCount = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_COUNT));
|
int seenCount = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_COUNT));
|
||||||
|
@ -91,7 +144,7 @@ public class MegaphoneDatabase extends Database {
|
||||||
String query = EVENT + " = ?";
|
String query = EVENT + " = ?";
|
||||||
String[] args = new String[]{missing};
|
String[] args = new String[]{missing};
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().delete(TABLE_NAME, query, args);
|
db.delete(TABLE_NAME, query, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
@ -109,7 +162,7 @@ public class MegaphoneDatabase extends Database {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(FIRST_VISIBLE, time);
|
values.put(FIRST_VISIBLE, time);
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
|
getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markSeen(@NonNull Event event, int seenCount, long lastSeen) {
|
public void markSeen(@NonNull Event event, int seenCount, long lastSeen) {
|
||||||
|
@ -120,7 +173,7 @@ public class MegaphoneDatabase extends Database {
|
||||||
values.put(SEEN_COUNT, seenCount);
|
values.put(SEEN_COUNT, seenCount);
|
||||||
values.put(LAST_SEEN, lastSeen);
|
values.put(LAST_SEEN, lastSeen);
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
|
getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markFinished(@NonNull Event event) {
|
public void markFinished(@NonNull Event event) {
|
||||||
|
@ -130,13 +183,38 @@ public class MegaphoneDatabase extends Database {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(FINISHED, 1);
|
values.put(FINISHED, 1);
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
|
getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(@NonNull Event event) {
|
public void delete(@NonNull Event event) {
|
||||||
String query = EVENT + " = ?";
|
String query = EVENT + " = ?";
|
||||||
String[] args = new String[]{event.getKey()};
|
String[] args = new String[]{event.getKey()};
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().delete(TABLE_NAME, query, args);
|
getWritableDatabase().delete(TABLE_NAME, query, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 megaphone", null)) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
|
||||||
|
values.put(EVENT, CursorUtil.requireString(cursor, "event"));
|
||||||
|
values.put(SEEN_COUNT, CursorUtil.requireInt(cursor, "seen_count"));
|
||||||
|
values.put(LAST_SEEN, CursorUtil.requireLong(cursor, "last_seen"));
|
||||||
|
values.put(FIRST_VISIBLE, CursorUtil.requireLong(cursor, "first_visible"));
|
||||||
|
values.put(FINISHED, CursorUtil.requireInt(cursor, "finished"));
|
||||||
|
|
||||||
|
newDb.insert(TABLE_NAME, null, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard hook for setting common SQLCipher PRAGMAs.
|
||||||
|
*/
|
||||||
|
public final class SqlCipherDatabaseHook implements 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;");
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.SqlCipherDatabaseHook;
|
||||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||||
import org.thoughtcrime.securesms.database.StorageKeyDatabase;
|
import org.thoughtcrime.securesms.database.StorageKeyDatabase;
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
|
@ -174,19 +175,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
||||||
private final DatabaseSecret databaseSecret;
|
private final DatabaseSecret databaseSecret;
|
||||||
|
|
||||||
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
|
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
|
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
|
||||||
@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.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
this.databaseSecret = databaseSecret;
|
this.databaseSecret = databaseSecret;
|
||||||
|
@ -209,7 +198,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
||||||
db.execSQL(SessionDatabase.CREATE_TABLE);
|
db.execSQL(SessionDatabase.CREATE_TABLE);
|
||||||
db.execSQL(StickerDatabase.CREATE_TABLE);
|
db.execSQL(StickerDatabase.CREATE_TABLE);
|
||||||
db.execSQL(StorageKeyDatabase.CREATE_TABLE);
|
db.execSQL(StorageKeyDatabase.CREATE_TABLE);
|
||||||
db.execSQL(MegaphoneDatabase.CREATE_TABLE);
|
|
||||||
db.execSQL(MentionDatabase.CREATE_TABLE);
|
db.execSQL(MentionDatabase.CREATE_TABLE);
|
||||||
executeStatements(db, SearchDatabase.CREATE_TABLE);
|
executeStatements(db, SearchDatabase.CREATE_TABLE);
|
||||||
executeStatements(db, JobDatabase.CREATE_TABLE);
|
executeStatements(db, JobDatabase.CREATE_TABLE);
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class MegaphoneRepository {
|
||||||
public MegaphoneRepository(@NonNull Application context) {
|
public MegaphoneRepository(@NonNull Application context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.executor = SignalExecutors.SERIAL;
|
this.executor = SignalExecutors.SERIAL;
|
||||||
this.database = DatabaseFactory.getMegaphoneDatabase(context);
|
this.database = MegaphoneDatabase.getInstance(context);
|
||||||
this.databaseCache = new HashMap<>();
|
this.databaseCache = new HashMap<>();
|
||||||
|
|
||||||
executor.execute(this::init);
|
executor.execute(this::init);
|
||||||
|
|
Loading…
Add table
Reference in a new issue