That fuck shit the fascists are using
at master 247 lines 8.4 kB view raw
1package org.tm.archive.database; 2 3import android.app.Application; 4import android.content.ContentValues; 5import android.content.Context; 6import android.database.Cursor; 7 8import androidx.annotation.NonNull; 9 10import net.zetetic.database.sqlcipher.SQLiteDatabase; 11import net.zetetic.database.sqlcipher.SQLiteOpenHelper; 12 13import org.signal.core.util.concurrent.SignalExecutors; 14import org.signal.core.util.logging.Log; 15import org.tm.archive.crypto.DatabaseSecret; 16import org.tm.archive.crypto.DatabaseSecretProvider; 17import org.tm.archive.keyvalue.KeyValueDataSet; 18import org.tm.archive.keyvalue.KeyValuePersistentStorage; 19import org.signal.core.util.CursorUtil; 20 21import java.util.Collection; 22import java.util.Map; 23 24/** 25 * Persists data for the {@link org.tm.archive.keyvalue.KeyValueStore}. 26 * 27 * This is it's own separate physical database, so it cannot do joins or queries with any other 28 * tables. 29 */ 30public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabaseOpenHelper, KeyValuePersistentStorage { 31 32 private static final String TAG = Log.tag(KeyValueDatabase.class); 33 34 private static final int DATABASE_VERSION = 1; 35 private static final String DATABASE_NAME = "signal-key-value.db"; 36 37 private static final String TABLE_NAME = "key_value"; 38 private static final String ID = "_id"; 39 private static final String KEY = "key"; 40 private static final String VALUE = "value"; 41 private static final String TYPE = "type"; 42 43 private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 44 KEY + " TEXT UNIQUE, " + 45 VALUE + " TEXT, " + 46 TYPE + " INTEGER)"; 47 48 private static volatile KeyValueDatabase instance; 49 50 private final Application application; 51 52 public static @NonNull KeyValueDatabase getInstance(@NonNull Application context) { 53 if (instance == null) { 54 synchronized (KeyValueDatabase.class) { 55 if (instance == null) { 56 SqlCipherLibraryLoader.load(); 57 instance = new KeyValueDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context)); 58 } 59 } 60 } 61 return instance; 62 } 63 64 public static boolean exists(Context context) { 65 return context.getDatabasePath(DATABASE_NAME).exists(); 66 } 67 68 69 private KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) { 70 super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0,new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook(), true); 71 72 this.application = application; 73 } 74 75 @Override 76 public void onCreate(SQLiteDatabase db) { 77 Log.i(TAG, "onCreate()"); 78 79 db.execSQL(CREATE_TABLE); 80 81 if (SignalDatabase.hasTable("key_value")) { 82 Log.i(TAG, "Found old key_value table. Migrating data."); 83 migrateDataFromPreviousDatabase(SignalDatabase.getRawDatabase(), db); 84 } 85 } 86 87 @Override 88 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 89 Log.i(TAG, "onUpgrade(" + oldVersion + ", " + newVersion + ")"); 90 } 91 92 @Override 93 public void onOpen(SQLiteDatabase db) { 94 Log.i(TAG, "onOpen()"); 95 96 db.setForeignKeyConstraintsEnabled(true); 97 98 SignalExecutors.BOUNDED.execute(() -> { 99 if (SignalDatabase.hasTable("key_value")) { 100 Log.i(TAG, "Dropping original key_value table from the main database."); 101 SignalDatabase.getRawDatabase().execSQL("DROP TABLE key_value"); 102 } 103 }); 104 } 105 106 @Override 107 public @NonNull KeyValueDataSet getDataSet() { 108 KeyValueDataSet dataSet = new KeyValueDataSet(); 109 110 try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)){ 111 while (cursor != null && cursor.moveToNext()) { 112 Type type = Type.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE))); 113 String key = cursor.getString(cursor.getColumnIndexOrThrow(KEY)); 114 115 switch (type) { 116 case BLOB: 117 dataSet.putBlob(key, cursor.getBlob(cursor.getColumnIndexOrThrow(VALUE))); 118 break; 119 case BOOLEAN: 120 dataSet.putBoolean(key, cursor.getInt(cursor.getColumnIndexOrThrow(VALUE)) == 1); 121 break; 122 case FLOAT: 123 dataSet.putFloat(key, cursor.getFloat(cursor.getColumnIndexOrThrow(VALUE))); 124 break; 125 case INTEGER: 126 dataSet.putInteger(key, cursor.getInt(cursor.getColumnIndexOrThrow(VALUE))); 127 break; 128 case LONG: 129 dataSet.putLong(key, cursor.getLong(cursor.getColumnIndexOrThrow(VALUE))); 130 break; 131 case STRING: 132 dataSet.putString(key, cursor.getString(cursor.getColumnIndexOrThrow(VALUE))); 133 break; 134 } 135 } 136 } 137 138 return dataSet; 139 } 140 141 @Override 142 public void writeDataSet(@NonNull KeyValueDataSet dataSet, @NonNull Collection<String> removes) { 143 SQLiteDatabase db = getWritableDatabase(); 144 145 db.beginTransaction(); 146 try { 147 for (Map.Entry<String, Object> entry : dataSet.getValues().entrySet()) { 148 String key = entry.getKey(); 149 Object value = entry.getValue(); 150 Class type = dataSet.getType(key); 151 152 ContentValues contentValues = new ContentValues(3); 153 contentValues.put(KEY, key); 154 155 if (type == byte[].class) { 156 contentValues.put(VALUE, (byte[]) value); 157 contentValues.put(TYPE, Type.BLOB.getId()); 158 } else if (type == Boolean.class) { 159 contentValues.put(VALUE, (boolean) value); 160 contentValues.put(TYPE, Type.BOOLEAN.getId()); 161 } else if (type == Float.class) { 162 contentValues.put(VALUE, (float) value); 163 contentValues.put(TYPE, Type.FLOAT.getId()); 164 } else if (type == Integer.class) { 165 contentValues.put(VALUE, (int) value); 166 contentValues.put(TYPE, Type.INTEGER.getId()); 167 } else if (type == Long.class) { 168 contentValues.put(VALUE, (long) value); 169 contentValues.put(TYPE, Type.LONG.getId()); 170 } else if (type == String.class) { 171 contentValues.put(VALUE, (String) value); 172 contentValues.put(TYPE, Type.STRING.getId()); 173 } else { 174 throw new AssertionError("Unknown type: " + type); 175 } 176 177 db.insertWithOnConflict(TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE); 178 } 179 180 String deleteQuery = KEY + " = ?"; 181 for (String remove : removes) { 182 db.delete(TABLE_NAME, deleteQuery, new String[] { remove }); 183 } 184 185 db.setTransactionSuccessful(); 186 } finally { 187 db.endTransaction(); 188 } 189 } 190 191 @Override 192 public @NonNull SQLiteDatabase getSqlCipherDatabase() { 193 return getWritableDatabase(); 194 } 195 196 private static void migrateDataFromPreviousDatabase(@NonNull SQLiteDatabase oldDb, @NonNull SQLiteDatabase newDb) { 197 try (Cursor cursor = oldDb.rawQuery("SELECT * FROM key_value", null)) { 198 while (cursor.moveToNext()) { 199 int type = CursorUtil.requireInt(cursor, "type"); 200 ContentValues values = new ContentValues(); 201 values.put(KEY, CursorUtil.requireString(cursor, "key")); 202 values.put(TYPE, type); 203 204 switch (type) { 205 case 0: 206 values.put(VALUE, CursorUtil.requireBlob(cursor, "value")); 207 break; 208 case 1: 209 values.put(VALUE, CursorUtil.requireBoolean(cursor, "value")); 210 break; 211 case 2: 212 values.put(VALUE, CursorUtil.requireFloat(cursor, "value")); 213 break; 214 case 3: 215 values.put(VALUE, CursorUtil.requireInt(cursor, "value")); 216 break; 217 case 4: 218 values.put(VALUE, CursorUtil.requireLong(cursor, "value")); 219 break; 220 case 5: 221 values.put(VALUE, CursorUtil.requireString(cursor, "value")); 222 break; 223 } 224 225 newDb.insert(TABLE_NAME, null, values); 226 } 227 } 228 } 229 230 private enum Type { 231 BLOB(0), BOOLEAN(1), FLOAT(2), INTEGER(3), LONG(4), STRING(5); 232 233 final int id; 234 235 Type(int id) { 236 this.id = id; 237 } 238 239 public int getId() { 240 return id; 241 } 242 243 public static Type fromId(int id) { 244 return values()[id]; 245 } 246 } 247}