Fetch data in ConversationDataSource in parallel.

This commit is contained in:
Greyson Parrelli
2023-08-30 15:57:46 -04:00
committed by Nicholas Tinsley
parent e46759f436
commit 95c6f569d6
21 changed files with 297 additions and 273 deletions

View File

@@ -207,8 +207,6 @@ public class ConversationMessage {
String formattedDate = MessageRecordUtil.isScheduled(messageRecord) ? DateUtils.getOnlyTimeString(context, Locale.getDefault(), ((MediaMmsMessageRecord) messageRecord).getScheduledDate())
: DateUtils.getSimpleRelativeTimeSpanString(context, Locale.getDefault(), messageRecord.getTimestamp());
return new ConversationMessage(messageRecord,
styledAndMentionBody != null ? styledAndMentionBody : mentionsUpdate != null ? mentionsUpdate.getBody() : body,
mentionsUpdate != null ? mentionsUpdate.getMentions() : null,

View File

@@ -208,7 +208,7 @@ class DraftRepository(
val quoteId: QuoteId = QuoteId.deserialize(context, serialized) ?: return null
val messageRecord: MessageRecord = SignalDatabase.messages.getMessageFor(quoteId.id, quoteId.author)?.let {
if (it is MediaMmsMessageRecord) {
it.withAttachments(context, SignalDatabase.attachments.getAttachmentsForMessage(it.id))
it.withAttachments(SignalDatabase.attachments.getAttachmentsForMessage(it.id))
} else {
it
}

View File

@@ -45,7 +45,7 @@ public class AttachmentHelper {
List<DatabaseAttachment> attachments = messageIdToAttachments.get(record.getId());
if (Util.hasItems(attachments)) {
return ((MediaMmsMessageRecord) record).withAttachments(context, attachments);
return ((MediaMmsMessageRecord) record).withAttachments(attachments);
}
}

View File

@@ -1,46 +0,0 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CallHelper {
private final Collection<Long> messageIds = new LinkedList<>();
private Map<Long, CallTable.Call> messageIdToCall = Collections.emptyMap();
public void add(MessageRecord messageRecord) {
if (messageRecord.isCallLog() && !messageRecord.isGroupCall()) {
messageIds.add(messageRecord.getId());
}
}
public void fetchCalls() {
if (!messageIds.isEmpty()) {
messageIdToCall = SignalDatabase.calls().getCalls(messageIds);
}
}
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record.isCallLog() && record instanceof MediaMmsMessageRecord) {
CallTable.Call call = messageIdToCall.get(record.getId());
if (call != null) {
return ((MediaMmsMessageRecord) record).withCall(call);
}
}
return record;
})
.collect(Collectors.toList());
}
}

View File

