Compute mediaName dynamically
This commit is contained in:
parent
3914c811be
commit
221043a998
@ -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 */,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user