That fuck shit the fascists are using
at master 518 lines 20 kB view raw
1package org.tm.archive.database; 2 3import android.content.ContentValues; 4import android.content.Context; 5import android.database.Cursor; 6import android.text.TextUtils; 7import android.util.Pair; 8 9import androidx.annotation.NonNull; 10import androidx.annotation.Nullable; 11 12import org.greenrobot.eventbus.EventBus; 13import org.signal.core.util.StreamUtil; 14import org.signal.core.util.logging.Log; 15import org.tm.archive.crypto.AttachmentSecret; 16import org.tm.archive.crypto.ModernDecryptingPartInputStream; 17import org.tm.archive.crypto.ModernEncryptingPartOutputStream; 18import org.tm.archive.database.model.IncomingSticker; 19import org.tm.archive.database.model.StickerPackRecord; 20import org.tm.archive.database.model.StickerRecord; 21import org.tm.archive.mms.DecryptableStreamUriLoader.DecryptableUri; 22import org.tm.archive.stickers.BlessedPacks; 23import org.tm.archive.stickers.StickerPackInstallEvent; 24import org.signal.core.util.CursorUtil; 25import org.signal.core.util.SqlUtil; 26 27import java.io.Closeable; 28import java.io.File; 29import java.io.IOException; 30import java.io.InputStream; 31import java.io.OutputStream; 32import java.util.HashSet; 33import java.util.List; 34import java.util.Set; 35 36public class StickerTable extends DatabaseTable { 37 38 private static final String TAG = Log.tag(StickerTable.class); 39 40 public static final String TABLE_NAME = "sticker"; 41 public static final String _ID = "_id"; 42 static final String PACK_ID = "pack_id"; 43 private static final String PACK_KEY = "pack_key"; 44 private static final String PACK_TITLE = "pack_title"; 45 private static final String PACK_AUTHOR = "pack_author"; 46 private static final String STICKER_ID = "sticker_id"; 47 private static final String EMOJI = "emoji"; 48 public static final String CONTENT_TYPE = "content_type"; 49 private static final String COVER = "cover"; 50 private static final String PACK_ORDER = "pack_order"; 51 private static final String INSTALLED = "installed"; 52 private static final String LAST_USED = "last_used"; 53 public static final String FILE_PATH = "file_path"; 54 public static final String FILE_LENGTH = "file_length"; 55 public static final String FILE_RANDOM = "file_random"; 56 57 public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 58 PACK_ID + " TEXT NOT NULL, " + 59 PACK_KEY + " TEXT NOT NULL, " + 60 PACK_TITLE + " TEXT NOT NULL, " + 61 PACK_AUTHOR + " TEXT NOT NULL, " + 62 STICKER_ID + " INTEGER, " + 63 COVER + " INTEGER, " + 64 PACK_ORDER + " INTEGER, " + 65 EMOJI + " TEXT NOT NULL, " + 66 CONTENT_TYPE + " TEXT DEFAULT NULL, " + 67 LAST_USED + " INTEGER, " + 68 INSTALLED + " INTEGER," + 69 FILE_PATH + " TEXT NOT NULL, " + 70 FILE_LENGTH + " INTEGER, " + 71 FILE_RANDOM + " BLOB, " + 72 "UNIQUE(" + PACK_ID + ", " + STICKER_ID + ", " + COVER + ") ON CONFLICT IGNORE)"; 73 74 public static final String[] CREATE_INDEXES = { 75 "CREATE INDEX IF NOT EXISTS sticker_pack_id_index ON " + TABLE_NAME + " (" + PACK_ID + ");", 76 "CREATE INDEX IF NOT EXISTS sticker_sticker_id_index ON " + TABLE_NAME + " (" + STICKER_ID + ");" 77 }; 78 79 public static final String DIRECTORY = "stickers"; 80 81 private final AttachmentSecret attachmentSecret; 82 83 public StickerTable(Context context, SignalDatabase databaseHelper, AttachmentSecret attachmentSecret) { 84 super(context, databaseHelper); 85 this.attachmentSecret = attachmentSecret; 86 } 87 88 public void insertSticker(@NonNull IncomingSticker sticker, @NonNull InputStream dataStream, boolean notify) throws IOException { 89 FileInfo fileInfo = saveStickerImage(dataStream); 90 ContentValues contentValues = new ContentValues(); 91 92 contentValues.put(PACK_ID, sticker.getPackId()); 93 contentValues.put(PACK_KEY, sticker.getPackKey()); 94 contentValues.put(PACK_TITLE, sticker.getPackTitle()); 95 contentValues.put(PACK_AUTHOR, sticker.getPackAuthor()); 96 contentValues.put(STICKER_ID, sticker.getStickerId()); 97 contentValues.put(EMOJI, sticker.getEmoji()); 98 contentValues.put(CONTENT_TYPE, sticker.getContentType()); 99 contentValues.put(COVER, sticker.isCover() ? 1 : 0); 100 contentValues.put(INSTALLED, sticker.isInstalled() ? 1 : 0); 101 contentValues.put(FILE_PATH, fileInfo.getFile().getAbsolutePath()); 102 contentValues.put(FILE_LENGTH, fileInfo.getLength()); 103 contentValues.put(FILE_RANDOM, fileInfo.getRandom()); 104 105 long id = databaseHelper.getSignalWritableDatabase().insert(TABLE_NAME, null, contentValues); 106 if (id == -1) { 107 String selection = PACK_ID + " = ? AND " + STICKER_ID + " = ? AND " + COVER + " = ?"; 108 String[] args = SqlUtil.buildArgs(sticker.getPackId(), sticker.getStickerId(), (sticker.isCover() ? 1 : 0)); 109 110 id = databaseHelper.getSignalWritableDatabase().update(TABLE_NAME, contentValues, selection, args); 111 } 112 113 if (id > 0) { 114 notifyStickerListeners(); 115 116 if (sticker.isCover()) { 117 notifyStickerPackListeners(); 118 119 if (sticker.isInstalled() && notify) { 120 broadcastInstallEvent(sticker.getPackId()); 121 } 122 } 123 } 124 } 125 126 public @Nullable StickerRecord getSticker(@NonNull String packId, int stickerId, boolean isCover) { 127 String selection = PACK_ID + " = ? AND " + STICKER_ID + " = ? AND " + COVER + " = ?"; 128 String[] args = new String[] { packId, String.valueOf(stickerId), String.valueOf(isCover ? 1 : 0) }; 129 130 try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, "1")) { 131 return new StickerRecordReader(cursor).getNext(); 132 } 133 } 134 135 public @Nullable StickerPackRecord getStickerPack(@NonNull String packId) { 136 String query = PACK_ID + " = ? AND " + COVER + " = ?"; 137 String[] args = new String[] { packId, "1" }; 138 139 try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, query, args, null, null, null, "1")) { 140 return new StickerPackRecordReader(cursor).getNext(); 141 } 142 } 143 144 public @Nullable Cursor getInstalledStickerPacks() { 145 String selection = COVER + " = ? AND " + INSTALLED + " = ?"; 146 String[] args = new String[] { "1", "1" }; 147 148 return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, PACK_ORDER + " ASC"); 149 } 150 151 public @Nullable Cursor getStickersByEmoji(@NonNull String emoji) { 152 String selection = EMOJI + " LIKE ? AND " + COVER + " = ?"; 153 String[] args = new String[] { "%"+emoji+"%", "0" }; 154 155 return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null); 156 } 157 158 public @Nullable Cursor getAllStickerPacks() { 159 return getAllStickerPacks(null); 160 } 161 162 public @Nullable Cursor getAllStickerPacks(@Nullable String limit) { 163 String query = COVER + " = ?"; 164 String[] args = new String[] { "1" }; 165 166 return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, query, args, null, null, PACK_ORDER + " ASC", limit); 167 } 168 169 public @Nullable Cursor getStickersForPack(@NonNull String packId) { 170 SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); 171 String selection = PACK_ID + " = ? AND " + COVER + " = ?"; 172 String[] args = new String[] { packId, "0" }; 173 174 return db.query(TABLE_NAME, null, selection, args, null, null, STICKER_ID + " ASC"); 175 } 176 177 public @Nullable Cursor getRecentlyUsedStickers(int limit) { 178 SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); 179 String selection = LAST_USED + " > ? AND " + COVER + " = ?"; 180 String[] args = new String[] { "0", "0" }; 181 182 return db.query(TABLE_NAME, null, selection, args, null, null, LAST_USED + " DESC", String.valueOf(limit)); 183 } 184 185 public @NonNull Set<String> getAllStickerFiles() { 186 SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); 187 188 Set<String> files = new HashSet<>(); 189 try (Cursor cursor = db.query(TABLE_NAME, new String[] { FILE_PATH }, null, null, null, null, null)) { 190 while (cursor != null && cursor.moveToNext()) { 191 files.add(CursorUtil.requireString(cursor, FILE_PATH)); 192 } 193 } 194 195 return files; 196 } 197 198 public @Nullable InputStream getStickerStream(long rowId) throws IOException { 199 String selection = _ID + " = ?"; 200 String[] args = new String[] { String.valueOf(rowId) }; 201 202 try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null)) { 203 if (cursor != null && cursor.moveToNext()) { 204 String path = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH)); 205 byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(FILE_RANDOM)); 206 207 if (path != null) { 208 return ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(path), 0); 209 } else { 210 Log.w(TAG, "getStickerStream("+rowId+") - No sticker data"); 211 } 212 } else { 213 Log.i(TAG, "getStickerStream("+rowId+") - Sticker not found."); 214 } 215 } 216 217 return null; 218 } 219 220 public boolean isPackInstalled(@NonNull String packId) { 221 StickerPackRecord record = getStickerPack(packId); 222 223 return (record != null && record.isInstalled()); 224 } 225 226 public boolean isPackAvailableAsReference(@NonNull String packId) { 227 return getStickerPack(packId) != null; 228 } 229 230 public void updateStickerLastUsedTime(long rowId, long lastUsed) { 231 String selection = _ID + " = ?"; 232 String[] args = new String[] { String.valueOf(rowId) }; 233 ContentValues values = new ContentValues(); 234 235 values.put(LAST_USED, lastUsed); 236 237 databaseHelper.getSignalWritableDatabase().update(TABLE_NAME, values, selection, args); 238 239 notifyStickerListeners(); 240 notifyStickerPackListeners(); 241 } 242 243 public void markPackAsInstalled(@NonNull String packKey, boolean notify) { 244 updatePackInstalled(databaseHelper.getSignalWritableDatabase(), packKey, true, notify); 245 notifyStickerPackListeners(); 246 } 247 248 public void deleteOrphanedPacks() { 249 SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); 250 String query = "SELECT " + PACK_ID + " FROM " + TABLE_NAME + " WHERE " + INSTALLED + " = ? AND " + 251 PACK_ID + " NOT IN (" + 252 "SELECT DISTINCT " + AttachmentTable.STICKER_PACK_ID + " FROM " + AttachmentTable.TABLE_NAME + " " + 253 "WHERE " + AttachmentTable.STICKER_PACK_ID + " NOT NULL" + 254 ")"; 255 String[] args = new String[] { "0" }; 256 257 db.beginTransaction(); 258 259 try { 260 boolean performedDelete = false; 261 262 try (Cursor cursor = db.rawQuery(query, args)) { 263 while (cursor != null && cursor.moveToNext()) { 264 String packId = cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)); 265 266 if (!BlessedPacks.contains(packId)) { 267 deletePack(db, packId); 268 performedDelete = true; 269 } 270 } 271 } 272 273 db.setTransactionSuccessful(); 274 275 if (performedDelete) { 276 notifyStickerPackListeners(); 277 notifyStickerListeners(); 278 } 279 } finally { 280 db.endTransaction(); 281 } 282 } 283 284 public void uninstallPack(@NonNull String packId) { 285 SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); 286 287 db.beginTransaction(); 288 try { 289 updatePackInstalled(db, packId, false, false); 290 deleteStickersInPackExceptCover(db, packId); 291 292 db.setTransactionSuccessful(); 293 notifyStickerPackListeners(); 294 notifyStickerListeners(); 295 } finally { 296 db.endTransaction(); 297 } 298 } 299 300 public void updatePackOrder(@NonNull List<StickerPackRecord> packsInOrder) { 301 SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); 302 303 db.beginTransaction(); 304 try { 305 String selection = PACK_ID + " = ? AND " + COVER + " = ?"; 306 307 for (int i = 0; i < packsInOrder.size(); i++) { 308 String[] args = new String[]{ packsInOrder.get(i).getPackId(), "1" }; 309 ContentValues values = new ContentValues(); 310 311 values.put(PACK_ORDER, i); 312 313 db.update(TABLE_NAME, values, selection, args); 314 } 315 316 db.setTransactionSuccessful(); 317 notifyStickerPackListeners(); 318 } finally { 319 db.endTransaction(); 320 } 321 } 322 323 private void updatePackInstalled(@NonNull SQLiteDatabase db, @NonNull String packId, boolean installed, boolean notify) { 324 StickerPackRecord existing = getStickerPack(packId); 325 326 if (existing != null && existing.isInstalled() == installed) { 327 return; 328 } 329 330 String selection = PACK_ID + " = ?"; 331 String[] args = new String[]{ packId }; 332 ContentValues values = new ContentValues(1); 333 334 values.put(INSTALLED, installed ? 1 : 0); 335 db.update(TABLE_NAME, values, selection, args); 336 337 if (installed && notify) { 338 broadcastInstallEvent(packId); 339 } 340 } 341 342 private FileInfo saveStickerImage(@NonNull InputStream inputStream) throws IOException { 343 File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE); 344 File file = File.createTempFile("sticker", ".mms", partsDirectory); 345 Pair<byte[], OutputStream> out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, file, false); 346 long length = StreamUtil.copy(inputStream, out.second); 347 348 return new FileInfo(file, length, out.first); 349 } 350 351 private void deleteSticker(@NonNull SQLiteDatabase db, long rowId, @Nullable String filePath) { 352 String selection = _ID + " = ?"; 353 String[] args = new String[] { String.valueOf(rowId) }; 354 355 db.delete(TABLE_NAME, selection, args); 356 357 if (!TextUtils.isEmpty(filePath)) { 358 new File(filePath).delete(); 359 } 360 } 361 362 private void deletePack(@NonNull SQLiteDatabase db, @NonNull String packId) { 363 String selection = PACK_ID + " = ?"; 364 String[] args = new String[] { packId }; 365 366 db.delete(TABLE_NAME, selection, args); 367 368 deleteStickersInPack(db, packId); 369 } 370 371 private void deleteStickersInPack(@NonNull SQLiteDatabase db, @NonNull String packId) { 372 String selection = PACK_ID + " = ?"; 373 String[] args = new String[] { packId }; 374 375 db.beginTransaction(); 376 377 try { 378 try (Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null)) { 379 while (cursor != null && cursor.moveToNext()) { 380 String filePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH)); 381 long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID)); 382 383 deleteSticker(db, rowId, filePath); 384 } 385 } 386 387 db.setTransactionSuccessful(); 388 } finally { 389 db.endTransaction(); 390 } 391 392 db.delete(TABLE_NAME, selection, args); 393 } 394 395 private void deleteStickersInPackExceptCover(@NonNull SQLiteDatabase db, @NonNull String packId) { 396 String selection = PACK_ID + " = ? AND " + COVER + " = ?"; 397 String[] args = new String[] { packId, "0" }; 398 399 db.beginTransaction(); 400 401 try { 402 try (Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null)) { 403 while (cursor != null && cursor.moveToNext()) { 404 long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID)); 405 String filePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH)); 406 407 deleteSticker(db, rowId, filePath); 408 } 409 } 410 411 db.setTransactionSuccessful(); 412 } finally { 413 db.endTransaction(); 414 } 415 } 416 417 private void broadcastInstallEvent(@NonNull String packId) { 418 StickerPackRecord pack = getStickerPack(packId); 419 420 if (pack != null) { 421 EventBus.getDefault().postSticky(new StickerPackInstallEvent(new DecryptableUri(pack.getCover().getUri()))); 422 } 423 } 424 425 private static final class FileInfo { 426 private final File file; 427 private final long length; 428 private final byte[] random; 429 430 private FileInfo(@NonNull File file, long length, @NonNull byte[] random) { 431 this.file = file; 432 this.length = length; 433 this.random = random; 434 } 435 436 public File getFile() { 437 return file; 438 } 439 440 public long getLength() { 441 return length; 442 } 443 444 public byte[] getRandom() { 445 return random; 446 } 447 } 448 449 public static final class StickerRecordReader implements Closeable { 450 451 private final Cursor cursor; 452 453 public StickerRecordReader(@Nullable Cursor cursor) { 454 this.cursor = cursor; 455 } 456 457 public @Nullable StickerRecord getNext() { 458 if (cursor == null || !cursor.moveToNext()) { 459 return null; 460 } 461 462 return getCurrent(); 463 } 464 465 public @NonNull StickerRecord getCurrent() { 466 return new StickerRecord(cursor.getLong(cursor.getColumnIndexOrThrow(_ID)), 467 cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)), 468 cursor.getString(cursor.getColumnIndexOrThrow(PACK_KEY)), 469 cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)), 470 cursor.getString(cursor.getColumnIndexOrThrow(EMOJI)), 471 cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)), 472 cursor.getLong(cursor.getColumnIndexOrThrow(FILE_LENGTH)), 473 cursor.getInt(cursor.getColumnIndexOrThrow(COVER)) == 1); 474 } 475 476 @Override 477 public void close() { 478 if (cursor != null) { 479 cursor.close(); 480 } 481 } 482 } 483 484 public static final class StickerPackRecordReader implements Closeable { 485 486 private final Cursor cursor; 487 488 public StickerPackRecordReader(@Nullable Cursor cursor) { 489 this.cursor = cursor; 490 } 491 492 public @Nullable StickerPackRecord getNext() { 493 if (cursor == null || !cursor.moveToNext()) { 494 return null; 495 } 496 497 return getCurrent(); 498 } 499 500 public @NonNull StickerPackRecord getCurrent() { 501 StickerRecord cover = new StickerRecordReader(cursor).getCurrent(); 502 503 return new StickerPackRecord(cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)), 504 cursor.getString(cursor.getColumnIndexOrThrow(PACK_KEY)), 505 cursor.getString(cursor.getColumnIndexOrThrow(PACK_TITLE)), 506 cursor.getString(cursor.getColumnIndexOrThrow(PACK_AUTHOR)), 507 cover, 508 cursor.getInt(cursor.getColumnIndexOrThrow(INSTALLED)) == 1); 509 } 510 511 @Override 512 public void close() { 513 if (cursor != null) { 514 cursor.close(); 515 } 516 } 517 } 518}