@@ -19,14 +19,11 @@ import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord.NoGroupsI
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord.RemovedContactHidden
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord.UniversalExpireTimerUpdate
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import org.whispersystems.signalservice.api.push.ServiceId
private typealias ConversationElement = MappingModel<*>
@@ -99,13 +96,6 @@ class ConversationDataSource(
override fun load(start: Int, length: Int, totalSize: Int, cancellationSignal: PagedDataSource.CancellationSignal): List<ConversationElement> {
val stopwatch = Stopwatch("load($start, $length), thread $threadId")
var records: MutableList<MessageRecord> = ArrayList(length)
val mentionHelper = MentionHelper()
val quotedHelper = QuotedHelper()
val attachmentHelper = AttachmentHelper()
val reactionHelper = ReactionHelper()
val paymentHelper = PaymentHelper()
val callHelper = CallHelper()
val referencedIds = hashSetOf<ServiceId>()
MessageTable.mmsReaderFor(SignalDatabase.messages.getConversation(threadId, start.toLong(), length.toLong()))
.use { reader ->
@@ -115,17 +105,6 @@ class ConversationDataSource(
}
records.add(record)
mentionHelper.add(record)
quotedHelper.add(record)
reactionHelper.add(record)
attachmentHelper.add(record)
paymentHelper.add(record)
callHelper.add(record)
val updateDescription = record.getUpdateDisplayBody(context, null)
if (updateDescription != null) {
referencedIds.addAll(updateDescription.mentioned)
}
}
}
@@ -143,46 +122,19 @@ class ConversationDataSource(
stopwatch.split("messages")
mentionHelper.fetchMentions(context)
stopwatch.split("mentions")
val extraData = MessageDataFetcher.fetch(records)
stopwatch.split("extra-data")
quotedHelper.fetchQuotedState()
stopwatch.split("is-quoted")
reactionHelper.fetchReactions()
stopwatch.split("reactions")
records = reactionHelper.buildUpdatedModels(records)
stopwatch.split("reaction-models")
attachmentHelper.fetchAttachments()
stopwatch.split("attachments")
records = attachmentHelper.buildUpdatedModels(context, records)
stopwatch.split("attachment-models")
paymentHelper.fetchPayments()
stopwatch.split("payments")
records = paymentHelper.buildUpdatedModels(records)
stopwatch.split("payment-models")
callHelper.fetchCalls()
stopwatch.split("calls")
records = callHelper.buildUpdatedModels(records)
stopwatch.split("call-models")
referencedIds.forEach { Recipient.resolved(RecipientId.from(it)) }
stopwatch.split("recipient-resolves")
records = MessageDataFetcher.updateModelsWithData(records, extraData).toMutableList()
stopwatch.split("models")
val messages = records.map { record ->
ConversationMessageFactory.createWithUnresolvedData(
context,
record,
record.getDisplayBody(context),
mentionHelper.getMentions(record.id),
quotedHelper.isQuoted(record.id),
extraData.mentionsById[record.id],
extraData.hasBeenQuoted.contains(record.id),
threadRecipient
).toMappingModel()
}
@@ -198,7 +150,8 @@ class ConversationDataSource(
}
stopwatch.split("header")
stopwatch.stop(TAG)
val log = stopwatch.stopAndGetLogString(TAG)
Log.d(TAG, "$log || ${extraData.timeLog}")
return if (threadHeaders.isNotEmpty()) messages + threadHeaders else messages
}
@@ -227,50 +180,29 @@ class ConversationDataSource(
stopwatch.split("message")
var extraData: MessageDataFetcher.ExtraMessageData? = null
try {
if (record == null) {
return null
} else {
val mentions = SignalDatabase.mentions.getMentionsForMessage(key.id)
stopwatch.split("mentions")
extraData = MessageDataFetcher.fetch(record)
stopwatch.split("extra-data")
val isQuoted = SignalDatabase.messages.isQuoted(record)
stopwatch.split("is-quoted")
val reactions = SignalDatabase.reactions.getReactions(MessageId(key.id))
record = ReactionHelper.recordWithReactions(record, reactions)
stopwatch.split("reactions")
val attachments = SignalDatabase.attachments.getAttachmentsForMessage(key.id)
if (attachments.size > 0) {
record = (record as MediaMmsMessageRecord).withAttachments(context, attachments)
}
stopwatch.split("attachments")
if (record.isPaymentNotification) {
record = SignalDatabase.payments.updateMessageWithPayment(record)
}
stopwatch.split("payments")
if (record.isCallLog && !record.isGroupCall) {
val call = SignalDatabase.calls.getCallByMessageId(record.id)
if (call != null && record is MediaMmsMessageRecord) {
record = record.withCall(call)
}
}
stopwatch.split("calls")
record = MessageDataFetcher.updateModelWithData(record, extraData)
stopwatch.split("models")
return ConversationMessageFactory.createWithUnresolvedData(
ApplicationDependencies.getApplication(),
record,
record.getDisplayBody(ApplicationDependencies.getApplication()),
mentions,
isQuoted,
extraData.mentionsById[record.id],
extraData.hasBeenQuoted.contains(record.id),
threadRecipient
).toMappingModel()
}
} finally {
stopwatch.stop(TAG)
val log = stopwatch.stopAndGetLogString(TAG)
Log.d(TAG, "$log || ${extraData?.timeLog}")
}
}

View File

@@ -1,35 +0,0 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import android.content.Context;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class MentionHelper {
private Collection<Long> messageIds = new LinkedList<>();
private Map<Long, List<Mention>> messageIdToMentions = new HashMap<>();
public void add(MessageRecord record) {
if (record.isMms()) {
messageIds.add(record.getId());
}
}
public void fetchMentions(Context context) {
messageIdToMentions = SignalDatabase.mentions().getMentionsForMessages(messageIds);
}
public @Nullable List<Mention> getMentions(long id) {
return messageIdToMentions.get(id);
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.conversation.v2.data
import androidx.annotation.WorkerThread
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.roundedString
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.Mention
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.database.model.withAttachments
import org.thoughtcrime.securesms.database.model.withCall
import org.thoughtcrime.securesms.database.model.withPayment
import org.thoughtcrime.securesms.database.model.withReactions
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.payments.Payment
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.util.UuidUtil
import java.util.UUID
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Future
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.DurationUnit
/**
* Fetches various pieces of associated message data in parallel and returns the result.
*/
object MessageDataFetcher {
/**
* Singular version of [fetch].
*/
fun fetch(messageRecord: MessageRecord): ExtraMessageData {
return fetch(listOf(messageRecord))
}
/**
* Fetches all associated message data in parallel.
* It also performs a side-effect of resolving recipients referenced in group update messages.
*
* While work is spun off on various threads, the calling thread is blocked until they all complete,
* so this should be called on a background thread.
*/
@WorkerThread
fun fetch(messageRecords: List<MessageRecord>): ExtraMessageData {
val startTimeNanos = System.nanoTime()
val context = ApplicationDependencies.getApplication()
val messageIds: List<Long> = messageRecords.map { it.id }
val executor = SignalExecutors.BOUNDED
val mentionsFuture = executor.submitTimed {
SignalDatabase.mentions.getMentionsForMessages(messageIds)
}
val hasBeenQuotedFuture = executor.submitTimed {
SignalDatabase.messages.isQuoted(messageRecords)
}
val reactionsFuture = executor.submitTimed {
SignalDatabase.reactions.getReactionsForMessages(messageIds)
}
val attachmentsFuture = executor.submitTimed {
SignalDatabase.attachments.getAttachmentsForMessages(messageIds)
}
val paymentsFuture = executor.submitTimed {
val paymentUuidToMessageId: Map<UUID, Long> = messageRecords
.filter { it.isMms && it.isPaymentNotification }
.map { UuidUtil.parseOrNull(it.body) to it.id }
.filter { it.first != null }
.associate { it.first to it.second }
SignalDatabase
.payments
.getPayments(paymentUuidToMessageId.keys)
.associateBy { paymentUuidToMessageId[it.uuid]!! }
}
val callsFuture = executor.submitTimed {
SignalDatabase.calls.getCalls(messageIds)
}
val recipientsFuture = executor.submitTimed {
messageRecords.forEach { record ->
record.getUpdateDisplayBody(context, null)?.let { description ->
val ids = description.mentioned.map { RecipientId.from(it) }
Recipient.resolvedList(ids)
}
}
}
val mentionsResult = mentionsFuture.get()
val hasBeenQuotedResult = hasBeenQuotedFuture.get()
val reactionsResult = reactionsFuture.get()
val attachmentsResult = attachmentsFuture.get()
val paymentsResult = paymentsFuture.get()
val callsResult = callsFuture.get()
val recipientsResult = recipientsFuture.get()
val wallTimeMs = (System.nanoTime() - startTimeNanos).nanoseconds.toDouble(DurationUnit.MILLISECONDS)
val cpuTimeNanos = arrayOf(mentionsResult, hasBeenQuotedResult, reactionsResult, attachmentsResult, paymentsResult, callsResult, recipientsResult).sumOf { it.durationNanos }
val cpuTimeMs = cpuTimeNanos.nanoseconds.toDouble(DurationUnit.MILLISECONDS)
return ExtraMessageData(
mentionsById = mentionsResult.result,
hasBeenQuoted = hasBeenQuotedResult.result,
reactions = reactionsResult.result,
attachments = attachmentsResult.result,
payments = paymentsResult.result,
calls = callsResult.result,
timeLog = "mentions: ${mentionsResult.duration} is-quoted: ${hasBeenQuotedResult.duration} reactions: ${reactionsResult.duration} attachments: ${attachmentsResult.duration} payments: ${paymentsResult.duration} calls: ${callsResult.duration} >> cpuTime: ${cpuTimeMs.roundedString(2)} wallTime: ${wallTimeMs.roundedString(2)}"
)
}
/**
* Merges the data in [ExtraMessageData] into the provided list of [MessageRecord], outputted as
* a new list of models.
*/
fun updateModelsWithData(messageRecords: List<MessageRecord>, data: ExtraMessageData): List<MessageRecord> {
return messageRecords.map { it.updateWithData(data) }
}
/**
* Singular version of [updateModelsWithData]
*/
fun updateModelWithData(messageRecord: MessageRecord, data: ExtraMessageData): MessageRecord {
return listOf(messageRecord).map { it.updateWithData(data) }.first()
}
private fun MessageRecord.updateWithData(data: ExtraMessageData): MessageRecord {
var output: MessageRecord = this
output = data.reactions[id]?.let {
output.withReactions(it)
} ?: output
output = data.attachments[id]?.let {
output.withAttachments(it)
} ?: output
output = data.payments[id]?.let {
output.withPayment(it)
} ?: output
output = data.calls[id]?.let {
output.withCall(it)
} ?: output
return output
}
private fun <T> ExecutorService.submitTimed(callable: Callable<T>): Future<TimedResult<T>> {
return this.submit(
Callable {
val start = System.nanoTime()
val result = callable.call()
val end = System.nanoTime()
TimedResult(result = result, durationNanos = end - start)
}
)
}
data class TimedResult<T>(
val result: T,
val durationNanos: Long
) {
val duration: String
get() = durationNanos.nanoseconds.toDouble(DurationUnit.MILLISECONDS).roundedString(2)
}
data class ExtraMessageData(
val mentionsById: Map<Long, List<Mention>>,
val hasBeenQuoted: Set<Long>,
val reactions: Map<Long, List<ReactionRecord>>,
val attachments: Map<Long, List<DatabaseAttachment>>,
val payments: Map<Long, Payment>,
val calls: Map<Long, CallTable.Call>,
val timeLog: String
)
}

View File

@@ -1,52 +0,0 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.payments.Payment;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
public class PaymentHelper {
private final Map<UUID, Long> paymentMessages = new HashMap<>();
private final Map<Long, Payment> messageIdToPayment = new HashMap<>();
public void add(MessageRecord messageRecord) {
if (messageRecord.isMms() && messageRecord.isPaymentNotification()) {
UUID paymentUuid = UuidUtil.parseOrNull(messageRecord.getBody());
if (paymentUuid != null) {
paymentMessages.put(paymentUuid, messageRecord.getId());
}
}
}
public void fetchPayments() {
List<Payment> payments = SignalDatabase.payments().getPayments(paymentMessages.keySet());
for (Payment payment : payments) {
if (payment != null) {
messageIdToPayment.put(paymentMessages.get(payment.getUuid()), payment);
}
}
}
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record instanceof MediaMmsMessageRecord) {
Payment payment = messageIdToPayment.get(record.getId());
if (payment != null) {
return ((MediaMmsMessageRecord) record).withPayment(payment);
}
}
return record;
})
.collect(Collectors.toList());
}
}

