diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MentionTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/MentionTable.java deleted file mode 100644 index a33425e6b8..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MentionTable.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.thoughtcrime.securesms.database; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.annimon.stream.Stream; - -import org.thoughtcrime.securesms.database.model.Mention; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.signal.core.util.CursorUtil; -import org.signal.core.util.SqlUtil; - -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -public class MentionTable extends DatabaseTable implements RecipientIdDatabaseReference, ThreadIdDatabaseReference { - - public static final String TABLE_NAME = "mention"; - - private static final String ID = "_id"; - static final String THREAD_ID = "thread_id"; - public static final String MESSAGE_ID = "message_id"; - static final String RECIPIENT_ID = "recipient_id"; - private static final String RANGE_START = "range_start"; - private static final String RANGE_LENGTH = "range_length"; - - public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + - THREAD_ID + " INTEGER, " + - MESSAGE_ID + " INTEGER, " + - RECIPIENT_ID + " INTEGER, " + - RANGE_START + " INTEGER, " + - RANGE_LENGTH + " INTEGER)"; - - public static final String[] CREATE_INDEXES = new String[] { - "CREATE INDEX IF NOT EXISTS mention_message_id_index ON " + TABLE_NAME + " (" + MESSAGE_ID + ");", - "CREATE INDEX IF NOT EXISTS mention_recipient_id_thread_id_index ON " + TABLE_NAME + " (" + RECIPIENT_ID + ", " + THREAD_ID + ");" - }; - - public MentionTable(@NonNull Context context, @NonNull SignalDatabase databaseHelper) { - super(context, databaseHelper); - } - - public void insert(long threadId, long messageId, @NonNull Collection mentions) { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - - db.beginTransaction(); - try { - for (Mention mention : mentions) { - ContentValues values = new ContentValues(); - values.put(THREAD_ID, threadId); - values.put(MESSAGE_ID, messageId); - values.put(RECIPIENT_ID, mention.getRecipientId().toLong()); - values.put(RANGE_START, mention.getStart()); - values.put(RANGE_LENGTH, mention.getLength()); - db.insert(TABLE_NAME, null, values); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public @NonNull List getMentionsForMessage(long messageId) { - SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); - List mentions = new LinkedList<>(); - - try (Cursor cursor = db.query(TABLE_NAME, null, MESSAGE_ID + " = ?", SqlUtil.buildArgs(messageId), null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - mentions.add(new Mention(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)), - CursorUtil.requireInt(cursor, RANGE_START), - CursorUtil.requireInt(cursor, RANGE_LENGTH))); - } - } - - return mentions; - } - - public @NonNull Map> getMentionsForMessages(@NonNull Collection messageIds) { - SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); - String ids = TextUtils.join(",", messageIds); - - try (Cursor cursor = db.query(TABLE_NAME, null, MESSAGE_ID + " IN (" + ids + ")", null, null, null, null)) { - return readMentions(cursor); - } - } - - public @NonNull Map> getMentionsContainingRecipients(@NonNull Collection recipientIds, long limit) { - return getMentionsContainingRecipients(recipientIds, -1, limit); - } - - public @NonNull Map> getMentionsContainingRecipients(@NonNull Collection recipientIds, long threadId, long limit) { - SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); - String ids = TextUtils.join(",", Stream.of(recipientIds).map(RecipientId::serialize).toList()); - - String where = " WHERE " + RECIPIENT_ID + " IN (" + ids + ")"; - if (threadId != -1) { - where += " AND " + THREAD_ID + " = " + threadId; - } - - String subSelect = "SELECT DISTINCT " + MESSAGE_ID + - " FROM " + TABLE_NAME + - where + - " ORDER BY " + ID + " DESC" + - " LIMIT " + limit; - - String query = "SELECT *" + - " FROM " + TABLE_NAME + - " WHERE " + MESSAGE_ID + - " IN (" + subSelect + ")"; - - try (Cursor cursor = db.rawQuery(query, null)) { - return readMentions(cursor); - } - } - - void deleteMentionsForMessage(long messageId) { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - String where = MESSAGE_ID + " = ?"; - - db.delete(TABLE_NAME, where, SqlUtil.buildArgs(messageId)); - } - - void deleteAbandonedMentions() { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - String where = MESSAGE_ID + " NOT IN (SELECT " + MessageTable.ID + " FROM " + MessageTable.TABLE_NAME + ") OR " + THREAD_ID + " NOT IN (SELECT " + ThreadTable.ID + " FROM " + ThreadTable.TABLE_NAME + " WHERE " + ThreadTable.ACTIVE + " = 1)"; - - db.delete(TABLE_NAME, where, null); - } - - void deleteAllMentions() { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - db.delete(TABLE_NAME, null, null); - } - - private @NonNull Map> readMentions(@Nullable Cursor cursor) { - Map> mentions = new HashMap<>(); - while (cursor != null && cursor.moveToNext()) { - long messageId = CursorUtil.requireLong(cursor, MESSAGE_ID); - List messageMentions = mentions.get(messageId); - - if (messageMentions == null) { - messageMentions = new LinkedList<>(); - mentions.put(messageId, messageMentions); - } - - messageMentions.add(new Mention(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)), - CursorUtil.requireInt(cursor, RANGE_START), - CursorUtil.requireInt(cursor, RANGE_LENGTH))); - } - return mentions; - } - - @Override - public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) { - ContentValues values = new ContentValues(); - values.put(RECIPIENT_ID, toId.serialize()); - getWritableDatabase().update(TABLE_NAME, values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(fromId)); - } - - @Override - public void remapThread(long fromId, long toId) { - ContentValues values = new ContentValues(); - values.put(MentionTable.THREAD_ID, toId); - - getWritableDatabase().update(TABLE_NAME, values, THREAD_ID + " = ?", SqlUtil.buildArgs(fromId)); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MentionTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MentionTable.kt new file mode 100644 index 0000000000..a8353e9455 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MentionTable.kt @@ -0,0 +1,174 @@ +package org.thoughtcrime.securesms.database + +import android.content.Context +import android.database.Cursor +import org.signal.core.util.delete +import org.signal.core.util.deleteAll +import org.signal.core.util.insertInto +import org.signal.core.util.readToList +import org.signal.core.util.requireInt +import org.signal.core.util.requireLong +import org.signal.core.util.select +import org.signal.core.util.update +import org.signal.core.util.withinTransaction +import org.thoughtcrime.securesms.database.model.Mention +import org.thoughtcrime.securesms.recipients.RecipientId + +class MentionTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference, ThreadIdDatabaseReference { + + companion object { + const val TABLE_NAME = "mention" + const val ID = "_id" + const val THREAD_ID = "thread_id" + const val MESSAGE_ID = "message_id" + const val RECIPIENT_ID = "recipient_id" + const val RANGE_START = "range_start" + const val RANGE_LENGTH = "range_length" + + const val CREATE_TABLE = """ + CREATE TABLE $TABLE_NAME( + $ID INTEGER PRIMARY KEY AUTOINCREMENT, + $THREAD_ID INTEGER, + $MESSAGE_ID INTEGER, + $RECIPIENT_ID INTEGER, + $RANGE_START INTEGER, + $RANGE_LENGTH INTEGER + ) + """ + + val CREATE_INDEXES = arrayOf( + "CREATE INDEX IF NOT EXISTS mention_message_id_index ON $TABLE_NAME ($MESSAGE_ID);", + "CREATE INDEX IF NOT EXISTS mention_recipient_id_thread_id_index ON $TABLE_NAME ($RECIPIENT_ID, $THREAD_ID);" + ) + } + + fun insert(threadId: Long, messageId: Long, mentions: Collection) { + writableDatabase.withinTransaction { db -> + for (mention in mentions) { + db.insertInto(TABLE_NAME) + .values( + THREAD_ID to threadId, + MESSAGE_ID to messageId, + RECIPIENT_ID to mention.recipientId.toLong(), + RANGE_START to mention.start, + RANGE_LENGTH to mention.length + ) + .run() + } + } + } + + fun getMentionsForMessage(messageId: Long): List { + return readableDatabase + .select() + .from(TABLE_NAME) + .where("$MESSAGE_ID = $messageId") + .run() + .readToList { cursor -> + Mention( + RecipientId.from(cursor.requireLong(RECIPIENT_ID)), + cursor.requireInt(RANGE_START), + cursor.requireInt(RANGE_LENGTH) + ) + } + } + + fun getMentionsForMessages(messageIds: Collection): Map> { + val ids = messageIds.joinToString(separator = ",") { it.toString() } + + return readableDatabase + .select() + .from(TABLE_NAME) + .where("$MESSAGE_ID IN ($ids)") + .run() + .use { cursor -> readMentions(cursor) } + } + + fun getMentionsContainingRecipients(recipientIds: Collection, limit: Long): Map> { + return getMentionsContainingRecipients(recipientIds, -1, limit) + } + + fun getMentionsContainingRecipients(recipientIds: Collection, threadId: Long, limit: Long): Map> { + val ids = recipientIds.joinToString(separator = ",") { it.serialize() } + + var where = "$RECIPIENT_ID IN ($ids)" + if (threadId != -1L) { + where += " AND $THREAD_ID = $threadId" + } + + return readableDatabase + .select() + .from(TABLE_NAME) + .where( + """ + $MESSAGE_ID IN ( + SELECT DISTINCT $MESSAGE_ID + FROM $TABLE_NAME + WHERE $where + ORDER BY $ID DESC LIMIT $limit + ) + """ + ) + .run() + .use { cursor -> readMentions(cursor) } + } + + fun deleteMentionsForMessage(messageId: Long) { + writableDatabase + .delete(TABLE_NAME) + .where("$MESSAGE_ID = $messageId") + .run() + } + + fun deleteAbandonedMentions() { + writableDatabase + .delete(TABLE_NAME) + .where( + """ + $MESSAGE_ID NOT IN ( + SELECT $MessageTable.ID + FROM ${MessageTable.TABLE_NAME} + ) + OR $THREAD_ID NOT IN ( + SELECT ${ThreadTable.ID} + FROM ${ThreadTable.TABLE_NAME} + WHERE ${ThreadTable.ACTIVE} = 1 + ) + """ + ) + .run() + } + + fun deleteAllMentions() { + writableDatabase.deleteAll(TABLE_NAME) + } + + private fun readMentions(cursor: Cursor): Map> { + return cursor.readToList { + val messageId = it.requireLong(MESSAGE_ID) + val mention = Mention( + RecipientId.from(it.requireLong(RECIPIENT_ID)), + it.requireInt(RANGE_START), + it.requireInt(RANGE_LENGTH) + ) + + messageId to mention + }.groupBy({ it.first }, { it.second }) + } + + override fun remapRecipient(fromId: RecipientId, toId: RecipientId) { + writableDatabase + .update(TABLE_NAME) + .values(RECIPIENT_ID to toId.serialize()) + .where("$RECIPIENT_ID = ?", fromId) + .run() + } + + override fun remapThread(fromId: Long, toId: Long) { + writableDatabase + .update(TABLE_NAME) + .values(THREAD_ID to toId) + .where("$THREAD_ID = $fromId") + .run() + } +}