That fuck shit the fascists are using
1package org.tm.archive.database
2
3import androidx.annotation.VisibleForTesting
4import org.tm.archive.database.model.PendingRetryReceiptModel
5import org.tm.archive.recipients.RecipientId
6import org.tm.archive.util.FeatureFlags
7
8/**
9 * A write-through cache for [PendingRetryReceiptTable].
10 *
11 * We have to read from this cache every time we process an incoming message. As a result, it's a very performance-sensitive operation.
12 *
13 * This cache is very similar to our job storage cache or our key-value store, in the sense that the first access of it will fetch all data from disk so all
14 * future reads can happen in memory.
15 */
16class PendingRetryReceiptCache @VisibleForTesting constructor(
17 private val database: PendingRetryReceiptTable = SignalDatabase.pendingRetryReceipts
18) {
19
20 private val pendingRetries: MutableMap<RemoteMessageId, PendingRetryReceiptModel> = HashMap()
21 private var populated: Boolean = false
22
23 fun insert(author: RecipientId, authorDevice: Int, sentTimestamp: Long, receivedTimestamp: Long, threadId: Long) {
24 if (!FeatureFlags.retryReceipts()) return
25 ensurePopulated()
26 val model: PendingRetryReceiptModel = database.insert(author, authorDevice, sentTimestamp, receivedTimestamp, threadId)
27 synchronized(pendingRetries) {
28 val key = RemoteMessageId(author, sentTimestamp)
29 val existing: PendingRetryReceiptModel? = pendingRetries[key]
30
31 // We rely on db unique constraint and auto-incrementing ids for conflict resolution here.
32 if (existing == null || existing.id < model.id) {
33 pendingRetries[key] = model
34 }
35 }
36 }
37
38 fun get(author: RecipientId, sentTimestamp: Long): PendingRetryReceiptModel? {
39 if (!FeatureFlags.retryReceipts()) return null
40 ensurePopulated()
41
42 synchronized(pendingRetries) {
43 return pendingRetries[RemoteMessageId(author, sentTimestamp)]
44 }
45 }
46
47 fun getOldest(): PendingRetryReceiptModel? {
48 if (!FeatureFlags.retryReceipts()) return null
49 ensurePopulated()
50
51 synchronized(pendingRetries) {
52 return pendingRetries.values.minByOrNull { it.receivedTimestamp }
53 }
54 }
55
56 fun delete(model: PendingRetryReceiptModel) {
57 if (!FeatureFlags.retryReceipts()) return
58 ensurePopulated()
59
60 synchronized(pendingRetries) {
61 pendingRetries.remove(RemoteMessageId(model.author, model.sentTimestamp))
62 }
63 database.delete(model)
64 }
65
66 fun clear() {
67 if (!FeatureFlags.retryReceipts()) return
68
69 synchronized(pendingRetries) {
70 pendingRetries.clear()
71 populated = false
72 }
73 }
74
75 private fun ensurePopulated() {
76 if (!populated) {
77 synchronized(pendingRetries) {
78 if (!populated) {
79 database.all.forEach { model ->
80 pendingRetries[RemoteMessageId(model.author, model.sentTimestamp)] = model
81 }
82
83 populated = true
84 }
85 }
86 }
87 }
88
89 data class RemoteMessageId(val author: RecipientId, val sentTimestamp: Long)
90}