View File

@@ -1,27 +0,0 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
public class QuotedHelper {
private Collection<MessageRecord> records = new LinkedList<>();
private Set<Long> hasBeenQuotedIds = new HashSet<>();
public void add(MessageRecord record) {
records.add(record);
}
public void fetchQuotedState() {
hasBeenQuotedIds = SignalDatabase.messages().isQuoted(records);
}
public boolean isQuoted(long id) {
return hasBeenQuotedIds.contains(id);
}
}

View File

@@ -18,11 +18,11 @@ import java.util.stream.Collectors;
public class ReactionHelper {
private Collection<MessageId> messageIds = new LinkedList<>();
private Map<MessageId, List<ReactionRecord>> messageIdToReactions = new HashMap<>();
private Collection<Long> messageIds = new LinkedList<>();
private Map<Long, List<ReactionRecord>> messageIdToReactions = new HashMap<>();
public void add(MessageRecord record) {
messageIds.add(new MessageId(record.getId()));
messageIds.add(record.getId());
}
public void addAll(List<MessageRecord> records) {
@@ -38,8 +38,7 @@ public class ReactionHelper {
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
MessageId messageId = new MessageId(record.getId());
List<ReactionRecord> reactions = messageIdToReactions.get(messageId);
List<ReactionRecord> reactions = messageIdToReactions.get(record.getId());
return recordWithReactions(record, reactions);
})

View File

@@ -5285,7 +5285,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val previews = getLinkPreviews(cursor, attachments)
val previewAttachments = previews.mapNotNull { it.thumbnail.orElse(null) }.toSet()
val slideDeck = buildSlideDeck(context, attachments.filterNot { contactAttachments.contains(it) }.filterNot { previewAttachments.contains(it) })
val slideDeck = buildSlideDeck(attachments.filterNot { contactAttachments.contains(it) }.filterNot { previewAttachments.contains(it) })
val quote = getQuote(cursor)
@@ -5437,7 +5437,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
companion object {
@JvmStatic
fun buildSlideDeck(context: Context, attachments: List<DatabaseAttachment>): SlideDeck {
fun buildSlideDeck(attachments: List<DatabaseAttachment>): SlideDeck {
val messageAttachments = attachments
.filterNot { it.isQuote }
.sortedWith(DisplayOrderComparator())

View File

@@ -70,22 +70,20 @@ class ReactionTable(context: Context, databaseHelper: SignalDatabase) : Database
return reactions
}
fun getReactionsForMessages(messageIds: Collection<MessageId>): Map<MessageId, List<ReactionRecord>> {
fun getReactionsForMessages(messageIds: Collection<Long>): Map<Long, List<ReactionRecord>> {
if (messageIds.isEmpty()) {
return emptyMap()
}
val messageIdToReactions: MutableMap<MessageId, MutableList<ReactionRecord>> = mutableMapOf()
val messageIdToReactions: MutableMap<Long, MutableList<ReactionRecord>> = mutableMapOf()
val args: List<Array<String>> = messageIds.map { SqlUtil.buildArgs(it.id) }
val args: List<Array<String>> = messageIds.map { SqlUtil.buildArgs(it) }
for (query: SqlUtil.Query in SqlUtil.buildCustomCollectionQuery("$MESSAGE_ID = ?", args)) {
readableDatabase.query(TABLE_NAME, null, query.where, query.whereArgs, null, null, null).use { cursor ->
while (cursor.moveToNext()) {
val reaction: ReactionRecord = readReaction(cursor)
val messageId = MessageId(
id = CursorUtil.requireLong(cursor, MESSAGE_ID)
)
val messageId = CursorUtil.requireLong(cursor, MESSAGE_ID)
var reactionsList: MutableList<ReactionRecord>? = messageIdToReactions[messageId]

View File

@@ -229,7 +229,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
getOriginalMessageId(), getRevisionNumber());
}
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List<DatabaseAttachment> attachments) {
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull List<DatabaseAttachment> attachments) {
Map<AttachmentId, DatabaseAttachment> attachmentIdMap = new HashMap<>();
for (DatabaseAttachment attachment : attachments) {
attachmentIdMap.put(attachment.getAttachmentId(), attachment);
@@ -239,10 +239,10 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
Set<Attachment> contactAttachments = contacts.stream().map(Contact::getAvatarAttachment).filter(Objects::nonNull).collect(Collectors.toSet());
List<LinkPreview> linkPreviews = updateLinkPreviews(getLinkPreviews(), attachmentIdMap);
Set<Attachment> linkPreviewAttachments = linkPreviews.stream().map(LinkPreview::getThumbnail).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toSet());
Quote quote = updateQuote(context, getQuote(), attachments);
Quote quote = updateQuote(getQuote(), attachments);
List<DatabaseAttachment> slideAttachments = attachments.stream().filter(a -> !contactAttachments.contains(a)).filter(a -> !linkPreviewAttachments.contains(a)).collect(Collectors.toList());
SlideDeck slideDeck = MessageTable.MmsReader.buildSlideDeck(context, slideAttachments);
SlideDeck slideDeck = MessageTable.MmsReader.buildSlideDeck(slideAttachments);
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck,
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
@@ -302,7 +302,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
.collect(Collectors.toList());
}
private static @Nullable Quote updateQuote(@NonNull Context context, @Nullable Quote quote, @NonNull List<DatabaseAttachment> attachments) {
private static @Nullable Quote updateQuote(@Nullable Quote quote, @NonNull List<DatabaseAttachment> attachments) {
if (quote == null) {
return null;
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.model
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.payments.Payment
fun MessageRecord.withReactions(reactions: List<ReactionRecord>): MessageRecord {
return if (this is MediaMmsMessageRecord) {
this.withReactions(reactions)
} else {
this
}
}
fun MessageRecord.withAttachments(attachments: List<DatabaseAttachment>): MessageRecord {
return if (this is MediaMmsMessageRecord) {
this.withAttachments(attachments)
} else {
this
}
}
fun MessageRecord.withPayment(payment: Payment): MessageRecord {
return if (this is MediaMmsMessageRecord) {
this.withPayment(payment)
} else {
this
}
}
fun MessageRecord.withCall(call: CallTable.Call): MessageRecord {
return if (this is MediaMmsMessageRecord) {
this.withCall(call)
} else {
this
}
}

View File

@@ -1047,7 +1047,7 @@ object DataMessageProcessor {
val attachments: MutableList<Attachment> = mutableListOf()
val mentions: MutableList<Mention> = mutableListOf()
quotedMessage = quotedMessage.withAttachments(context, SignalDatabase.attachments.getAttachmentsForMessage(quotedMessage.id))
quotedMessage = quotedMessage.withAttachments(SignalDatabase.attachments.getAttachmentsForMessage(quotedMessage.id))
mentions.addAll(SignalDatabase.mentions.getMentionsForMessage(quotedMessage.id))

View File

@@ -87,7 +87,7 @@ object EditMessageProcessor {
DataMessageProcessor.notifyTypingStoppedFromIncomingMessage(context, senderRecipient, threadRecipient.id, metadata.sourceDeviceId)
targetMessage = targetMessage.withAttachments(context, SignalDatabase.attachments.getAttachmentsForMessage(targetMessage.id))
targetMessage = targetMessage.withAttachments(SignalDatabase.attachments.getAttachmentsForMessage(targetMessage.id))
val insertResult: InsertResult? = if (isMediaMessage || targetMessage.quote != null || targetMessage.slideDeck.slides.isNotEmpty()) {
handleEditMediaMessage(senderRecipient.id, groupId, envelope, metadata, message, targetMessage)

View File

@@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.isStoryReaction
@@ -55,7 +54,7 @@ object NotificationStateProvider {
if (record is MediaMmsMessageRecord) {
val attachments = SignalDatabase.attachments.getAttachmentsForMessage(record.id)
if (attachments.isNotEmpty()) {
record = record.withAttachments(ApplicationDependencies.getApplication(), attachments)
record = record.withAttachments(attachments)
}
}

View File

@@ -121,7 +121,7 @@ data class StoryTextPostModel(
override fun decode(source: StoryTextPostModel, width: Int, height: Int, options: Options): Resource<Bitmap> {
val message = SignalDatabase.messages.getMessageFor(source.storySentAtMillis, source.storyAuthor).run {
if (this is MediaMmsMessageRecord) {
this.withAttachments(ApplicationDependencies.getApplication(), SignalDatabase.attachments.getAttachmentsForMessage(this.id))
this.withAttachments(SignalDatabase.attachments.getAttachmentsForMessage(this.id))
} else {
this
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.core.util
import kotlin.math.pow
import kotlin.math.round
/**
* Rounds a number to the specified number of places. e.g.
*
* 1.123456f.roundedString(2) = 1.12
* 1.123456f.roundedString(5) = 1.12346
*/
fun Double.roundedString(places: Int): String {
val powerMultiplier = 10f.pow(places)
return (round(this * powerMultiplier) / powerMultiplier).toString()
}

View File

@@ -9,7 +9,7 @@ import kotlin.math.pow
import kotlin.math.round
/**
* Rounds a number to the specififed number of places. e.g.
* Rounds a number to the specified number of places. e.g.
*
* 1.123456f.roundedString(2) = 1.12
* 1.123456f.roundedString(5) = 1.12346

View File

@@ -24,6 +24,10 @@ public class Stopwatch {
}
public void stop(@NonNull String tag) {
Log.d(tag, stopAndGetLogString(tag));
}
public String stopAndGetLogString(String tag) {
StringBuilder out = new StringBuilder();
out.append("[").append(title).append("] ");
@@ -43,9 +47,10 @@ public class Stopwatch {
out.append("total: ").append(splits.get(splits.size() - 1).time - startTime);
}
Log.d(tag, out.toString());
return out.toString();
}
private static class Split {
final long time;
final String label;