Create a write-through cache for PendingRetryReceiptDatabase.

This commit is contained in:
Greyson Parrelli
2021-07-01 12:56:25 -04:00
committed by Alex Hart
parent 0921ebe5f1
commit 62040d06b4
9 changed files with 152 additions and 34 deletions

View File

@@ -172,6 +172,20 @@ private static final String[] GROUP_PROJECTION = {
}
}
public Optional<GroupRecord> getGroupByDistributionId(@NonNull DistributionId distributionId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String query = DISTRIBUTION_ID + " = ?";
String[] args = SqlUtil.buildArgs(distributionId);
try (Cursor cursor = db.query(TABLE_NAME, null, query, args, null, null, null)) {
if (cursor.moveToFirst()) {
return getGroup(cursor);
} else {
return Optional.absent();
}
}
}
/**
* Removes the specified members from the list of 'unmigrated V1 members' -- the list of members
* that were either dropped or had to be invited when migrating the group from V1->V2.

View File

@@ -0,0 +1,78 @@
package org.thoughtcrime.securesms.database
import android.content.Context
import androidx.annotation.VisibleForTesting
import org.thoughtcrime.securesms.database.model.PendingRetryReceiptModel
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.FeatureFlags
/**
* A write-through cache for [PendingRetryReceiptDatabase].
*
* We have to read from this cache every time we process an incoming message. As a result, it's a very performance-sensitive operation.
*
* 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
* future reads can happen in memory.
*/
class PendingRetryReceiptCache @VisibleForTesting constructor(
private val database: PendingRetryReceiptDatabase
) {
constructor(context: Context) : this(DatabaseFactory.getPendingRetryReceiptDatabase(context))
private val pendingRetries: MutableMap<RemoteMessageId, PendingRetryReceiptModel> = HashMap()
private var populated: Boolean = false
fun insert(author: RecipientId, authorDevice: Int, sentTimestamp: Long, receivedTimestamp: Long, threadId: Long) {
if (!FeatureFlags.senderKey()) return
ensurePopulated()
synchronized(pendingRetries) {
val model: PendingRetryReceiptModel = database.insert(author, authorDevice, sentTimestamp, receivedTimestamp, threadId)
pendingRetries[RemoteMessageId(author, sentTimestamp)] = model
}
}
fun get(author: RecipientId, sentTimestamp: Long): PendingRetryReceiptModel? {
if (!FeatureFlags.senderKey()) return null
ensurePopulated()
synchronized(pendingRetries) {
return pendingRetries[RemoteMessageId(author, sentTimestamp)]
}
}
fun getOldest(): PendingRetryReceiptModel? {
if (!FeatureFlags.senderKey()) return null
ensurePopulated()
synchronized(pendingRetries) {
return pendingRetries.values.minByOrNull { it.receivedTimestamp }
}
}
fun delete(model: PendingRetryReceiptModel) {
if (!FeatureFlags.senderKey()) return
ensurePopulated()
synchronized(pendingRetries) {
pendingRetries.remove(RemoteMessageId(model.author, model.sentTimestamp))
database.delete(model)
}
}
private fun ensurePopulated() {
if (!populated) {
synchronized(pendingRetries) {
if (!populated) {
database.all.forEach { model ->
pendingRetries[RemoteMessageId(model.author, model.sentTimestamp)] = model
}
populated = true
}
}
}
}
data class RemoteMessageId(val author: RecipientId, val sentTimestamp: Long)
}

View File

@@ -5,7 +5,6 @@ import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.PendingRetryReceiptModel;
@@ -13,8 +12,13 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
import java.util.LinkedList;
import java.util.List;
/**
* Holds information about messages we've sent out retry receipts for.
*
* Do not use directly! The only class that should be accessing this is {@link PendingRetryReceiptCache}
*/
public final class PendingRetryReceiptDatabase extends Database {
@@ -39,7 +43,7 @@ public final class PendingRetryReceiptDatabase extends Database {
super(context, databaseHelper);
}
public void insert(@NonNull RecipientId author, int authorDevice, long sentTimestamp, long receivedTimestamp, long threadId) {
@NonNull PendingRetryReceiptModel insert(@NonNull RecipientId author, int authorDevice, long sentTimestamp, long receivedTimestamp, long threadId) {
ContentValues values = new ContentValues();
values.put(AUTHOR, author.serialize());
values.put(DEVICE, authorDevice);
@@ -47,37 +51,27 @@ public final class PendingRetryReceiptDatabase extends Database {
values.put(RECEIVED_TIMESTAMP, receivedTimestamp);
values.put(THREAD_ID, threadId);
databaseHelper.getWritableDatabase().insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
long id = databaseHelper.getWritableDatabase().insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
return new PendingRetryReceiptModel(id, author, authorDevice, sentTimestamp, receivedTimestamp, threadId);
}
public @Nullable PendingRetryReceiptModel get(@NonNull RecipientId author, long sentTimestamp) {
String query = AUTHOR + " = ? AND " + SENT_TIMESTAMP + " = ?";
String[] args = SqlUtil.buildArgs(author, sentTimestamp);
@NonNull List<PendingRetryReceiptModel> getAll() {
List<PendingRetryReceiptModel> models = new LinkedList<>();
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, query, args, null, null, null)) {
if (cursor.moveToFirst()) {
return fromCursor(cursor);
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)) {
while (cursor.moveToNext()) {
models.add(fromCursor(cursor));
}
}
return null;
return models;
}
public @Nullable PendingRetryReceiptModel getOldest() {
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, RECEIVED_TIMESTAMP + " ASC", "1")) {
if (cursor.moveToFirst()) {
return fromCursor(cursor);
}
}
return null;
void delete(@NonNull PendingRetryReceiptModel model) {
databaseHelper.getWritableDatabase().delete(TABLE_NAME, ID_WHERE, SqlUtil.buildArgs(model.getId()));
}
public void delete(long id) {
databaseHelper.getWritableDatabase().delete(TABLE_NAME, ID_WHERE, SqlUtil.buildArgs(id));
}
private static @NonNull PendingRetryReceiptModel fromCursor(@NonNull Cursor cursor) {
return new PendingRetryReceiptModel(CursorUtil.requireLong(cursor, ID),
RecipientId.from(CursorUtil.requireString(cursor, AUTHOR)),