mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-21 02:08:40 +00:00
Refactor and improve attachment deduping logic.
This commit is contained in:
committed by
Cody Henthorne
parent
b7ee6bfcb3
commit
6df1a68213
@@ -3,13 +3,14 @@ package org.thoughtcrime.securesms.attachments
|
||||
import android.os.Parcelable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.signal.core.util.DatabaseId
|
||||
|
||||
@Parcelize
|
||||
data class AttachmentId(
|
||||
@JsonProperty("rowId")
|
||||
@JvmField
|
||||
val id: Long
|
||||
) : Parcelable {
|
||||
) : Parcelable, DatabaseId {
|
||||
|
||||
val isValid: Boolean
|
||||
get() = id >= 0
|
||||
@@ -17,4 +18,8 @@ data class AttachmentId(
|
||||
override fun toString(): String {
|
||||
return "AttachmentId::$id"
|
||||
}
|
||||
|
||||
override fun serialize(): String {
|
||||
return id.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.attachments
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.signal.core.util.Base64.encodeWithPadding
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
@@ -14,7 +15,8 @@ import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import java.util.Optional
|
||||
|
||||
class PointerAttachment : Attachment {
|
||||
private constructor(
|
||||
@VisibleForTesting
|
||||
constructor(
|
||||
contentType: String,
|
||||
transferState: Int,
|
||||
size: Long,
|
||||
|
||||
@@ -194,7 +194,7 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
|
||||
throws IOException
|
||||
{
|
||||
File dataFile = AttachmentTable.newFile(context);
|
||||
File dataFile = AttachmentTable.newDataFile(context);
|
||||
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
||||
boolean isLegacyTable = SqlUtil.tableExists(db, "part");
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,7 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD
|
||||
${AttachmentTable.TABLE_NAME}.${AttachmentTable.UPLOAD_TIMESTAMP},
|
||||
${AttachmentTable.TABLE_NAME}.${AttachmentTable.REMOTE_INCREMENTAL_DIGEST},
|
||||
${AttachmentTable.TABLE_NAME}.${AttachmentTable.REMOTE_INCREMENTAL_DIGEST_CHUNK_SIZE},
|
||||
${AttachmentTable.TABLE_NAME}.${AttachmentTable.DATA_HASH},
|
||||
${AttachmentTable.TABLE_NAME}.${AttachmentTable.DATA_HASH_END},
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.TYPE},
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.DATE_SENT},
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.DATE_RECEIVED},
|
||||
@@ -71,13 +71,7 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD
|
||||
${MessageTable.VIEW_ONCE} = 0 AND
|
||||
${MessageTable.STORY_TYPE} = 0 AND
|
||||
${MessageTable.LATEST_REVISION_ID} IS NULL AND
|
||||
(
|
||||
${AttachmentTable.QUOTE} = 0 OR
|
||||
(
|
||||
${AttachmentTable.QUOTE} = 1 AND
|
||||
${AttachmentTable.DATA_HASH} IS NULL
|
||||
)
|
||||
) AND
|
||||
${AttachmentTable.QUOTE} = 0 AND
|
||||
${AttachmentTable.STICKER_PACK_ID} IS NULL AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID} > 0 AND
|
||||
$THREAD_RECIPIENT_ID > 0
|
||||
|
||||
@@ -79,6 +79,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V218_RecipientPniSi
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V219_PniPreKeyStores
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V220_PreKeyConstraints
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V221_AddReadColumnToCallEventsTable
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V222_DataHashRefactor
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
@@ -160,10 +161,11 @@ object SignalDatabaseMigrations {
|
||||
218 to V218_RecipientPniSignatureVerified,
|
||||
219 to V219_PniPreKeyStores,
|
||||
220 to V220_PreKeyConstraints,
|
||||
221 to V221_AddReadColumnToCallEventsTable
|
||||
221 to V221_AddReadColumnToCallEventsTable,
|
||||
222 to V222_DataHashRefactor
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 221
|
||||
const val DATABASE_VERSION = 222
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Adds the new data hash columns and indexes.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V222_DataHashRefactor : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("DROP INDEX attachment_data_hash_index")
|
||||
db.execSQL("ALTER TABLE attachment DROP COLUMN data_hash")
|
||||
|
||||
db.execSQL("ALTER TABLE attachment ADD COLUMN data_hash_start TEXT DEFAULT NULL")
|
||||
db.execSQL("ALTER TABLE attachment ADD COLUMN data_hash_end TEXT DEFAULT NULL")
|
||||
db.execSQL("CREATE INDEX attachment_data_hash_start_index ON attachment (data_hash_start)")
|
||||
db.execSQL("CREATE INDEX attachment_data_hash_end_index ON attachment (data_hash_end)")
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
||||
} else if (constraints.canResize(attachment)) {
|
||||
Log.i(TAG, "Compressing image.");
|
||||
try (MediaStream converted = compressImage(context, attachment, constraints)) {
|
||||
attachmentDatabase.updateAttachmentData(attachment, converted, false);
|
||||
attachmentDatabase.updateAttachmentData(attachment, converted);
|
||||
}
|
||||
attachmentDatabase.markAttachmentAsTransformed(attachmentId, false);
|
||||
} else if (constraints.isSatisfied(context, attachment)) {
|
||||
@@ -263,7 +263,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
||||
Log.i(TAG, "Compressing with streaming muxer");
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
|
||||
File file = AttachmentTable.newFile(context);
|
||||
File file = AttachmentTable.newDataFile(context);
|
||||
file.deleteOnExit();
|
||||
|
||||
boolean faststart = false;
|
||||
@@ -296,7 +296,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
||||
|
||||
final long plaintextLength = ModernEncryptingPartOutputStream.getPlaintextLength(file.length());
|
||||
try (MediaStream mediaStream = new MediaStream(postProcessor.process(plaintextLength), MimeTypes.VIDEO_MP4, 0, 0, true)) {
|
||||
attachmentDatabase.updateAttachmentData(attachment, mediaStream, true);
|
||||
attachmentDatabase.updateAttachmentData(attachment, mediaStream);
|
||||
faststart = true;
|
||||
} catch (VideoPostProcessingException e) {
|
||||
Log.w(TAG, "Exception thrown during post processing.", e);
|
||||
@@ -310,7 +310,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
||||
|
||||
if (!faststart) {
|
||||
try (MediaStream mediaStream = new MediaStream(ModernDecryptingPartInputStream.createFor(attachmentSecret, file, 0), MimeTypes.VIDEO_MP4, 0, 0, false)) {
|
||||
attachmentDatabase.updateAttachmentData(attachment, mediaStream, true);
|
||||
attachmentDatabase.updateAttachmentData(attachment, mediaStream);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -339,7 +339,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
||||
100,
|
||||
percent));
|
||||
}, cancelationSignal)) {
|
||||
attachmentDatabase.updateAttachmentData(attachment, mediaStream, true);
|
||||
attachmentDatabase.updateAttachmentData(attachment, mediaStream);
|
||||
attachmentDatabase.markAttachmentAsTransformed(attachment.attachmentId, mediaStream.getFaststart());
|
||||
}
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ public final class AttachmentDownloadJob extends BaseJob {
|
||||
if (attachment.cdnNumber != ReleaseChannel.CDN_NUMBER) {
|
||||
retrieveAttachment(messageId, attachmentId, attachment);
|
||||
} else {
|
||||
retrieveUrlAttachment(messageId, attachmentId, attachment);
|
||||
retrieveAttachmentForReleaseChannel(messageId, attachmentId, attachment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ public final class AttachmentDownloadJob extends BaseJob {
|
||||
return isCanceled();
|
||||
}
|
||||
});
|
||||
database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream);
|
||||
database.finalizeAttachmentAfterDownload(messageId, attachmentId, stream);
|
||||
} catch (RangeException e) {
|
||||
Log.w(TAG, "Range exception, file size " + attachmentFile.length(), e);
|
||||
if (attachmentFile.delete()) {
|
||||
@@ -278,9 +278,9 @@ public final class AttachmentDownloadJob extends BaseJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void retrieveUrlAttachment(long messageId,
|
||||
final AttachmentId attachmentId,
|
||||
final Attachment attachment)
|
||||
private void retrieveAttachmentForReleaseChannel(long messageId,
|
||||
final AttachmentId attachmentId,
|
||||
final Attachment attachment)
|
||||
throws IOException
|
||||
{
|
||||
try (Response response = S3.getObject(Objects.requireNonNull(attachment.fileName))) {
|
||||
@@ -289,7 +289,7 @@ public final class AttachmentDownloadJob extends BaseJob {
|
||||
if (body.contentLength() > FeatureFlags.maxAttachmentReceiveSizeBytes()) {
|
||||
throw new MmsException("Attachment too large, failing download");
|
||||
}
|
||||
SignalDatabase.attachments().insertAttachmentsForPlaceholder(messageId, attachmentId, Okio.buffer(body.source()).inputStream());
|
||||
SignalDatabase.attachments().finalizeAttachmentAfterDownload(messageId, attachmentId, Okio.buffer(body.source()).inputStream());
|
||||
}
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, "Experienced exception while trying to download an attachment.", e);
|
||||
|
||||
@@ -42,6 +42,7 @@ import java.io.IOException
|
||||
import java.util.Objects
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
@@ -60,7 +61,7 @@ class AttachmentUploadJob private constructor(
|
||||
|
||||
private val TAG = Log.tag(AttachmentUploadJob::class.java)
|
||||
|
||||
private val UPLOAD_REUSE_THRESHOLD = TimeUnit.DAYS.toMillis(3)
|
||||
val UPLOAD_REUSE_THRESHOLD = 3.days.inWholeMilliseconds
|
||||
|
||||
/**
|
||||
* Foreground notification shows while uploading attachments above this.
|
||||
@@ -162,7 +163,7 @@ class AttachmentUploadJob private constructor(
|
||||
buildAttachmentStream(databaseAttachment, notification, uploadSpec!!).use { localAttachment ->
|
||||
val remoteAttachment = messageSender.uploadAttachment(localAttachment)
|
||||
val attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.fastPreflightId).get()
|
||||
SignalDatabase.attachments.updateAttachmentAfterUpload(databaseAttachment.attachmentId, attachment, remoteAttachment.uploadTimestamp)
|
||||
SignalDatabase.attachments.finalizeAttachmentAfterUpload(databaseAttachment.attachmentId, attachment, remoteAttachment.uploadTimestamp)
|
||||
}
|
||||
}
|
||||
} catch (e: NonSuccessfulResumableUploadResponseCodeException) {
|
||||
|
||||
@@ -139,7 +139,7 @@ public final class LegacyAttachmentUploadJob extends BaseJob {
|
||||
SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment);
|
||||
Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.fastPreflightId).get();
|
||||
|
||||
database.updateAttachmentAfterUpload(databaseAttachment.attachmentId, attachment, remoteAttachment.getUploadTimestamp());
|
||||
database.finalizeAttachmentAfterUpload(databaseAttachment.attachmentId, attachment, remoteAttachment.getUploadTimestamp());
|
||||
}
|
||||
} catch (NonSuccessfulResumableUploadResponseCodeException e) {
|
||||
if (e.getCode() == 400) {
|
||||
|
||||
@@ -64,11 +64,6 @@ public final class StickerSearchRepository {
|
||||
return out;
|
||||
}
|
||||
|
||||
public @NonNull Single<Boolean> getStickerFeatureAvailability() {
|
||||
return Single.fromCallable(this::getStickerFeatureAvailabilitySync)
|
||||
.observeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public void getStickerFeatureAvailability(@NonNull Callback<Boolean> callback) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
callback.onResult(getStickerFeatureAvailabilitySync());
|
||||
|
||||
Reference in New Issue
Block a user