mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 19:29:54 +01:00
Fix issues with archive uploads matching digest.
This commit is contained in:
@@ -42,6 +42,8 @@ class ArchiveAttachmentBackfillJob private constructor(parameters: Parameters) :
|
||||
val jobs = SignalDatabase.attachments.getAttachmentsThatNeedArchiveUpload()
|
||||
.map { attachmentId -> UploadAttachmentToArchiveJob(attachmentId, forBackfill = true) }
|
||||
|
||||
SignalDatabase.attachments.createKeyIvDigestForAttachmentsThatNeedArchiveUpload()
|
||||
|
||||
SignalStore.backup.totalAttachmentUploadCount = jobs.size.toLong()
|
||||
SignalStore.backup.currentAttachmentUploadCount = 0
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.jobs.protos.ArchiveThumbnailUploadJobData
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.util.ImageCompressionUtil
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
@@ -93,13 +94,23 @@ class ArchiveThumbnailUploadJob private constructor(
|
||||
|
||||
val backupKey = SignalStore.svr.getOrCreateMasterKey().deriveBackupKey()
|
||||
|
||||
val resumableUpload = when (val result = BackupRepository.getMediaUploadSpec(secretKey = backupKey.deriveThumbnailTransitKey(attachment.getThumbnailMediaName()))) {
|
||||
val specResult = BackupRepository
|
||||
.getAttachmentUploadForm()
|
||||
.then { form ->
|
||||
SignalNetwork.attachments.getResumableUploadSpec(
|
||||
key = backupKey.deriveThumbnailTransitKey(attachment.getThumbnailMediaName()),
|
||||
iv = attachment.remoteIv!!,
|
||||
uploadForm = form
|
||||
)
|
||||
}
|
||||
|
||||
val resumableUpload = when (specResult) {
|
||||
is NetworkResult.Success -> {
|
||||
Log.d(TAG, "Got an upload spec!")
|
||||
result.result.toProto()
|
||||
specResult.result.toProto()
|
||||
}
|
||||
is NetworkResult.ApplicationError -> {
|
||||
Log.w(TAG, "Failed to get an upload spec due to an application error. Retrying.", result.throwable)
|
||||
Log.w(TAG, "Failed to get an upload spec due to an application error. Retrying.", specResult.throwable)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
is NetworkResult.NetworkError -> {
|
||||
@@ -107,7 +118,7 @@ class ArchiveThumbnailUploadJob private constructor(
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
is NetworkResult.StatusCodeError -> {
|
||||
Log.w(TAG, "Failed to get an upload spec with status code ${result.code}")
|
||||
Log.w(TAG, "Failed to get an upload spec with status code ${specResult.code}")
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
}
|
||||
@@ -125,7 +136,7 @@ class ArchiveThumbnailUploadJob private constructor(
|
||||
val backupDirectories = BackupRepository.getCdnBackupDirectories().successOrThrow()
|
||||
val mediaSecrets = backupKey.deriveMediaSecrets(attachment.getThumbnailMediaName())
|
||||
|
||||
return when (val result = BackupRepository.archiveThumbnail(attachmentPointer, attachment)) {
|
||||
return when (val result = BackupRepository.copyThumbnailToArchive(attachmentPointer, attachment)) {
|
||||
is NetworkResult.Success -> {
|
||||
// save attachment thumbnail
|
||||
val archiveMediaId = attachment.archiveMediaId ?: backupKey.deriveMediaId(attachment.getMediaName()).encode()
|
||||
|
||||
@@ -408,7 +408,7 @@ class AttachmentDownloadJob private constructor(
|
||||
messageId,
|
||||
attachmentId,
|
||||
LimitedInputStream.withoutLimits((body.source() as Source).buffer().inputStream()),
|
||||
iv = updatedAttachment.remoteIv
|
||||
iv = updatedAttachment.remoteIv!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupV2Event
|
||||
@@ -64,11 +65,17 @@ class BackupMessagesJob private constructor(parameters: Parameters) : Job(parame
|
||||
override fun onFailure() = Unit
|
||||
|
||||
override fun run(): Result {
|
||||
val stopwatch = Stopwatch("BackupMessagesJob")
|
||||
|
||||
SignalDatabase.attachments.createKeyIvDigestForAttachmentsThatNeedArchiveUpload().takeIf { it > 0 }?.let { count -> Log.w(TAG, "Needed to create $count key/iv/digests.") }
|
||||
stopwatch.split("key-iv-digest")
|
||||
|
||||
EventBus.getDefault().postSticky(BackupV2Event(type = BackupV2Event.Type.PROGRESS_MESSAGES, count = 0, estimatedTotalCount = 0))
|
||||
val tempBackupFile = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
|
||||
val outputStream = FileOutputStream(tempBackupFile)
|
||||
BackupRepository.export(outputStream = outputStream, append = { tempBackupFile.appendBytes(it) }, plaintext = false)
|
||||
stopwatch.split("export")
|
||||
|
||||
FileInputStream(tempBackupFile).use {
|
||||
when (val result = BackupRepository.uploadBackupFile(it, tempBackupFile.length())) {
|
||||
@@ -78,6 +85,7 @@ class BackupMessagesJob private constructor(parameters: Parameters) : Job(parame
|
||||
is NetworkResult.ApplicationError -> throw result.throwable
|
||||
}
|
||||
}
|
||||
stopwatch.split("upload")
|
||||
|
||||
SignalStore.backup.lastBackupProtoSize = tempBackupFile.length()
|
||||
if (!tempBackupFile.delete()) {
|
||||
@@ -94,6 +102,8 @@ class BackupMessagesJob private constructor(parameters: Parameters) : Job(parame
|
||||
}
|
||||
is NetworkResult.ApplicationError -> throw result.throwable
|
||||
}
|
||||
stopwatch.split("used-space")
|
||||
stopwatch.stop(TAG)
|
||||
|
||||
if (SignalDatabase.attachments.doAnyAttachmentsNeedArchiveUpload()) {
|
||||
Log.i(TAG, "Enqueuing attachment backfill job.")
|
||||
|
||||
@@ -91,7 +91,7 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val result = when (val archiveResult = BackupRepository.archiveMedia(attachment)) {
|
||||
val result = when (val archiveResult = BackupRepository.copyAttachmentToArchive(attachment)) {
|
||||
is NetworkResult.Success -> {
|
||||
Log.i(TAG, "[$attachmentId] Successfully copied the archive tier.")
|
||||
Result.success()
|
||||
|
||||
@@ -113,7 +113,7 @@ public final class JobManagerFactories {
|
||||
put(AttachmentCopyJob.KEY, new AttachmentCopyJob.Factory());
|
||||
put(AttachmentDownloadJob.KEY, new AttachmentDownloadJob.Factory());
|
||||
put(AttachmentHashBackfillJob.KEY, new AttachmentHashBackfillJob.Factory());
|
||||
put(AttachmentMarkUploadedJob.KEY, new AttachmentMarkUploadedJob.Factory());
|
||||
put(MarkNoteToSelfAttachmentUploadedJob.KEY, new MarkNoteToSelfAttachmentUploadedJob.Factory());
|
||||
put(AttachmentUploadJob.KEY, new AttachmentUploadJob.Factory());
|
||||
put(AutomaticSessionResetJob.KEY, new AutomaticSessionResetJob.Factory());
|
||||
put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory());
|
||||
|
||||
@@ -11,18 +11,18 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Only marks an attachment as uploaded.
|
||||
* Marks a note to self attachment (that didn't need to be uploaded, because there's no linked devices) as being uploaded for UX purposes.
|
||||
* Also generates a key/iv/digest that otherwise wouldn't exist due to the lack of upload.
|
||||
*/
|
||||
public final class AttachmentMarkUploadedJob extends BaseJob {
|
||||
public final class MarkNoteToSelfAttachmentUploadedJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "AttachmentMarkUploadedJob";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(AttachmentMarkUploadedJob.class);
|
||||
private static final String TAG = Log.tag(MarkNoteToSelfAttachmentUploadedJob.class);
|
||||
|
||||
private static final String KEY_ATTACHMENT_ID = "row_id";
|
||||
private static final String KEY_MESSAGE_ID = "message_id";
|
||||
@@ -30,7 +30,7 @@ public final class AttachmentMarkUploadedJob extends BaseJob {
|
||||
private final AttachmentId attachmentId;
|
||||
private final long messageId;
|
||||
|
||||
public AttachmentMarkUploadedJob(long messageId, @NonNull AttachmentId attachmentId) {
|
||||
public MarkNoteToSelfAttachmentUploadedJob(long messageId, @NonNull AttachmentId attachmentId) {
|
||||
this(new Parameters.Builder()
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
@@ -39,7 +39,7 @@ public final class AttachmentMarkUploadedJob extends BaseJob {
|
||||
attachmentId);
|
||||
}
|
||||
|
||||
private AttachmentMarkUploadedJob(@NonNull Parameters parameters, long messageId, @NonNull AttachmentId attachmentId) {
|
||||
private MarkNoteToSelfAttachmentUploadedJob(@NonNull Parameters parameters, long messageId, @NonNull AttachmentId attachmentId) {
|
||||
super(parameters);
|
||||
this.attachmentId = attachmentId;
|
||||
this.messageId = messageId;
|
||||
@@ -59,14 +59,14 @@ public final class AttachmentMarkUploadedJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
public void onRun() throws Exception {
|
||||
AttachmentTable database = SignalDatabase.attachments();
|
||||
DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId);
|
||||
DatabaseAttachment databaseAttachment = SignalDatabase.attachments().getAttachment(attachmentId);
|
||||
|
||||
if (databaseAttachment == null) {
|
||||
throw new InvalidAttachmentException("Cannot find the specified attachment.");
|
||||
}
|
||||
|
||||
database.markAttachmentUploaded(messageId, databaseAttachment);
|
||||
SignalDatabase.attachments().markAttachmentUploaded(messageId, databaseAttachment);
|
||||
SignalDatabase.attachments().createKeyIvDigestIfNecessary(databaseAttachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,7 +75,7 @@ public final class AttachmentMarkUploadedJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception exception) {
|
||||
return exception instanceof IOException;
|
||||
return false;
|
||||
}
|
||||
|
||||
private class InvalidAttachmentException extends Exception {
|
||||
@@ -84,14 +84,14 @@ public final class AttachmentMarkUploadedJob extends BaseJob {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<AttachmentMarkUploadedJob> {
|
||||
public static final class Factory implements Job.Factory<MarkNoteToSelfAttachmentUploadedJob> {
|
||||
@Override
|
||||
public @NonNull AttachmentMarkUploadedJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
public @NonNull MarkNoteToSelfAttachmentUploadedJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
JsonJobData data = JsonJobData.deserialize(serializedData);
|
||||
|
||||
return new AttachmentMarkUploadedJob(parameters,
|
||||
data.getLong(KEY_MESSAGE_ID),
|
||||
new AttachmentId(data.getLong(KEY_ATTACHMENT_ID)));
|
||||
return new MarkNoteToSelfAttachmentUploadedJob(parameters,
|
||||
data.getLong(KEY_MESSAGE_ID),
|
||||
new AttachmentId(data.getLong(KEY_ATTACHMENT_ID)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.protos.resumableuploads.ResumableUpload
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
@@ -100,6 +101,12 @@ class UploadAttachmentToArchiveJob private constructor(
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
if (attachment.remoteKey == null || attachment.remoteIv == null) {
|
||||
Log.w(TAG, "[$attachmentId] Attachment is missing remote key or IV! Cannot upload.")
|
||||
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE)
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
if (uploadSpec != null && System.currentTimeMillis() > uploadSpec!!.timeout) {
|
||||
Log.w(TAG, "[$attachmentId] Upload spec expired! Clearing.")
|
||||
uploadSpec = null
|
||||
@@ -108,7 +115,7 @@ class UploadAttachmentToArchiveJob private constructor(
|
||||
if (uploadSpec == null) {
|
||||
Log.d(TAG, "[$attachmentId] Need an upload spec. Fetching...")
|
||||
|
||||
val (spec, result) = fetchResumableUploadSpec()
|
||||
val (spec, result) = fetchResumableUploadSpec(key = Base64.decode(attachment.remoteKey), iv = attachment.remoteIv)
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
@@ -154,15 +161,19 @@ class UploadAttachmentToArchiveJob private constructor(
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
private fun fetchResumableUploadSpec(): Pair<ResumableUpload?, Result?> {
|
||||
return when (val spec = BackupRepository.getMediaUploadSpec()) {
|
||||
private fun fetchResumableUploadSpec(key: ByteArray, iv: ByteArray): Pair<ResumableUpload?, Result?> {
|
||||
val uploadSpec = BackupRepository
|
||||
.getAttachmentUploadForm()
|
||||
.then { form -> SignalNetwork.attachments.getResumableUploadSpec(key, iv, form) }
|
||||
|
||||
return when (uploadSpec) {
|
||||
is NetworkResult.Success -> {
|
||||
Log.d(TAG, "[$attachmentId] Got an upload spec!")
|
||||
spec.result.toProto() to null
|
||||
uploadSpec.result.toProto() to null
|
||||
}
|
||||
|
||||
is NetworkResult.ApplicationError -> {
|
||||
Log.w(TAG, "[$attachmentId] Failed to get an upload spec due to an application error. Retrying.", spec.throwable)
|
||||
Log.w(TAG, "[$attachmentId] Failed to get an upload spec due to an application error. Retrying.", uploadSpec.throwable)
|
||||
return null to Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
@@ -172,9 +183,9 @@ class UploadAttachmentToArchiveJob private constructor(
|
||||
}
|
||||
|
||||
is NetworkResult.StatusCodeError -> {
|
||||
Log.w(TAG, "[$attachmentId] Failed request with status code ${spec.code}")
|
||||
Log.w(TAG, "[$attachmentId] Failed request with status code ${uploadSpec.code}")
|
||||
|
||||
when (ArchiveMediaUploadFormStatusCodes.from(spec.code)) {
|
||||
when (ArchiveMediaUploadFormStatusCodes.from(uploadSpec.code)) {
|
||||
ArchiveMediaUploadFormStatusCodes.BadArguments,
|
||||
ArchiveMediaUploadFormStatusCodes.InvalidPresentationOrSignature,
|
||||
ArchiveMediaUploadFormStatusCodes.InsufficientPermissions,
|
||||
|
||||
Reference in New Issue
Block a user