That fuck shit the fascists are using
at master 220 lines 7.9 kB view raw
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}