Skip to content

Commit 86542fe

Browse files
greyson-signalalan-signal
authored andcommitted
Move the MegaphoneDatabase to a separate physical database.
1 parent 9da49f9 commit 86542fe

7 files changed

Lines changed: 130 additions & 57 deletions

File tree

app/src/flipper/java/org/thoughtcrime/securesms/database/FlipperSqlCipherAdapter.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ public List<Descriptor> getDatabases() {
4545
Field databaseHelperField = DatabaseFactory.class.getDeclaredField("databaseHelper");
4646
databaseHelperField.setAccessible(true);
4747

48-
SignalDatabase mainOpenHelper = Objects.requireNonNull((SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext())));
49-
SignalDatabase keyValueOpenHelper = KeyValueDatabase.getInstance((Application) getContext());
50-
51-
return Arrays.asList(new Descriptor(mainOpenHelper), new Descriptor(keyValueOpenHelper));
48+
SignalDatabase mainOpenHelper = Objects.requireNonNull((SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext())));
49+
SignalDatabase keyValueOpenHelper = KeyValueDatabase.getInstance((Application) getContext());
50+
SignalDatabase megaphoneOpenHelper = MegaphoneDatabase.getInstance((Application) getContext());
5251

52+
return Arrays.asList(new Descriptor(mainOpenHelper),
53+
new Descriptor(keyValueOpenHelper),
54+
new Descriptor(megaphoneOpenHelper));
5355
} catch (Exception e) {
5456
Log.i(TAG, "Unable to use reflection to access raw database.", e);
5557
}

app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ public class DatabaseFactory {
6262
private final JobDatabase jobDatabase;
6363
private final StickerDatabase stickerDatabase;
6464
private final StorageKeyDatabase storageKeyDatabase;
65-
private final MegaphoneDatabase megaphoneDatabase;
6665
private final RemappedRecordsDatabase remappedRecordsDatabase;
6766
private final MentionDatabase mentionDatabase;
6867

@@ -157,10 +156,6 @@ public static StorageKeyDatabase getStorageKeyDatabase(Context context) {
157156
return getInstance(context).storageKeyDatabase;
158157
}
159158

160-
public static MegaphoneDatabase getMegaphoneDatabase(Context context) {
161-
return getInstance(context).megaphoneDatabase;
162-
}
163-
164159
static RemappedRecordsDatabase getRemappedRecordsDatabase(Context context) {
165160
return getInstance(context).remappedRecordsDatabase;
166161
}
@@ -179,6 +174,7 @@ public static void upgradeRestored(Context context, SQLiteDatabase database){
179174
getInstance(context).databaseHelper.markCurrent(database);
180175
getInstance(context).mms.trimEntriesForExpiredMessages();
181176
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS key_value");
177+
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS megaphone");
182178

183179
instance.databaseHelper.close();
184180
instance = null;
@@ -216,7 +212,6 @@ private DatabaseFactory(@NonNull Context context) {
216212
this.jobDatabase = new JobDatabase(context, databaseHelper);
217213
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
218214
this.storageKeyDatabase = new StorageKeyDatabase(context, databaseHelper);
219-
this.megaphoneDatabase = new MegaphoneDatabase(context, databaseHelper);
220215
this.remappedRecordsDatabase = new RemappedRecordsDatabase(context, databaseHelper);
221216
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
222217
}

app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,7 @@ public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase
6161
}
6262

6363
public KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
64-
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
65-
@Override
66-
public void preKey(SQLiteDatabase db) {
67-
db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;");
68-
db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;");
69-
}
70-
71-
@Override
72-
public void postKey(SQLiteDatabase db) {
73-
db.rawExecSQL("PRAGMA kdf_iter = '1';");
74-
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
75-
}
76-
});
64+
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
7765

7866
this.application = application;
7967
this.databaseSecret = databaseSecret;

app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java

Lines changed: 97 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package org.thoughtcrime.securesms.database;
22

3+
import android.app.Application;
34
import android.content.ContentValues;
4-
import android.content.Context;
55
import android.database.Cursor;
66

77
import androidx.annotation.NonNull;
88

9+
import net.sqlcipher.database.SQLiteDatabase;
10+
import net.sqlcipher.database.SQLiteOpenHelper;
11+
912
import org.signal.core.util.logging.Log;
10-
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
13+
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
14+
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
1115
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
1216
import org.thoughtcrime.securesms.megaphone.Megaphones.Event;
17+
import org.thoughtcrime.securesms.util.CursorUtil;
1318

