mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-20 17:57:29 +00:00
Add support for AttachmentBackfill sync messages.
This commit is contained in:
@@ -11,9 +11,11 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.internal.push.AddressableMessage
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentPointer
|
||||
import org.whispersystems.signalservice.internal.push.BodyRange
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
import org.whispersystems.signalservice.internal.push.ConversationIdentifier
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.EditMessage
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
@@ -163,13 +165,13 @@ object MessageContentFuzzer {
|
||||
val conversation = Recipient.resolved(conversationId)
|
||||
SyncMessage.DeleteForMe.MessageDeletes(
|
||||
conversation = if (conversation.isGroup) {
|
||||
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
|
||||
ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
|
||||
} else {
|
||||
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
|
||||
ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
|
||||
},
|
||||
|
||||
messages = conversationDeletes.map { (author, timestamp) ->
|
||||
SyncMessage.DeleteForMe.AddressableMessage(
|
||||
AddressableMessage(
|
||||
authorServiceId = Recipient.resolved(author).requireAci().toString(),
|
||||
sentTimestamp = timestamp
|
||||
)
|
||||
@@ -191,20 +193,20 @@ object MessageContentFuzzer {
|
||||
val conversation = Recipient.resolved(delete.conversationId)
|
||||
SyncMessage.DeleteForMe.ConversationDelete(
|
||||
conversation = if (conversation.isGroup) {
|
||||
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
|
||||
ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
|
||||
} else {
|
||||
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
|
||||
ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
|
||||
},
|
||||
|
||||
mostRecentMessages = delete.messages.map { (author, timestamp) ->
|
||||
SyncMessage.DeleteForMe.AddressableMessage(
|
||||
AddressableMessage(
|
||||
authorServiceId = Recipient.resolved(author).requireAci().toString(),
|
||||
sentTimestamp = timestamp
|
||||
)
|
||||
},
|
||||
|
||||
mostRecentNonExpiringMessages = delete.nonExpiringMessages.map { (author, timestamp) ->
|
||||
SyncMessage.DeleteForMe.AddressableMessage(
|
||||
AddressableMessage(
|
||||
authorServiceId = Recipient.resolved(author).requireAci().toString(),
|
||||
sentTimestamp = timestamp
|
||||
)
|
||||
@@ -228,9 +230,9 @@ object MessageContentFuzzer {
|
||||
val conversation = Recipient.resolved(conversationId)
|
||||
SyncMessage.DeleteForMe.LocalOnlyConversationDelete(
|
||||
conversation = if (conversation.isGroup) {
|
||||
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
|
||||
ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
|
||||
} else {
|
||||
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
|
||||
ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -250,11 +252,11 @@ object MessageContentFuzzer {
|
||||
attachmentDeletes = listOf(
|
||||
SyncMessage.DeleteForMe.AttachmentDelete(
|
||||
conversation = if (conversation.isGroup) {
|
||||
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
|
||||
ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
|
||||
} else {
|
||||
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
|
||||
ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
|
||||
},
|
||||
targetMessage = SyncMessage.DeleteForMe.AddressableMessage(
|
||||
targetMessage = AddressableMessage(
|
||||
authorServiceId = Recipient.resolved(message.first).requireAci().toString(),
|
||||
sentTimestamp = message.second
|
||||
),
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.attachments
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
|
||||
import org.whispersystems.signalservice.api.util.toByteArray
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentPointer
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Converts an [Attachment] to an [AttachmentPointer]. Will return null if any essential data is invalid.
|
||||
*/
|
||||
fun Attachment.toAttachmentPointer(context: Context): AttachmentPointer? {
|
||||
val attachment = this
|
||||
|
||||
if (TextUtils.isEmpty(attachment.remoteLocation)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(attachment.remoteKey)) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
val remoteId = SignalServiceAttachmentRemoteId.from(attachment.remoteLocation!!)
|
||||
|
||||
var attachmentWidth = attachment.width
|
||||
var attachmentHeight = attachment.height
|
||||
|
||||
if ((attachmentWidth == 0 || attachmentHeight == 0) && MediaUtil.hasVideoThumbnail(context, attachment.uri)) {
|
||||
val thumbnail = MediaUtil.getVideoThumbnail(context, attachment.uri, 1000)
|
||||
|
||||
if (thumbnail != null) {
|
||||
attachmentWidth = thumbnail.width
|
||||
attachmentHeight = thumbnail.height
|
||||
}
|
||||
}
|
||||
|
||||
return AttachmentPointer.Builder().apply {
|
||||
cdnNumber = attachment.cdn.cdnNumber
|
||||
contentType = attachment.contentType
|
||||
key = Base64.decode(attachment.remoteKey!!).toByteString()
|
||||
digest = attachment.remoteDigest?.toByteString()
|
||||
size = Util.toIntExact(attachment.size)
|
||||
uploadTimestamp = attachment.uploadTimestamp
|
||||
width = attachmentWidth.takeIf { it > 0 }
|
||||
height = attachmentHeight.takeIf { it > 0 }
|
||||
fileName = attachment.fileName
|
||||
incrementalMac = attachment.incrementalDigest?.toByteString()
|
||||
chunkSize = attachment.incrementalMacChunkSize.takeIf { it > 0 }
|
||||
flags = attachment.toFlags()
|
||||
caption = attachment.caption
|
||||
blurHash = attachment.blurHash?.hash
|
||||
clientUuid = attachment.uuid?.toByteArray()?.toByteString()
|
||||
|
||||
if (remoteId is SignalServiceAttachmentRemoteId.V2) {
|
||||
cdnId = remoteId.cdnId
|
||||
}
|
||||
|
||||
if (remoteId is SignalServiceAttachmentRemoteId.V4) {
|
||||
cdnKey = remoteId.cdnKey
|
||||
}
|
||||
}.build()
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
} catch (e: ArithmeticException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Attachment.toFlags(): Int {
|
||||
var flags = 0
|
||||
|
||||
if (this.voiceNote) {
|
||||
flags = flags or AttachmentPointer.Flags.VOICE_MESSAGE.value
|
||||
}
|
||||
|
||||
if (this.borderless) {
|
||||
flags = flags or AttachmentPointer.Flags.BORDERLESS.value
|
||||
}
|
||||
|
||||
if (this.videoGif) {
|
||||
flags = flags or AttachmentPointer.Flags.GIF.value
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
@@ -145,6 +145,8 @@ import org.thoughtcrime.securesms.database.ThreadTable;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceAttachmentBackfillMissingJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceAttachmentBackfillUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.AccountValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
|
||||
@@ -3570,6 +3570,15 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
.readToSingleLongOrNull()
|
||||
}
|
||||
|
||||
fun getMessageIdOrNull(message: SyncMessageId, threadId: Long): Long? {
|
||||
return readableDatabase
|
||||
.select(ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$DATE_SENT = ? AND $FROM_RECIPIENT_ID = ? AND $THREAD_ID = $threadId", message.timetamp, message.recipientId)
|
||||
.run()
|
||||
.readToSingleLongOrNull()
|
||||
}
|
||||
|
||||
fun deleteMessages(messagesToDelete: List<SyncMessageId>): List<SyncMessageId> {
|
||||
val threads = mutableSetOf<Long>()
|
||||
val unhandled = mutableListOf<SyncMessageId>()
|
||||
|
||||
@@ -42,6 +42,7 @@ public abstract class Job {
|
||||
private long lastRunAttemptTime;
|
||||
private long nextBackoffInterval;
|
||||
|
||||
private volatile boolean cascadingFailure;
|
||||
private volatile boolean canceled;
|
||||
|
||||
protected Context context;
|
||||
@@ -106,6 +107,16 @@ public abstract class Job {
|
||||
this.canceled = true;
|
||||
}
|
||||
|
||||
/** Indicates that this job is failing because a job earlier in the chain failed. */
|
||||
final void markCascadingFailure() {
|
||||
this.cascadingFailure = true;
|
||||
}
|
||||
|
||||
/** Whether or not this job is failing because a job earlier in the chain failed. */
|
||||
protected boolean isCascadingFailure() {
|
||||
return this.cascadingFailure;
|
||||
}
|
||||
|
||||
/** Provides a default exponential backoff given the current run attempt. */
|
||||
protected final long defaultBackoff() {
|
||||
return BackoffUtil.exponentialBackoff(runAttempt + 1, RemoteConfig.getDefaultMaxBackoff());
|
||||
|
||||
@@ -145,7 +145,10 @@ class JobController {
|
||||
List<Job> dependents = onFailure(job);
|
||||
job.setContext(application);
|
||||
job.onFailure();
|
||||
Stream.of(dependents).forEach(Job::onFailure);
|
||||
for (Job child : dependents) {
|
||||
child.markCascadingFailure();
|
||||
child.onFailure();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -535,18 +535,6 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Chain after(@NonNull Job job) {
|
||||
return after(Collections.singletonList(job));
|
||||
}
|
||||
|
||||
public Chain after(@NonNull List<? extends Job> jobs) {
|
||||
if (!jobs.isEmpty()) {
|
||||
this.jobs.add(0, new ArrayList<>(jobs));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void enqueue() {
|
||||
jobManager.enqueueChain(this);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,6 @@ class AttachmentUploadJob private constructor(
|
||||
|
||||
SignalDatabase.attachments.createKeyIvIfNecessary(attachmentId)
|
||||
|
||||
val messageSender = AppDependencies.signalServiceMessageSender
|
||||
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId) ?: throw InvalidAttachmentException("Cannot find the specified attachment.")
|
||||
|
||||
val timeSinceUpload = System.currentTimeMillis() - databaseAttachment.uploadTimestamp
|
||||
|
||||
@@ -113,156 +113,158 @@ import java.util.Map;
|
||||
public final class JobManagerFactories {
|
||||
|
||||
public static Map<String, Job.Factory> getJobFactories(@NonNull Application application) {
|
||||
return new HashMap<String, Job.Factory>() {{
|
||||
put(AccountConsistencyWorkerJob.KEY, new AccountConsistencyWorkerJob.Factory());
|
||||
put(AnalyzeDatabaseJob.KEY, new AnalyzeDatabaseJob.Factory());
|
||||
put(ApkUpdateJob.KEY, new ApkUpdateJob.Factory());
|
||||
put(ArchiveAttachmentBackfillJob.KEY, new ArchiveAttachmentBackfillJob.Factory());
|
||||
put(ArchiveThumbnailUploadJob.KEY, new ArchiveThumbnailUploadJob.Factory());
|
||||
put(AttachmentCompressionJob.KEY, new AttachmentCompressionJob.Factory());
|
||||
put(AttachmentCopyJob.KEY, new AttachmentCopyJob.Factory());
|
||||
put(AttachmentDownloadJob.KEY, new AttachmentDownloadJob.Factory());
|
||||
put(AttachmentHashBackfillJob.KEY, new AttachmentHashBackfillJob.Factory());
|
||||
put(AttachmentUploadJob.KEY, new AttachmentUploadJob.Factory());
|
||||
put(AutomaticSessionResetJob.KEY, new AutomaticSessionResetJob.Factory());
|
||||
put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory());
|
||||
put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory());
|
||||
put(BackfillDigestJob.KEY, new BackfillDigestJob.Factory());
|
||||
put(BackfillDigestsForDataFileJob.KEY, new BackfillDigestsForDataFileJob.Factory());
|
||||
put(BackupMessagesJob.KEY, new BackupMessagesJob.Factory());
|
||||
put(BackupRestoreJob.KEY, new BackupRestoreJob.Factory());
|
||||
put(BackupRestoreMediaJob.KEY, new BackupRestoreMediaJob.Factory());
|
||||
put(BackupSubscriptionCheckJob.KEY, new BackupSubscriptionCheckJob.Factory());
|
||||
put(BuildExpirationConfirmationJob.KEY, new BuildExpirationConfirmationJob.Factory());
|
||||
put(CallLinkPeekJob.KEY, new CallLinkPeekJob.Factory());
|
||||
put(CallLinkUpdateSendJob.KEY, new CallLinkUpdateSendJob.Factory());
|
||||
put(CallLogEventSendJob.KEY, new CallLogEventSendJob.Factory());
|
||||
put(CallSyncEventJob.KEY, new CallSyncEventJob.Factory());
|
||||
put(CheckRestoreMediaLeftJob.KEY, new CheckRestoreMediaLeftJob.Factory());
|
||||
put(CheckServiceReachabilityJob.KEY, new CheckServiceReachabilityJob.Factory());
|
||||
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
|
||||
put(ConversationShortcutRankingUpdateJob.KEY, new ConversationShortcutRankingUpdateJob.Factory());
|
||||
put(ConversationShortcutUpdateJob.KEY, new ConversationShortcutUpdateJob.Factory());
|
||||
put(CopyAttachmentToArchiveJob.KEY, new CopyAttachmentToArchiveJob.Factory());
|
||||
put(CreateReleaseChannelJob.KEY, new CreateReleaseChannelJob.Factory());
|
||||
put(DeleteAbandonedAttachmentsJob.KEY, new DeleteAbandonedAttachmentsJob.Factory());
|
||||
put(NewLinkedDeviceNotificationJob.KEY, new NewLinkedDeviceNotificationJob.Factory());
|
||||
put(DeviceNameChangeJob.KEY, new DeviceNameChangeJob.Factory());
|
||||
put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory());
|
||||
put(DownloadLatestEmojiDataJob.KEY, new DownloadLatestEmojiDataJob.Factory());
|
||||
put(EmojiSearchIndexDownloadJob.KEY, new EmojiSearchIndexDownloadJob.Factory());
|
||||
put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory());
|
||||
put(FetchRemoteMegaphoneImageJob.KEY, new FetchRemoteMegaphoneImageJob.Factory());
|
||||
put(FontDownloaderJob.KEY, new FontDownloaderJob.Factory());
|
||||
put(ForceUpdateGroupV2Job.KEY, new ForceUpdateGroupV2Job.Factory());
|
||||
put(ForceUpdateGroupV2WorkerJob.KEY, new ForceUpdateGroupV2WorkerJob.Factory());
|
||||
put(GenerateAudioWaveFormJob.KEY, new GenerateAudioWaveFormJob.Factory());
|
||||
put(GroupCallUpdateSendJob.KEY, new GroupCallUpdateSendJob.Factory());
|
||||
put(GroupCallPeekJob.KEY, new GroupCallPeekJob.Factory());
|
||||
put(GroupCallPeekWorkerJob.KEY, new GroupCallPeekWorkerJob.Factory());
|
||||
put(GroupRingCleanupJob.KEY, new GroupRingCleanupJob.Factory());
|
||||
put(GroupV2UpdateSelfProfileKeyJob.KEY, new GroupV2UpdateSelfProfileKeyJob.Factory());
|
||||
put(InAppPaymentAuthCheckJob.KEY, new InAppPaymentAuthCheckJob.Factory());
|
||||
put(InAppPaymentGiftSendJob.KEY, new InAppPaymentGiftSendJob.Factory());
|
||||
put(InAppPaymentKeepAliveJob.KEY, new InAppPaymentKeepAliveJob.Factory());
|
||||
put(InAppPaymentPurchaseTokenJob.KEY, new InAppPaymentPurchaseTokenJob.Factory());
|
||||
put(InAppPaymentRecurringContextJob.KEY, new InAppPaymentRecurringContextJob.Factory());
|
||||
put(InAppPaymentOneTimeContextJob.KEY, new InAppPaymentOneTimeContextJob.Factory());
|
||||
put(InAppPaymentRedemptionJob.KEY, new InAppPaymentRedemptionJob.Factory());
|
||||
put(IndividualSendJob.KEY, new IndividualSendJob.Factory());
|
||||
put(LeaveGroupV2Job.KEY, new LeaveGroupV2Job.Factory());
|
||||
put(LeaveGroupV2WorkerJob.KEY, new LeaveGroupV2WorkerJob.Factory());
|
||||
put(LinkedDeviceInactiveCheckJob.KEY, new LinkedDeviceInactiveCheckJob.Factory());
|
||||
put(LocalArchiveJob.KEY, new LocalArchiveJob.Factory());
|
||||
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
|
||||
put(LocalBackupJobApi29.KEY, new LocalBackupJobApi29.Factory());
|
||||
put(MarkerJob.KEY, new MarkerJob.Factory());
|
||||
put(MultiDeviceBlockedUpdateJob.KEY, new MultiDeviceBlockedUpdateJob.Factory());
|
||||
put(MultiDeviceCallLinkSyncJob.KEY, new MultiDeviceCallLinkSyncJob.Factory());
|
||||
put(MultiDeviceConfigurationUpdateJob.KEY, new MultiDeviceConfigurationUpdateJob.Factory());
|
||||
put(MultiDeviceContactSyncJob.KEY, new MultiDeviceContactSyncJob.Factory());
|
||||
put(MultiDeviceContactUpdateJob.KEY, new MultiDeviceContactUpdateJob.Factory());
|
||||
put(MultiDeviceDeleteSyncJob.KEY, new MultiDeviceDeleteSyncJob.Factory());
|
||||
put(MultiDeviceKeysUpdateJob.KEY, new MultiDeviceKeysUpdateJob.Factory());
|
||||
put(MultiDeviceMessageRequestResponseJob.KEY, new MultiDeviceMessageRequestResponseJob.Factory());
|
||||
put(MultiDeviceOutgoingPaymentSyncJob.KEY, new MultiDeviceOutgoingPaymentSyncJob.Factory());
|
||||
put(MultiDeviceProfileContentUpdateJob.KEY, new MultiDeviceProfileContentUpdateJob.Factory());
|
||||
put(MultiDeviceProfileKeyUpdateJob.KEY, new MultiDeviceProfileKeyUpdateJob.Factory());
|
||||
put(MultiDeviceReadUpdateJob.KEY, new MultiDeviceReadUpdateJob.Factory());
|
||||
put(MultiDeviceStickerPackOperationJob.KEY, new MultiDeviceStickerPackOperationJob.Factory());
|
||||
put(MultiDeviceStickerPackSyncJob.KEY, new MultiDeviceStickerPackSyncJob.Factory());
|
||||
put(MultiDeviceStorageSyncRequestJob.KEY, new MultiDeviceStorageSyncRequestJob.Factory());
|
||||
put(MultiDeviceSubscriptionSyncRequestJob.KEY, new MultiDeviceSubscriptionSyncRequestJob.Factory());
|
||||
put(MultiDeviceVerifiedUpdateJob.KEY, new MultiDeviceVerifiedUpdateJob.Factory());
|
||||
put(MultiDeviceViewOnceOpenJob.KEY, new MultiDeviceViewOnceOpenJob.Factory());
|
||||
put(MultiDeviceViewedUpdateJob.KEY, new MultiDeviceViewedUpdateJob.Factory());
|
||||
put(NullMessageSendJob.KEY, new NullMessageSendJob.Factory());
|
||||
put(OptimizeMediaJob.KEY, new OptimizeMediaJob.Factory());
|
||||
put(OptimizeMessageSearchIndexJob.KEY, new OptimizeMessageSearchIndexJob.Factory());
|
||||
put(PaymentLedgerUpdateJob.KEY, new PaymentLedgerUpdateJob.Factory());
|
||||
put(PaymentNotificationSendJob.KEY, new PaymentNotificationSendJob.Factory());
|
||||
put(PaymentNotificationSendJobV2.KEY, new PaymentNotificationSendJobV2.Factory());
|
||||
put(PaymentSendJob.KEY, new PaymentSendJob.Factory());
|
||||
put(PaymentTransactionCheckJob.KEY, new PaymentTransactionCheckJob.Factory());
|
||||
put(PnpInitializeDevicesJob.KEY, new PnpInitializeDevicesJob.Factory());
|
||||
put(PreKeysSyncJob.KEY, new PreKeysSyncJob.Factory());
|
||||
put(ProfileKeySendJob.KEY, new ProfileKeySendJob.Factory());
|
||||
put(ProfileUploadJob.KEY, new ProfileUploadJob.Factory());
|
||||
put(PushDistributionListSendJob.KEY, new PushDistributionListSendJob.Factory());
|
||||
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
|
||||
put(PushGroupSilentUpdateSendJob.KEY, new PushGroupSilentUpdateSendJob.Factory());
|
||||
put(MessageFetchJob.KEY, new MessageFetchJob.Factory());
|
||||
put(PushProcessEarlyMessagesJob.KEY, new PushProcessEarlyMessagesJob.Factory());
|
||||
put(PushProcessMessageErrorJob.KEY, new PushProcessMessageErrorJob.Factory());
|
||||
put(PushProcessMessageJob.KEY, new PushProcessMessageJob.Factory());
|
||||
put(ReactionSendJob.KEY, new ReactionSendJob.Factory());
|
||||
put(RebuildMessageSearchIndexJob.KEY, new RebuildMessageSearchIndexJob.Factory());
|
||||
put(ReclaimUsernameAndLinkJob.KEY, new ReclaimUsernameAndLinkJob.Factory());
|
||||
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
|
||||
put(RefreshCallLinkDetailsJob.KEY, new RefreshCallLinkDetailsJob.Factory());
|
||||
put(RefreshSvrCredentialsJob.KEY, new RefreshSvrCredentialsJob.Factory());
|
||||
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
|
||||
put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory());
|
||||
put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory());
|
||||
put(ReportSpamJob.KEY, new ReportSpamJob.Factory());
|
||||
put(ResendMessageJob.KEY, new ResendMessageJob.Factory());
|
||||
put(ResumableUploadSpecJob.KEY, new ResumableUploadSpecJob.Factory());
|
||||
put(RequestGroupV2InfoWorkerJob.KEY, new RequestGroupV2InfoWorkerJob.Factory());
|
||||
put(RequestGroupV2InfoJob.KEY, new RequestGroupV2InfoJob.Factory());
|
||||
put(RestoreAttachmentJob.KEY, new RestoreAttachmentJob.Factory());
|
||||
put(RestoreAttachmentThumbnailJob.KEY, new RestoreAttachmentThumbnailJob.Factory());
|
||||
put(RestoreLocalAttachmentJob.KEY, new RestoreLocalAttachmentJob.Factory());
|
||||
put(RestoreOptimizedMediaJob.KEY, new RestoreOptimizedMediaJob.Factory());
|
||||
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory());
|
||||
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory());
|
||||
put(RetrieveRemoteAnnouncementsJob.KEY, new RetrieveRemoteAnnouncementsJob.Factory());
|
||||
put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory());
|
||||
put(RotateProfileKeyJob.KEY, new RotateProfileKeyJob.Factory());
|
||||
put(SenderKeyDistributionSendJob.KEY, new SenderKeyDistributionSendJob.Factory());
|
||||
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
|
||||
put(SendPaymentsActivatedJob.KEY, new SendPaymentsActivatedJob.Factory());
|
||||
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application));
|
||||
put(SendRetryReceiptJob.KEY, new SendRetryReceiptJob.Factory());
|
||||
put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application));
|
||||
put(StorageRotateManifestJob.KEY, new StorageRotateManifestJob.Factory());
|
||||
put(SyncSystemContactLinksJob.KEY, new SyncSystemContactLinksJob.Factory());
|
||||
put(MultiDeviceStorySendSyncJob.KEY, new MultiDeviceStorySendSyncJob.Factory());
|
||||
put(ResetSvrGuessCountJob.KEY, new ResetSvrGuessCountJob.Factory());
|
||||
put(ServiceOutageDetectionJob.KEY, new ServiceOutageDetectionJob.Factory());
|
||||
put(StickerDownloadJob.KEY, new StickerDownloadJob.Factory());
|
||||
put(StickerPackDownloadJob.KEY, new StickerPackDownloadJob.Factory());
|
||||
put(StorageAccountRestoreJob.KEY, new StorageAccountRestoreJob.Factory());
|
||||
put(StorageForcePushJob.KEY, new StorageForcePushJob.Factory());
|
||||
put(StorageSyncJob.KEY, new StorageSyncJob.Factory());
|
||||
put(StoryOnboardingDownloadJob.KEY, new StoryOnboardingDownloadJob.Factory());
|
||||
put(SubmitRateLimitPushChallengeJob.KEY, new SubmitRateLimitPushChallengeJob.Factory());
|
||||
put(Svr2MirrorJob.KEY, new Svr2MirrorJob.Factory());
|
||||
put(Svr3MirrorJob.KEY, new Svr3MirrorJob.Factory());
|
||||
put(SyncArchivedMediaJob.KEY, new SyncArchivedMediaJob.Factory());
|
||||
put(ThreadUpdateJob.KEY, new ThreadUpdateJob.Factory());
|
||||
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
|
||||
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
||||
put(UploadAttachmentToArchiveJob.KEY, new UploadAttachmentToArchiveJob.Factory());
|
||||
return new HashMap<>() {{
|
||||
put(AccountConsistencyWorkerJob.KEY, new AccountConsistencyWorkerJob.Factory());
|
||||
put(AnalyzeDatabaseJob.KEY, new AnalyzeDatabaseJob.Factory());
|
||||
put(ApkUpdateJob.KEY, new ApkUpdateJob.Factory());
|
||||
put(ArchiveAttachmentBackfillJob.KEY, new ArchiveAttachmentBackfillJob.Factory());
|
||||
put(ArchiveThumbnailUploadJob.KEY, new ArchiveThumbnailUploadJob.Factory());
|
||||
put(AttachmentCompressionJob.KEY, new AttachmentCompressionJob.Factory());
|
||||
put(AttachmentCopyJob.KEY, new AttachmentCopyJob.Factory());
|
||||
put(AttachmentDownloadJob.KEY, new AttachmentDownloadJob.Factory());
|
||||
put(AttachmentHashBackfillJob.KEY, new AttachmentHashBackfillJob.Factory());
|
||||
put(AttachmentUploadJob.KEY, new AttachmentUploadJob.Factory());
|
||||
put(AutomaticSessionResetJob.KEY, new AutomaticSessionResetJob.Factory());
|
||||
put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory());
|
||||
put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory());
|
||||
put(BackfillDigestJob.KEY, new BackfillDigestJob.Factory());
|
||||
put(BackfillDigestsForDataFileJob.KEY, new BackfillDigestsForDataFileJob.Factory());
|
||||
put(BackupMessagesJob.KEY, new BackupMessagesJob.Factory());
|
||||
put(BackupRestoreJob.KEY, new BackupRestoreJob.Factory());
|
||||
put(BackupRestoreMediaJob.KEY, new BackupRestoreMediaJob.Factory());
|
||||
put(BackupSubscriptionCheckJob.KEY, new BackupSubscriptionCheckJob.Factory());
|
||||
put(BuildExpirationConfirmationJob.KEY, new BuildExpirationConfirmationJob.Factory());
|
||||
put(CallLinkPeekJob.KEY, new CallLinkPeekJob.Factory());
|
||||
put(CallLinkUpdateSendJob.KEY, new CallLinkUpdateSendJob.Factory());
|
||||
put(CallLogEventSendJob.KEY, new CallLogEventSendJob.Factory());
|
||||
put(CallSyncEventJob.KEY, new CallSyncEventJob.Factory());
|
||||
put(CheckRestoreMediaLeftJob.KEY, new CheckRestoreMediaLeftJob.Factory());
|
||||
put(CheckServiceReachabilityJob.KEY, new CheckServiceReachabilityJob.Factory());
|
||||
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
|
||||
put(ConversationShortcutRankingUpdateJob.KEY, new ConversationShortcutRankingUpdateJob.Factory());
|
||||
put(ConversationShortcutUpdateJob.KEY, new ConversationShortcutUpdateJob.Factory());
|
||||
put(CopyAttachmentToArchiveJob.KEY, new CopyAttachmentToArchiveJob.Factory());
|
||||
put(CreateReleaseChannelJob.KEY, new CreateReleaseChannelJob.Factory());
|
||||
put(DeleteAbandonedAttachmentsJob.KEY, new DeleteAbandonedAttachmentsJob.Factory());
|
||||
put(NewLinkedDeviceNotificationJob.KEY, new NewLinkedDeviceNotificationJob.Factory());
|
||||
put(DeviceNameChangeJob.KEY, new DeviceNameChangeJob.Factory());
|
||||
put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory());
|
||||
put(DownloadLatestEmojiDataJob.KEY, new DownloadLatestEmojiDataJob.Factory());
|
||||
put(EmojiSearchIndexDownloadJob.KEY, new EmojiSearchIndexDownloadJob.Factory());
|
||||
put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory());
|
||||
put(FetchRemoteMegaphoneImageJob.KEY, new FetchRemoteMegaphoneImageJob.Factory());
|
||||
put(FontDownloaderJob.KEY, new FontDownloaderJob.Factory());
|
||||
put(ForceUpdateGroupV2Job.KEY, new ForceUpdateGroupV2Job.Factory());
|
||||
put(ForceUpdateGroupV2WorkerJob.KEY, new ForceUpdateGroupV2WorkerJob.Factory());
|
||||
put(GenerateAudioWaveFormJob.KEY, new GenerateAudioWaveFormJob.Factory());
|
||||
put(GroupCallUpdateSendJob.KEY, new GroupCallUpdateSendJob.Factory());
|
||||
put(GroupCallPeekJob.KEY, new GroupCallPeekJob.Factory());
|
||||
put(GroupCallPeekWorkerJob.KEY, new GroupCallPeekWorkerJob.Factory());
|
||||
put(GroupRingCleanupJob.KEY, new GroupRingCleanupJob.Factory());
|
||||
put(GroupV2UpdateSelfProfileKeyJob.KEY, new GroupV2UpdateSelfProfileKeyJob.Factory());
|
||||
put(InAppPaymentAuthCheckJob.KEY, new InAppPaymentAuthCheckJob.Factory());
|
||||
put(InAppPaymentGiftSendJob.KEY, new InAppPaymentGiftSendJob.Factory());
|
||||
put(InAppPaymentKeepAliveJob.KEY, new InAppPaymentKeepAliveJob.Factory());
|
||||
put(InAppPaymentPurchaseTokenJob.KEY, new InAppPaymentPurchaseTokenJob.Factory());
|
||||
put(InAppPaymentRecurringContextJob.KEY, new InAppPaymentRecurringContextJob.Factory());
|
||||
put(InAppPaymentOneTimeContextJob.KEY, new InAppPaymentOneTimeContextJob.Factory());
|
||||
put(InAppPaymentRedemptionJob.KEY, new InAppPaymentRedemptionJob.Factory());
|
||||
put(IndividualSendJob.KEY, new IndividualSendJob.Factory());
|
||||
put(LeaveGroupV2Job.KEY, new LeaveGroupV2Job.Factory());
|
||||
put(LeaveGroupV2WorkerJob.KEY, new LeaveGroupV2WorkerJob.Factory());
|
||||
put(LinkedDeviceInactiveCheckJob.KEY, new LinkedDeviceInactiveCheckJob.Factory());
|
||||
put(LocalArchiveJob.KEY, new LocalArchiveJob.Factory());
|
||||
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
|
||||
put(LocalBackupJobApi29.KEY, new LocalBackupJobApi29.Factory());
|
||||
put(MarkerJob.KEY, new MarkerJob.Factory());
|
||||
put(MultiDeviceAttachmentBackfillMissingJob.KEY, new MultiDeviceAttachmentBackfillMissingJob.Factory());
|
||||
put(MultiDeviceAttachmentBackfillUpdateJob.KEY, new MultiDeviceAttachmentBackfillUpdateJob.Factory());
|
||||
put(MultiDeviceBlockedUpdateJob.KEY, new MultiDeviceBlockedUpdateJob.Factory());
|
||||
put(MultiDeviceCallLinkSyncJob.KEY, new MultiDeviceCallLinkSyncJob.Factory());
|
||||
put(MultiDeviceConfigurationUpdateJob.KEY, new MultiDeviceConfigurationUpdateJob.Factory());
|
||||
put(MultiDeviceContactSyncJob.KEY, new MultiDeviceContactSyncJob.Factory());
|
||||
put(MultiDeviceContactUpdateJob.KEY, new MultiDeviceContactUpdateJob.Factory());
|
||||
put(MultiDeviceDeleteSyncJob.KEY, new MultiDeviceDeleteSyncJob.Factory());
|
||||
put(MultiDeviceKeysUpdateJob.KEY, new MultiDeviceKeysUpdateJob.Factory());
|
||||
put(MultiDeviceMessageRequestResponseJob.KEY, new MultiDeviceMessageRequestResponseJob.Factory());
|
||||
put(MultiDeviceOutgoingPaymentSyncJob.KEY, new MultiDeviceOutgoingPaymentSyncJob.Factory());
|
||||
put(MultiDeviceProfileContentUpdateJob.KEY, new MultiDeviceProfileContentUpdateJob.Factory());
|
||||
put(MultiDeviceProfileKeyUpdateJob.KEY, new MultiDeviceProfileKeyUpdateJob.Factory());
|
||||
put(MultiDeviceReadUpdateJob.KEY, new MultiDeviceReadUpdateJob.Factory());
|
||||
put(MultiDeviceStickerPackOperationJob.KEY, new MultiDeviceStickerPackOperationJob.Factory());
|
||||
put(MultiDeviceStickerPackSyncJob.KEY, new MultiDeviceStickerPackSyncJob.Factory());
|
||||
put(MultiDeviceStorageSyncRequestJob.KEY, new MultiDeviceStorageSyncRequestJob.Factory());
|
||||
put(MultiDeviceSubscriptionSyncRequestJob.KEY, new MultiDeviceSubscriptionSyncRequestJob.Factory());
|
||||
put(MultiDeviceVerifiedUpdateJob.KEY, new MultiDeviceVerifiedUpdateJob.Factory());
|
||||
put(MultiDeviceViewOnceOpenJob.KEY, new MultiDeviceViewOnceOpenJob.Factory());
|
||||
put(MultiDeviceViewedUpdateJob.KEY, new MultiDeviceViewedUpdateJob.Factory());
|
||||
put(NullMessageSendJob.KEY, new NullMessageSendJob.Factory());
|
||||
put(OptimizeMediaJob.KEY, new OptimizeMediaJob.Factory());
|
||||
put(OptimizeMessageSearchIndexJob.KEY, new OptimizeMessageSearchIndexJob.Factory());
|
||||
put(PaymentLedgerUpdateJob.KEY, new PaymentLedgerUpdateJob.Factory());
|
||||
put(PaymentNotificationSendJob.KEY, new PaymentNotificationSendJob.Factory());
|
||||
put(PaymentNotificationSendJobV2.KEY, new PaymentNotificationSendJobV2.Factory());
|
||||
put(PaymentSendJob.KEY, new PaymentSendJob.Factory());
|
||||
put(PaymentTransactionCheckJob.KEY, new PaymentTransactionCheckJob.Factory());
|
||||
put(PnpInitializeDevicesJob.KEY, new PnpInitializeDevicesJob.Factory());
|
||||
put(PreKeysSyncJob.KEY, new PreKeysSyncJob.Factory());
|
||||
put(ProfileKeySendJob.KEY, new ProfileKeySendJob.Factory());
|
||||
put(ProfileUploadJob.KEY, new ProfileUploadJob.Factory());
|
||||
put(PushDistributionListSendJob.KEY, new PushDistributionListSendJob.Factory());
|
||||
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
|
||||
put(PushGroupSilentUpdateSendJob.KEY, new PushGroupSilentUpdateSendJob.Factory());
|
||||
put(MessageFetchJob.KEY, new MessageFetchJob.Factory());
|
||||
put(PushProcessEarlyMessagesJob.KEY, new PushProcessEarlyMessagesJob.Factory());
|
||||
put(PushProcessMessageErrorJob.KEY, new PushProcessMessageErrorJob.Factory());
|
||||
put(PushProcessMessageJob.KEY, new PushProcessMessageJob.Factory());
|
||||
put(ReactionSendJob.KEY, new ReactionSendJob.Factory());
|
||||
put(RebuildMessageSearchIndexJob.KEY, new RebuildMessageSearchIndexJob.Factory());
|
||||
put(ReclaimUsernameAndLinkJob.KEY, new ReclaimUsernameAndLinkJob.Factory());
|
||||
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
|
||||
put(RefreshCallLinkDetailsJob.KEY, new RefreshCallLinkDetailsJob.Factory());
|
||||
put(RefreshSvrCredentialsJob.KEY, new RefreshSvrCredentialsJob.Factory());
|
||||
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
|
||||
put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory());
|
||||
put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory());
|
||||
put(ReportSpamJob.KEY, new ReportSpamJob.Factory());
|
||||
put(ResendMessageJob.KEY, new ResendMessageJob.Factory());
|
||||
put(ResumableUploadSpecJob.KEY, new ResumableUploadSpecJob.Factory());
|
||||
put(RequestGroupV2InfoWorkerJob.KEY, new RequestGroupV2InfoWorkerJob.Factory());
|
||||
put(RequestGroupV2InfoJob.KEY, new RequestGroupV2InfoJob.Factory());
|
||||
put(RestoreAttachmentJob.KEY, new RestoreAttachmentJob.Factory());
|
||||
put(RestoreAttachmentThumbnailJob.KEY, new RestoreAttachmentThumbnailJob.Factory());
|
||||
put(RestoreLocalAttachmentJob.KEY, new RestoreLocalAttachmentJob.Factory());
|
||||
put(RestoreOptimizedMediaJob.KEY, new RestoreOptimizedMediaJob.Factory());
|
||||
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory());
|
||||
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory());
|
||||
put(RetrieveRemoteAnnouncementsJob.KEY, new RetrieveRemoteAnnouncementsJob.Factory());
|
||||
put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory());
|
||||
put(RotateProfileKeyJob.KEY, new RotateProfileKeyJob.Factory());
|
||||
put(SenderKeyDistributionSendJob.KEY, new SenderKeyDistributionSendJob.Factory());
|
||||
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
|
||||
put(SendPaymentsActivatedJob.KEY, new SendPaymentsActivatedJob.Factory());
|
||||
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application));
|
||||
put(SendRetryReceiptJob.KEY, new SendRetryReceiptJob.Factory());
|
||||
put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application));
|
||||
put(StorageRotateManifestJob.KEY, new StorageRotateManifestJob.Factory());
|
||||
put(SyncSystemContactLinksJob.KEY, new SyncSystemContactLinksJob.Factory());
|
||||
put(MultiDeviceStorySendSyncJob.KEY, new MultiDeviceStorySendSyncJob.Factory());
|
||||
put(ResetSvrGuessCountJob.KEY, new ResetSvrGuessCountJob.Factory());
|
||||
put(ServiceOutageDetectionJob.KEY, new ServiceOutageDetectionJob.Factory());
|
||||
put(StickerDownloadJob.KEY, new StickerDownloadJob.Factory());
|
||||
put(StickerPackDownloadJob.KEY, new StickerPackDownloadJob.Factory());
|
||||
put(StorageAccountRestoreJob.KEY, new StorageAccountRestoreJob.Factory());
|
||||
put(StorageForcePushJob.KEY, new StorageForcePushJob.Factory());
|
||||
put(StorageSyncJob.KEY, new StorageSyncJob.Factory());
|
||||
put(StoryOnboardingDownloadJob.KEY, new StoryOnboardingDownloadJob.Factory());
|
||||
put(SubmitRateLimitPushChallengeJob.KEY, new SubmitRateLimitPushChallengeJob.Factory());
|
||||
put(Svr2MirrorJob.KEY, new Svr2MirrorJob.Factory());
|
||||
put(Svr3MirrorJob.KEY, new Svr3MirrorJob.Factory());
|
||||
put(SyncArchivedMediaJob.KEY, new SyncArchivedMediaJob.Factory());
|
||||
put(ThreadUpdateJob.KEY, new ThreadUpdateJob.Factory());
|
||||
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
|
||||
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
||||
put(UploadAttachmentToArchiveJob.KEY, new UploadAttachmentToArchiveJob.Factory());
|
||||
|
||||
// Migrations
|
||||
put(AccountConsistencyMigrationJob.KEY, new AccountConsistencyMigrationJob.Factory());
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.protos.MultiDeviceAttachmentBackfillMissingJobData
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
|
||||
import org.whispersystems.signalservice.internal.push.AddressableMessage
|
||||
import org.whispersystems.signalservice.internal.push.ConversationIdentifier
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import java.io.IOException
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
* Tells linked devices that the requested message from a [SyncMessage.attachmentBackfillRequest] could not be found.
|
||||
*/
|
||||
class MultiDeviceAttachmentBackfillMissingJob(
|
||||
parameters: Parameters,
|
||||
private val targetMessage: AddressableMessage,
|
||||
private val targetConversation: ConversationIdentifier
|
||||
) : Job(parameters) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(MultiDeviceAttachmentBackfillMissingJob::class)
|
||||
|
||||
const val KEY = "MultiDeviceAttachmentBackfillMissingJob"
|
||||
|
||||
fun enqueue(targetMessage: AddressableMessage, targetConversation: ConversationIdentifier) {
|
||||
AppDependencies.jobManager.add(MultiDeviceAttachmentBackfillMissingJob(targetMessage, targetConversation))
|
||||
}
|
||||
}
|
||||
|
||||
constructor(targetMessage: AddressableMessage, targetConversation: ConversationIdentifier) : this(
|
||||
Parameters.Builder()
|
||||
.setLifespan(1.days.inWholeMilliseconds)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.build(),
|
||||
targetMessage,
|
||||
targetConversation
|
||||
)
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun serialize(): ByteArray {
|
||||
return MultiDeviceAttachmentBackfillMissingJobData(
|
||||
targetMessage = targetMessage,
|
||||
targetConversation = targetConversation
|
||||
).encode()
|
||||
}
|
||||
|
||||
override fun run(): Result {
|
||||
val syncMessage = SignalServiceSyncMessage.forAttachmentBackfillResponse(
|
||||
SyncMessage.AttachmentBackfillResponse(
|
||||
targetMessage = targetMessage,
|
||||
targetConversation = targetConversation,
|
||||
error = SyncMessage.AttachmentBackfillResponse.Error.MESSAGE_NOT_FOUND
|
||||
)
|
||||
)
|
||||
|
||||
return try {
|
||||
val result = AppDependencies.signalServiceMessageSender.sendSyncMessage(syncMessage)
|
||||
if (result.isSuccess) {
|
||||
Log.i(TAG, "[${targetMessage.sentTimestamp}] Successfully sent backfill missing message response.")
|
||||
Result.success()
|
||||
} else {
|
||||
Log.w(TAG, "[${targetMessage.sentTimestamp}] Non-successful result. Retrying.")
|
||||
Result.retry(defaultBackoff())
|
||||
}
|
||||
} catch (e: ServerRejectedException) {
|
||||
Log.w(TAG, e)
|
||||
Result.failure()
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
Result.retry(defaultBackoff())
|
||||
} catch (e: UntrustedIdentityException) {
|
||||
Log.w(TAG, e)
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
class Factory : Job.Factory<MultiDeviceAttachmentBackfillMissingJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): MultiDeviceAttachmentBackfillMissingJob {
|
||||
val data = MultiDeviceAttachmentBackfillMissingJobData.ADAPTER.decode(serializedData!!)
|
||||
return MultiDeviceAttachmentBackfillMissingJob(
|
||||
parameters,
|
||||
data.targetMessage!!,
|
||||
data.targetConversation!!
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.attachments.toAttachmentPointer
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.protos.MultiDeviceAttachmentBackfillUpdateJobData
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
|
||||
import org.whispersystems.signalservice.internal.push.AddressableMessage
|
||||
import org.whispersystems.signalservice.internal.push.ConversationIdentifier
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.AttachmentBackfillResponse.AttachmentData
|
||||
import java.io.IOException
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
* Tells linked devices about all the attachments that have been re-uploaded for a given [SyncMessage.attachmentBackfillRequest].
|
||||
*/
|
||||
class MultiDeviceAttachmentBackfillUpdateJob(
|
||||
parameters: Parameters,
|
||||
private val targetMessage: AddressableMessage,
|
||||
private val targetConversation: ConversationIdentifier,
|
||||
private val messageId: Long
|
||||
) : Job(parameters) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(MultiDeviceAttachmentBackfillUpdateJob::class)
|
||||
|
||||
const val KEY = "MultiDeviceAttachmentBackfillUpdateJob"
|
||||
|
||||
private val JOB_LIFESPAN = 1.days.inWholeMilliseconds
|
||||
private val UPLOAD_THRESHOLD = AttachmentUploadJob.UPLOAD_REUSE_THRESHOLD + JOB_LIFESPAN
|
||||
|
||||
fun enqueue(targetMessage: AddressableMessage, targetConversation: ConversationIdentifier, messageId: Long) {
|
||||
AppDependencies.jobManager.add(MultiDeviceAttachmentBackfillUpdateJob(targetMessage, targetConversation, messageId))
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
targetMessage: AddressableMessage,
|
||||
targetConversation: ConversationIdentifier,
|
||||
messageId: Long
|
||||
) : this(
|
||||
Parameters.Builder()
|
||||
.setLifespan(JOB_LIFESPAN)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.build(),
|
||||
targetMessage,
|
||||
targetConversation,
|
||||
messageId
|
||||
)
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun serialize(): ByteArray {
|
||||
return MultiDeviceAttachmentBackfillUpdateJobData(
|
||||
targetMessage = targetMessage,
|
||||
targetConversation = targetConversation,
|
||||
messageId = messageId
|
||||
).encode()
|
||||
}
|
||||
|
||||
override fun run(): Result {
|
||||
val attachments = SignalDatabase.attachments.getAttachmentsForMessage(messageId).sortedBy { it.displayOrder }
|
||||
if (attachments.isEmpty()) {
|
||||
Log.w(TAG, "Failed to find any attachments for the message! Sending a missing response.")
|
||||
MultiDeviceAttachmentBackfillMissingJob.enqueue(targetMessage, targetConversation)
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val attachmentDatas = attachments.map { attachment ->
|
||||
when {
|
||||
attachment.hasData && !attachment.isInProgress && attachment.withinUploadThreshold() -> {
|
||||
AttachmentData(attachment = attachment.toAttachmentPointer(context))
|
||||
}
|
||||
!attachment.hasData || attachment.isPermanentlyFailed -> {
|
||||
AttachmentData(status = AttachmentData.Status.TERMINAL_ERROR)
|
||||
}
|
||||
else -> {
|
||||
AttachmentData(status = AttachmentData.Status.PENDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val syncMessage = SignalServiceSyncMessage.forAttachmentBackfillResponse(
|
||||
SyncMessage.AttachmentBackfillResponse(
|
||||
targetMessage = targetMessage,
|
||||
targetConversation = targetConversation,
|
||||
attachments = SyncMessage.AttachmentBackfillResponse.AttachmentDataList(
|
||||
attachments = attachmentDatas
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return try {
|
||||
val result = AppDependencies.signalServiceMessageSender.sendSyncMessage(syncMessage)
|
||||
if (result.isSuccess) {
|
||||
Log.i(TAG, "[${targetMessage.sentTimestamp}] Successfully sent backfill update message response.")
|
||||
Result.success()
|
||||
} else {
|
||||
Log.w(TAG, "[${targetMessage.sentTimestamp}] Non-successful result. Retrying.")
|
||||
Result.retry(defaultBackoff())
|
||||
}
|
||||
} catch (e: ServerRejectedException) {
|
||||
Log.w(TAG, e)
|
||||
Result.failure()
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
Result.retry(defaultBackoff())
|
||||
} catch (e: UntrustedIdentityException) {
|
||||
Log.w(TAG, e)
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure() {
|
||||
if (isCascadingFailure) {
|
||||
Log.w(TAG, "The upload job failed! Enqueuing another instance of the job to notify of the failure.")
|
||||
MultiDeviceAttachmentBackfillUpdateJob.enqueue(targetMessage, targetConversation, messageId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DatabaseAttachment.withinUploadThreshold(): Boolean {
|
||||
return this.uploadTimestamp > 0 && System.currentTimeMillis() - this.uploadTimestamp < UPLOAD_THRESHOLD
|
||||
}
|
||||
|
||||
class Factory : Job.Factory<MultiDeviceAttachmentBackfillUpdateJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): MultiDeviceAttachmentBackfillUpdateJob {
|
||||
val data = MultiDeviceAttachmentBackfillUpdateJobData.ADAPTER.decode(serializedData!!)
|
||||
return MultiDeviceAttachmentBackfillUpdateJob(
|
||||
parameters,
|
||||
data.targetMessage!!,
|
||||
data.targetConversation!!,
|
||||
data.messageId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.protos.DeleteSyncJobData
|
||||
import org.thoughtcrime.securesms.jobs.protos.DeleteSyncJobData.AddressableMessage
|
||||
import org.thoughtcrime.securesms.jobs.protos.DeleteSyncJobData.AttachmentDelete
|
||||
import org.thoughtcrime.securesms.jobs.protos.DeleteSyncJobData.ThreadDelete
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -28,7 +27,9 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.internal.push.AddressableMessage
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
import org.whispersystems.signalservice.internal.push.ConversationIdentifier
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.DeleteForMe
|
||||
import java.io.IOException
|
||||
@@ -108,7 +109,7 @@ class MultiDeviceDeleteSyncJob private constructor(
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun createMessageDeletes(messageRecords: Collection<MessageRecord>): List<AddressableMessage> {
|
||||
private fun createMessageDeletes(messageRecords: Collection<MessageRecord>): List<DeleteSyncJobData.AddressableMessage> {
|
||||
return messageRecords.mapNotNull { message ->
|
||||
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(message.threadId)
|
||||
if (threadRecipient == null) {
|
||||
@@ -120,7 +121,7 @@ class MultiDeviceDeleteSyncJob private constructor(
|
||||
} else if (threadRecipient.isDistributionList || !message.canDeleteSync()) {
|
||||
null
|
||||
} else {
|
||||
AddressableMessage(
|
||||
DeleteSyncJobData.AddressableMessage(
|
||||
threadRecipientId = threadRecipient.id.toLong(),
|
||||
sentTimestamp = message.dateSent,
|
||||
authorRecipientId = message.fromRecipient.id.toLong()
|
||||
@@ -145,7 +146,7 @@ class MultiDeviceDeleteSyncJob private constructor(
|
||||
} else if (threadRecipient.isDistributionList || !message.canDeleteSync()) {
|
||||
null
|
||||
} else {
|
||||
AddressableMessage(
|
||||
DeleteSyncJobData.AddressableMessage(
|
||||
threadRecipientId = threadRecipient.id.toLong(),
|
||||
sentTimestamp = message.dateSent,
|
||||
authorRecipientId = message.fromRecipient.id.toLong()
|
||||
@@ -188,13 +189,13 @@ class MultiDeviceDeleteSyncJob private constructor(
|
||||
threadRecipientId = threadRecipient.id.toLong(),
|
||||
isFullDelete = isFullDelete,
|
||||
messages = messages.map {
|
||||
AddressableMessage(
|
||||
DeleteSyncJobData.AddressableMessage(
|
||||
sentTimestamp = it.dateSent,
|
||||
authorRecipientId = it.fromRecipient.id.toLong()
|
||||
)
|
||||
},
|
||||
nonExpiringMessages = nonExpiringMessages.map {
|
||||
AddressableMessage(
|
||||
DeleteSyncJobData.AddressableMessage(
|
||||
sentTimestamp = it.dateSent,
|
||||
authorRecipientId = it.fromRecipient.id.toLong()
|
||||
)
|
||||
@@ -207,7 +208,7 @@ class MultiDeviceDeleteSyncJob private constructor(
|
||||
|
||||
@VisibleForTesting
|
||||
constructor(
|
||||
messages: List<AddressableMessage> = emptyList(),
|
||||
messages: List<DeleteSyncJobData.AddressableMessage> = emptyList(),
|
||||
threads: List<ThreadDelete> = emptyList(),
|
||||
localOnlyThreads: List<ThreadDelete> = emptyList(),
|
||||
attachments: List<AttachmentDelete> = emptyList()
|
||||
@@ -370,17 +371,17 @@ class MultiDeviceDeleteSyncJob private constructor(
|
||||
return Content(syncMessage = syncMessage.build())
|
||||
}
|
||||
|
||||
private fun Recipient.toDeleteSyncConversationId(): DeleteForMe.ConversationIdentifier? {
|
||||
private fun Recipient.toDeleteSyncConversationId(): ConversationIdentifier? {
|
||||
return when {
|
||||
isGroup -> DeleteForMe.ConversationIdentifier(threadGroupId = requireGroupId().decodedId.toByteString())
|
||||
hasAci -> DeleteForMe.ConversationIdentifier(threadServiceId = requireAci().toString())
|
||||
hasPni -> DeleteForMe.ConversationIdentifier(threadServiceId = requirePni().toString())
|
||||
hasE164 -> DeleteForMe.ConversationIdentifier(threadE164 = requireE164())
|
||||
isGroup -> ConversationIdentifier(threadGroupId = requireGroupId().decodedId.toByteString())
|
||||
hasAci -> ConversationIdentifier(threadServiceId = requireAci().toString())
|
||||
hasPni -> ConversationIdentifier(threadServiceId = requirePni().toString())
|
||||
hasE164 -> ConversationIdentifier(threadE164 = requireE164())
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun AddressableMessage.toDeleteSyncMessage(): DeleteForMe.AddressableMessage? {
|
||||
private fun DeleteSyncJobData.AddressableMessage.toDeleteSyncMessage(): AddressableMessage? {
|
||||
val author: Recipient = Recipient.resolved(RecipientId.from(authorRecipientId))
|
||||
val authorServiceId: String? = author.aci.orNull()?.toString() ?: author.pni.orNull()?.toString()
|
||||
val authorE164: String? = if (authorServiceId == null) {
|
||||
@@ -393,7 +394,7 @@ class MultiDeviceDeleteSyncJob private constructor(
|
||||
Log.w(TAG, "Unable to send sync message without serviceId or e164 recipient: ${author.id}")
|
||||
null
|
||||
} else {
|
||||
DeleteForMe.AddressableMessage(
|
||||
AddressableMessage(
|
||||
authorServiceId = authorServiceId,
|
||||
authorE164 = authorE164,
|
||||
sentTimestamp = sentTimestamp
|
||||
|
||||
@@ -47,6 +47,9 @@ import org.thoughtcrime.securesms.groups.BadGroupIdException
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceAttachmentBackfillMissingJob
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceAttachmentBackfillUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactSyncJob
|
||||
@@ -95,6 +98,7 @@ import org.thoughtcrime.securesms.util.EarlyMessageCacheEntry
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.MessageConstraintsUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
|
||||
@@ -106,7 +110,9 @@ import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.internal.push.AddressableMessage
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
import org.whispersystems.signalservice.internal.push.ConversationIdentifier
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.EditMessage
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
@@ -129,6 +135,7 @@ import java.util.Optional
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
object SyncMessageProcessor {
|
||||
@@ -162,6 +169,8 @@ object SyncMessageProcessor {
|
||||
syncMessage.callLinkUpdate != null -> handleSynchronizeCallLink(syncMessage.callLinkUpdate!!, envelope.timestamp!!)
|
||||
syncMessage.callLogEvent != null -> handleSynchronizeCallLogEvent(syncMessage.callLogEvent!!, envelope.timestamp!!)
|
||||
syncMessage.deleteForMe != null -> handleSynchronizeDeleteForMe(context, syncMessage.deleteForMe!!, envelope.timestamp!!, earlyMessageCacheEntry)
|
||||
syncMessage.attachmentBackfillRequest != null -> handleSynchronizeAttachmentBackfillRequest(syncMessage.attachmentBackfillRequest!!, envelope.timestamp!!)
|
||||
syncMessage.attachmentBackfillResponse != null -> warn(envelope.timestamp!!, "Contains a backfill response, but we don't handle these!")
|
||||
else -> warn(envelope.timestamp!!, "Contains no known sync types...")
|
||||
}
|
||||
}
|
||||
@@ -1634,7 +1643,70 @@ object SyncMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private fun SyncMessage.DeleteForMe.ConversationIdentifier.toRecipientId(): RecipientId? {
|
||||
private fun handleSynchronizeAttachmentBackfillRequest(request: SyncMessage.AttachmentBackfillRequest, timestamp: Long) {
|
||||
if (!RemoteConfig.attachmentBackfillSync) {
|
||||
warn(timestamp, "[AttachmentBackfillRequest] Remote config not enabled! Skipping.")
|
||||
return
|
||||
}
|
||||
|
||||
if (request.targetMessage == null || request.targetConversation == null) {
|
||||
warn(timestamp, "[AttachmentBackfillRequest] Target message or target conversation was unset! Can't formulate a response, ignoring.")
|
||||
return
|
||||
}
|
||||
|
||||
val syncMessageId = request.targetMessage!!.toSyncMessageId(timestamp)
|
||||
if (syncMessageId == null) {
|
||||
warn(timestamp, "[AttachmentBackfillRequest] Invalid targetMessageId! Can't formulate a response, ignoring.")
|
||||
MultiDeviceAttachmentBackfillMissingJob.enqueue(request.targetMessage!!, request.targetConversation!!)
|
||||
return
|
||||
}
|
||||
|
||||
val conversationRecipientId: RecipientId? = request.targetConversation!!.toRecipientId()
|
||||
if (conversationRecipientId == null) {
|
||||
warn(timestamp, "[AttachmentBackfillRequest] Failed to find the target conversation! Enqueuing a 'missing' response.")
|
||||
MultiDeviceAttachmentBackfillMissingJob.enqueue(request.targetMessage!!, request.targetConversation!!)
|
||||
return
|
||||
}
|
||||
|
||||
val threadId = SignalDatabase.threads.getThreadIdFor(conversationRecipientId)
|
||||
if (threadId == null) {
|
||||
warn(timestamp, "[AttachmentBackfillRequest] No thread exists for the conversation! Enqueuing a 'missing' response.")
|
||||
MultiDeviceAttachmentBackfillMissingJob.enqueue(request.targetMessage!!, request.targetConversation!!)
|
||||
return
|
||||
}
|
||||
|
||||
val messageId: Long? = SignalDatabase.messages.getMessageIdOrNull(syncMessageId, threadId)
|
||||
if (messageId == null) {
|
||||
warn(timestamp, "[AttachmentBackfillRequest] Unable to find message! Enqueuing a 'missing' response.")
|
||||
MultiDeviceAttachmentBackfillMissingJob.enqueue(request.targetMessage!!, request.targetConversation!!)
|
||||
return
|
||||
}
|
||||
|
||||
val attachments: List<DatabaseAttachment> = SignalDatabase.attachments.getAttachmentsForMessage(messageId).sortedBy { it.displayOrder }
|
||||
if (attachments.isEmpty()) {
|
||||
warn(timestamp, "[AttachmentBackfillRequest] There were no attachments found for the message! Enqueuing a 'missing' response.")
|
||||
MultiDeviceAttachmentBackfillMissingJob.enqueue(request.targetMessage!!, request.targetConversation!!)
|
||||
return
|
||||
}
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val needsUpload = attachments.filter { now - it.uploadTimestamp > 3.days.inWholeMilliseconds }
|
||||
log(timestamp, "[AttachmentBackfillRequest] ${needsUpload.size}/${attachments.size} attachments need to be re-uploaded.")
|
||||
|
||||
for (attachment in needsUpload) {
|
||||
AppDependencies.jobManager
|
||||
.startChain(AttachmentUploadJob(attachment.attachmentId))
|
||||
.then(MultiDeviceAttachmentBackfillUpdateJob(request.targetMessage!!, request.targetConversation!!, messageId))
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
if (needsUpload.size != attachments.size) {
|
||||
log(timestamp, "[AttachmentBackfillRequest] At least one attachment didn't need to be uploaded. Enqueuing update job immediately.")
|
||||
MultiDeviceAttachmentBackfillUpdateJob.enqueue(request.targetMessage!!, request.targetConversation!!, messageId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConversationIdentifier.toRecipientId(): RecipientId? {
|
||||
return when {
|
||||
threadGroupId != null -> {
|
||||
try {
|
||||
@@ -1659,7 +1731,7 @@ object SyncMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private fun SyncMessage.DeleteForMe.AddressableMessage.toSyncMessageId(envelopeTimestamp: Long): MessageTable.SyncMessageId? {
|
||||
private fun AddressableMessage.toSyncMessageId(envelopeTimestamp: Long): MessageTable.SyncMessageId? {
|
||||
return if (this.sentTimestamp != null && (this.authorServiceId != null || this.authorE164 != null)) {
|
||||
val serviceId = ServiceId.parseOrNull(this.authorServiceId)
|
||||
val id = if (serviceId != null) {
|
||||
|
||||
@@ -1127,5 +1127,12 @@ object RemoteConfig {
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
/** Whether or not this device respect attachment backfill requests. */
|
||||
val attachmentBackfillSync: Boolean by remoteBoolean(
|
||||
key = "android.attachmentBackfillSync",
|
||||
defaultValue = false,
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import "ResumableUploads.proto";
|
||||
option java_package = "org.thoughtcrime.securesms.jobs.protos";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import SignalService.proto;
|
||||
|
||||
message CallSyncEventJobRecord {
|
||||
|
||||
@@ -157,4 +158,15 @@ message BackupMessagesJobData {
|
||||
message NewLinkedDeviceNotificationJobData {
|
||||
uint32 deviceId = 1;
|
||||
uint64 deviceCreatedAt = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message MultiDeviceAttachmentBackfillMissingJobData {
|
||||
signalservice.AddressableMessage targetMessage = 1;
|
||||
signalservice.ConversationIdentifier targetConversation = 2;
|
||||
}
|
||||
|
||||
message MultiDeviceAttachmentBackfillUpdateJobData {
|
||||
signalservice.AddressableMessage targetMessage = 1;
|
||||
signalservice.ConversationIdentifier targetConversation = 2;
|
||||
uint64 messageId = 3;
|
||||
}
|
||||
|
||||
@@ -761,6 +761,8 @@ public class SignalServiceMessageSender {
|
||||
content = createCallLogEventContent(message.getCallLogEvent().get());
|
||||
} else if (message.getDeviceNameChange().isPresent()) {
|
||||
content = createDeviceNameChangeContent(message.getDeviceNameChange().get());
|
||||
} else if (message.getAttachmentBackfillResponse().isPresent()) {
|
||||
content = createAttachmentBackfillResponseContent(message.getAttachmentBackfillResponse().get());
|
||||
} else {
|
||||
throw new IOException("Unsupported sync message!");
|
||||
}
|
||||
@@ -1740,6 +1742,13 @@ public class SignalServiceMessageSender {
|
||||
return container.syncMessage(builder.build()).build();
|
||||
}
|
||||
|
||||
private Content createAttachmentBackfillResponseContent(SyncMessage.AttachmentBackfillResponse proto) {
|
||||
Content.Builder container = new Content.Builder();
|
||||
SyncMessage.Builder builder = createSyncMessageBuilder().attachmentBackfillResponse(proto);
|
||||
|
||||
return container.syncMessage(builder.build()).build();
|
||||
}
|
||||
|
||||
private SyncMessage.Builder createSyncMessageBuilder() {
|
||||
byte[] padding = Util.getRandomLengthSecretBytes(512);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
package org.whispersystems.signalservice.api.messages.multidevice;
|
||||
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage;
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.AttachmentBackfillResponse;
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.DeviceNameChange;
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallEvent;
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate;
|
||||
@@ -37,6 +39,7 @@ public class SignalServiceSyncMessage {
|
||||
private final Optional<CallLinkUpdate> callLinkUpdate;
|
||||
private final Optional<CallLogEvent> callLogEvent;
|
||||
private final Optional<DeviceNameChange> deviceNameChange;
|
||||
private final Optional<AttachmentBackfillResponse> attachmentBackfillResponse;
|
||||
|
||||
private SignalServiceSyncMessage(Optional<SentTranscriptMessage> sent,
|
||||
Optional<ContactsMessage> contacts,
|
||||
@@ -55,26 +58,28 @@ public class SignalServiceSyncMessage {
|
||||
Optional<CallEvent> callEvent,
|
||||
Optional<CallLinkUpdate> callLinkUpdate,
|
||||
Optional<CallLogEvent> callLogEvent,
|
||||
Optional<DeviceNameChange> deviceNameChange)
|
||||
Optional<DeviceNameChange> deviceNameChange,
|
||||
Optional<AttachmentBackfillResponse> attachmentBackfillResponse)
|
||||
{
|
||||
this.sent = sent;
|
||||
this.contacts = contacts;
|
||||
this.blockedList = blockedList;
|
||||
this.request = request;
|
||||
this.reads = reads;
|
||||
this.viewOnceOpen = viewOnceOpen;
|
||||
this.verified = verified;
|
||||
this.configuration = configuration;
|
||||
this.stickerPackOperations = stickerPackOperations;
|
||||
this.fetchType = fetchType;
|
||||
this.keys = keys;
|
||||
this.messageRequestResponse = messageRequestResponse;
|
||||
this.outgoingPaymentMessage = outgoingPaymentMessage;
|
||||
this.views = views;
|
||||
this.callEvent = callEvent;
|
||||
this.callLinkUpdate = callLinkUpdate;
|
||||
this.callLogEvent = callLogEvent;
|
||||
this.deviceNameChange = deviceNameChange;
|
||||
this.sent = sent;
|
||||
this.contacts = contacts;
|
||||
this.blockedList = blockedList;
|
||||
this.request = request;
|
||||
this.reads = reads;
|
||||
this.viewOnceOpen = viewOnceOpen;
|
||||
this.verified = verified;
|
||||
this.configuration = configuration;
|
||||
this.stickerPackOperations = stickerPackOperations;
|
||||
this.fetchType = fetchType;
|
||||
this.keys = keys;
|
||||
this.messageRequestResponse = messageRequestResponse;
|
||||
this.outgoingPaymentMessage = outgoingPaymentMessage;
|
||||
this.views = views;
|
||||
this.callEvent = callEvent;
|
||||
this.callLinkUpdate = callLinkUpdate;
|
||||
this.callLogEvent = callLogEvent;
|
||||
this.deviceNameChange = deviceNameChange;
|
||||
this.attachmentBackfillResponse = attachmentBackfillResponse;
|
||||
}
|
||||
|
||||
public static SignalServiceSyncMessage forSentTranscript(SentTranscriptMessage sent) {
|
||||
@@ -95,6 +100,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -116,6 +122,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -137,6 +144,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -158,6 +166,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -179,6 +188,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -200,6 +210,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -224,6 +235,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -245,6 +257,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -266,6 +279,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -287,6 +301,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -308,6 +323,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -329,6 +345,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -350,6 +367,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -371,6 +389,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -392,6 +411,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -413,6 +433,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.of(callEvent),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -434,6 +455,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.of(callLinkUpdate),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -455,6 +477,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.of(callLogEvent),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -476,7 +499,30 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.of(deviceNameChange));
|
||||
Optional.of(deviceNameChange),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
public static SignalServiceSyncMessage forAttachmentBackfillResponse(@Nonnull AttachmentBackfillResponse backfillResponse) {
|
||||
return new SignalServiceSyncMessage(Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.of(backfillResponse));
|
||||
}
|
||||
|
||||
public static SignalServiceSyncMessage empty() {
|
||||
@@ -497,6 +543,7 @@ public class SignalServiceSyncMessage {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
@@ -572,6 +619,10 @@ public class SignalServiceSyncMessage {
|
||||
return deviceNameChange;
|
||||
}
|
||||
|
||||
public Optional<AttachmentBackfillResponse> getAttachmentBackfillResponse() {
|
||||
return attachmentBackfillResponse;
|
||||
}
|
||||
|
||||
public enum FetchType {
|
||||
LOCAL_PROFILE,
|
||||
STORAGE_MANIFEST,
|
||||
|
||||
@@ -650,22 +650,6 @@ message SyncMessage {
|
||||
}
|
||||
|
||||
message DeleteForMe {
|
||||
message ConversationIdentifier {
|
||||
oneof identifier {
|
||||
string threadServiceId = 1;
|
||||
bytes threadGroupId = 2;
|
||||
string threadE164 = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message AddressableMessage {
|
||||
oneof author {
|
||||
string authorServiceId = 1;
|
||||
string authorE164 = 2;
|
||||
}
|
||||
optional uint64 sentTimestamp = 3;
|
||||
}
|
||||
|
||||
message MessageDeletes {
|
||||
optional ConversationIdentifier conversation = 1;
|
||||
repeated AddressableMessage messages = 2;
|
||||
@@ -704,6 +688,41 @@ message SyncMessage {
|
||||
optional uint32 deviceId = 2;
|
||||
}
|
||||
|
||||
message AttachmentBackfillRequest {
|
||||
optional AddressableMessage targetMessage = 1;
|
||||
optional ConversationIdentifier targetConversation = 2;
|
||||
}
|
||||
|
||||
message AttachmentBackfillResponse {
|
||||
message AttachmentData {
|
||||
enum Status {
|
||||
PENDING = 0;
|
||||
TERMINAL_ERROR = 1;
|
||||
}
|
||||
|
||||
oneof data {
|
||||
AttachmentPointer attachment = 1;
|
||||
Status status = 2;
|
||||
}
|
||||
}
|
||||
|
||||
enum Error {
|
||||
MESSAGE_NOT_FOUND = 0;
|
||||
}
|
||||
|
||||
message AttachmentDataList {
|
||||
repeated AttachmentData attachments = 1;
|
||||
}
|
||||
|
||||
optional AddressableMessage targetMessage = 1;
|
||||
optional ConversationIdentifier targetConversation = 2;
|
||||
|
||||
oneof data {
|
||||
AttachmentDataList attachments = 3;
|
||||
Error error = 4;
|
||||
}
|
||||
}
|
||||
|
||||
optional Sent sent = 1;
|
||||
optional Contacts contacts = 2;
|
||||
reserved /*groups*/ 3;
|
||||
@@ -727,6 +746,8 @@ message SyncMessage {
|
||||
optional CallLogEvent callLogEvent = 21;
|
||||
optional DeleteForMe deleteForMe = 22;
|
||||
optional DeviceNameChange deviceNameChange = 23;
|
||||
optional AttachmentBackfillRequest attachmentBackfillRequest = 24;
|
||||
optional AttachmentBackfillResponse attachmentBackfillResponse = 25;
|
||||
}
|
||||
|
||||
message AttachmentPointer {
|
||||
@@ -836,4 +857,20 @@ message BodyRange {
|
||||
string mentionAci = 3;
|
||||
Style style = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message AddressableMessage {
|
||||
oneof author {
|
||||
string authorServiceId = 1;
|
||||
string authorE164 = 2;
|
||||
}
|
||||
optional uint64 sentTimestamp = 3;
|
||||
}
|
||||
|
||||
message ConversationIdentifier {
|
||||
oneof identifier {
|
||||
string threadServiceId = 1;
|
||||
bytes threadGroupId = 2;
|
||||
string threadE164 = 3;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user