That fuck shit the fascists are using
1package org.tm.archive.database;
2
3import android.app.Application;
4import android.content.ContentValues;
5import android.database.Cursor;
6
7import androidx.annotation.NonNull;
8
9import net.zetetic.database.sqlcipher.SQLiteDatabase;
10import net.zetetic.database.sqlcipher.SQLiteOpenHelper;
11
12import org.signal.core.util.concurrent.SignalExecutors;
13import org.signal.core.util.logging.Log;
14import org.tm.archive.crypto.DatabaseSecret;
15import org.tm.archive.crypto.DatabaseSecretProvider;
16import org.tm.archive.database.model.MegaphoneRecord;
17import org.tm.archive.megaphone.Megaphones.Event;
18import org.signal.core.util.CursorUtil;
19
20import java.util.ArrayList;
21import java.util.Collection;
22import java.util.HashSet;
23import java.util.List;
24import java.util.Set;
25
26/**
27 * IMPORTANT: Writes should only be made through {@link org.tm.archive.megaphone.MegaphoneRepository}.
28 */
29public class MegaphoneDatabase extends SQLiteOpenHelper implements SignalDatabaseOpenHelper {
30
31 private static final String TAG = Log.tag(MegaphoneDatabase.class);
32
33 private static final int DATABASE_VERSION = 1;
34 private static final String DATABASE_NAME = "signal-megaphone.db";
35
36 private static final String TABLE_NAME = "megaphone";
37 private static final String ID = "_id";
38 private static final String EVENT = "event";
39 private static final String SEEN_COUNT = "seen_count";
40 private static final String LAST_SEEN = "last_seen";
41 private static final String FIRST_VISIBLE = "first_visible";
42 private static final String FINISHED = "finished";
43
44 public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
45 EVENT + " TEXT UNIQUE, " +
46 SEEN_COUNT + " INTEGER, " +
47 LAST_SEEN + " INTEGER, " +
48 FIRST_VISIBLE + " INTEGER, " +
49 FINISHED + " INTEGER)";
50
51 private static volatile MegaphoneDatabase instance;
52
53 private final Application application;
54
55 public static @NonNull MegaphoneDatabase getInstance(@NonNull Application context) {
56 if (instance == null) {
57 synchronized (MegaphoneDatabase.class) {
58 if (instance == null) {
59 SqlCipherLibraryLoader.load();
60 instance = new MegaphoneDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context));
61 }
62 }
63 }
64 return instance;
65 }
66
67 public MegaphoneDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
68 super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0, new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook(), true);
69
70 this.application = application;
71 }
72
73 @Override
74 public void onCreate(SQLiteDatabase db) {
75 Log.i(TAG, "onCreate()");
76
77 db.execSQL(CREATE_TABLE);
78
79 if (SignalDatabase.hasTable("megaphone")) {
80 Log.i(TAG, "Found old megaphone table. Migrating data.");
81 migrateDataFromPreviousDatabase(SignalDatabase.getRawDatabase(), db);
82 }
83 }
84
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 db.setForeignKeyConstraintsEnabled(true);
95
96 SignalExecutors.BOUNDED.execute(() -> {
97 if (SignalDatabase.hasTable("megaphone")) {
98 Log.i(TAG, "Dropping original megaphone table from the main database.");
99 SignalDatabase.getRawDatabase().execSQL("DROP TABLE megaphone");
100 }
101 });
102 }
103
104 public void insert(@NonNull Collection<Event> events) {
105 SQLiteDatabase db = getWritableDatabase();
106
107 db.beginTransaction();
108 try {
109 for (Event event : events) {
110 ContentValues values = new ContentValues();
111 values.put(EVENT, event.getKey());
112
113 db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
114 }
115
116 db.setTransactionSuccessful();
117 } finally {
118 db.endTransaction();
119 }
120 }
121
122 public @NonNull List<MegaphoneRecord> getAllAndDeleteMissing() {
123 SQLiteDatabase db = getWritableDatabase();
124 List<MegaphoneRecord> records = new ArrayList<>();
125
126 db.beginTransaction();
127 try {
128 Set<String> missingKeys = new HashSet<>();
129
130 try (Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null)) {
131 while (cursor != null && cursor.moveToNext()) {
132 String event = cursor.getString(cursor.getColumnIndexOrThrow(EVENT));
133 int seenCount = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_COUNT));
134 long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(LAST_SEEN));
135 long firstVisible = cursor.getLong(cursor.getColumnIndexOrThrow(FIRST_VISIBLE));
136 boolean finished = cursor.getInt(cursor.getColumnIndexOrThrow(FINISHED)) == 1;
137
138 if (Event.hasKey(event)) {
139 records.add(new MegaphoneRecord(Event.fromKey(event), seenCount, lastSeen, firstVisible, finished));
140 } else {
141 Log.w(TAG, "No in-app handing for event '" + event + "'! Deleting it from the database.");
142 missingKeys.add(event);
143 }
144 }
145 }
146
147 for (String missing : missingKeys) {
148 String query = EVENT + " = ?";
149 String[] args = new String[]{missing};
150
151 db.delete(TABLE_NAME, query, args);
152 }
153
154 db.setTransactionSuccessful();
155 } finally {
156 db.endTransaction();
157 }
158
159 return records;
160 }
161
162 public void markFirstVisible(@NonNull Event event, long time) {
163 String query = EVENT + " = ?";
164 String[] args = new String[]{event.getKey()};
165
166 ContentValues values = new ContentValues();
167 values.put(FIRST_VISIBLE, time);
168
169 getWritableDatabase().update(TABLE_NAME, values, query, args);
170 }
171
172 public void markSeen(@NonNull Event event, int seenCount, long lastSeen) {
173 String query = EVENT + " = ?";
174 String[] args = new String[]{event.getKey()};
175
176 ContentValues values = new ContentValues();
177 values.put(SEEN_COUNT, seenCount);
178 values.put(LAST_SEEN, lastSeen);
179
180 getWritableDatabase().update(TABLE_NAME, values, query, args);
181 }
182
183 public void markFinished(@NonNull Event event) {
184 String query = EVENT + " = ?";
185 String[] args = new String[]{event.getKey()};
186
187 ContentValues values = new ContentValues();
188 values.put(FINISHED, 1);
189
190 getWritableDatabase().update(TABLE_NAME, values, query, args);
191 }
192
193 public void delete(@NonNull Event event) {
194 String query = EVENT + " = ?";
195 String[] args = new String[]{event.getKey()};
196
197 getWritableDatabase().delete(TABLE_NAME, query, args);
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 }
219 }
220}