mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Fetch data in ConversationDataSource in parallel.
This commit is contained in:
committed by
Nicholas Tinsley
parent
e46759f436
commit
95c6f569d6
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
})
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user