1419
import java.util.ArrayList;
1520
import java.util.Collection;
@@ -20,32 +25,80 @@
2025
/**
2126
* IMPORTANT: Writes should only be made through {@link org.thoughtcrime.securesms.megaphone.MegaphoneRepository}.
2227
*/
23-
public class MegaphoneDatabase extends Database {
28+
public class MegaphoneDatabase extends SQLiteOpenHelper implements SignalDatabase {
2429

2530
private static final String TAG = Log.tag(MegaphoneDatabase.class);
2631

27-
private static final String TABLE_NAME = "megaphone";
32+
private static final int DATABASE_VERSION = 1;
33+
private static final String DATABASE_NAME = "signal-megaphone.db";
2834

35+
private static final String TABLE_NAME = "megaphone";
2936
private static final String ID = "_id";
3037
private static final String EVENT = "event";
3138
private static final String SEEN_COUNT = "seen_count";
3239
private static final String LAST_SEEN = "last_seen";
3340
private static final String FIRST_VISIBLE = "first_visible";
3441
private static final String FINISHED = "finished";
3542

36-
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
37-
EVENT + " TEXT UNIQUE, " +
38-
SEEN_COUNT + " INTEGER, " +
39-
LAST_SEEN + " INTEGER, " +
43+
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
44+
EVENT + " TEXT UNIQUE, " +
45+
SEEN_COUNT + " INTEGER, " +
46+
LAST_SEEN + " INTEGER, " +
4047
FIRST_VISIBLE + " INTEGER, " +
41-
FINISHED + " INTEGER)";
48+
FINISHED + " INTEGER)";
49+
50+
private static volatile MegaphoneDatabase instance;
51+
52+
private final Application application;
53+
private final DatabaseSecret databaseSecret;
54+
55+
public static @NonNull MegaphoneDatabase getInstance(@NonNull Application context) {
56+
if (instance == null) {
57+
synchronized (MegaphoneDatabase.class) {
58+
if (instance == null) {
59+
instance = new MegaphoneDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context));
60+
}
61+
}
62+
}
63+
return instance;
64+
}
65+
66+
public MegaphoneDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
67+
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
68+
69+
this.application = application;
70+
this.databaseSecret = databaseSecret;
71+
}
72+
73+
@Override
74+
public void onCreate(SQLiteDatabase db) {
75+
Log.i(TAG, "onCreate()");
76+
77+
db.execSQL(CREATE_TABLE);
78+
79+
if (DatabaseFactory.getInstance(application).hasTable("megaphone")) {
80+
Log.i(TAG, "Found old megaphone table. Migrating data.");
81+
migrateDataFromPreviousDatabase(DatabaseFactory.getInstance(application).getRawDatabase(), db);
82+
}
83+
}
4284

43-
MegaphoneDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
44-
super(context, databaseHelper);
85+
@Override
86+
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
87+
Log.i(TAG, "onUpgrade(" + oldVersion + ", " + newVersion + ")");
88+
}
89+
90+
@Override
91+
public void onOpen(SQLiteDatabase db) {
92+
Log.i(TAG, "onOpen()");
93+
94+
if (DatabaseFactory.getInstance(application).hasTable("megaphone")) {
95+
Log.i(TAG, "Dropping original megaphone table from the main database.");
96+
DatabaseFactory.getInstance(application).getRawDatabase().rawExecSQL("DROP TABLE megaphone");
97+
}
4598
}
4699

