That fuck shit the fascists are using
1package org.tm.archive.database
2
3import android.content.ContentValues
4import android.content.Context
5import android.database.Cursor
6import org.signal.core.util.CursorUtil
7import org.signal.core.util.SqlUtil
8import org.signal.core.util.delete
9import org.signal.core.util.update
10import org.tm.archive.database.model.MessageId
11import org.tm.archive.database.model.ReactionRecord
12import org.tm.archive.dependencies.ApplicationDependencies
13import org.tm.archive.recipients.RecipientId
14
15/**
16 * Store reactions on messages.
17 */
18class ReactionTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
19
20 companion object {
21 const val TABLE_NAME = "reaction"
22
23 private const val ID = "_id"
24 const val MESSAGE_ID = "message_id"
25 const val AUTHOR_ID = "author_id"
26 const val EMOJI = "emoji"
27 const val DATE_SENT = "date_sent"
28 const val DATE_RECEIVED = "date_received"
29
30 @JvmField
31 val CREATE_TABLE = """
32 CREATE TABLE $TABLE_NAME (
33 $ID INTEGER PRIMARY KEY,
34 $MESSAGE_ID INTEGER NOT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE CASCADE,
35 $AUTHOR_ID INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
36 $EMOJI TEXT NOT NULL,
37 $DATE_SENT INTEGER NOT NULL,
38 $DATE_RECEIVED INTEGER NOT NULL,
39 UNIQUE($MESSAGE_ID, $AUTHOR_ID) ON CONFLICT REPLACE
40 )
41 """
42
43 @JvmField
44 val CREATE_INDEXES = arrayOf(
45 "CREATE INDEX IF NOT EXISTS reaction_author_id_index ON $TABLE_NAME ($AUTHOR_ID)"
46 )
47
48 private fun readReaction(cursor: Cursor): ReactionRecord {
49 return ReactionRecord(
50 emoji = CursorUtil.requireString(cursor, EMOJI),
51 author = RecipientId.from(CursorUtil.requireLong(cursor, AUTHOR_ID)),
52 dateSent = CursorUtil.requireLong(cursor, DATE_SENT),
53 dateReceived = CursorUtil.requireLong(cursor, DATE_RECEIVED)
54 )
55 }
56 }
57
58 fun getReactions(messageId: MessageId): List<ReactionRecord> {
59 val query = "$MESSAGE_ID = ?"
60 val args = SqlUtil.buildArgs(messageId.id)
61
62 val reactions: MutableList<ReactionRecord> = mutableListOf()
63
64 readableDatabase.query(TABLE_NAME, null, query, args, null, null, null).use { cursor ->
65 while (cursor.moveToNext()) {
66 reactions += readReaction(cursor)
67 }
68 }
69
70 return reactions
71 }
72
73 fun getReactionsForMessages(messageIds: Collection<Long>): Map<Long, List<ReactionRecord>> {
74 if (messageIds.isEmpty()) {
75 return emptyMap()
76 }
77
78 val messageIdToReactions: MutableMap<Long, MutableList<ReactionRecord>> = mutableMapOf()
79
80 val args: List<Array<String>> = messageIds.map { SqlUtil.buildArgs(it) }
81
82 for (query: SqlUtil.Query in SqlUtil.buildCustomCollectionQuery("$MESSAGE_ID = ?", args)) {
83 readableDatabase.query(TABLE_NAME, null, query.where, query.whereArgs, null, null, null).use { cursor ->
84 while (cursor.moveToNext()) {
85 val reaction: ReactionRecord = readReaction(cursor)
86 val messageId = CursorUtil.requireLong(cursor, MESSAGE_ID)
87
88 var reactionsList: MutableList<ReactionRecord>? = messageIdToReactions[messageId]
89
90 if (reactionsList == null) {
91 reactionsList = mutableListOf()
92 messageIdToReactions[messageId] = reactionsList
93 }
94
95 reactionsList.add(reaction)
96 }
97 }
98 }
99
100 return messageIdToReactions
101 }
102
103 fun addReaction(messageId: MessageId, reaction: ReactionRecord) {
104 writableDatabase.beginTransaction()
105 try {
106 val values = ContentValues().apply {
107 put(MESSAGE_ID, messageId.id)
108 put(EMOJI, reaction.emoji)
109 put(AUTHOR_ID, reaction.author.serialize())
110 put(DATE_SENT, reaction.dateSent)
111 put(DATE_RECEIVED, reaction.dateReceived)
112 }
113
114 writableDatabase.insert(TABLE_NAME, null, values)
115 SignalDatabase.messages.updateReactionsUnread(writableDatabase, messageId.id, hasReactions(messageId), false)
116
117 writableDatabase.setTransactionSuccessful()
118 } finally {
119 writableDatabase.endTransaction()
120 }
121
122 ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(messageId)
123 }
124
125 fun deleteReaction(messageId: MessageId, recipientId: RecipientId) {
126 writableDatabase.beginTransaction()
127 try {
128 writableDatabase
129 .delete(TABLE_NAME)
130 .where("$MESSAGE_ID = ? AND $AUTHOR_ID = ?", messageId.id, recipientId)
131 .run()
132
133 SignalDatabase.messages.updateReactionsUnread(writableDatabase, messageId.id, hasReactions(messageId), true)
134
135 writableDatabase.setTransactionSuccessful()
136 } finally {
137 writableDatabase.endTransaction()
138 }
139
140 ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(messageId)
141 }
142
143 fun deleteReactions(messageId: MessageId) {
144 writableDatabase
145 .delete(TABLE_NAME)
146 .where("$MESSAGE_ID = ?", messageId.id)
147 .run()
148 }
149
150 fun hasReaction(messageId: MessageId, reaction: ReactionRecord): Boolean {
151 val query = "$MESSAGE_ID = ? AND $AUTHOR_ID = ? AND $EMOJI = ?"
152 val args = SqlUtil.buildArgs(messageId.id, reaction.author, reaction.emoji)
153
154 readableDatabase.query(TABLE_NAME, arrayOf(MESSAGE_ID), query, args, null, null, null).use { cursor ->
155 return cursor.moveToFirst()
156 }
157 }
158
159 private fun hasReactions(messageId: MessageId): Boolean {
160 val query = "$MESSAGE_ID = ?"
161 val args = SqlUtil.buildArgs(messageId.id)
162
163 readableDatabase.query(TABLE_NAME, arrayOf(MESSAGE_ID), query, args, null, null, null).use { cursor ->
164 return cursor.moveToFirst()
165 }
166 }
167
168 override fun remapRecipient(oldAuthorId: RecipientId, newAuthorId: RecipientId) {
169 val query = "$AUTHOR_ID = ?"
170 val args = SqlUtil.buildArgs(oldAuthorId)
171 val values = ContentValues().apply {
172 put(AUTHOR_ID, newAuthorId.serialize())
173 }
174
175 readableDatabase.update(TABLE_NAME, values, query, args)
176 }
177
178 fun deleteAbandonedReactions() {
179 writableDatabase
180 .delete(TABLE_NAME)
181 .where("$MESSAGE_ID NOT IN (SELECT ${MessageTable.ID} FROM ${MessageTable.TABLE_NAME})")
182 .run()
183 }
184
185 fun moveReactionsToNewMessage(newMessageId: Long, previousId: Long) {
186 writableDatabase
187 .update(TABLE_NAME)
188 .values(MESSAGE_ID to newMessageId)
189 .where("$MESSAGE_ID = ?", previousId)
190 .run()
191 }
192}