Bring back proper archive delete reconciliation.

This commit is contained in:
Greyson Parrelli
2025-09-15 16:00:26 -04:00
parent a575626abb
commit 7f429dc769
3 changed files with 96 additions and 25 deletions

View File

@@ -73,6 +73,7 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.attachments.LocalStickerAttachment
import org.thoughtcrime.securesms.attachments.WallpaperAttachment
import org.thoughtcrime.securesms.audio.AudioHash
import org.thoughtcrime.securesms.backup.v2.ArchivedMediaObject
import org.thoughtcrime.securesms.backup.v2.exporters.ChatItemArchiveExporter
import org.thoughtcrime.securesms.backup.v2.proto.BackupDebugInfo
import org.thoughtcrime.securesms.blurhash.BlurHash
@@ -3174,6 +3175,32 @@ class AttachmentTable(
}
}
fun getMediaObjectsThatCantBeFound(objects: Set<ArchivedMediaObject>): Set<ArchivedMediaObject> {
if (objects.isEmpty()) {
return emptySet()
}
val objectsByMediaId: MutableMap<String, ArchivedMediaObject> = objects.associateBy { it.mediaId }.toMutableMap()
readableDatabase
.select(*PROJECTION)
.from(TABLE_NAME)
.where("$REMOTE_KEY NOT NULL AND $DATA_HASH_END NOT NULL")
.groupBy("$DATA_HASH_END, $REMOTE_KEY")
.run()
.forEach { cursor ->
val remoteKey = Base64.decode(cursor.requireNonNullString(REMOTE_KEY))
val plaintextHash = Base64.decode(cursor.requireNonNullString(DATA_HASH_END))
val mediaId = MediaName.fromPlaintextHashAndRemoteKey(plaintextHash, remoteKey).toMediaId(SignalStore.backup.mediaRootBackupKey).encode()
val mediaIdThumbnail = MediaName.fromPlaintextHashAndRemoteKeyForThumbnail(plaintextHash, remoteKey).toMediaId(SignalStore.backup.mediaRootBackupKey).encode()
objectsByMediaId.remove(mediaId)
objectsByMediaId.remove(mediaIdThumbnail)
}
return objectsByMediaId.values.toSet()
}
/**
* Important: This is an expensive query that involves iterating over every row in the table. Only call this for debug stuff!
*/
@@ -3189,7 +3216,6 @@ class AttachmentTable(
.groupBy(DATA_HASH_END)
.run()
.forEach { cursor ->
val remoteKey = Base64.decode(cursor.requireNonNullString(REMOTE_KEY))
val plaintextHash = Base64.decode(cursor.requireNonNullString(DATA_HASH_END))
val mediaId = MediaName.fromPlaintextHashAndRemoteKey(plaintextHash, remoteKey).toMediaId(SignalStore.backup.mediaRootBackupKey).value.toByteString()

View File

@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting
import androidx.core.content.contentValuesOf
import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.forEach
import org.signal.core.util.readToList
import org.signal.core.util.readToSet
import org.signal.core.util.readToSingleLong
@@ -214,6 +215,8 @@ class BackupMediaSnapshotTable(context: Context, database: SignalDatabase) : Dat
return emptySet()
}
val objectsByMediaId: MutableMap<String, ArchivedMediaObject> = objects.associateBy { it.mediaId }.toMutableMap()
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(
column = MEDIA_ID,
values = objects.map { it.mediaId },
@@ -221,20 +224,19 @@ class BackupMediaSnapshotTable(context: Context, database: SignalDatabase) : Dat
prefix = "$SNAPSHOT_VERSION = $MAX_VERSION AND "
)
val foundObjects: MutableSet<String> = mutableSetOf()
for (query in queries) {
foundObjects += readableDatabase
readableDatabase
.select(MEDIA_ID, CDN)
.from(TABLE_NAME)
.where(query.where, query.whereArgs)
.run()
.readToSet {
it.requireNonNullString(MEDIA_ID)
.forEach {
val mediaId = it.requireNonNullString(MEDIA_ID)
objectsByMediaId.remove(mediaId)
}
}
return objects.filterNot { foundObjects.contains(it.mediaId) }.toSet()
return objectsByMediaId.values.toSet()
}
fun getMediaEntriesForObjects(objects: List<ArchivedMediaObject>): Set<MediaEntry> {