47100
public void insert(@NonNull Collection<Event> events) {
48-
SQLiteDatabase db = databaseHelper.getWritableDatabase();
101+
SQLiteDatabase db = getWritableDatabase();
49102

50103
db.beginTransaction();
51104
try {
@@ -63,14 +116,14 @@ public void insert(@NonNull Collection<Event> events) {
63116
}
64117

65118
public @NonNull List<MegaphoneRecord> getAllAndDeleteMissing() {
66-
SQLiteDatabase db = databaseHelper.getWritableDatabase();
119+
SQLiteDatabase db = getWritableDatabase();
67120
List<MegaphoneRecord> records = new ArrayList<>();
68121

69122
db.beginTransaction();
70123
try {
71124
Set<String> missingKeys = new HashSet<>();
72125

73-
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)) {
126+
try (Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null)) {
74127
while (cursor != null && cursor.moveToNext()) {
75128
String event = cursor.getString(cursor.getColumnIndexOrThrow(EVENT));
76129
int seenCount = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_COUNT));
@@ -91,7 +144,7 @@ public void insert(@NonNull Collection<Event> events) {
91144
String query = EVENT + " = ?";
92145
String[] args = new String[]{missing};
93146

94-
databaseHelper.getWritableDatabase().delete(TABLE_NAME, query, args);
147+
db.delete(TABLE_NAME, query, args);
95148
}
96149

97150
db.setTransactionSuccessful();
@@ -109,7 +162,7 @@ public void markFirstVisible(@NonNull Event event, long time) {
109162
ContentValues values = new ContentValues();
110163
values.put(FIRST_VISIBLE, time);
111164

112-
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
165+
getWritableDatabase().update(TABLE_NAME, values, query, args);
113166
}
114167

115168
public void markSeen(@NonNull Event event, int seenCount, long lastSeen) {
@@ -120,7 +173,7 @@ public void markSeen(@NonNull Event event, int seenCount, long lastSeen) {
120173
values.put(SEEN_COUNT, seenCount);
121174
values.put(LAST_SEEN, lastSeen);
122175

123-
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
176+
getWritableDatabase().update(TABLE_NAME, values, query, args);
124177
}
125178

126179
public void markFinished(@NonNull Event event) {
@@ -130,13 +183,38 @@ public void markFinished(@NonNull Event event) {
130183
ContentValues values = new ContentValues();
131184
values.put(FINISHED, 1);
132185

133-
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
186+
getWritableDatabase().update(TABLE_NAME, values, query, args);
134187
}
135188

136189
public void delete(@NonNull Event event) {
137190
String query = EVENT + " = ?";
138191
String[] args = new String[]{event.getKey()};
139192

140-
databaseHelper.getWritableDatabase().delete(TABLE_NAME, query, args);
193+
getWritableDatabase().delete(TABLE_NAME, query, args);
194+
}
195+
196+
private @NonNull SQLiteDatabase getWritableDatabase() {
197+
return getWritableDatabase(databaseSecret.asString());
198+
}
199+
200+
@Override
201+
public @NonNull SQLiteDatabase getSqlCipherDatabase() {
202+
return getWritableDatabase();
203+
}
204+
205+
private static void migrateDataFromPreviousDatabase(@NonNull SQLiteDatabase oldDb, @NonNull SQLiteDatabase newDb) {
206+
try (Cursor cursor = oldDb.rawQuery("SELECT * FROM megaphone", null)) {
207+
while (cursor.moveToNext()) {
208+
ContentValues values = new ContentValues();
209+
210+
values.put(EVENT, CursorUtil.requireString(cursor, "event"));
211+
values.put(SEEN_COUNT, CursorUtil.requireInt(cursor, "seen_count"));
212+
values.put(LAST_SEEN, CursorUtil.requireLong(cursor, "last_seen"));
213+
values.put(FIRST_VISIBLE, CursorUtil.requireLong(cursor, "first_visible"));
214+
values.put(FINISHED, CursorUtil.requireInt(cursor, "finished"));
215+
216+
newDb.insert(TABLE_NAME, null, values);
217+
}
218+
}
141219
}
142220
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.thoughtcrime.securesms.database;
2+
3+
import net.sqlcipher.database.SQLiteDatabase;
4+
import net.sqlcipher.database.SQLiteDatabaseHook;
5+
6+
/**
7+
* Standard hook for setting common SQLCipher PRAGMAs.
8+
*/
9+
public final class SqlCipherDatabaseHook implements SQLiteDatabaseHook {
10+
11+
@Override
12+
public void preKey(SQLiteDatabase db) {
13+
db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;");
14+
db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;");
15+
}
16+
17+
@Override
18+
public void postKey(SQLiteDatabase db) {
19+
db.rawExecSQL("PRAGMA kdf_iter = '1';");
20+
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
21+
}
22+
}

app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.thoughtcrime.securesms.database.SignalDatabase;
4545
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
4646
import org.thoughtcrime.securesms.database.SmsDatabase;
47+
import org.thoughtcrime.securesms.database.SqlCipherDatabaseHook;
4748
import org.thoughtcrime.securesms.database.StickerDatabase;
4849
import org.thoughtcrime.securesms.database.StorageKeyDatabase;
4950
import org.thoughtcrime.securesms.database.ThreadDatabase;
@@ -174,19 +175,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
174175
private final DatabaseSecret databaseSecret;
175176

176177
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
177-
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
178-
@Override
179-
public void preKey(SQLiteDatabase db) {
180-
db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;");
181-
db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;");
182-
}
183-
184-
@Override
185-
public void postKey(SQLiteDatabase db) {
186-
db.rawExecSQL("PRAGMA kdf_iter = '1';");
187-
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
188-
}
189-
});
178+
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
190179

191180
this.context = context.getApplicationContext();
192181
this.databaseSecret = databaseSecret;
@@ -209,7 +198,6 @@ public void onCreate(SQLiteDatabase db) {
209198
db.execSQL(SessionDatabase.CREATE_TABLE);
210199
db.execSQL(StickerDatabase.CREATE_TABLE);
211200
db.execSQL(StorageKeyDatabase.CREATE_TABLE);
212-
db.execSQL(MegaphoneDatabase.CREATE_TABLE);
213201
db.execSQL(MentionDatabase.CREATE_TABLE);
214202
executeStatements(db, SearchDatabase.CREATE_TABLE);
215203
executeStatements(db, JobDatabase.CREATE_TABLE);

app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class MegaphoneRepository {
3939
public MegaphoneRepository(@NonNull Application context) {
4040
this.context = context;
4141
this.executor = SignalExecutors.SERIAL;
42-
this.database = DatabaseFactory.getMegaphoneDatabase(context);
42+
this.database = MegaphoneDatabase.getInstance(context);
4343
this.databaseCache = new HashMap<>();
4444

4545
executor.execute(this::init);

0 commit comments

Comments
 (0)