Compute mediaName dynamically

This commit is contained in:
Max Radermacher 2026-06-10 13:51:02 -05:00 committed by GitHub
parent 3914c811be
commit 221043a998
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 186 additions and 144 deletions

View File

@ -856,6 +856,7 @@
50D839512F916A3700EE009A /* MessageRequestDecliner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D839502F916A3700EE009A /* MessageRequestDecliner.swift */; }; 50D839512F916A3700EE009A /* MessageRequestDecliner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D839502F916A3700EE009A /* MessageRequestDecliner.swift */; };
50D8796A2A16D2C20031345D /* MessageLoaderBatchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D879692A16D2C20031345D /* MessageLoaderBatchTest.swift */; }; 50D8796A2A16D2C20031345D /* MessageLoaderBatchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D879692A16D2C20031345D /* MessageLoaderBatchTest.swift */; };
50D9CD8D2C52D78000273D6C /* StoryRecipientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D9CD8C2C52D78000273D6C /* StoryRecipientManager.swift */; }; 50D9CD8D2C52D78000273D6C /* StoryRecipientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D9CD8C2C52D78000273D6C /* StoryRecipientManager.swift */; };
50DAF7E02FD87BEC00BE7430 /* OrphanedBackupAttachmentTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DAF7DF2FD87BEC00BE7430 /* OrphanedBackupAttachmentTest.swift */; };
50DCCBFA2F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DCCBF92F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift */; }; 50DCCBFA2F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DCCBF92F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift */; };
50DCCBFC2F181A790024D124 /* ProfileKeyMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DCCBFB2F181A790024D124 /* ProfileKeyMessage.swift */; }; 50DCCBFC2F181A790024D124 /* ProfileKeyMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DCCBFB2F181A790024D124 /* ProfileKeyMessage.swift */; };
50DCCBFE2F1820600024D124 /* OutgoingSenderKeyDistributionMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DCCBFD2F1820600024D124 /* OutgoingSenderKeyDistributionMessage.swift */; }; 50DCCBFE2F1820600024D124 /* OutgoingSenderKeyDistributionMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DCCBFD2F1820600024D124 /* OutgoingSenderKeyDistributionMessage.swift */; };
@ -5121,6 +5122,7 @@
50D839502F916A3700EE009A /* MessageRequestDecliner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestDecliner.swift; sourceTree = "<group>"; }; 50D839502F916A3700EE009A /* MessageRequestDecliner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestDecliner.swift; sourceTree = "<group>"; };
50D879692A16D2C20031345D /* MessageLoaderBatchTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLoaderBatchTest.swift; sourceTree = "<group>"; }; 50D879692A16D2C20031345D /* MessageLoaderBatchTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLoaderBatchTest.swift; sourceTree = "<group>"; };
50D9CD8C2C52D78000273D6C /* StoryRecipientManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryRecipientManager.swift; sourceTree = "<group>"; }; 50D9CD8C2C52D78000273D6C /* StoryRecipientManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryRecipientManager.swift; sourceTree = "<group>"; };
50DAF7DF2FD87BEC00BE7430 /* OrphanedBackupAttachmentTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrphanedBackupAttachmentTest.swift; sourceTree = "<group>"; };
50DCCBF92F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisappearingMessagesConfigurationMessage.swift; sourceTree = "<group>"; }; 50DCCBF92F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisappearingMessagesConfigurationMessage.swift; sourceTree = "<group>"; };
50DCCBFB2F181A790024D124 /* ProfileKeyMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileKeyMessage.swift; sourceTree = "<group>"; }; 50DCCBFB2F181A790024D124 /* ProfileKeyMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileKeyMessage.swift; sourceTree = "<group>"; };
50DCCBFD2F1820600024D124 /* OutgoingSenderKeyDistributionMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingSenderKeyDistributionMessage.swift; sourceTree = "<group>"; }; 50DCCBFD2F1820600024D124 /* OutgoingSenderKeyDistributionMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingSenderKeyDistributionMessage.swift; sourceTree = "<group>"; };
@ -9898,6 +9900,22 @@
path = Debugging; path = Debugging;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
50DAF7E12FD87BFD00BE7430 /* Backups */ = {
isa = PBXGroup;
children = (
50DAF7E22FD87C7000BE7430 /* Attachments */,
);
path = Backups;
sourceTree = "<group>";
};
50DAF7E22FD87C7000BE7430 /* Attachments */ = {
isa = PBXGroup;
children = (
50DAF7DF2FD87BEC00BE7430 /* OrphanedBackupAttachmentTest.swift */,
);
path = Attachments;
sourceTree = "<group>";
};
50E0198E2CC2491A0063EA48 /* Concurrency */ = { 50E0198E2CC2491A0063EA48 /* Concurrency */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -14633,6 +14651,7 @@
F94261FF289B1B5400460798 /* Account */, F94261FF289B1B5400460798 /* Account */,
D92EFDED2F69B9D00031D257 /* AttachmentBackfill */, D92EFDED2F69B9D00031D257 /* AttachmentBackfill */,
50ED28002F0EDAFB00E57C54 /* Attachments */, 50ED28002F0EDAFB00E57C54 /* Attachments */,
50DAF7E12FD87BFD00BE7430 /* Backups */,
F945FE4B298481D800C835C7 /* Calls */, F945FE4B298481D800C835C7 /* Calls */,
D985D86229B91C2B0087C90C /* ChangePhoneNumber */, D985D86229B91C2B0087C90C /* ChangePhoneNumber */,
50E0198E2CC2491A0063EA48 /* Concurrency */, 50E0198E2CC2491A0063EA48 /* Concurrency */,
@ -20171,6 +20190,7 @@
D979CC3A2AD3964E006AAC49 /* Numbers+Random.swift in Sources */, D979CC3A2AD3964E006AAC49 /* Numbers+Random.swift in Sources */,
D95E149D2E3D22FD00B5B70B /* ObjectRetainerTest.swift in Sources */, D95E149D2E3D22FD00B5B70B /* ObjectRetainerTest.swift in Sources */,
663D02DF2C069AB600350632 /* OrphanedAttachmentCleanerTest.swift in Sources */, 663D02DF2C069AB600350632 /* OrphanedAttachmentCleanerTest.swift in Sources */,
50DAF7E02FD87BEC00BE7430 /* OrphanedBackupAttachmentTest.swift in Sources */,
D9AA37A02A86E0910088EFFB /* OutgoingCallEventSyncMessageTest.swift in Sources */, D9AA37A02A86E0910088EFFB /* OutgoingCallEventSyncMessageTest.swift in Sources */,
D925C7BB2B7BEC0F00AC73B0 /* OutgoingCallLogEventSyncMessageTest.swift in Sources */, D925C7BB2B7BEC0F00AC73B0 /* OutgoingCallLogEventSyncMessageTest.swift in Sources */,
D9D3216A2A8AC9B0004FC110 /* OutgoingGroupCallUpdateMessageTest.swift in Sources */, D9D3216A2A8AC9B0004FC110 /* OutgoingGroupCallUpdateMessageTest.swift in Sources */,

View File

@ -394,7 +394,7 @@ class BackupListMediaManagerImpl: BackupListMediaManager {
var query = Attachment.Record var query = Attachment.Record
.order(Column(Attachment.Record.CodingKeys.sqliteId).asc) .order(Column(Attachment.Record.CodingKeys.sqliteId).asc)
.filter(Column(Attachment.Record.CodingKeys.mediaName) != nil) .filter(Column(Attachment.Record.CodingKeys.plaintextHash) != nil)
if let id = txContext.lastEnumeratedAttachmentId { if let id = txContext.lastEnumeratedAttachmentId {
query = query query = query
@ -761,7 +761,6 @@ class BackupListMediaManagerImpl: BackupListMediaManager {
attachment, attachment,
listedMedia: matchedListedMedia, listedMedia: matchedListedMedia,
isThumbnail: isThumbnail, isThumbnail: isThumbnail,
fullsizeMediaName: fullsizeMediaName,
uploadEraAtStartOfListMedia: uploadEraAtStartOfListMedia, uploadEraAtStartOfListMedia: uploadEraAtStartOfListMedia,
currentBackupPlan: currentBackupPlan, currentBackupPlan: currentBackupPlan,
remoteConfig: remoteConfig, remoteConfig: remoteConfig,
@ -950,7 +949,6 @@ class BackupListMediaManagerImpl: BackupListMediaManager {
_ attachment: Attachment, _ attachment: Attachment,
listedMedia: ListedBackupMediaObject, listedMedia: ListedBackupMediaObject,
isThumbnail: Bool, isThumbnail: Bool,
fullsizeMediaName: String,
uploadEraAtStartOfListMedia: String, uploadEraAtStartOfListMedia: String,
currentBackupPlan: BackupPlan, currentBackupPlan: BackupPlan,
remoteConfig: RemoteConfig, remoteConfig: RemoteConfig,
@ -962,7 +960,6 @@ class BackupListMediaManagerImpl: BackupListMediaManager {
from: listedMedia, from: listedMedia,
isThumbnail: isThumbnail, isThumbnail: isThumbnail,
uploadEraAtStartOfListMedia: uploadEraAtStartOfListMedia, uploadEraAtStartOfListMedia: uploadEraAtStartOfListMedia,
fullsizeMediaName: fullsizeMediaName,
tx: tx, tx: tx,
) )
@ -1015,7 +1012,6 @@ class BackupListMediaManagerImpl: BackupListMediaManager {
from listedMedia: ListedBackupMediaObject, from listedMedia: ListedBackupMediaObject,
isThumbnail: Bool, isThumbnail: Bool,
uploadEraAtStartOfListMedia: String, uploadEraAtStartOfListMedia: String,
fullsizeMediaName: String,
tx: DBWriteTransaction, tx: DBWriteTransaction,
) -> Bool { ) -> Bool {
if isThumbnail { if isThumbnail {
@ -1027,7 +1023,6 @@ class BackupListMediaManagerImpl: BackupListMediaManager {
uploadEra: uploadEraAtStartOfListMedia, uploadEra: uploadEraAtStartOfListMedia,
lastDownloadAttemptTimestamp: nil, lastDownloadAttemptTimestamp: nil,
), ),
mediaName: fullsizeMediaName,
tx: tx, tx: tx,
) )
return true return true
@ -1068,7 +1063,6 @@ class BackupListMediaManagerImpl: BackupListMediaManager {
uploadEra: uploadEraAtStartOfListMedia, uploadEra: uploadEraAtStartOfListMedia,
lastDownloadAttemptTimestamp: nil, lastDownloadAttemptTimestamp: nil,
), ),
mediaName: fullsizeMediaName,
tx: tx, tx: tx,
) )
return true return true

View File

@ -91,12 +91,14 @@ public class BackupListMediaManagerTests {
// Case 1: Attachment exists locally but not on CDN // Case 1: Attachment exists locally but not on CDN
let localOnlyIds = await db.awaitableWrite { tx in let localOnlyIds = await db.awaitableWrite { tx in
return (0..<numAttachmentsPerCase).map { _ in return (0..<numAttachmentsPerCase).map { _ in
let plaintextHash = Randomness.generateRandomBytes(32)
return insertAttachment( return insertAttachment(
mediaName: UUID().uuidString, plaintextHash: plaintextHash,
encryptionKey: .generate(),
mediaTierInfo: .init( mediaTierInfo: .init(
cdnNumber: 1, cdnNumber: 1,
unencryptedByteCount: 100, unencryptedByteCount: 100,
plaintextHash: UUID().data, plaintextHash: plaintextHash,
incrementalMacInfo: nil, incrementalMacInfo: nil,
uploadEra: localUploadEra, uploadEra: localUploadEra,
lastDownloadAttemptTimestamp: nil, lastDownloadAttemptTimestamp: nil,
@ -111,7 +113,7 @@ public class BackupListMediaManagerTests {
let remoteOnlyCdnNumberMedia = (0..<numAttachmentsPerCase).map { _ in let remoteOnlyCdnNumberMedia = (0..<numAttachmentsPerCase).map { _ in
return BackupArchive.Response.StoredMedia( return BackupArchive.Response.StoredMedia(
cdn: orphanCdnNumber, cdn: orphanCdnNumber,
mediaId: UUID().uuidString, mediaId: Randomness.generateRandomBytes(15).hexadecimalString,
objectLength: 100, objectLength: 100,
) )
} }
@ -124,7 +126,12 @@ public class BackupListMediaManagerTests {
var discoveredCdnNumberMedia = [BackupArchive.Response.StoredMedia]() var discoveredCdnNumberMedia = [BackupArchive.Response.StoredMedia]()
let discoveredCdnNumberIds = await db.awaitableWrite { tx in let discoveredCdnNumberIds = await db.awaitableWrite { tx in
return (0..<numAttachmentsPerCase).map { _ in return (0..<numAttachmentsPerCase).map { _ in
let mediaName = UUID().uuidString let plaintextHash = Randomness.generateRandomBytes(32)
let encryptionKey = AttachmentKey.generate()
let mediaName = Attachment.mediaName(
plaintextHash: plaintextHash,
encryptionKey: encryptionKey.combinedKey,
)
let fullsizeMediaId = try! mediaRootBackupKey.deriveMediaId(mediaName) let fullsizeMediaId = try! mediaRootBackupKey.deriveMediaId(mediaName)
let thumbnailMediaId = try! mediaRootBackupKey.deriveMediaId(AttachmentBackupThumbnail.thumbnailMediaName(fullsizeMediaName: mediaName)) let thumbnailMediaId = try! mediaRootBackupKey.deriveMediaId(AttachmentBackupThumbnail.thumbnailMediaName(fullsizeMediaName: mediaName))
for mediaId in [fullsizeMediaId, thumbnailMediaId] { for mediaId in [fullsizeMediaId, thumbnailMediaId] {
@ -135,7 +142,8 @@ public class BackupListMediaManagerTests {
)) ))
} }
return insertAttachment( return insertAttachment(
mediaName: mediaName, plaintextHash: plaintextHash,
encryptionKey: encryptionKey,
mediaTierInfo: nil, mediaTierInfo: nil,
tx: tx, tx: tx,
) )
@ -147,7 +155,12 @@ public class BackupListMediaManagerTests {
var matchingCdnNumberMedia = [BackupArchive.Response.StoredMedia]() var matchingCdnNumberMedia = [BackupArchive.Response.StoredMedia]()
let matchingCdnNumberIds = await db.awaitableWrite { tx in let matchingCdnNumberIds = await db.awaitableWrite { tx in
return (0..<numAttachmentsPerCase).map { _ in return (0..<numAttachmentsPerCase).map { _ in
let mediaName = UUID().uuidString let plaintextHash = Randomness.generateRandomBytes(32)
let encryptionKey = AttachmentKey.generate()
let mediaName = Attachment.mediaName(
plaintextHash: plaintextHash,
encryptionKey: encryptionKey.combinedKey,
)
let fullsizeMediaId = try! mediaRootBackupKey.deriveMediaId(mediaName) let fullsizeMediaId = try! mediaRootBackupKey.deriveMediaId(mediaName)
let thumbnailMediaId = try! mediaRootBackupKey.deriveMediaId(AttachmentBackupThumbnail.thumbnailMediaName(fullsizeMediaName: mediaName)) let thumbnailMediaId = try! mediaRootBackupKey.deriveMediaId(AttachmentBackupThumbnail.thumbnailMediaName(fullsizeMediaName: mediaName))
for mediaId in [fullsizeMediaId, thumbnailMediaId] { for mediaId in [fullsizeMediaId, thumbnailMediaId] {
@ -163,11 +176,12 @@ public class BackupListMediaManagerTests {
)) ))
} }
return insertAttachment( return insertAttachment(
mediaName: mediaName, plaintextHash: plaintextHash,
encryptionKey: encryptionKey,
mediaTierInfo: .init( mediaTierInfo: .init(
cdnNumber: matchingCdnNumber, cdnNumber: matchingCdnNumber,
unencryptedByteCount: 100, unencryptedByteCount: 100,
plaintextHash: UUID().data, plaintextHash: plaintextHash,
incrementalMacInfo: nil, incrementalMacInfo: nil,
uploadEra: localUploadEra, uploadEra: localUploadEra,
lastDownloadAttemptTimestamp: nil, lastDownloadAttemptTimestamp: nil,
@ -181,7 +195,12 @@ public class BackupListMediaManagerTests {
var nonMatchingCdnNumberMedia = [BackupArchive.Response.StoredMedia]() var nonMatchingCdnNumberMedia = [BackupArchive.Response.StoredMedia]()
let nonMatchingCdnNumberIds = await db.awaitableWrite { tx in let nonMatchingCdnNumberIds = await db.awaitableWrite { tx in
return (0..<numAttachmentsPerCase).map { _ in return (0..<numAttachmentsPerCase).map { _ in
let mediaName = UUID().uuidString let plaintextHash = Randomness.generateRandomBytes(32)
let encryptionKey = AttachmentKey.generate()
let mediaName = Attachment.mediaName(
plaintextHash: plaintextHash,
encryptionKey: encryptionKey.combinedKey,
)
let fullsizeMediaId = try! mediaRootBackupKey.deriveMediaId(mediaName) let fullsizeMediaId = try! mediaRootBackupKey.deriveMediaId(mediaName)
let thumbnailMediaId = try! mediaRootBackupKey.deriveMediaId(AttachmentBackupThumbnail.thumbnailMediaName(fullsizeMediaName: mediaName)) let thumbnailMediaId = try! mediaRootBackupKey.deriveMediaId(AttachmentBackupThumbnail.thumbnailMediaName(fullsizeMediaName: mediaName))
for mediaId in [fullsizeMediaId, thumbnailMediaId] { for mediaId in [fullsizeMediaId, thumbnailMediaId] {
@ -199,11 +218,12 @@ public class BackupListMediaManagerTests {
)) ))
} }
return insertAttachment( return insertAttachment(
mediaName: mediaName, plaintextHash: plaintextHash,
encryptionKey: encryptionKey,
mediaTierInfo: .init( mediaTierInfo: .init(
cdnNumber: matchingCdnNumber, cdnNumber: matchingCdnNumber,
unencryptedByteCount: 100, unencryptedByteCount: 100,
plaintextHash: UUID().data, plaintextHash: plaintextHash,
incrementalMacInfo: nil, incrementalMacInfo: nil,
uploadEra: localUploadEra, uploadEra: localUploadEra,
lastDownloadAttemptTimestamp: nil, lastDownloadAttemptTimestamp: nil,
@ -329,14 +349,20 @@ public class BackupListMediaManagerTests {
typealias Attachment = SignalServiceKit.Attachment typealias Attachment = SignalServiceKit.Attachment
private func insertAttachment( private func insertAttachment(
mediaName: String, plaintextHash: Data,
encryptionKey: AttachmentKey,
mediaTierInfo: Attachment.MediaTierInfo?, mediaTierInfo: Attachment.MediaTierInfo?,
tx: DBWriteTransaction, tx: DBWriteTransaction,
) -> Attachment.IDType { ) -> Attachment.IDType {
owsPrecondition(mediaTierInfo == nil || mediaTierInfo!.plaintextHash == plaintextHash)
let thread = TSThread(uniqueId: UUID().uuidString) let thread = TSThread(uniqueId: UUID().uuidString)
try! thread.insert(tx.database) try! thread.insert(tx.database)
var attachmentRecord = Attachment.Record.mockStream(mediaName: mediaName) var attachmentRecord = Attachment.Record.mockStream(
encryptionKey: encryptionKey,
plaintextHash: plaintextHash,
)
try! attachmentRecord.insert(tx.database) try! attachmentRecord.insert(tx.database)
if let mediaTierInfo { if let mediaTierInfo {

View File

@ -58,7 +58,11 @@ public class Attachment {
/// MediaName used for backups (but assigned even if backups disabled). /// MediaName used for backups (but assigned even if backups disabled).
/// Nonnull if downloaded OR if restored from a backup. /// Nonnull if downloaded OR if restored from a backup.
public var mediaName: String? public var mediaName: String? {
return self.plaintextHash.map {
return Attachment.mediaName(plaintextHash: $0, encryptionKey: self.encryptionKey)
}
}
/// If null, the resource has not been uploaded to the media tier. /// If null, the resource has not been uploaded to the media tier.
public var mediaTierInfo: MediaTierInfo? public var mediaTierInfo: MediaTierInfo?
@ -146,7 +150,7 @@ public class Attachment {
init(pendingAttachment: PendingAttachment) { init(pendingAttachment: PendingAttachment) {
self.init( self.init(
plaintextHash: pendingAttachment.plaintextHash, plaintextHash: pendingAttachment.plaintextHash,
mediaName: pendingAttachment.mediaName, encryptionKey: pendingAttachment.encryptionKey,
encryptedByteCount: pendingAttachment.encryptedByteCount, encryptedByteCount: pendingAttachment.encryptedByteCount,
unencryptedByteCount: pendingAttachment.unencryptedByteCount, unencryptedByteCount: pendingAttachment.unencryptedByteCount,
cachedMediaSizePixels: pendingAttachment.mediaPixelSize, cachedMediaSizePixels: pendingAttachment.mediaPixelSize,
@ -161,7 +165,7 @@ public class Attachment {
init( init(
plaintextHash: Data, plaintextHash: Data,
mediaName: String, encryptionKey: Data,
encryptedByteCount: UInt32, encryptedByteCount: UInt32,
unencryptedByteCount: UInt32, unencryptedByteCount: UInt32,
cachedMediaSizePixels: CGSize?, cachedMediaSizePixels: CGSize?,
@ -173,7 +177,7 @@ public class Attachment {
localRelativeFilePath: String, localRelativeFilePath: String,
) { ) {
self.plaintextHash = plaintextHash self.plaintextHash = plaintextHash
self.mediaName = mediaName self.mediaName = Attachment.mediaName(plaintextHash: plaintextHash, encryptionKey: encryptionKey)
self.encryptedByteCount = encryptedByteCount self.encryptedByteCount = encryptedByteCount
self.unencryptedByteCount = unencryptedByteCount self.unencryptedByteCount = unencryptedByteCount
self.cachedMediaSizePixels = cachedMediaSizePixels self.cachedMediaSizePixels = cachedMediaSizePixels
@ -279,13 +283,11 @@ public class Attachment {
self.encryptionKey = record.encryptionKey self.encryptionKey = record.encryptionKey
self.originalAttachmentIdForQuotedReply = record.originalAttachmentIdForQuotedReply self.originalAttachmentIdForQuotedReply = record.originalAttachmentIdForQuotedReply
self.plaintextHash = record.plaintextHash self.plaintextHash = record.plaintextHash
self.mediaName = record.mediaName
self.localRelativeFilePathThumbnail = record.localRelativeFilePathThumbnail self.localRelativeFilePathThumbnail = record.localRelativeFilePathThumbnail
self.lastFullscreenViewTimestamp = record.lastFullscreenViewTimestamp self.lastFullscreenViewTimestamp = record.lastFullscreenViewTimestamp
if if
let plaintextHash = record.plaintextHash, let plaintextHash = record.plaintextHash,
let mediaName = record.mediaName,
let encryptedByteCount = record.encryptedByteCount, let encryptedByteCount = record.encryptedByteCount,
let unencryptedByteCount = record.unencryptedByteCount, let unencryptedByteCount = record.unencryptedByteCount,
let ciphertextDigest = record.ciphertextDigest, let ciphertextDigest = record.ciphertextDigest,
@ -293,7 +295,7 @@ public class Attachment {
{ {
self.streamInfo = StreamInfo( self.streamInfo = StreamInfo(
plaintextHash: plaintextHash, plaintextHash: plaintextHash,
mediaName: mediaName, encryptionKey: self.encryptionKey,
encryptedByteCount: encryptedByteCount, encryptedByteCount: encryptedByteCount,
unencryptedByteCount: unencryptedByteCount, unencryptedByteCount: unencryptedByteCount,
cachedMediaSizePixels: { cachedMediaSizePixels: {

View File

@ -463,8 +463,7 @@ public class AttachmentManagerImpl: AttachmentManager {
// duplicate to the first attachment), but this does drop cdn info if, // duplicate to the first attachment), but this does drop cdn info if,
// for example, this copy had valid cdn info and the older one did not. // for example, this copy had valid cdn info and the older one did not.
// It is on the exporter to dedupe and merge as needed so this doesn't happen. // It is on the exporter to dedupe and merge as needed so this doesn't happen.
fallthrough
case .duplicateMediaName(let existingAttachmentId):
// We already have an attachment with the same mediaName (likely from this same // We already have an attachment with the same mediaName (likely from this same
// backup). Just point the reference at the existing attachment. // backup). Just point the reference at the existing attachment.
attachmentStore.addReference( attachmentStore.addReference(
@ -606,7 +605,6 @@ public class AttachmentManagerImpl: AttachmentManager {
encryptionKey: pendingAttachment.encryptionKey, encryptionKey: pendingAttachment.encryptionKey,
streamInfo: streamInfo, streamInfo: streamInfo,
plaintextHash: pendingAttachment.plaintextHash, plaintextHash: pendingAttachment.plaintextHash,
mediaName: pendingAttachment.mediaName,
) )
let hasOrphanRecord = orphanedAttachmentStore.orphanAttachmentExists( let hasOrphanRecord = orphanedAttachmentStore.orphanAttachmentExists(
@ -729,11 +727,7 @@ public class AttachmentManagerImpl: AttachmentManager {
switch error { switch error {
case .duplicatePlaintextHash(let id): case .duplicatePlaintextHash(let id):
existingAttachmentId = id existingAttachmentId = id
case .duplicateMediaName(let id):
owsFailDebug("How did we match mediaName when using a random encryption key?")
existingAttachmentId = id
} }
// Already have an attachment with the same plaintext hash or media name! // Already have an attachment with the same plaintext hash or media name!
// Move all existing references to that copy, instead. // Move all existing references to that copy, instead.
// Doing so should delete the original attachment pointer. // Doing so should delete the original attachment pointer.
@ -799,9 +793,7 @@ public class AttachmentManagerImpl: AttachmentManager {
) throws -> Attachment.IDType { ) throws -> Attachment.IDType {
let existingAttachmentId: Attachment.IDType let existingAttachmentId: Attachment.IDType
switch error { switch error {
case case .duplicatePlaintextHash(let id):
.duplicatePlaintextHash(let id),
.duplicateMediaName(let id):
existingAttachmentId = id existingAttachmentId = id
} }

View File

@ -10,10 +10,6 @@ public enum AttachmentInsertError: Error {
/// attachment a duplicate. Callers should instead create a new owner reference to /// attachment a duplicate. Callers should instead create a new owner reference to
/// the same existing attachment. /// the same existing attachment.
case duplicatePlaintextHash(existingAttachmentId: Attachment.IDType) case duplicatePlaintextHash(existingAttachmentId: Attachment.IDType)
/// An existing attachment was found with the same media name, making the new
/// attachment a duplicate. Callers should instead create a new owner reference to
/// the same existing attachment and possibly update it with any stream info.
case duplicateMediaName(existingAttachmentId: Attachment.IDType)
} }
// MARK: - // MARK: -
@ -192,20 +188,6 @@ public struct AttachmentStore {
} }
} }
/// Fetch an existing Attachment record with the given mediaName. There will
/// be at most one.
public func fetchAttachmentRecord(
mediaName: String,
tx: DBReadTransaction,
) -> Attachment.Record? {
let query = Attachment.Record
.filter(Column(Attachment.Record.CodingKeys.mediaName) == mediaName)
return failIfThrows {
try query.fetchOne(tx.database)
}
}
// MARK: - // MARK: -
/// Fetch an arbitrary referenced attachment for the provided owner. /// Fetch an arbitrary referenced attachment for the provided owner.
@ -629,20 +611,6 @@ public struct AttachmentStore {
throw AttachmentInsertError.duplicatePlaintextHash(existingAttachmentId: existingAttachmentId) throw AttachmentInsertError.duplicatePlaintextHash(existingAttachmentId: existingAttachmentId)
} }
// Find if there is already an attachment with the same media name.
if
let existingAttachmentId = fetchAttachmentRecord(
mediaName: Attachment.mediaName(
plaintextHash: streamInfo.plaintextHash,
encryptionKey: attachment.encryptionKey,
),
tx: tx,
)?.sqliteId,
existingAttachmentId != attachment.id
{
throw AttachmentInsertError.duplicateMediaName(existingAttachmentId: existingAttachmentId)
}
// We count it as a "view" if the download was initiated by the user // We count it as a "view" if the download was initiated by the user
let lastFullscreenViewTimestamp: UInt64? let lastFullscreenViewTimestamp: UInt64?
switch priority { switch priority {
@ -682,13 +650,11 @@ public struct AttachmentStore {
attachment.streamInfo = streamInfo attachment.streamInfo = streamInfo
attachment.plaintextHash = streamInfo.plaintextHash attachment.plaintextHash = streamInfo.plaintextHash
attachment.latestTransitTierInfo = latestTransitTierInfo attachment.latestTransitTierInfo = latestTransitTierInfo
attachment.mediaName = streamInfo.mediaName
attachment.lastFullscreenViewTimestamp = lastFullscreenViewTimestamp ?? attachment.lastFullscreenViewTimestamp attachment.lastFullscreenViewTimestamp = lastFullscreenViewTimestamp ?? attachment.lastFullscreenViewTimestamp
case .mediaTierFullsize: case .mediaTierFullsize:
attachment.streamInfo = streamInfo attachment.streamInfo = streamInfo
attachment.plaintextHash = streamInfo.plaintextHash attachment.plaintextHash = streamInfo.plaintextHash
attachment.latestTransitTierInfo = latestTransitTierInfo attachment.latestTransitTierInfo = latestTransitTierInfo
attachment.mediaName = streamInfo.mediaName
if var mediaTierInfo = attachment.mediaTierInfo { if var mediaTierInfo = attachment.mediaTierInfo {
// Wipe the last download attempt time; its now succeeded. // Wipe the last download attempt time; its now succeeded.
mediaTierInfo.lastDownloadAttemptTimestamp = nil mediaTierInfo.lastDownloadAttemptTimestamp = nil
@ -735,7 +701,6 @@ public struct AttachmentStore {
attachment.latestTransitTierInfo = latestTransitTierInfo attachment.latestTransitTierInfo = latestTransitTierInfo
attachment.originalTransitTierInfo = originalTransitTierInfo attachment.originalTransitTierInfo = originalTransitTierInfo
attachment.plaintextHash = streamInfo.plaintextHash attachment.plaintextHash = streamInfo.plaintextHash
attachment.mediaName = streamInfo.mediaName
attachment.mediaTierInfo = mediaTierInfo attachment.mediaTierInfo = mediaTierInfo
attachment.thumbnailMediaTierInfo = thumbnailMediaTierInfo attachment.thumbnailMediaTierInfo = thumbnailMediaTierInfo
attachment.localRelativeFilePathThumbnail = nil attachment.localRelativeFilePathThumbnail = nil
@ -824,11 +789,9 @@ public struct AttachmentStore {
public func saveMediaTierInfo( public func saveMediaTierInfo(
attachment: Attachment, attachment: Attachment,
mediaTierInfo: Attachment.MediaTierInfo, mediaTierInfo: Attachment.MediaTierInfo,
mediaName: String,
tx: DBWriteTransaction, tx: DBWriteTransaction,
) { ) {
attachment.mediaTierInfo = mediaTierInfo attachment.mediaTierInfo = mediaTierInfo
attachment.mediaName = mediaName
let record = Attachment.Record(attachment: attachment) let record = Attachment.Record(attachment: attachment)
failIfThrows { failIfThrows {
@ -839,10 +802,8 @@ public struct AttachmentStore {
func saveMediaTierThumbnailInfo( func saveMediaTierThumbnailInfo(
attachment: Attachment, attachment: Attachment,
thumbnailMediaTierInfo: Attachment.ThumbnailMediaTierInfo, thumbnailMediaTierInfo: Attachment.ThumbnailMediaTierInfo,
mediaName: String,
tx: DBWriteTransaction, tx: DBWriteTransaction,
) { ) {
attachment.mediaName = mediaName
attachment.thumbnailMediaTierInfo = thumbnailMediaTierInfo attachment.thumbnailMediaTierInfo = thumbnailMediaTierInfo
let record = Attachment.Record(attachment: attachment) let record = Attachment.Record(attachment: attachment)
@ -1081,17 +1042,6 @@ public struct AttachmentStore {
throw AttachmentInsertError.duplicatePlaintextHash(existingAttachmentId: existingAttachmentId) throw AttachmentInsertError.duplicatePlaintextHash(existingAttachmentId: existingAttachmentId)
} }
// Find if there is already an attachment with the same media name.
if
let mediaName = attachmentRecord.mediaName,
let existingAttachmentId = fetchAttachmentRecord(
mediaName: mediaName,
tx: tx,
)?.sqliteId
{
throw AttachmentInsertError.duplicateMediaName(existingAttachmentId: existingAttachmentId)
}
let attachment = failIfThrows { let attachment = failIfThrows {
// Note that there are UNIQUE constraints on this table (e.g., // Note that there are UNIQUE constraints on this table (e.g.,
// plaintext hash and mediaName). Importantly, those are checked // plaintext hash and mediaName). Importantly, those are checked

View File

@ -144,30 +144,16 @@ class AttachmentStoreTests: XCTestCase {
// MARK: - // MARK: -
func testInsertSameMediaName() {
let mediaName = Data(repeating: 27, count: 10).hexadecimalString
switch testAttachmentInsertError(
attachmentParams1: Attachment.Record.mockStream(streamInfo: .mock(mediaName: mediaName)),
attachmentParams2: Attachment.Record.mockStream(streamInfo: .mock(mediaName: mediaName)),
) {
case .duplicateMediaName:
break
case nil, .duplicatePlaintextHash:
XCTFail()
}
}
func testInsertSamePlaintextHash() throws { func testInsertSamePlaintextHash() throws {
let plaintextHash = UUID().data let plaintextHash = Randomness.generateRandomBytes(32)
switch testAttachmentInsertError( switch testAttachmentInsertError(
attachmentParams1: Attachment.Record.mockStream(streamInfo: .mock(plaintextHash: plaintextHash)), attachmentParams1: Attachment.Record.mockStream(plaintextHash: plaintextHash),
attachmentParams2: Attachment.Record.mockStream(streamInfo: .mock(plaintextHash: plaintextHash)), attachmentParams2: Attachment.Record.mockStream(plaintextHash: plaintextHash),
) { ) {
case .duplicatePlaintextHash: case .duplicatePlaintextHash:
break break
case nil, .duplicateMediaName: case nil:
XCTFail() XCTFail()
} }
} }

View File

@ -47,10 +47,7 @@ public struct PendingAttachment {
} }
var mediaName: String { var mediaName: String {
Attachment.mediaName( return Attachment.mediaName(plaintextHash: self.plaintextHash, encryptionKey: self.encryptionKey)
plaintextHash: plaintextHash,
encryptionKey: encryptionKey,
)
} }
mutating func removeBorderlessRenderingFlagIfPresent() { mutating func removeBorderlessRenderingFlagIfPresent() {

View File

@ -2415,7 +2415,6 @@ public class AttachmentDownloadManagerImpl: AttachmentDownloadManager {
encryptionKey: pendingAttachment.encryptionKey, encryptionKey: pendingAttachment.encryptionKey,
streamInfo: streamInfo, streamInfo: streamInfo,
plaintextHash: pendingAttachment.plaintextHash, plaintextHash: pendingAttachment.plaintextHash,
mediaName: pendingAttachment.mediaName,
) )
let attachment = try self.attachmentStore.insert( let attachment = try self.attachmentStore.insert(
@ -2589,7 +2588,6 @@ public class AttachmentDownloadManagerImpl: AttachmentDownloadManager {
encryptionKey: pendingThumbnailAttachment.encryptionKey, encryptionKey: pendingThumbnailAttachment.encryptionKey,
streamInfo: streamInfo, streamInfo: streamInfo,
plaintextHash: pendingThumbnailAttachment.plaintextHash, plaintextHash: pendingThumbnailAttachment.plaintextHash,
mediaName: pendingThumbnailAttachment.mediaName,
) )
let newAttachment = try self.attachmentStore.insert( let newAttachment = try self.attachmentStore.insert(

View File

@ -10,9 +10,9 @@ import Foundation
// MARK: - Infos // MARK: - Infos
extension Attachment.StreamInfo { extension Attachment.StreamInfo {
public static func mock( static func mock(
plaintextHash: Data = Randomness.generateRandomBytes(32), plaintextHash: Data = Randomness.generateRandomBytes(32),
mediaName: String = UUID().uuidString, encryptionKey: AttachmentKey = .generate(),
encryptedByteCount: UInt32 = .random(in: 0..<95_000_000), encryptedByteCount: UInt32 = .random(in: 0..<95_000_000),
unencryptedByteCount: UInt32 = .random(in: 0..<95_000_000), unencryptedByteCount: UInt32 = .random(in: 0..<95_000_000),
ciphertextDigest: Data = Randomness.generateRandomBytes(32), ciphertextDigest: Data = Randomness.generateRandomBytes(32),
@ -20,7 +20,7 @@ extension Attachment.StreamInfo {
) -> Attachment.StreamInfo { ) -> Attachment.StreamInfo {
return Attachment.StreamInfo( return Attachment.StreamInfo(
plaintextHash: plaintextHash, plaintextHash: plaintextHash,
mediaName: mediaName, encryptionKey: encryptionKey.combinedKey,
encryptedByteCount: encryptedByteCount, encryptedByteCount: encryptedByteCount,
unencryptedByteCount: unencryptedByteCount, unencryptedByteCount: unencryptedByteCount,
cachedMediaSizePixels: nil, cachedMediaSizePixels: nil,
@ -109,22 +109,23 @@ extension Attachment.Record {
) )
} }
public static func mockStream( static func mockStream(
blurHash: String? = UUID().uuidString, blurHash: String? = UUID().uuidString,
mimeType: String = MimeType.imageJpeg.rawValue, mimeType: String = MimeType.imageJpeg.rawValue,
encryptionKey: Data = Randomness.generateRandomBytes(64), encryptionKey: AttachmentKey = .generate(),
plaintextHash: Data? = nil, plaintextHash: Data = Randomness.generateRandomBytes(32),
mediaName: String? = nil, streamInfo: Attachment.StreamInfo? = nil,
streamInfo: Attachment.StreamInfo = .mock(),
) -> Attachment.Record { ) -> Attachment.Record {
return .forInsertingStream( return .forInsertingStream(
blurHash: blurHash, blurHash: blurHash,
mimeType: mimeType, mimeType: mimeType,
contentType: Attachment.ContentType(mimeType: mimeType), contentType: Attachment.ContentType(mimeType: mimeType),
encryptionKey: encryptionKey, encryptionKey: encryptionKey.combinedKey,
streamInfo: streamInfo, streamInfo: streamInfo ?? .mock(
plaintextHash: plaintextHash ?? streamInfo.plaintextHash, plaintextHash: plaintextHash,
mediaName: mediaName ?? streamInfo.mediaName, encryptionKey: encryptionKey,
),
plaintextHash: plaintextHash,
) )
} }
} }
@ -136,7 +137,6 @@ extension Attachment {
mimeType: String = MimeType.applicationOctetStream.rawValue, mimeType: String = MimeType.applicationOctetStream.rawValue,
encryptionKey: Data = Randomness.generateRandomBytes(64), encryptionKey: Data = Randomness.generateRandomBytes(64),
plaintextHash: Data? = nil, plaintextHash: Data? = nil,
mediaName: String? = nil,
streamInfo: Attachment.StreamInfo? = nil, streamInfo: Attachment.StreamInfo? = nil,
transitTierInfo: Attachment.TransitTierInfo? = nil, transitTierInfo: Attachment.TransitTierInfo? = nil,
mediaTierInfo: Attachment.MediaTierInfo? = nil, mediaTierInfo: Attachment.MediaTierInfo? = nil,
@ -152,7 +152,6 @@ extension Attachment {
contentType: Attachment.ContentType(mimeType: mimeType), contentType: Attachment.ContentType(mimeType: mimeType),
encryptionKey: encryptionKey, encryptionKey: encryptionKey,
plaintextHash: plaintextHash ?? streamInfo?.plaintextHash, plaintextHash: plaintextHash ?? streamInfo?.plaintextHash,
mediaName: mediaName ?? streamInfo?.mediaName,
localRelativeFilePathThumbnail: localRelativeFilePathThumbnail, localRelativeFilePathThumbnail: localRelativeFilePathThumbnail,
streamInfo: streamInfo, streamInfo: streamInfo,
latestTransitTierInfo: transitTierInfo, latestTransitTierInfo: transitTierInfo,
@ -169,10 +168,9 @@ extension Attachment {
extension AttachmentStream { extension AttachmentStream {
public static func mock( static func mock(
blurHash: String? = nil, blurHash: String? = nil,
mimeType: String = MimeType.applicationOctetStream.rawValue, mimeType: String = MimeType.applicationOctetStream.rawValue,
mediaName: String? = nil,
streamInfo: Attachment.StreamInfo = .mock(), streamInfo: Attachment.StreamInfo = .mock(),
transitTierInfo: Attachment.TransitTierInfo? = nil, transitTierInfo: Attachment.TransitTierInfo? = nil,
mediaTierInfo: Attachment.MediaTierInfo? = nil, mediaTierInfo: Attachment.MediaTierInfo? = nil,
@ -182,7 +180,6 @@ extension AttachmentStream {
let attachment = Attachment.mock( let attachment = Attachment.mock(
blurHash: blurHash, blurHash: blurHash,
mimeType: mimeType, mimeType: mimeType,
mediaName: mediaName,
streamInfo: streamInfo, streamInfo: streamInfo,
transitTierInfo: transitTierInfo, transitTierInfo: transitTierInfo,
mediaTierInfo: mediaTierInfo, mediaTierInfo: mediaTierInfo,

View File

@ -32,7 +32,7 @@ extension Attachment {
let latestTransitCiphertextDigest: Data? let latestTransitCiphertextDigest: Data?
@DBUInt64Optional @DBUInt64Optional
var latestTransitLastDownloadAttemptTimestamp: UInt64? var latestTransitLastDownloadAttemptTimestamp: UInt64?
let mediaName: String? private let _mediaName: String? // deprecated, always NULL
let mediaTierCdnNumber: UInt32? let mediaTierCdnNumber: UInt32?
let mediaTierUnencryptedByteCount: UInt32? let mediaTierUnencryptedByteCount: UInt32?
let mediaTierUploadEra: String? let mediaTierUploadEra: String?
@ -66,6 +66,12 @@ extension Attachment {
let originalTransitTierIncrementalMac: Data? let originalTransitTierIncrementalMac: Data?
let originalTransitTierIncrementalMacChunkSize: UInt32? let originalTransitTierIncrementalMacChunkSize: UInt32?
var mediaName: String? {
return self.plaintextHash.map {
return Attachment.mediaName(plaintextHash: $0, encryptionKey: self.encryptionKey)
}
}
public var allFilesRelativePaths: [String] { public var allFilesRelativePaths: [String] {
return [ return [
localRelativeFilePath, localRelativeFilePath,
@ -94,7 +100,7 @@ extension Attachment {
case latestTransitUnencryptedByteCount = "transitUnencryptedByteCount" case latestTransitUnencryptedByteCount = "transitUnencryptedByteCount"
case latestTransitCiphertextDigest = "transitDigestSHA256Ciphertext" case latestTransitCiphertextDigest = "transitDigestSHA256Ciphertext"
case latestTransitLastDownloadAttemptTimestamp = "lastTransitDownloadAttemptTimestamp" case latestTransitLastDownloadAttemptTimestamp = "lastTransitDownloadAttemptTimestamp"
case mediaName case _mediaName = "mediaName"
case mediaTierCdnNumber case mediaTierCdnNumber
case mediaTierUnencryptedByteCount case mediaTierUnencryptedByteCount
case mediaTierUploadEra case mediaTierUploadEra
@ -143,7 +149,6 @@ extension Attachment {
contentType: attachment.contentType, contentType: attachment.contentType,
encryptionKey: attachment.encryptionKey, encryptionKey: attachment.encryptionKey,
plaintextHash: attachment.plaintextHash, plaintextHash: attachment.plaintextHash,
mediaName: attachment.mediaName,
localRelativeFilePathThumbnail: attachment.localRelativeFilePathThumbnail, localRelativeFilePathThumbnail: attachment.localRelativeFilePathThumbnail,
streamInfo: attachment.streamInfo, streamInfo: attachment.streamInfo,
latestTransitTierInfo: attachment.latestTransitTierInfo, latestTransitTierInfo: attachment.latestTransitTierInfo,
@ -162,7 +167,6 @@ extension Attachment {
contentType: Attachment.ContentType, contentType: Attachment.ContentType,
encryptionKey: Data, encryptionKey: Data,
plaintextHash: Data?, plaintextHash: Data?,
mediaName: String?,
localRelativeFilePathThumbnail: String?, localRelativeFilePathThumbnail: String?,
streamInfo: Attachment.StreamInfo?, streamInfo: Attachment.StreamInfo?,
latestTransitTierInfo: Attachment.TransitTierInfo?, latestTransitTierInfo: Attachment.TransitTierInfo?,
@ -209,7 +213,7 @@ extension Attachment {
self.originalTransitTierIncrementalMac = originalTransitTierInfo?.incrementalMacInfo?.mac self.originalTransitTierIncrementalMac = originalTransitTierInfo?.incrementalMacInfo?.mac
self.originalTransitTierIncrementalMacChunkSize = originalTransitTierInfo?.incrementalMacInfo?.chunkSize self.originalTransitTierIncrementalMacChunkSize = originalTransitTierInfo?.incrementalMacInfo?.chunkSize
self.mediaName = mediaName self._mediaName = nil
self.mediaTierCdnNumber = mediaTierInfo?.cdnNumber self.mediaTierCdnNumber = mediaTierInfo?.cdnNumber
self.mediaTierUnencryptedByteCount = mediaTierInfo?.unencryptedByteCount self.mediaTierUnencryptedByteCount = mediaTierInfo?.unencryptedByteCount
self.mediaTierIncrementalMac = mediaTierInfo?.incrementalMacInfo?.mac self.mediaTierIncrementalMac = mediaTierInfo?.incrementalMacInfo?.mac
@ -248,7 +252,6 @@ extension Attachment {
contentType: contentType, contentType: contentType,
encryptionKey: encryptionKey, encryptionKey: encryptionKey,
plaintextHash: nil, plaintextHash: nil,
mediaName: nil,
localRelativeFilePathThumbnail: nil, localRelativeFilePathThumbnail: nil,
streamInfo: nil, streamInfo: nil,
latestTransitTierInfo: latestTransitTierInfo, latestTransitTierInfo: latestTransitTierInfo,
@ -268,7 +271,6 @@ extension Attachment {
encryptionKey: Data, encryptionKey: Data,
streamInfo: Attachment.StreamInfo, streamInfo: Attachment.StreamInfo,
plaintextHash: Data, plaintextHash: Data,
mediaName: String,
) -> Record { ) -> Record {
return Record( return Record(
sqliteId: nil, sqliteId: nil,
@ -277,7 +279,6 @@ extension Attachment {
contentType: contentType, contentType: contentType,
encryptionKey: encryptionKey, encryptionKey: encryptionKey,
plaintextHash: plaintextHash, plaintextHash: plaintextHash,
mediaName: mediaName,
localRelativeFilePathThumbnail: nil, localRelativeFilePathThumbnail: nil,
streamInfo: streamInfo, streamInfo: streamInfo,
latestTransitTierInfo: nil, latestTransitTierInfo: nil,
@ -306,9 +307,6 @@ extension Attachment {
contentType: contentType, contentType: contentType,
encryptionKey: encryptionKey, encryptionKey: encryptionKey,
plaintextHash: plaintextHash, plaintextHash: plaintextHash,
mediaName: plaintextHash.map {
return Attachment.mediaName(plaintextHash: $0, encryptionKey: encryptionKey)
},
localRelativeFilePathThumbnail: nil, localRelativeFilePathThumbnail: nil,
streamInfo: nil, streamInfo: nil,
latestTransitTierInfo: latestTransitTierInfo, latestTransitTierInfo: latestTransitTierInfo,
@ -335,7 +333,6 @@ extension Attachment {
// encryption key we use is irrelevant. Just generate a new one. // encryption key we use is irrelevant. Just generate a new one.
encryptionKey: AttachmentKey.generate().combinedKey, encryptionKey: AttachmentKey.generate().combinedKey,
plaintextHash: nil, plaintextHash: nil,
mediaName: nil,
localRelativeFilePathThumbnail: nil, localRelativeFilePathThumbnail: nil,
streamInfo: nil, streamInfo: nil,
latestTransitTierInfo: nil, latestTransitTierInfo: nil,
@ -362,7 +359,6 @@ extension Attachment {
contentType: thumbnailContentType, contentType: thumbnailContentType,
encryptionKey: thumbnailEncryptionKey, encryptionKey: thumbnailEncryptionKey,
plaintextHash: nil, plaintextHash: nil,
mediaName: nil,
localRelativeFilePathThumbnail: nil, localRelativeFilePathThumbnail: nil,
streamInfo: nil, streamInfo: nil,
latestTransitTierInfo: thumbnailTransitTierInfo, latestTransitTierInfo: thumbnailTransitTierInfo,

View File

@ -334,6 +334,7 @@ public class GRDBSchemaMigrator {
case purgeMyStoryDeletedAtTimestamp case purgeMyStoryDeletedAtTimestamp
case addRecoverablePlaceholderExpirationIndex case addRecoverablePlaceholderExpirationIndex
case backfillRecoverablePlaceholderErrorType case backfillRecoverablePlaceholderErrorType
case clearAttachmentMediaName
// NOTE: Every time we add a migration id, consider // NOTE: Every time we add a migration id, consider
// incrementing grdbSchemaVersionLatest. // incrementing grdbSchemaVersionLatest.
@ -457,7 +458,7 @@ public class GRDBSchemaMigrator {
} }
public static let grdbSchemaVersionDefault: UInt = 0 public static let grdbSchemaVersionDefault: UInt = 0
public static let grdbSchemaVersionLatest: UInt = 144 public static let grdbSchemaVersionLatest: UInt = 145
private class DatabaseMigratorWrapper { private class DatabaseMigratorWrapper {
// Run with immediate (or disabled) foreign key checks so that pre-existing // Run with immediate (or disabled) foreign key checks so that pre-existing
@ -5220,6 +5221,29 @@ public class GRDBSchemaMigrator {
return .success(()) return .success(())
} }
migrator.registerMigration(.clearAttachmentMediaName) { tx in
try tx.database.execute(sql: "DROP TRIGGER IF EXISTS __Attachment_ad_backup_fullsize")
try tx.database.execute(sql: "DROP TRIGGER IF EXISTS __Attachment_ad_backup_thumbnail")
try tx.database.execute(sql: "UPDATE Attachment SET mediaName = NULL")
try tx.database.execute(sql: """
CREATE TRIGGER "__Attachment_ad_backup_fullsize" AFTER DELETE ON "Attachment"
WHEN (OLD.mediaTierCdnNumber IS NOT NULL AND OLD.sha256ContentHash IS NOT NULL)
BEGIN
INSERT INTO OrphanedBackupAttachment (cdnNumber, mediaName, mediaId, type)
VALUES (OLD.mediaTierCdnNumber, LOWER(HEX(OLD.sha256ContentHash || OLD.encryptionKey)), NULL, 0);
END
""")
try tx.database.execute(sql: """
CREATE TRIGGER "__Attachment_ad_backup_thumbnail" AFTER DELETE ON "Attachment"
WHEN (OLD.thumbnailCdnNumber IS NOT NULL AND OLD.sha256ContentHash IS NOT NULL)
BEGIN
INSERT INTO OrphanedBackupAttachment (cdnNumber, mediaName, mediaId, type)
VALUES (OLD.thumbnailCdnNumber, LOWER(HEX(OLD.sha256ContentHash || OLD.encryptionKey)), NULL, 1);
END;
""")
return .success(())
}
// MARK: - Schema Migration Insertion Point // MARK: - Schema Migration Insertion Point
} }

View File

@ -439,7 +439,6 @@ public actor AttachmentUploadManagerImpl: AttachmentUploadManager {
attachmentStore.saveMediaTierInfo( attachmentStore.saveMediaTierInfo(
attachment: attachmentStream.attachment, attachment: attachmentStream.attachment,
mediaTierInfo: mediaTierInfo, mediaTierInfo: mediaTierInfo,
mediaName: attachmentStream.mediaName,
tx: tx, tx: tx,
) )
@ -553,7 +552,6 @@ public actor AttachmentUploadManagerImpl: AttachmentUploadManager {
attachmentStore.saveMediaTierThumbnailInfo( attachmentStore.saveMediaTierThumbnailInfo(
attachment: attachmentStream.attachment, attachment: attachmentStream.attachment,
thumbnailMediaTierInfo: thumbnailInfo, thumbnailMediaTierInfo: thumbnailInfo,
mediaName: attachmentStream.mediaName,
tx: tx, tx: tx,
) )

View File

@ -0,0 +1,62 @@
//
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import Testing
@testable import SignalServiceKit
struct OrphanedBackupAttachmentTest {
@Test
func testTriggers() throws {
let db = InMemoryDB()
let encryptionKey = AttachmentKey.generate()
let plaintextHash = Randomness.generateRandomBytes(32)
var record = Attachment.Record.forInsertingFromBackup(
blurHash: nil,
mimeType: "image/png",
contentType: .image,
encryptionKey: encryptionKey.combinedKey,
latestTransitTierInfo: nil,
plaintextHash: plaintextHash,
mediaTierInfo: Attachment.MediaTierInfo(
cdnNumber: 2,
unencryptedByteCount: 123,
plaintextHash: plaintextHash,
incrementalMacInfo: nil,
uploadEra: "initialUploadEra",
),
thumbnailMediaTierInfo: Attachment.ThumbnailMediaTierInfo(
cdnNumber: 5,
uploadEra: "initialUploadEra",
),
)
try db.write { tx in
try record.insert(tx.database)
try record.delete(tx.database)
}
let orphanedAttachments = try db.read { tx in
return try OrphanedBackupAttachment.fetchAll(tx.database)
}
struct OrphanedBackupAttachment2: Hashable {
var mediaName: String?
var cdnNumber: UInt32
var type: Int?
}
let actualValues = orphanedAttachments.map {
return OrphanedBackupAttachment2(mediaName: $0.mediaName, cdnNumber: $0.cdnNumber, type: $0.type?.rawValue)
}
let mediaName = (plaintextHash + encryptionKey.combinedKey).hexadecimalString
let expectedValues = [
OrphanedBackupAttachment2(mediaName: mediaName, cdnNumber: 2, type: OrphanedBackupAttachment.SizeType.fullsize.rawValue),
OrphanedBackupAttachment2(mediaName: mediaName, cdnNumber: 5, type: OrphanedBackupAttachment.SizeType.thumbnail.rawValue),
]
#expect(Set(actualValues) == Set(expectedValues))
}
}