diff --git a/WireNetwork/Sources/WireNetwork/Models/Backend/BackendEnvironment2.swift b/WireNetwork/Sources/WireNetwork/Models/Backend/BackendEnvironment2.swift index 71db457d5d3..8dd5e169653 100644 --- a/WireNetwork/Sources/WireNetwork/Models/Backend/BackendEnvironment2.swift +++ b/WireNetwork/Sources/WireNetwork/Models/Backend/BackendEnvironment2.swift @@ -181,3 +181,23 @@ public struct BackendEnvironment2: Sendable, Equatable, Hashable { } } + +public extension BackendEnvironment2 { + + var isCloudEnvironment: Bool { + config.endpoints.restAPIURL.host() == "prod-nginz-https.wire.com" + } + + static func isCloudDomain(_ domain: String) -> Bool { + domain == "wire.com" + } + + var isStagingEnvironment: Bool { + config.endpoints.restAPIURL.host() == "staging-nginz-https.zinfra.io" + } + + static func isStagingDomain(_ domain: String) -> Bool { + domain == "staging.zinfra.io" + } + +} diff --git a/wire-ios-data-model/Source/Model/Conversation/SendableImage.swift b/wire-ios-data-model/Source/Model/Conversation/SendableImage.swift new file mode 100644 index 00000000000..570f9cec3a8 --- /dev/null +++ b/wire-ios-data-model/Source/Model/Conversation/SendableImage.swift @@ -0,0 +1,61 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import UniformTypeIdentifiers + +public struct SendableImage { + + public let name: String + public let utType: UTType? + public let data: Data + + public init( + name: String?, + utType: UTType?, + data: Data + ) { + if let utType { + self.utType = utType + } else { + self.utType = Self.determineUTType(from: data) + } + + if let name { + self.name = name + } else if let fileExtension = self.utType?.preferredFilenameExtension { + self.name = "picture.\(fileExtension)" + } else { + self.name = "picture" + } + + self.data = data + } + + private static func determineUTType(from data: Data) -> UTType? { + guard + !data.isEmpty, + let imageSource = CGImageSourceCreateWithData(data as CFData, nil), + let uti = CGImageSourceGetType(imageSource) as String? + else { + return nil + } + + return UTType(uti) + } + +} diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+Message.swift b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+Message.swift index 63e07c334a9..5a1a53c7134 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+Message.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+Message.swift @@ -258,7 +258,7 @@ public extension ZMConversation { /// Append an image message. /// /// - Parameters: - /// - url: A url locating some image data. + /// - image: The image to append. /// - nonce: The nonce of the message. /// /// - Throws: @@ -268,55 +268,42 @@ public extension ZMConversation { /// The appended message. @discardableResult - func appendImage(at URL: URL, nonce: UUID = UUID()) throws -> ZMConversationMessage { - guard - URL.isFileURL, - ZMImagePreprocessor.sizeOfPrerotatedImage(at: URL) != .zero, - let imageData = try? Data(contentsOf: URL, options: []) - else { - throw AppendMessageError.invalidImageUrl - } - - return try appendImage(from: imageData) - } - - /// Append an image message. - /// - /// - Parameters: - /// - imageData: Data representing an image. - /// - nonce: The nonce of the message. - /// - /// - Throws: - /// - `AppendMessageError` if the message couldn't be appended. - /// - /// - Returns: - /// The appended message. - - @discardableResult - func appendImage(from imageData: Data, nonce: UUID = UUID()) throws -> ZMConversationMessage { + func appendImage( + _ image: SendableImage, + nonce: UUID + ) throws -> ZMConversationMessage { guard let moc = managedObjectContext else { throw AppendMessageError.missingManagedObjectContext } - guard let imageData = try? imageData.wr_removingImageMetadata() else { + guard let imageData = try? image.data.wr_removingImageMetadata() else { throw AppendMessageError.failedToRemoveImageMetadata } // mimeType is assigned first, to make sure UI can handle animated GIF file correctly. - let mimeType = imageData.mimeType ?? "" + let mimeType = image.utType?.preferredMIMEType // We update the size again when the the preprocessing is done. let imageSize = ZMImagePreprocessor.sizeOfPrerotatedImage(with: imageData) let asset = GenericMessageProtocol.Asset( + name: image.name, + mimeType: mimeType ?? "", imageSize: imageSize, - mimeType: mimeType, size: UInt64(imageData.count) ) - return try append(asset: asset, nonce: nonce, expires: true, prepareMessage: { message in - moc.zm_fileAssetCache.storeOriginalImage(data: imageData, for: message) - }) + return try append( + asset: asset, + nonce: nonce, + expires: true, + prepareMessage: { message in + moc.zm_fileAssetCache.storeOriginalImage( + data: imageData, + for: message + ) + } + ) } /// Append a file message. @@ -563,21 +550,6 @@ public extension ZMConversation { try? appendLocation(with: locationData) } - @discardableResult @objc(appendMessageWithImageData:) - func _appendImage(from imageData: Data) -> ZMConversationMessage? { - try? appendImage(from: imageData) - } - - @discardableResult @objc(appendImageFromData:nonce:) - func _appendImage(from imageData: Data, nonce: UUID) -> ZMConversationMessage? { - try? appendImage(from: imageData, nonce: nonce) - } - - @discardableResult @objc(appendImageAtURL:nonce:) - func _appendImage(at URL: URL, nonce: UUID) -> ZMConversationMessage? { - try? appendImage(at: URL, nonce: nonce) - } - @discardableResult @objc(appendMessageWithFileMetadata:) func _appendFile(with fileMetadata: ZMFileMetadata) -> ZMConversationMessage? { try? appendFile(with: fileMetadata) diff --git a/wire-ios-data-model/Source/Model/Message/V2Asset.swift b/wire-ios-data-model/Source/Model/Message/V2Asset.swift index fb3bb00313c..78ff09a5d37 100644 --- a/wire-ios-data-model/Source/Model/Message/V2Asset.swift +++ b/wire-ios-data-model/Source/Model/Message/V2Asset.swift @@ -22,6 +22,17 @@ import MobileCoreServices @objcMembers public class V2Asset: NSObject, ZMImageMessageData { + public var name: String? { + guard + let message = assetClientMessage.underlyingMessage, + let original = message.assetData?.original + else { + return nil + } + + return original.name + } + public var isDownloaded: Bool { hasDownloadedFile } diff --git a/wire-ios-data-model/Source/Model/Message/V3Asset.swift b/wire-ios-data-model/Source/Model/Message/V3Asset.swift index a0b43132468..cfdc3e0d65f 100644 --- a/wire-ios-data-model/Source/Model/Message/V3Asset.swift +++ b/wire-ios-data-model/Source/Model/Message/V3Asset.swift @@ -98,6 +98,17 @@ public class V3Asset: NSObject, ZMImageMessageData { } } + public var name: String? { + guard + let message = assetClientMessage.underlyingMessage, + let original = message.assetData?.original + else { + return nil + } + + return original.name + } + public var isDownloaded: Bool { hasDownloadedFile } diff --git a/wire-ios-data-model/Source/Model/Message/ZMFileMetadata.swift b/wire-ios-data-model/Source/Model/Message/ZMFileMetadata.swift index dbcc85cefe0..08b837fd663 100644 --- a/wire-ios-data-model/Source/Model/Message/ZMFileMetadata.swift +++ b/wire-ios-data-model/Source/Model/Message/ZMFileMetadata.swift @@ -38,7 +38,14 @@ open class ZMFileMetadata: NSObject { } else { nil } - let endName = name ?? (fileURL.lastPathComponent.isEmpty ? "unnamed" : fileURL.lastPathComponent) + + let endName: String = if let name { + name + } else if !fileURL.lastPathComponent.isEmpty { + fileURL.lastPathComponent + } else { + "file" + } self.filename = endName.removingExtremeCombiningCharacters super.init() diff --git a/wire-ios-data-model/Source/Public/ZMMessage.h b/wire-ios-data-model/Source/Public/ZMMessage.h index 5c8afd41348..316d0404d25 100644 --- a/wire-ios-data-model/Source/Public/ZMMessage.h +++ b/wire-ios-data-model/Source/Public/ZMMessage.h @@ -39,6 +39,7 @@ @protocol ZMImageMessageData +@property (nonatomic, readonly, nullable) NSString *name; @property (nonatomic, readonly, nullable) NSData *imageData; ///< This will either returns the mediumData or the original image data. Useful only for newly inserted messages. @property (nonatomic, readonly, nullable) NSString *imageDataIdentifier; /// This can be used as a cache key for @c -imageData diff --git a/wire-ios-data-model/Source/Repositories/LegacyFeatureRepository.swift b/wire-ios-data-model/Source/Repositories/LegacyFeatureRepository.swift index 7ac106b9658..c213b3aa5fd 100644 --- a/wire-ios-data-model/Source/Repositories/LegacyFeatureRepository.swift +++ b/wire-ios-data-model/Source/Repositories/LegacyFeatureRepository.swift @@ -51,6 +51,7 @@ public protocol LegacyFeatureRepositoryInterface { func storeConsumableNotifications(_ consumableNotifications: Feature.ConsumableNotifications) func fetchChatBubblesSimple() -> Feature.ChatBubblesSimple func storeChatBubblesSimple(_ chatBubblesSimple: Feature.ChatBubblesSimple) + func fetchAssetAuditLog() -> Feature.AssetAuditLog } /// **Do not use it for new code, use FeatureConfigRepository instead** @@ -536,6 +537,16 @@ public class LegacyFeatureRepository: LegacyFeatureRepositoryInterface { } } + // MARK: - Asset audit log + + public func fetchAssetAuditLog() -> Feature.AssetAuditLog { + guard let feature = Feature.fetch(name: .assetAuditLog, context: context) else { + return .init() + } + + return .init(status: feature.status) + } + // MARK: - Methods func createDefaultConfigsIfNeeded() { diff --git a/wire-ios-data-model/Source/Utilis/Protos/GenericMessage+Assets.swift b/wire-ios-data-model/Source/Utilis/Protos/GenericMessage+Assets.swift index d4a635b46bd..4b494b948ca 100644 --- a/wire-ios-data-model/Source/Utilis/Protos/GenericMessage+Assets.swift +++ b/wire-ios-data-model/Source/Utilis/Protos/GenericMessage+Assets.swift @@ -60,11 +60,17 @@ public extension GenericMessageProtocol.Asset { } } - init(imageSize: CGSize, mimeType: String, size: UInt64) { + init( + name: String, + mimeType: String, + imageSize: CGSize, + size: UInt64 + ) { self = GenericMessageProtocol.Asset.with { $0.original = GenericMessageProtocol.Asset.Original.with { - $0.size = size + $0.name = name $0.mimeType = mimeType + $0.size = size $0.image = GenericMessageProtocol.Asset.ImageMetaData.with { $0.width = Int32(imageSize.width) $0.height = Int32(imageSize.height) @@ -228,12 +234,14 @@ public extension GenericMessageProtocol.Asset.RemoteData { extension GenericMessage { mutating func updateAssetOriginal(withImageProperties imageProperties: ZMIImageProperties) { - let asset = GenericMessageProtocol.Asset( - imageSize: imageProperties.size, - mimeType: imageProperties.mimeType, - size: UInt64(imageProperties.length) - ) - update(asset: asset) + updateAsset { existingAsset in + existingAsset.original.mimeType = imageProperties.mimeType + existingAsset.original.size = UInt64(imageProperties.length) + existingAsset.original.image = GenericMessageProtocol.Asset.ImageMetaData.with { + $0.width = Int32(imageProperties.size.width) + $0.height = Int32(imageProperties.size.height) + } + } } mutating func updateAssetPreview(withUploadedOTRKey otrKey: Data, sha256: Data) { diff --git a/wire-ios-data-model/Source/Utilis/Protos/GenericMessage+Helper.swift b/wire-ios-data-model/Source/Utilis/Protos/GenericMessage+Helper.swift index cbbc7e60364..2094f7df816 100644 --- a/wire-ios-data-model/Source/Utilis/Protos/GenericMessage+Helper.swift +++ b/wire-ios-data-model/Source/Utilis/Protos/GenericMessage+Helper.swift @@ -774,8 +774,9 @@ public extension LinkPreview { $0.summary = articleMetadata.summary ?? "" if let imageData = articleMetadata.imageData.first { $0.image = GenericMessageProtocol.Asset( - imageSize: CGSize(width: 0, height: 0), + name: "picture.jpeg", mimeType: "image/jpeg", + imageSize: CGSize(width: 0, height: 0), size: UInt64(imageData.count) ) } @@ -791,8 +792,9 @@ public extension LinkPreview { $0.title = twitterMetadata.message ?? "" if let imageData = twitterMetadata.imageData.first { $0.image = GenericMessageProtocol.Asset( - imageSize: CGSize(width: 0, height: 0), + name: "picture.jpeg", mimeType: "image/jpeg", + imageSize: CGSize(width: 0, height: 0), size: UInt64(imageData.count) ) } diff --git a/wire-ios-data-model/Support/Sourcery/generated/AutoMockable.generated.swift b/wire-ios-data-model/Support/Sourcery/generated/AutoMockable.generated.swift index 8710700e87d..46b7463e2c6 100644 --- a/wire-ios-data-model/Support/Sourcery/generated/AutoMockable.generated.swift +++ b/wire-ios-data-model/Support/Sourcery/generated/AutoMockable.generated.swift @@ -3787,6 +3787,24 @@ public class MockLegacyFeatureRepositoryInterface: LegacyFeatureRepositoryInterf mock(chatBubblesSimple) } + // MARK: - fetchAssetAuditLog + + public var fetchAssetAuditLog_Invocations: [Void] = [] + public var fetchAssetAuditLog_MockMethod: (() -> Feature.AssetAuditLog)? + public var fetchAssetAuditLog_MockValue: Feature.AssetAuditLog? + + public func fetchAssetAuditLog() -> Feature.AssetAuditLog { + fetchAssetAuditLog_Invocations.append(()) + + if let mock = fetchAssetAuditLog_MockMethod { + return mock() + } else if let mock = fetchAssetAuditLog_MockValue { + return mock + } else { + fatalError("no mock for `fetchAssetAuditLog`") + } + } + } class MockMLSActionsProviderProtocol: MLSActionsProviderProtocol { diff --git a/wire-ios-data-model/Tests/OneOnOne/OneOnOneMigratorTests.swift b/wire-ios-data-model/Tests/OneOnOne/OneOnOneMigratorTests.swift index 3d9ba053a50..d6a63831a8e 100644 --- a/wire-ios-data-model/Tests/OneOnOne/OneOnOneMigratorTests.swift +++ b/wire-ios-data-model/Tests/OneOnOne/OneOnOneMigratorTests.swift @@ -232,7 +232,10 @@ final class OneOnOneMigratorTests: XCTestCase { message = try proteusConversation.appendKnock() message.updateServerTimestamp(with: 1) - message = try proteusConversation.appendImage(from: ZMTBaseTest.verySmallJPEGData()) + message = try proteusConversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: ZMTBaseTest.verySmallJPEGData()), + nonce: UUID() + ) message.updateServerTimestamp(with: 2) XCTAssertEqual(proteusConversation.allMessages.count, 3) @@ -322,7 +325,10 @@ final class OneOnOneMigratorTests: XCTestCase { message = try proteusConversation.appendKnock() message.updateServerTimestamp(with: 1) - message = try proteusConversation.appendImage(from: ZMTBaseTest.verySmallJPEGData()) + message = try proteusConversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: ZMTBaseTest.verySmallJPEGData()), + nonce: UUID() + ) message.updateServerTimestamp(with: 2) XCTAssertEqual(proteusConversation.allMessages.count, 3) @@ -337,7 +343,10 @@ final class OneOnOneMigratorTests: XCTestCase { message = try duplicateProteusConversation.appendKnock() message.updateServerTimestamp(with: 11) - message = try duplicateProteusConversation.appendImage(from: ZMTBaseTest.verySmallJPEGData()) + message = try duplicateProteusConversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: ZMTBaseTest.verySmallJPEGData()), + nonce: UUID() + ) message.updateServerTimestamp(with: 12) XCTAssertEqual(proteusConversation.allMessages.count, 3) @@ -453,7 +462,10 @@ final class OneOnOneMigratorTests: XCTestCase { message = try proteusConversation.appendKnock() message.updateServerTimestamp(with: 1) - message = try proteusConversation.appendImage(from: ZMTBaseTest.verySmallJPEGData()) + message = try proteusConversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: ZMTBaseTest.verySmallJPEGData()), + nonce: UUID() + ) message.updateServerTimestamp(with: 2) XCTAssertEqual(proteusConversation.allMessages.count, 3) @@ -468,7 +480,10 @@ final class OneOnOneMigratorTests: XCTestCase { message = try duplicateProteusConversation.appendKnock() message.updateServerTimestamp(with: 11) - message = try duplicateProteusConversation.appendImage(from: ZMTBaseTest.verySmallJPEGData()) + message = try duplicateProteusConversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: ZMTBaseTest.verySmallJPEGData()), + nonce: UUID() + ) message.updateServerTimestamp(with: 12) XCTAssertEqual(proteusConversation.allMessages.count, 3) diff --git a/wire-ios-data-model/Tests/Source/Model/Conversation/AssetCollectionBatchedTests.swift b/wire-ios-data-model/Tests/Source/Model/Conversation/AssetCollectionBatchedTests.swift index b89083f939c..085f77edd3e 100644 --- a/wire-ios-data-model/Tests/Source/Model/Conversation/AssetCollectionBatchedTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Conversation/AssetCollectionBatchedTests.swift @@ -51,7 +51,10 @@ final class AssetColletionBatchedTests: ModelObjectsTests { var offset: TimeInterval = 0 var messages = [ZMMessage]() (0 ..< count).forEach { _ in - let message = try! conversation.appendImage(from: verySmallJPEGData()) as! ZMMessage + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMMessage offset += 5 message.setValue(Date().addingTimeInterval(offset), forKey: "serverTimestamp") messages.append(message) @@ -300,7 +303,8 @@ final class AssetColletionBatchedTests: ModelObjectsTests { func testThatItExcludesDefinedCategories_PreCategorized() { // given let data = data(forResource: "animated", extension: "gif")! - _ = try! conversation.appendImage(from: data) as! ZMAssetClientMessage + let image = SendableImage(name: "picture.gif", utType: .gif, data: data) + _ = try! conversation.appendImage(image, nonce: UUID()) as! ZMAssetClientMessage uiMOC.saveOrRollback() // when @@ -322,7 +326,8 @@ final class AssetColletionBatchedTests: ModelObjectsTests { func testThatItExcludesDefinedCategories_NotPreCategorized() { // given let data = data(forResource: "animated", extension: "gif")! - _ = try! conversation.appendImage(from: data) as! ZMAssetClientMessage + let image = SendableImage(name: "picture.gif", utType: .gif, data: data) + _ = try! conversation.appendImage(image, nonce: UUID()) as! ZMAssetClientMessage uiMOC.saveOrRollback() // when @@ -370,7 +375,8 @@ final class AssetColletionBatchedTests: ModelObjectsTests { // given insertAssetMessages(count: 1) let data = data(forResource: "animated", extension: "gif")! - _ = try! conversation.appendImage(from: data) as! ZMAssetClientMessage + let image = SendableImage(name: "picture.gif", utType: .gif, data: data) + _ = try! conversation.appendImage(image, nonce: UUID()) as! ZMAssetClientMessage uiMOC.saveOrRollback() // when diff --git a/wire-ios-data-model/Tests/Source/Model/Conversation/AssetColletionTests.swift b/wire-ios-data-model/Tests/Source/Model/Conversation/AssetColletionTests.swift index 0b96b719bd9..049c1207494 100644 --- a/wire-ios-data-model/Tests/Source/Model/Conversation/AssetColletionTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Conversation/AssetColletionTests.swift @@ -90,7 +90,10 @@ final class AssetColletionTests: ModelObjectsTests { var offset: TimeInterval = 0 var messages = [ZMMessage]() (0 ..< count).forEach { _ in - let message = try! conversation.appendImage(from: verySmallJPEGData()) as! ZMMessage + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMMessage offset += 5 message.setValue(Date().addingTimeInterval(offset), forKey: "serverTimestamp") messages.append(message) @@ -299,7 +302,8 @@ final class AssetColletionTests: ModelObjectsTests { func testThatItExcludesDefinedCategories_PreCategorized() { // given let data = data(forResource: "animated", extension: "gif")! - _ = try! conversation.appendImage(from: data) as! ZMAssetClientMessage + let image = SendableImage(name: "picture.gif", utType: .gif, data: data) + _ = try! conversation.appendImage(image, nonce: UUID()) as! ZMAssetClientMessage // when conversation.allMessages.forEach { _ = $0.cachedCategory } @@ -319,7 +323,8 @@ final class AssetColletionTests: ModelObjectsTests { // given insertAssetMessages(count: 1) let data = data(forResource: "animated", extension: "gif")! - _ = try! conversation.appendImage(from: data) as! ZMAssetClientMessage + let image = SendableImage(name: "picture.gif", utType: .gif, data: data) + _ = try! conversation.appendImage(image, nonce: UUID()) as! ZMAssetClientMessage uiMOC.saveOrRollback() // when @@ -340,7 +345,8 @@ final class AssetColletionTests: ModelObjectsTests { insertAssetMessages(count: 1) let data = data(forResource: "animated", extension: "gif")! - _ = try! conversation.appendImage(from: data) as! ZMAssetClientMessage + let image = SendableImage(name: "picture.gif", utType: .gif, data: data) + _ = try! conversation.appendImage(image, nonce: UUID()) as! ZMAssetClientMessage uiMOC.saveOrRollback() // when diff --git a/wire-ios-data-model/Tests/Source/Model/Conversation/ConversationTests.swift b/wire-ios-data-model/Tests/Source/Model/Conversation/ConversationTests.swift index ff191b33e37..404072d5f48 100644 --- a/wire-ios-data-model/Tests/Source/Model/Conversation/ConversationTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Conversation/ConversationTests.swift @@ -275,7 +275,10 @@ extension ConversationTests { let conversation = ZMConversation.insertNewObject(in: self.syncMOC) conversation.remoteIdentifier = UUID.create() - let message = try! conversation.appendImage(from: self.verySmallJPEGData(), nonce: messageID) + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: messageID + ) // store asset data syncMOC.zm_fileAssetCache.storeOriginalImage(data: imageData, for: message) diff --git a/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests+Messages.swift b/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests+Messages.swift index 408381bf93b..882aec37ba2 100644 --- a/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests+Messages.swift +++ b/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests+Messages.swift @@ -71,7 +71,10 @@ final class ZMConversationMessagesTests: ZMConversationTestsBase { conversation.lastModifiedDate = msg1.serverTimestamp // when - guard let msg2 = try? conversation.appendImage(from: verySmallJPEGData()) as? ZMAssetClientMessage else { + guard let msg2 = try? conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as? ZMAssetClientMessage else { XCTFail() return } @@ -121,99 +124,18 @@ final class ZMConversationMessagesTests: ZMConversationTestsBase { XCTAssertEqual(message.textMessageData?.messageText, originalText) } - func testThatWeCanInsertAnImageMessageFromAFileURL() { - // given - let selfUser = ZMUser.selfUser(in: uiMOC) - let imageFileURL = fileURL(forResource: "1900x1500", extension: "jpg")! - let conversation = ZMConversation.insertNewObject(in: uiMOC) - conversation.remoteIdentifier = UUID() - - // when - let message = try! conversation.appendImage(at: imageFileURL) as! ZMAssetClientMessage - - // then - XCTAssertNotNil(message) - XCTAssertNotNil(message.nonce) - XCTAssertTrue(message.imageMessageData!.originalSize.equalTo(CGSize(width: 1900, height: 1500))) - XCTAssertEqual(message.conversation, conversation) - XCTAssertEqual(conversation.lastMessage as! ZMMessage, message) - XCTAssertNotNil(message.nonce) - - let expectedData = try! (try! Data(contentsOf: imageFileURL)).wr_removingImageMetadata() - XCTAssertNotNil(expectedData) - XCTAssertEqual(message.imageMessageData?.imageData, expectedData) - XCTAssertEqual(selfUser, message.sender) - } - - func testThatNoMessageIsInsertedWhenTheImageFileURLIsPointingToSomethingThatIsNotAnImage() { - // given - let imageFileURL = fileURL(forResource: "1900x1500", extension: "jpg")! - let conversation = ZMConversation.insertNewObject(in: uiMOC) - conversation.remoteIdentifier = UUID() - - // when - let message = try! conversation.appendImage(at: imageFileURL) as! ZMAssetClientMessage - - // then - XCTAssertNotNil(message) - XCTAssertNotNil(message.nonce) - XCTAssertTrue(message.imageMessageData!.originalSize.equalTo(CGSize(width: 1900, height: 1500))) - XCTAssertEqual(message.conversation, conversation) - XCTAssertEqual(conversation.lastMessage as! ZMMessage, message) - XCTAssertNotNil(message.nonce) - - let expectedData = try! (try! Data(contentsOf: imageFileURL)).wr_removingImageMetadata() - XCTAssertNotNil(expectedData) - XCTAssertEqual(message.imageMessageData?.imageData, expectedData) - } - - func testThatNoMessageIsInsertedWhenTheImageFileURLIsNotAFileURL() { - // given - let imageURL = URL(string: "http://www.placehold.it/350x150")! - let conversation = ZMConversation.insertNewObject(in: uiMOC) - conversation.remoteIdentifier = UUID() - let start = uiMOC.insertedObjects - - // when - var message: Any? - performIgnoringZMLogError { - message = try? conversation.appendImage(at: imageURL) - } - - // then - XCTAssertNil(message) - XCTAssertEqual(start, uiMOC.insertedObjects) - } - - func testThatNoMessageIsInsertedWhenTheImageFileURLIsNotPointingToAFile() { - // given - let textFileURL = fileURL(forResource: "Lorem Ipsum", extension: "txt")! - let conversation = ZMConversation.insertNewObject(in: uiMOC) - conversation.remoteIdentifier = UUID() - let start = uiMOC.insertedObjects - - // when - var message: Any? - performIgnoringZMLogError { - message = try? conversation.appendImage(at: textFileURL) - } - - // then - XCTAssertNil(message) - XCTAssertEqual(start, uiMOC.insertedObjects) - } - // swiftlint:disable:next todo_requires_jira_link // TODO: check why fail on Xcode 11 func disable_testThatWeCanInsertAnImageMessageFromImageData() { // given let imageData = try! data(forResource: "1900x1500", extension: "jpg").wr_removingImageMetadata() + let image = SendableImage(name: "picture.jpg", utType: .jpeg, data: imageData) XCTAssertNotNil(imageData) let conversation = ZMConversation.insertNewObject(in: uiMOC) conversation.remoteIdentifier = UUID() // when - guard let message = try? conversation.appendImage(from: imageData) as? ZMAssetClientMessage else { + guard let message = try? conversation.appendImage(image, nonce: UUID()) as? ZMAssetClientMessage else { XCTFail() return } @@ -234,11 +156,12 @@ final class ZMConversationMessagesTests: ZMConversationTestsBase { // given let originalImageData = try! data(forResource: "1900x1500", extension: "jpg").wr_removingImageMetadata() var imageData = originalImageData + let image = SendableImage(name: "picture.jpg", utType: .jpeg, data: imageData) let conversation = ZMConversation.insertNewObject(in: uiMOC) conversation.remoteIdentifier = UUID() // when - guard let message = try? conversation.appendImage(from: imageData) as? ZMAssetClientMessage else { + guard let message = try? conversation.appendImage(image, nonce: UUID()) as? ZMAssetClientMessage else { XCTFail() return } @@ -251,6 +174,7 @@ final class ZMConversationMessagesTests: ZMConversationTestsBase { func testThatNoMessageIsInsertedWhenTheImageDataIsNotAnImage() { // given let textData = data(forResource: "Lorem Ipsum", extension: "txt")! + let image = SendableImage(name: "text.txt", utType: .text, data: textData) let conversation = ZMConversation.insertNewObject(in: uiMOC) conversation.remoteIdentifier = UUID() let start = uiMOC.insertedObjects @@ -258,7 +182,7 @@ final class ZMConversationMessagesTests: ZMConversationTestsBase { // when var message: ZMConversationMessage? performIgnoringZMLogError { - message = try? conversation.appendImage(from: textData) + message = try? conversation.appendImage(image, nonce: UUID()) } // then @@ -342,7 +266,10 @@ final class ZMConversationMessagesTests: ZMConversationTestsBase { // given let conversation = ZMConversation.insertNewObject(in: uiMOC) conversation.remoteIdentifier = UUID() - let imageMessage = try? conversation.appendImage(from: verySmallJPEGData()) + let imageMessage = try? conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) // when let textMessage = try? conversation.appendText(content: "Hello World", replyingTo: imageMessage) diff --git a/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests+SecurityLevel.swift b/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests+SecurityLevel.swift index c1debb8450a..19e41b9695e 100644 --- a/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests+SecurityLevel.swift +++ b/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests+SecurityLevel.swift @@ -605,7 +605,8 @@ final class ZMConversationTests_SecurityLevel: ZMConversationTestsBase { conversation.addParticipantsAndUpdateConversationState(users: Set([user, selfUser]), role: nil) conversation.securityLevel = .secure - let message1 = try! conversation.appendImage(from: self.verySmallJPEGData()) as! ZMOTRMessage + let image = SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()) + let message1 = try! conversation.appendImage(image, nonce: UUID()) as! ZMOTRMessage Thread.sleep(forTimeInterval: 0.1) // cause system time to advance let message2 = try! conversation.appendText(content: "foo 2") as! ZMOTRMessage @@ -638,7 +639,10 @@ final class ZMConversationTests_SecurityLevel: ZMConversationTestsBase { let user = self.insertUser(conversation: conversation, userIsTrusted: true, moc: context) conversation.securityLevel = .secure - let message1 = try! conversation.appendImage(from: self.verySmallJPEGData()) as! ZMOTRMessage + let message1 = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMOTRMessage Thread.sleep(forTimeInterval: 0.1) // cause system time to advance let message2 = try! conversation.appendText(content: "foo 2") as! ZMOTRMessage Thread.sleep(forTimeInterval: 0.1) // cause system time to advance @@ -646,7 +650,10 @@ final class ZMConversationTests_SecurityLevel: ZMConversationTestsBase { Thread.sleep(forTimeInterval: 0.1) // cause system time to advance let message4 = try! conversation.appendText(content: "foo 4") as! ZMOTRMessage Thread.sleep(forTimeInterval: 0.1) // cause system time to advance - let message5 = try! conversation.appendImage(from: self.verySmallJPEGData()) as! ZMOTRMessage + let message5 = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMOTRMessage let client = UserClient.insertNewObject(in: context) client.remoteIdentifier = "aabbccdd" @@ -690,7 +697,10 @@ final class ZMConversationTests_SecurityLevel: ZMConversationTestsBase { Thread.sleep(forTimeInterval: 0.1) // cause system time to advance message2 = try! conversation.appendText(content: "foo 3") as! ZMOTRMessage Thread.sleep(forTimeInterval: 0.1) // cause system time to advance - message3 = try! conversation.appendImage(from: self.verySmallJPEGData()) as! ZMOTRMessage + message3 = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMOTRMessage let client = UserClient.insertNewObject(in: context) client.remoteIdentifier = "aabbccdd" @@ -803,7 +813,10 @@ final class ZMConversationTests_SecurityLevel: ZMConversationTestsBase { Thread.sleep(forTimeInterval: 0.1) // cause system time to advance message2 = try! conversation.appendText(content: "foo 3") as! ZMOTRMessage Thread.sleep(forTimeInterval: 0.1) // cause system time to advance - message3 = try! conversation.appendImage(from: self.verySmallJPEGData()) as! ZMOTRMessage + message3 = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMOTRMessage let client = UserClient.insertNewObject(in: context) client.remoteIdentifier = "aabbccdd" diff --git a/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests.m b/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests.m index 14d6a5df429..9f6910e25e6 100644 --- a/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests.m +++ b/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests.m @@ -2226,13 +2226,6 @@ - (void)testThatAppendingATextMessageInAnArchivedConversationUnarchivesIt }]; } -- (void)testThatAppendingAnImageMessageInAnArchivedConversationUnarchivesIt -{ - [self assertThatAppendingAMessageUnarchivesAConversation:^(ZMConversation *conversation) { - [conversation appendMessageWithImageData:self.verySmallJPEGData]; - }]; -} - - (void)testThatAppendingALocationMessageInAnArchivedConversationUnarchivesIt { [self assertThatAppendingAMessageUnarchivesAConversation:^(ZMConversation *conversation) { diff --git a/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests.swift b/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests.swift index f276531f7ba..d5a4b81a6da 100644 --- a/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Conversation/ZMConversationTests.swift @@ -238,7 +238,10 @@ extension ZMConversationTests { let conversation = ZMConversation.insertNewObject(in: uiMOC) // when - conversation._appendImage(from: verySmallJPEGData()) + try conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) // then let request = NSFetchRequest(entityName: ZMMessage.entityName()) @@ -300,6 +303,40 @@ extension ZMConversationTests { self.syncMOC.isFederationEnabled = false } } + + // MARK: - Appending image messages + + func testThatAppendingAnImageMessageInAnArchivedConversationUnarchivesIt() throws { + try assertThatAppendingAMessageUnarchivesAConversation { conversation in + try conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) + } + } + + private func assertThatAppendingAMessageUnarchivesAConversation( + insertBlock: (ZMConversation) throws + -> Void + ) throws { + // given + let conversation = ZMConversation.insertNewObject(in: uiMOC) + conversation.conversationType = .group + let selfUser = ZMUser.selfUser(in: uiMOC) + selfUser.remoteIdentifier = UUID() + let otherUser = ZMUser.insertNewObject(in: uiMOC) + conversation.addParticipantAndUpdateConversationState(user: otherUser, role: nil) + conversation.isArchived = true + XCTAssertTrue(conversation.isArchived) + + // when + try insertBlock(conversation) + XCTAssert(waitForAllGroupsToBeEmpty(withTimeout: 0.5)) + + // then + XCTAssertFalse(conversation.isArchived) + } + } // MARK: - Helper Extension diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/GenericMessageTests+LegalHoldStatus.swift b/wire-ios-data-model/Tests/Source/Model/Messages/GenericMessageTests+LegalHoldStatus.swift index 299584876d4..049fdf7aab3 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/GenericMessageTests+LegalHoldStatus.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/GenericMessageTests+LegalHoldStatus.swift @@ -90,8 +90,9 @@ class GenericMessageTests_LegalHoldStatus: BaseZMClientMessageTests { // given var genericMessage = GenericMessage( content: GenericMessageProtocol.Asset( - imageSize: CGSize(width: 42, height: 12), + name: "picture.jpg", mimeType: "image/jpeg", + imageSize: CGSize(width: 42, height: 12), size: 123 ), nonce: UUID.create() @@ -109,8 +110,9 @@ class GenericMessageTests_LegalHoldStatus: BaseZMClientMessageTests { // given let asset = GenericMessageProtocol.Asset( - imageSize: CGSize(width: 42, height: 12), + name: "picture.jpg", mimeType: "image/jpeg", + imageSize: CGSize(width: 42, height: 12), size: 123 ) var genericMessage = GenericMessage(content: asset, nonce: UUID.create(), expiresAfter: .tenSeconds) diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/GenericMessageTests.swift b/wire-ios-data-model/Tests/Source/Model/Messages/GenericMessageTests.swift index 5f3ac170e1a..5bdf559abad 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/GenericMessageTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/GenericMessageTests.swift @@ -68,8 +68,9 @@ final class GenericMessageTests: XCTestCase { func testThatItConsidersAssetMessageTypeAsKnownMessage() { let assetMessageType = GenericMessage(content: GenericMessageProtocol.Asset( - imageSize: .zero, + name: "picture.jpg", mimeType: "image/jpeg", + imageSize: .zero, size: 0 )) XCTAssertNotNil(assetMessageType.content) diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests+AssetMessage.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests+AssetMessage.swift index 1bad3bebbd0..71463d5afb2 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests+AssetMessage.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests+AssetMessage.swift @@ -38,7 +38,10 @@ class ZMAssetClientMessageTests_AssetMessage: BaseZMClientMessageTests { func testThatReturnsAssetsForImageMessage() { // given - let message = try! conversation.appendImage(from: verySmallJPEGData()) as! ZMAssetClientMessage + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMAssetClientMessage // then XCTAssertEqual(message.assets.count, 1) diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests+Ephemeral.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests+Ephemeral.swift index ec154086fd9..d6c555316c8 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests+Ephemeral.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests+Ephemeral.swift @@ -276,7 +276,12 @@ extension ZMAssetClientMessageTests_Ephemeral { let imageData = verySmallJPEGData() let assetMessage = GenericMessage( - content: GenericMessageProtocol.Asset(imageSize: .zero, mimeType: "", size: UInt64(imageData.count)), + content: GenericMessageProtocol.Asset( + name: "picture.jpg", + mimeType: "", + imageSize: .zero, + size: UInt64(imageData.count) + ), nonce: nonce, expiresAfter: .tenSeconds ) diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests.swift index 79a02a7b630..6e7d794dee7 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMAssetClientMessageTests.swift @@ -58,9 +58,10 @@ class BaseZMAssetClientMessageTests: BaseZMClientMessageTests { func appendV2ImageMessage(to conversation: ZMConversation) throws { let imageData = verySmallJPEGData() + let image = SendableImage(name: "picture.jpg", utType: .jpeg, data: imageData) let messageNonce = UUID.create() - message = try conversation.appendImage(from: imageData, nonce: messageNonce) as? ZMAssetClientMessage + message = try conversation.appendImage(image, nonce: messageNonce) as? ZMAssetClientMessage let imageSize = ZMImagePreprocessor.sizeOfPrerotatedImage(with: imageData) let properties = ZMIImageProperties(size: imageSize, length: UInt(imageData.count), mimeType: "image/jpeg") @@ -90,8 +91,9 @@ class BaseZMAssetClientMessageTests: BaseZMClientMessageTests { func appendImageMessage(to conversation: ZMConversation, imageData: Data? = nil) -> ZMAssetClientMessage { let data = imageData ?? verySmallJPEGData() + let image = SendableImage(name: "picture.jpg", utType: .jpeg, data: data) let nonce = UUID.create() - let message = try! conversation.appendImage(from: data, nonce: nonce) as! ZMAssetClientMessage + let message = try! conversation.appendImage(image, nonce: nonce) as! ZMAssetClientMessage let uploaded = GenericMessageProtocol.Asset( withUploadedOTRKey: .randomEncryptionKey(), @@ -531,8 +533,9 @@ extension ZMAssetClientMessageTests { sut.visibleInConversation = syncConversation let original = GenericMessage( content: GenericMessageProtocol.Asset( + name: "picture.jpg", + mimeType: "image/jpeg", imageSize: CGSize(width: 10, height: 10), - mimeType: "text/plain", size: 256 ), nonce: sut.nonce! diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+Deletion.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+Deletion.swift index 1253d7fc4bb..6e6ec13d252 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+Deletion.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+Deletion.swift @@ -65,7 +65,10 @@ class ZMClientMessageTests_Deletion: BaseZMClientMessageTests { func testThatItDeletesAnAssetMessage_Image() { // given let conversation = ZMConversation.insertNewObject(in: uiMOC) - let sut = try! conversation.appendImage(from: mediumJPEGData(), nonce: .create()) as! ZMAssetClientMessage + let sut = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: mediumJPEGData()), + nonce: UUID() + ) as! ZMAssetClientMessage let cache = uiMOC.zm_fileAssetCache! cache.storePreviewImage(data: verySmallJPEGData(), for: sut) diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+Ephemeral.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+Ephemeral.swift index 00a2f7b75e4..ba3483a5a4c 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+Ephemeral.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+Ephemeral.swift @@ -125,7 +125,10 @@ extension ZMClientMessageTests_Ephemeral { func testItCreatesAnEphemeralMessageForImages() { checkItCreatesAnEphemeralMessage { conv -> ZMMessage in - let message = try! conv.appendImage(from: verySmallJPEGData()) as! ZMAssetClientMessage + let message = try! conv.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: mediumJPEGData()), + nonce: UUID() + ) as! ZMAssetClientMessage var hasImage = false if case .image? = message.underlyingMessage?.ephemeral.content { hasImage = true @@ -167,7 +170,12 @@ extension ZMClientMessageTests_Ephemeral { let imageData = self.verySmallJPEGData() let assetMessage = GenericMessage( - content: GenericMessageProtocol.Asset(imageSize: .zero, mimeType: "", size: UInt64(imageData.count)), + content: GenericMessageProtocol.Asset( + name: "picture.jpg", + mimeType: "image/jpeg", + imageSize: .zero, + size: UInt64(imageData.count) + ), nonce: nonce, expiresAfter: .tenSeconds ) diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+MLSEncryptedPayloadGenerator.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+MLSEncryptedPayloadGenerator.swift index 44c8af6a877..d81908866c5 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+MLSEncryptedPayloadGenerator.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMClientMessageTests+MLSEncryptedPayloadGenerator.swift @@ -90,7 +90,10 @@ final class ZMClientMessageTests_MLSEncryptedPayloadGenerator: BaseZMClientMessa func test_EncryptForTransport_AssetClientMessage() async throws { // Given let message = try await syncMOC.perform { - try self.syncConversation.appendImage(from: self.verySmallJPEGData()) as? ZMAssetClientMessage + try self.syncConversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: self.verySmallJPEGData()), + nonce: UUID() + ) as? ZMAssetClientMessage } guard let message else { diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageCategorizationTests.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageCategorizationTests.swift index ed4b2610bc8..fe4c3504e18 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageCategorizationTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageCategorizationTests.swift @@ -113,7 +113,10 @@ class ZMMessageCategorizationTests: ZMBaseManagedObjectTest { func testThatItCategorizesAnImageMessage() { // GIVEN - let message = try! conversation.appendImage(from: verySmallJPEGData()) + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) // THEN XCTAssertEqual(message.categorization, MessageCategory.image) @@ -122,7 +125,10 @@ class ZMMessageCategorizationTests: ZMBaseManagedObjectTest { func testThatItCategorizesAnImageMessage_WithoutData() { // GIVEN - let message = try! conversation.appendImage(from: verySmallJPEGData()) + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) uiMOC.zm_fileAssetCache.deleteOriginalImageData(for: message) // THEN @@ -133,7 +139,10 @@ class ZMMessageCategorizationTests: ZMBaseManagedObjectTest { // GIVEN let data = data(forResource: "animated", extension: "gif")! - let message = try! conversation.appendImage(from: data) as! ZMAssetClientMessage + let message = try! conversation.appendImage( + SendableImage(name: "picture.gif", utType: .gif, data: data), + nonce: UUID() + ) as! ZMAssetClientMessage // THEN XCTAssertEqual(message.categorization, [MessageCategory.image, MessageCategory.GIF]) @@ -229,7 +238,10 @@ class ZMMessageCategorizationTests: ZMBaseManagedObjectTest { func testThatItCategorizesV3Images() { // GIVEN - let message = try! conversation.appendImage(from: verySmallJPEGData()) + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: mediumJPEGData()), + nonce: UUID() + ) // THEN XCTAssertEqual(message.categorization, MessageCategory.image) @@ -628,7 +640,10 @@ extension ZMMessageCategorizationTests { func testThatItCategorizesAnImageMessageOnInsert() { // when - let message = try! conversation.appendImage(from: verySmallJPEGData()) as! ZMMessage + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMMessage // then XCTAssertEqual( diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests+GenericMessage.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests+GenericMessage.swift index 83aec26abd2..89ef76e8556 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests+GenericMessage.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests+GenericMessage.swift @@ -151,7 +151,10 @@ extension ZMMessageTests_GenericMessage { conversation.remoteIdentifier = UUID.create() // when - let message = try! conversation.appendImage(from: verySmallJPEGData()) as! ZMOTRMessage + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMOTRMessage let dataSet = message.dataSet XCTAssertNotNil(message.managedObjectContext) diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests.m b/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests.m index 067d1efe8df..ba206be3dec 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests.m +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests.m @@ -1100,24 +1100,6 @@ - (void)testThatASystemMessageHasSystemMessageData XCTAssertNil(message.knockMessageData); } -- (void)testThatItReturnsTheOriginalImageDataWhenTheMediumDataIsNotAvailable; -{ - // given - ZMConversation *conversation = [ZMConversation insertNewObjectInManagedObjectContext:self.uiMOC]; - conversation.remoteIdentifier = [NSUUID createUUID]; - NSData *jpegData = [self.verySmallJPEGData wr_imageDataWithoutMetadataAndReturnError:nil]; - id temporaryMessage = [conversation appendMessageWithImageData:jpegData]; - - // when - NSData *imageData = [temporaryMessage imageMessageData].imageData; - - // then - XCTAssertNotNil(imageData); - // swiftlint:disable:next todo_requires_jira_link - // TODO: [Bill] check why 1 btye is removed from jpegData? - XCTAssertEqual(imageData.length, jpegData.length + 1); -} - - (void)testThatFlagIsSetWhenSenderIsTheOnlyUser { // given diff --git a/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests.swift b/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests.swift index 5c70231b0f5..d16c0b9440a 100644 --- a/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Messages/ZMMessageTests.swift @@ -88,4 +88,24 @@ extension ZMMessageTests { // then XCTAssertFalse(message!.userIsTheSender) } + + func testThatItReturnsTheOriginalImageDataWhenTheMediumDataIsNotAvailable() throws { + // given + let conversation = ZMConversation.insertNewObject(in: uiMOC) + conversation.remoteIdentifier = UUID() + let jpegData = try verySmallJPEGData().wr_removingImageMetadata() + let temporaryMessage = try conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: jpegData), + nonce: UUID() + ) + + // when + let imageData = try XCTUnwrap(temporaryMessage.imageMessageData?.imageData) + + // then + // swiftlint:disable:next todo_requires_jira_link + // TODO: [Bill] check why 1 btye is removed from jpegData? + XCTAssertEqual(imageData.count, jpegData.count + 1) + } + } diff --git a/wire-ios-data-model/Tests/Source/Model/Observer/MessageObserverTests.swift b/wire-ios-data-model/Tests/Source/Model/Observer/MessageObserverTests.swift index be32b95d5a6..8d2dee55b1c 100644 --- a/wire-ios-data-model/Tests/Source/Model/Observer/MessageObserverTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Observer/MessageObserverTests.swift @@ -339,7 +339,8 @@ class MessageObserverTests: NotificationDispatcherTestBase { func testThatItNotifiesWhenUserReadsTheMessage_Asset() { let conversation = ZMConversation.insertNewObject(in: uiMOC) - let message = try! conversation.appendImage(from: verySmallJPEGData()) as! ZMAssetClientMessage + let image = SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()) + let message = try! conversation.appendImage(image, nonce: UUID()) as! ZMAssetClientMessage uiMOC.saveOrRollback() // when diff --git a/wire-ios-data-model/Tests/Source/Model/TextSearchQueryTests.swift b/wire-ios-data-model/Tests/Source/Model/TextSearchQueryTests.swift index b07fa074bcc..207d2689014 100644 --- a/wire-ios-data-model/Tests/Source/Model/TextSearchQueryTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/TextSearchQueryTests.swift @@ -592,9 +592,15 @@ class TextSearchQueryTests: BaseZMClientMessageTests { name: "Berlin, Germany", zoomLevel: 8 )) - try conversation.appendImage(from: mediumJPEGData()) + try conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: mediumJPEGData()), + nonce: UUID() + ) try conversation.appendKnock() - try conversation.appendImage(from: verySmallJPEGData()) + try conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) fillConversationWithMessages(conversation: conversation, messageCount: 10, normalized: true) XCTAssert(waitForAllGroupsToBeEmpty(withTimeout: 0.5)) verifyAllMessagesAreIndexed(in: conversation) diff --git a/wire-ios-data-model/Tests/Source/Model/Utils/ProtobufUtilitiesTests.swift b/wire-ios-data-model/Tests/Source/Model/Utils/ProtobufUtilitiesTests.swift index 40912bba3db..8da9bdc8fd2 100644 --- a/wire-ios-data-model/Tests/Source/Model/Utils/ProtobufUtilitiesTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Utils/ProtobufUtilitiesTests.swift @@ -267,8 +267,9 @@ extension ProtobufUtilitiesTests { // given let (assetId, token, domain) = ("id", "token", "domain") let asset = GenericMessageProtocol.Asset( - imageSize: CGSize(width: 42, height: 12), + name: "picture.jpg", mimeType: "image/jpeg", + imageSize: CGSize(width: 42, height: 12), size: 123 ) var sut = GenericMessage(content: asset, nonce: UUID.create()) @@ -288,8 +289,9 @@ extension ProtobufUtilitiesTests { // given let (assetId, token, domain) = ("id", "token", "domain") let asset = GenericMessageProtocol.Asset( - imageSize: CGSize(width: 42, height: 12), + name: "picture.jpg", mimeType: "image/jpeg", + imageSize: CGSize(width: 42, height: 12), size: 123 ) var sut = GenericMessage(content: asset, nonce: UUID.create(), expiresAfter: .tenSeconds) diff --git a/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactory.swift b/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactory.swift index 4a567cd0441..fca423f83c7 100644 --- a/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactory.swift +++ b/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactory.swift @@ -38,11 +38,21 @@ public final class AssetRequestFactory: NSObject { case eternalInfrequentAccess = "eternal-infrequent_access" } - struct AssetAuditLogMetaData { - - let conversationID: QualifiedID - let fileName: String - let mimeType: String + public struct AssetAuditLogMetaData { + + public let conversationID: QualifiedID + public let fileName: String + public let mimeType: String + + public init( + conversationID: QualifiedID, + fileName: String, + mimeType: String + ) { + self.conversationID = conversationID + self.fileName = fileName + self.mimeType = mimeType + } } @@ -69,6 +79,7 @@ public final class AssetRequestFactory: NSObject { withData data: Data, shareable: Bool = true, retention: Retention, + assetAuditLogMetaData: AssetAuditLogMetaData?, apiVersion: APIVersion ) -> ZMTransportRequest? { guard let uploadURL = uploadURL( @@ -76,6 +87,7 @@ public final class AssetRequestFactory: NSObject { in: message.managedObjectContext!, shareable: shareable, retention: retention, + assetAuditLogMetaData: assetAuditLogMetaData, data: data ) else { return nil @@ -109,13 +121,14 @@ public final class AssetRequestFactory: NSObject { withData data: Data, shareable: Bool = true, retention: Retention, + assetAuditLogMetaData: AssetAuditLogMetaData?, apiVersion: APIVersion ) -> ZMTransportRequest? { guard let multipartData = try? dataForMultipartAssetUploadRequest( data, shareable: shareable, retention: retention, - assetAuditLogMetaData: nil // TODO: [WPB-20714] pass in metadata + assetAuditLogMetaData: assetAuditLogMetaData ) else { return nil } let path = switch apiVersion { @@ -170,13 +183,14 @@ public final class AssetRequestFactory: NSObject { in moc: NSManagedObjectContext, shareable: Bool, retention: Retention, + assetAuditLogMetaData: AssetAuditLogMetaData?, data: Data ) -> URL? { guard let multipartData = try? dataForMultipartAssetUploadRequest( data, shareable: shareable, retention: retention, - assetAuditLogMetaData: nil // TODO: [WPB-20714] pass in metadata + assetAuditLogMetaData: assetAuditLogMetaData, ) else { return nil } diff --git a/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactoryTests.swift b/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactoryTests.swift index 81035773608..83e5979e662 100644 --- a/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactoryTests.swift +++ b/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactoryTests.swift @@ -96,6 +96,7 @@ class AssetRequestFactoryTests: MessagingTestBase { let request = try XCTUnwrap(sut.upstreamRequestForAsset( withData: data, retention: .eternal, + assetAuditLogMetaData: nil, apiVersion: apiVersion )) @@ -113,6 +114,7 @@ class AssetRequestFactoryTests: MessagingTestBase { let request = try XCTUnwrap(sut.upstreamRequestForAsset( withData: data, retention: .eternal, + assetAuditLogMetaData: nil, apiVersion: apiVersion )) @@ -130,6 +132,7 @@ class AssetRequestFactoryTests: MessagingTestBase { let request = try XCTUnwrap(sut.upstreamRequestForAsset( withData: data, retention: .eternal, + assetAuditLogMetaData: nil, apiVersion: apiVersion )) @@ -154,6 +157,7 @@ class AssetRequestFactoryTests: MessagingTestBase { message: message, withData: data, retention: .eternal, + assetAuditLogMetaData: nil, apiVersion: apiVersion )) @@ -179,6 +183,7 @@ class AssetRequestFactoryTests: MessagingTestBase { message: message, withData: data, retention: .eternal, + assetAuditLogMetaData: nil, apiVersion: apiVersion )) @@ -204,6 +209,7 @@ class AssetRequestFactoryTests: MessagingTestBase { message: message, withData: data, retention: .eternal, + assetAuditLogMetaData: nil, apiVersion: apiVersion )) @@ -212,4 +218,43 @@ class AssetRequestFactoryTests: MessagingTestBase { } } + func test_AssetAuditLogMetaData() throws { + // Given + let sut = AssetRequestFactory() + let data = Data([1, 2, 3]) + let id = UUID() + let metaData = AssetRequestFactory.AssetAuditLogMetaData( + conversationID: QualifiedID(uuid: id, domain: "wire.com"), + fileName: "picture.jpg", + mimeType: "image/jpeg" + ) + + // When + let multipartData = try sut.dataForMultipartAssetUploadRequest( + data, + shareable: false, + retention: .eternal, + assetAuditLogMetaData: metaData + ) + + // Then + var string = String(decoding: multipartData, as: UTF8.self) + + guard let jsonSubstring = string.firstMatch(of: /{.*}/)?.0 else { + XCTFail("No JSON object found") + return + } + + let jsonString = String(jsonSubstring) + let jsonData = Data(jsonString.utf8) + let json = try JSONSerialization.jsonObject(with: jsonData) as! [String: Any] + + XCTAssertEqual((json["convId"] as? [String: String])?["id"], id.transportString()) + XCTAssertEqual((json["convId"] as? [String: String])?["domain"], "wire.com") + XCTAssertEqual(json["filetype"] as? String, "image/jpeg") + XCTAssertEqual(json["filename"] as? String, "picture.jpg") + XCTAssertEqual(json["retention"] as? String, "eternal") + XCTAssertEqual(json["public"] as? Bool, false) + } + } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetClientMessageRequestStrategyTests.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetClientMessageRequestStrategyTests.swift index ea2d5eab2a8..222c335177b 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetClientMessageRequestStrategyTests.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetClientMessageRequestStrategyTests.swift @@ -76,7 +76,10 @@ final class AssetClientMessageRequestStrategyTests: MessagingTestBase { let targetConversation = conversation ?? groupConversation! let message: ZMAssetClientMessage! if isImage { - message = try! targetConversation.appendImage(from: imageData) as? ZMAssetClientMessage + message = try! targetConversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: imageData), + nonce: UUID() + ) as? ZMAssetClientMessage } else { let url = Bundle(for: AssetClientMessageRequestStrategyTests.self).url( forResource: "Lorem Ipsum", diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategy.swift index 4f0abf48d73..0e8586c28a9 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireDataModel import WireLogging /// AssetV3UploadRequestStrategy is responsible for uploading all the assets associated with a asset message @@ -28,13 +29,29 @@ public final class AssetV3UploadRequestStrategy: AbstractRequestStrategy, ZMCont var upstreamSync: ZMUpstreamModifiedObjectSync! var preprocessor: AssetsPreprocessor + private let featureRepository: LegacyFeatureRepository + public var shouldUseBackgroundSession = true + let localDomain: String? + private let isCloudDomain: Bool + + private var shouldUploadExtraMetaData: Bool { + guard !isCloudDomain else { return false } + return managedObjectContext.performAndWait { + featureRepository.fetchAssetAuditLog().status == .enabled + } + } - public override init( + public init( withManagedObjectContext managedObjectContext: NSManagedObjectContext, - applicationStatus: ApplicationStatus + applicationStatus: ApplicationStatus, + localDomain: String?, + isCloudDomain: Bool ) { self.preprocessor = AssetsPreprocessor(managedObjectContext: managedObjectContext) + self.localDomain = localDomain + self.isCloudDomain = isCloudDomain + self.featureRepository = LegacyFeatureRepository(context: managedObjectContext) super.init(withManagedObjectContext: managedObjectContext, applicationStatus: applicationStatus) configuration = .allowsRequestsWhileOnline @@ -142,19 +159,57 @@ extension AssetV3UploadRequestStrategy: ZMUpstreamTranscoder { for message: ZMAssetClientMessage, apiVersion: APIVersion ) -> ZMUpstreamRequest? { + let logAttributes: LogAttributes = [ + .public: true, + .nonce: message.nonce?.safeForLoggingDescription ?? "" + ] + guard let data = asset.encrypted else { - WireLogger.assets.warn("Encrypted data not available") + WireLogger.assets.warn( + "Encrypted data not available", + attributes: logAttributes + ) return nil } - guard let retention = message.conversation.map(AssetRequestFactory.Retention.init) - else { - WireLogger.assets.warn("Trying to send message that doesn't have a conversation") + + guard let conversation = message.conversation else { + WireLogger.assets.warn( + "Trying to send message that doesn't have a conversation", + attributes: logAttributes + ) return nil } + let retention = AssetRequestFactory.Retention(conversation: conversation) + + var extraMetaData: AssetRequestFactory.AssetAuditLogMetaData? + if shouldUploadExtraMetaData { + guard + let asset = message.underlyingMessage?.assetData?.original, + let domain = conversation.domain ?? localDomain + else { + WireLogger.assets.warn( + "should include extra metadata but not able to", + attributes: logAttributes + ) + return nil + } + + let conversationID = QualifiedID( + uuid: conversation.remoteIdentifier, + domain: domain + ) + + extraMetaData = .init( + conversationID: conversationID, + fileName: asset.name, + mimeType: asset.mimeType + ) + } + WireLogger.assets.debug( "sending request for asset", - attributes: [.nonce: message.nonce?.safeForLoggingDescription ?? ""] + attributes: logAttributes ) let request: ZMTransportRequest? = if shouldUseBackgroundSession { requestFactory.backgroundUpstreamRequestForAsset( @@ -162,6 +217,7 @@ extension AssetV3UploadRequestStrategy: ZMUpstreamTranscoder { withData: data, shareable: false, retention: retention, + assetAuditLogMetaData: extraMetaData, apiVersion: apiVersion ) } else { @@ -169,6 +225,7 @@ extension AssetV3UploadRequestStrategy: ZMUpstreamTranscoder { withData: data, shareable: false, retention: retention, + assetAuditLogMetaData: extraMetaData, apiVersion: apiVersion ) } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategyTests.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategyTests.swift index 1acd9c0c28f..d8d598e1053 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategyTests.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategyTests.swift @@ -29,7 +29,12 @@ class AssetV3UploadRequestStrategyTests: MessagingTestBase { mockApplicationStatus = MockApplicationStatus() mockApplicationStatus.mockSynchronizationState = .online - sut = AssetV3UploadRequestStrategy(withManagedObjectContext: syncMOC, applicationStatus: mockApplicationStatus) + sut = AssetV3UploadRequestStrategy( + withManagedObjectContext: syncMOC, + applicationStatus: mockApplicationStatus, + localDomain: "wire.com", + isCloudDomain: false + ) } override func tearDown() { @@ -86,7 +91,10 @@ class AssetV3UploadRequestStrategyTests: MessagingTestBase { line: UInt = #line ) -> ZMAssetClientMessage { let targetConversation = groupConversation! - let message = try! targetConversation.appendImage(from: verySmallJPEGData()) as! ZMAssetClientMessage + let message = try! targetConversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMAssetClientMessage message.updateTransferState(transferState, synchronize: true) for asset in message.assets { diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Helpers/AssetsPreprocessorTests.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Helpers/AssetsPreprocessorTests.swift index 1132dacfd84..417e817e759 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Helpers/AssetsPreprocessorTests.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Helpers/AssetsPreprocessorTests.swift @@ -41,7 +41,10 @@ class AssetsPreprocessorTests: MessagingTestBase { func testThatItPreprocessAssetMessage() { // given - let message = try! conversation.appendImage(from: verySmallJPEGData()) as! ZMAssetClientMessage + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMAssetClientMessage let asset = message.assets.first! let messageSet: Set = [message] @@ -80,7 +83,10 @@ class AssetsPreprocessorTests: MessagingTestBase { func testThatItMarksTheTransferStateAsModifiedAfterItsDoneProcessing() { // given - let message = try! conversation.appendImage(from: verySmallJPEGData()) as! ZMAssetClientMessage + let message = try! conversation.appendImage( + SendableImage(name: "picture.jpg", utType: .jpeg, data: verySmallJPEGData()), + nonce: UUID() + ) as! ZMAssetClientMessage let messageSet: Set = [message] // when diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategy.swift index c1b5479ddd6..5e12754ebb2 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategy.swift @@ -19,6 +19,7 @@ import Foundation import GenericMessageProtocol import WireLinkPreview +import WireLogging public final class LinkPreviewDetectorHelper: NSObject { fileprivate static var _test_debug_linkPreviewDetector: LinkPreviewDetectorType? @@ -65,6 +66,7 @@ extension ZMImagePreprocessingTracker { public final class LinkPreviewAssetUploadRequestStrategy: AbstractRequestStrategy, ZMContextChangeTrackerSource { let requestFactory = AssetRequestFactory() + private let featureRepository: LegacyFeatureRepository /// Processors fileprivate let linkPreviewPreprocessor: LinkPreviewPreprocessor @@ -73,6 +75,15 @@ public final class LinkPreviewAssetUploadRequestStrategy: AbstractRequestStrateg /// Upstream sync fileprivate var assetUpstreamSync: ZMUpstreamModifiedObjectSync! + let localDomain: String? + private let isCloudDomain: Bool + + private var shouldUploadExtraMetaData: Bool { + guard !isCloudDomain else { return false } + return managedObjectContext.performAndWait { + featureRepository.fetchAssetAuditLog().status == .enabled + } + } @available(*, unavailable) public override init( @@ -86,7 +97,9 @@ public final class LinkPreviewAssetUploadRequestStrategy: AbstractRequestStrateg managedObjectContext: NSManagedObjectContext, applicationStatus: ApplicationStatus, linkPreviewPreprocessor: LinkPreviewPreprocessor?, - previewImagePreprocessor: ZMImagePreprocessingTracker? + previewImagePreprocessor: ZMImagePreprocessingTracker?, + localDomain: String?, + isCloudDomain: Bool ) { if LinkPreviewDetectorHelper.test_debug_linkPreviewDetector() == nil { LinkPreviewDetectorHelper.setTest_debug_linkPreviewDetector(LinkPreviewDetector()) @@ -97,6 +110,9 @@ public final class LinkPreviewAssetUploadRequestStrategy: AbstractRequestStrateg ) self.previewImagePreprocessor = previewImagePreprocessor ?? ZMImagePreprocessingTracker .createPreviewImagePreprocessingTracker(managedObjectContext: managedObjectContext) + self.featureRepository = LegacyFeatureRepository(context: managedObjectContext) + self.localDomain = localDomain + self.isCloudDomain = isCloudDomain super.init(withManagedObjectContext: managedObjectContext, applicationStatus: applicationStatus) @@ -147,19 +163,62 @@ extension LinkPreviewAssetUploadRequestStrategy: ZMUpstreamTranscoder { return nil } - guard let retention = message.conversation.map(AssetRequestFactory.Retention.init) else { - fatal("Trying to send message that doesn't have a conversation") + let logAttributes: LogAttributes = [ + .public: true, + .nonce: message.nonce?.safeForLoggingDescription ?? "" + ] + + guard let conversation = message.conversation else { + WireLogger.assets.warn( + "Trying to send message that doesn't have a conversation", + attributes: logAttributes + ) + return nil } + let retention = AssetRequestFactory.Retention(conversation: conversation) + guard let imageData = managedObjectContext.zm_fileAssetCache.encryptedMediumImageData(for: message) else { return nil } + var extraMetaData: AssetRequestFactory.AssetAuditLogMetaData? + if shouldUploadExtraMetaData { + guard + let original = managedObjectContext.zm_fileAssetCache.originalImageData(for: message), + let domain = conversation.domain ?? localDomain + else { + WireLogger.assets.warn( + "should include extra metadata for link preview but not able to", + attributes: logAttributes + ) + return nil + } + + let conversationID = QualifiedID( + uuid: conversation.remoteIdentifier, + domain: domain + ) + + let image = SendableImage( + name: nil, + utType: nil, + data: imageData + ) + + extraMetaData = .init( + conversationID: conversationID, + fileName: image.name, + mimeType: image.utType?.preferredMIMEType ?? "" + ) + } + return ZMUpstreamRequest( keys: [ZMClientMessage.linkPreviewStateKey], transportRequest: requestFactory.upstreamRequestForAsset( withData: imageData, retention: retention, + assetAuditLogMetaData: extraMetaData, apiVersion: apiVersion ) ) diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategyTests.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategyTests.swift index 0d59c3d177e..cbc6afde715 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategyTests.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategyTests.swift @@ -40,7 +40,9 @@ class LinkPreviewAssetUploadRequestStrategyTests: MessagingTestBase { managedObjectContext: syncMOC, applicationStatus: mockApplicationStatus, linkPreviewPreprocessor: nil, - previewImagePreprocessor: nil + previewImagePreprocessor: nil, + localDomain: "wire.com", + isCloudDomain: false ) } diff --git a/wire-ios-request-strategy/Tests/Sources/Notifications/PushNotifications/ZMLocalNotificationTests_Message.swift b/wire-ios-request-strategy/Tests/Sources/Notifications/PushNotifications/ZMLocalNotificationTests_Message.swift index 20b219fed6d..a9ca60b26d6 100644 --- a/wire-ios-request-strategy/Tests/Sources/Notifications/PushNotifications/ZMLocalNotificationTests_Message.swift +++ b/wire-ios-request-strategy/Tests/Sources/Notifications/PushNotifications/ZMLocalNotificationTests_Message.swift @@ -644,8 +644,9 @@ extension ZMLocalNotificationTests_Message { let imageData = verySmallJPEGData() let assetMessage = GenericMessage( content: GenericMessageProtocol.Asset( - imageSize: .zero, + name: "picture.jpg", mimeType: "image/jpeg", + imageSize: .zero, size: UInt64(imageData.count) ), nonce: UUID.create(), diff --git a/wire-ios-share-engine/Sources/SharingTarget.swift b/wire-ios-share-engine/Sources/SharingTarget.swift index 8ed18c4ae18..7a1a4526dab 100644 --- a/wire-ios-share-engine/Sources/SharingTarget.swift +++ b/wire-ios-share-engine/Sources/SharingTarget.swift @@ -26,7 +26,7 @@ public protocol SharingTarget { func appendTextMessage(_ message: String, fetchLinkPreview: Bool) -> Sendable? /// Appends an image in the conversation - func appendImage(_ data: Data) -> Sendable? + func appendImage(_ image: SendableImage) -> Sendable? /// Appends a file in the conversation func appendFile(_ metaData: ZMFileMetadata) -> Sendable? diff --git a/wire-ios-share-engine/Sources/StrategyFactory.swift b/wire-ios-share-engine/Sources/StrategyFactory.swift index 80f36b732d1..28e97d1e68c 100644 --- a/wire-ios-share-engine/Sources/StrategyFactory.swift +++ b/wire-ios-share-engine/Sources/StrategyFactory.swift @@ -18,6 +18,7 @@ import Foundation import WireLinkPreview +import WireNetwork import WireRequestStrategy import WireTransport.ZMRequestCancellation @@ -30,6 +31,7 @@ final class StrategyFactory { private(set) var strategies = [AnyObject]() private let apiVersion: WireTransport.APIVersion? private let localDomain: String? + private let isCloudDomain: Bool private var tornDown = false @@ -46,6 +48,7 @@ final class StrategyFactory { let apiProvider = APIProvider(httpClient: httpClient) let sessionEstablisher = SessionEstablisher(context: syncContext, apiProvider: apiProvider) let messageDependencyResolver = MessageDependencyResolver(context: syncContext) + let featureRepository = LegacyFeatureRepository(context: syncContext) self.linkPreviewPreprocessor = linkPreviewPreprocessor self.syncContext = syncContext self.applicationStatus = applicationStatus @@ -56,11 +59,18 @@ final class StrategyFactory { context: syncContext, incrementalSyncObserver: NoOpIncrementalSyncObserver(), initiateResetMLSConversationUseCase: initiateResetMLSConversationUseCase, - featureRepository: LegacyFeatureRepository(context: syncContext), + featureRepository: featureRepository, apiVersion: apiVersion ) self.apiVersion = apiVersion self.localDomain = localDomain + + if let localDomain, !BackendEnvironment2.isCloudDomain(localDomain) { + self.isCloudDomain = true + } else { + self.isCloudDomain = false + } + self.strategies = createStrategies(linkPreviewPreprocessor: linkPreviewPreprocessor) } @@ -130,7 +140,9 @@ final class StrategyFactory { managedObjectContext: syncContext, applicationStatus: applicationStatus, linkPreviewPreprocessor: linkPreviewPreprocessor, - previewImagePreprocessor: nil + previewImagePreprocessor: nil, + localDomain: localDomain, + isCloudDomain: isCloudDomain ) } @@ -146,7 +158,9 @@ final class StrategyFactory { private func createAssetV3UploadRequestStrategy() -> AssetV3UploadRequestStrategy { let strategy = AssetV3UploadRequestStrategy( withManagedObjectContext: syncContext, - applicationStatus: applicationStatus + applicationStatus: applicationStatus, + localDomain: localDomain, + isCloudDomain: isCloudDomain ) // WORKAROUND: diff --git a/wire-ios-share-engine/Sources/ZMConversation+Conversation.swift b/wire-ios-share-engine/Sources/ZMConversation+Conversation.swift index c9106c8190d..48ec0204571 100644 --- a/wire-ios-share-engine/Sources/ZMConversation+Conversation.swift +++ b/wire-ios-share-engine/Sources/ZMConversation+Conversation.swift @@ -36,9 +36,9 @@ extension ZMConversation: Conversation { } } - public func appendImage(_ data: Data) -> Sendable? { + public func appendImage(_ image: SendableImage) -> Sendable? { do { - return try appendImage(from: data) as? Sendable + return try appendImage(image, nonce: UUID()) as? Sendable } catch { WireLogger.messageProcessing .warn("Failed to append image message from Share Ext. Reason: \(error.localizedDescription)") diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift index 8304d62991e..be2254fbf95 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift @@ -1526,12 +1526,6 @@ public final class SessionManager: NSObject, SessionManagerType { activeUserSession != nil } - func updateProfileImage(imageData: Data) { - activeUserSession?.enqueue { - self.activeUserSession?.userProfileImage.updateImage(imageData: imageData) - } - } - public var callNotificationStyle: CallNotificationStyle = .callKit { didSet { updateCallNotificationStyle() @@ -1744,13 +1738,6 @@ extension SessionManager: UnauthenticatedSessionDelegate { update(credentials: credentials) } - public func session( - session: UnauthenticatedSession, - updatedProfileImage imageData: Data - ) { - updateProfileImage(imageData: imageData) - } - public func session( session: UnauthenticatedSession, createdAccount account: Account, @@ -1779,10 +1766,6 @@ extension SessionManager: UnauthenticatedSessionDelegate { updateCurrentAccount(in: userSession.managedObjectContext) - if let profileImageData = session.authenticationStatus.profileImageData { - updateProfileImage(imageData: profileImageData) - } - switch session.backupImportDidSucceed { case true?: userSession.trackAnalyticsEvent(.Backup.restored) diff --git a/wire-ios-sync-engine/Source/Synchronization/LinkPreviewAssetUploadRequestStrategy+Helper.swift b/wire-ios-sync-engine/Source/Synchronization/LinkPreviewAssetUploadRequestStrategy+Helper.swift deleted file mode 100644 index e8b83c8bdde..00000000000 --- a/wire-ios-sync-engine/Source/Synchronization/LinkPreviewAssetUploadRequestStrategy+Helper.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation - -public extension LinkPreviewAssetUploadRequestStrategy { - - static func create( - withManagedObjectContext managedObjectContext: NSManagedObjectContext, - applicationStatus: ApplicationStatus - ) -> LinkPreviewAssetUploadRequestStrategy { - LinkPreviewAssetUploadRequestStrategy( - managedObjectContext: managedObjectContext, - applicationStatus: applicationStatus, - linkPreviewPreprocessor: nil, - previewImagePreprocessor: nil - ) - } - -} diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserImageAssetUpdateStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserImageAssetUpdateStrategy.swift index b6526b66c74..298cd8d4925 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserImageAssetUpdateStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserImageAssetUpdateStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireRequestStrategy enum AssetTransportError: Error { @@ -48,19 +49,31 @@ public final class UserImageAssetUpdateStrategy: AbstractRequestStrategy, ZMCont fileprivate var observers: [Any] = [] private let localDomain: String? + private let isCloudDomain: Bool + + private let featureRepository: LegacyFeatureRepository + + private var shouldUploadExtraMetaData: Bool { + guard !isCloudDomain else { return false } + return managedObjectContext.performAndWait { + featureRepository.fetchAssetAuditLog().status == .enabled + } + } @objc public convenience init( managedObjectContext: NSManagedObjectContext, applicationStatusDirectory: ApplicationStatusDirectory, userProfileImageUpdateStatus: UserProfileImageUpdateStatus, - localDomain: String? + localDomain: String?, + isCloudDomain: Bool ) { self.init( managedObjectContext: managedObjectContext, applicationStatus: applicationStatusDirectory, imageUploadStatus: userProfileImageUpdateStatus, - localDomain: localDomain + localDomain: localDomain, + isCloudDomain: isCloudDomain ) } @@ -68,11 +81,14 @@ public final class UserImageAssetUpdateStrategy: AbstractRequestStrategy, ZMCont managedObjectContext: NSManagedObjectContext, applicationStatus: ApplicationStatus, imageUploadStatus: UserProfileImageUploadStatusProtocol, - localDomain: String? + localDomain: String?, + isCloudDomain: Bool ) { self.moc = managedObjectContext self.imageUploadStatus = imageUploadStatus self.localDomain = localDomain + self.isCloudDomain = isCloudDomain + self.featureRepository = LegacyFeatureRepository(context: managedObjectContext) super.init(withManagedObjectContext: managedObjectContext, applicationStatus: applicationStatus) downstreamRequestSyncs[.preview] = whitelistUserImageSync(for: .preview) @@ -242,10 +258,38 @@ public final class UserImageAssetUpdateStrategy: AbstractRequestStrategy, ZMCont public func request(for sync: ZMSingleRequestSync, apiVersion: APIVersion) -> ZMTransportRequest? { if let size = size(for: sync), let image = imageUploadStatus?.consumeImage(for: size) { + var extraMetaData: AssetRequestFactory.AssetAuditLogMetaData? + if shouldUploadExtraMetaData { + guard + // As per the spec: there's no conversation so we use a null id instead. + let nullID = UUID(uuidString: "00000000-0000-0000-0000-000000000000"), + let localDomain + else { + WireLogger.assets.warn( + "should include extra metadata for profile image but not able to", + attributes: .safePublic + ) + return nil + } + + let image = SendableImage( + name: nil, + utType: nil, + data: image + ) + + extraMetaData = .init( + conversationID: QualifiedID(uuid: nullID, domain: localDomain), + fileName: image.name, + mimeType: image.utType?.preferredMIMEType ?? "" + ) + } + let request = requestFactory.upstreamRequestForAsset( withData: image, shareable: true, retention: .eternal, + assetAuditLogMetaData: extraMetaData, apiVersion: apiVersion ) diff --git a/wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift b/wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift index 2b541c2c082..4095d3f275f 100644 --- a/wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift +++ b/wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift @@ -121,8 +121,9 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol { metadata: BackendMetadataProvider ) -> [Any] { let syncMOC = contextProvider.syncContext + let featureRepository = LegacyFeatureRepository(context: syncMOC) - let mlsFeature = LegacyFeatureRepository(context: syncMOC).fetchMLS() + let mlsFeature = featureRepository.fetchMLS() let oneOnOneResolver = LegacyOneOnOneResolver( migrator: OneOnOneMigrator(mlsService: mlsService), isMLSEnabled: mlsFeature.isEnabled @@ -132,6 +133,13 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol { mlsService: mlsService ) + let assetAuditLog = featureRepository.fetchAssetAuditLog() + + var isCloudDomain = false + if let localDomain = metadata.domain, !BackendEnvironment2.isCloudDomain(localDomain) { + isCloudDomain = true + } + return [ UserClientRequestStrategy( clientRegistrationStatus: applicationStatusDirectory.clientRegistrationStatus, @@ -171,7 +179,9 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol { ), AssetV3UploadRequestStrategy( withManagedObjectContext: syncMOC, - applicationStatus: applicationStatusDirectory + applicationStatus: applicationStatusDirectory, + localDomain: metadata.domain, + isCloudDomain: isCloudDomain ), AssetV2DownloadRequestStrategy( withManagedObjectContext: syncMOC, @@ -201,7 +211,9 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol { managedObjectContext: syncMOC, applicationStatus: applicationStatusDirectory, linkPreviewPreprocessor: nil, - previewImagePreprocessor: nil + previewImagePreprocessor: nil, + localDomain: metadata.domain, + isCloudDomain: isCloudDomain ), LinkPreviewAssetDownloadRequestStrategy( withManagedObjectContext: syncMOC, @@ -366,7 +378,8 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol { managedObjectContext: syncMOC, applicationStatusDirectory: applicationStatusDirectory, userProfileImageUpdateStatus: applicationStatusDirectory.userProfileImageUpdateStatus, - localDomain: metadata.domain + localDomain: metadata.domain, + isCloudDomain: isCloudDomain ), localNotificationDispatcher, MLSRequestStrategy( diff --git a/wire-ios-sync-engine/Source/UnauthenticatedSession/UnauthenticatedSessionDelegate.swift b/wire-ios-sync-engine/Source/UnauthenticatedSession/UnauthenticatedSessionDelegate.swift index b7d9120441f..ac469a33440 100644 --- a/wire-ios-sync-engine/Source/UnauthenticatedSession/UnauthenticatedSessionDelegate.swift +++ b/wire-ios-sync-engine/Source/UnauthenticatedSession/UnauthenticatedSessionDelegate.swift @@ -27,11 +27,6 @@ public protocol UnauthenticatedSessionDelegate: AnyObject { updatedCredentials credentials: UserCredentials ) -> Bool - func session( - session: UnauthenticatedSession, - updatedProfileImage imageData: Data - ) - func session( session: UnauthenticatedSession, createdAccount account: Account, diff --git a/wire-ios-sync-engine/Source/UnauthenticatedSession/ZMAuthenticationStatus.h b/wire-ios-sync-engine/Source/UnauthenticatedSession/ZMAuthenticationStatus.h index bcebac561d4..c33b6ab7a7e 100644 --- a/wire-ios-sync-engine/Source/UnauthenticatedSession/ZMAuthenticationStatus.h +++ b/wire-ios-sync-engine/Source/UnauthenticatedSession/ZMAuthenticationStatus.h @@ -77,7 +77,6 @@ typedef NS_ENUM(NSUInteger, ZMAuthenticationPhase) { @property (nonatomic, readonly) ZMAuthenticationPhase currentPhase; @property (nonatomic, readonly) NSUUID *authenticatedUserIdentifier; -@property (nonatomic) NSData *profileImageData; @property (nonatomic) NSData *authenticationCookieData; diff --git a/wire-ios-sync-engine/Source/Use cases/AppendImageMessageUseCase.swift b/wire-ios-sync-engine/Source/Use cases/AppendImageMessageUseCase.swift index b370602c8e4..191665df77d 100644 --- a/wire-ios-sync-engine/Source/Use cases/AppendImageMessageUseCase.swift +++ b/wire-ios-sync-engine/Source/Use cases/AppendImageMessageUseCase.swift @@ -24,7 +24,7 @@ import WireDataModel public protocol AppendImageMessageUseCaseProtocol { func invoke( - withImageData imageData: Data, + image: SendableImage, in conversation: Conversation ) throws } @@ -38,10 +38,10 @@ public struct AppendImageMessageUseCase: AppendImageMessageUseCaseProtocol { } public func invoke( - withImageData imageData: Data, + image: SendableImage, in conversation: some MessageAppendableConversation ) throws { - try conversation.appendImage(from: imageData, nonce: UUID()) + try conversation.appendImage(image, nonce: UUID()) analyticsEventTracker?.trackEvent( .Contributed.conversationContribution( .imageMessage, diff --git a/wire-ios-sync-engine/Source/Use cases/MessageAppendableConversation.swift b/wire-ios-sync-engine/Source/Use cases/MessageAppendableConversation.swift index 81683bd4547..5cc8dbdc842 100644 --- a/wire-ios-sync-engine/Source/Use cases/MessageAppendableConversation.swift +++ b/wire-ios-sync-engine/Source/Use cases/MessageAppendableConversation.swift @@ -35,7 +35,7 @@ public protocol MessageAppendableConversation { func appendKnock(nonce: UUID) throws -> any ZMConversationMessage @discardableResult - func appendImage(from imageData: Data, nonce: UUID) throws -> any ZMConversationMessage + func appendImage(_ image: SendableImage, nonce: UUID) throws -> any ZMConversationMessage @discardableResult func appendLocation(with locationData: LocationData, nonce: UUID) throws -> ZMConversationMessage diff --git a/wire-ios-sync-engine/Support/Sourcery/generated/AutoMockable.manual.swift b/wire-ios-sync-engine/Support/Sourcery/generated/AutoMockable.manual.swift index 4bbb265b6c6..ba001f3a88f 100644 --- a/wire-ios-sync-engine/Support/Sourcery/generated/AutoMockable.manual.swift +++ b/wire-ios-sync-engine/Support/Sourcery/generated/AutoMockable.manual.swift @@ -178,21 +178,21 @@ public class MockMessageAppendableConversation: MessageAppendableConversation { // MARK: - appendImage - public var appendImage_Invocations: [(imageData: Data, nonce: UUID)] = [] + public var appendImage_Invocations: [(image: SendableImage, nonce: UUID)] = [] public var appendImage_MockError: Error? - public var appendImage_MockMethod: ((Data, UUID) throws -> any ZMConversationMessage)? + public var appendImage_MockMethod: ((SendableImage, UUID) throws -> any ZMConversationMessage)? public var appendImage_MockValue: (any ZMConversationMessage)? @discardableResult - public func appendImage(from imageData: Data, nonce: UUID) throws -> any ZMConversationMessage { - appendImage_Invocations.append((imageData: imageData, nonce: nonce)) + public func appendImage(_ image: SendableImage, nonce: UUID) throws -> any ZMConversationMessage { + appendImage_Invocations.append((image: image, nonce: nonce)) if let error = appendImage_MockError { throw error } if let mock = appendImage_MockMethod { - return try mock(imageData, nonce) + return try mock(image, nonce) } else if let mock = appendImage_MockValue { return mock } else { diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/UserImageAssetUpdateStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/UserImageAssetUpdateStrategyTests.swift index f861ca86d6c..0be37113840 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/UserImageAssetUpdateStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/UserImageAssetUpdateStrategyTests.swift @@ -75,7 +75,8 @@ class UserImageAssetUpdateStrategyTests: MessagingTest { managedObjectContext: self.syncMOC, applicationStatus: mockApplicationStatus, imageUploadStatus: updateStatus, - localDomain: "wire.com" + localDomain: "wire.com", + isCloudDomain: false ) } @@ -165,6 +166,7 @@ class UserImageAssetUpdateStrategyTests: MessagingTest { withData: previewData, shareable: true, retention: .eternal, + assetAuditLogMetaData: nil, apiVersion: .v0 ) let completeData = Data("1111111".utf8) @@ -172,6 +174,7 @@ class UserImageAssetUpdateStrategyTests: MessagingTest { withData: completeData, shareable: true, retention: .eternal, + assetAuditLogMetaData: nil, apiVersion: .v0 ) diff --git a/wire-ios-sync-engine/Tests/Source/Use cases/AppendImageMessageUseCaseTests.swift b/wire-ios-sync-engine/Tests/Source/Use cases/AppendImageMessageUseCaseTests.swift index ea0423b43aa..60ff86b46b4 100644 --- a/wire-ios-sync-engine/Tests/Source/Use cases/AppendImageMessageUseCaseTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Use cases/AppendImageMessageUseCaseTests.swift @@ -62,14 +62,15 @@ final class AppendImageMessageUseCaseTests: XCTestCase { analyticsEventTracker.trackEventEventAnalyticsEventVoidClosure = { _ in } let testImageData = Data("test image data".utf8) + let image = SendableImage(name: "picture.jpg", utType: .jpeg, data: testImageData) // WHEN - try sut.invoke(withImageData: testImageData, in: mockConversation) + try sut.invoke(image: image, in: mockConversation) // THEN XCTAssertEqual(mockConversation.appendImage_Invocations.count, 1) let appendImageInvocation = try XCTUnwrap(mockConversation.appendImage_Invocations.first) - XCTAssertEqual(appendImageInvocation.imageData, testImageData) + XCTAssertEqual(appendImageInvocation.image.data, testImageData) XCTAssertNotNil(appendImageInvocation.nonce) let expectedEvent = AnalyticsEvent.Contributed.conversationContribution( diff --git a/wire-ios-sync-engine/WireSyncEngine.xcodeproj/project.pbxproj b/wire-ios-sync-engine/WireSyncEngine.xcodeproj/project.pbxproj index b20561cc2bc..911e5ef7784 100644 --- a/wire-ios-sync-engine/WireSyncEngine.xcodeproj/project.pbxproj +++ b/wire-ios-sync-engine/WireSyncEngine.xcodeproj/project.pbxproj @@ -288,7 +288,6 @@ 5E9D32712109C54B0032FB06 /* CompanyLoginActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9D32702109C54B0032FB06 /* CompanyLoginActionTests.swift */; }; 5EC2C5912137F80E00C6CE35 /* CallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC2C5902137F80E00C6CE35 /* CallState.swift */; }; 5EC2C593213827BF00C6CE35 /* WireCallCenterV3+Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EC2C592213827BF00C6CE35 /* WireCallCenterV3+Events.swift */; }; - 5EDF03EC2245563C00C04007 /* LinkPreviewAssetUploadRequestStrategy+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EDF03EB2245563C00C04007 /* LinkPreviewAssetUploadRequestStrategy+Helper.swift */; }; 5EFE9C15212AB138007932A6 /* UnregisteredUser+Payload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFE9C14212AB138007932A6 /* UnregisteredUser+Payload.swift */; }; 5EFE9C17212AB945007932A6 /* RegistrationPhase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFE9C16212AB945007932A6 /* RegistrationPhase.swift */; }; 632A582025CC43DA00F0C4BD /* CallParticipantsListKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 632A581F25CC43DA00F0C4BD /* CallParticipantsListKind.swift */; }; @@ -870,7 +869,6 @@ 5E9D32702109C54B0032FB06 /* CompanyLoginActionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompanyLoginActionTests.swift; sourceTree = ""; }; 5EC2C5902137F80E00C6CE35 /* CallState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallState.swift; sourceTree = ""; }; 5EC2C592213827BF00C6CE35 /* WireCallCenterV3+Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WireCallCenterV3+Events.swift"; sourceTree = ""; }; - 5EDF03EB2245563C00C04007 /* LinkPreviewAssetUploadRequestStrategy+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LinkPreviewAssetUploadRequestStrategy+Helper.swift"; sourceTree = ""; }; 5EFE9C14212AB138007932A6 /* UnregisteredUser+Payload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnregisteredUser+Payload.swift"; sourceTree = ""; }; 5EFE9C16212AB945007932A6 /* RegistrationPhase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationPhase.swift; sourceTree = ""; }; 632A581F25CC43DA00F0C4BD /* CallParticipantsListKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallParticipantsListKind.swift; sourceTree = ""; }; @@ -2124,7 +2122,6 @@ 1693151025836E5800709F15 /* EventProcessor.swift */, 160C31431E8049320012E4BC /* ApplicationStatusDirectory.swift */, 7C26879C21F7193800570AA9 /* EventProcessingTracker.swift */, - 5EDF03EB2245563C00C04007 /* LinkPreviewAssetUploadRequestStrategy+Helper.swift */, 06DE14CE24B85BD0006CB6B3 /* ZMClientRegistrationStatusDelegate.h */, 3416A3172DC0D53600827BCB /* IncrementalSyncObserver.swift */, ); @@ -3195,7 +3192,6 @@ 165D3A221E1D43870052E654 /* VoiceChannel.swift in Sources */, EE9CDE9027DA04A300C4DAC8 /* SessionManager+APIVersionResolver.swift in Sources */, 63E313D627553CA0002EAF1D /* ZMConversation+AVSIdentifier.swift in Sources */, - 5EDF03EC2245563C00C04007 /* LinkPreviewAssetUploadRequestStrategy+Helper.swift in Sources */, F96DBEEB1DF9A570008FE832 /* ZMSyncStrategy+ManagedObjectChanges.m in Sources */, EEE95CF62A442A0100E136CB /* WireCallCenterV3+MLS.swift in Sources */, 168CF4292007840A009FCB89 /* Team+Invite.swift in Sources */, diff --git a/wire-ios/Tests/Mocks/MockMessage.swift b/wire-ios/Tests/Mocks/MockMessage.swift index c3cf37ffa97..8b7140875ee 100644 --- a/wire-ios/Tests/Mocks/MockMessage.swift +++ b/wire-ios/Tests/Mocks/MockMessage.swift @@ -292,6 +292,8 @@ final class MockKnockMessageData: NSObject, ZMKnockMessageData {} final class MockImageMessageData: NSObject, ZMImageMessageData { + var name: String? + var mockOriginalSize: CGSize = .zero var mockImageData = Data() var mockImageDataIdentifier = String() diff --git a/wire-ios/Wire-iOS Share Extension/Sources/Content/UnsentGifImageSendable.swift b/wire-ios/Wire-iOS Share Extension/Sources/Content/UnsentGifImageSendable.swift index 7587f3e5c0a..32c29b1acf4 100644 --- a/wire-ios/Wire-iOS Share Extension/Sources/Content/UnsentGifImageSendable.swift +++ b/wire-ios/Wire-iOS Share Extension/Sources/Content/UnsentGifImageSendable.swift @@ -19,6 +19,7 @@ import Foundation import MobileCoreServices import UniformTypeIdentifiers +import WireDataModel import WireShareEngine /// `UnsentSendable` implementation to send GIF image messages @@ -26,7 +27,7 @@ final class UnsentGifImageSendable: UnsentSendableBase, UnsentSendable { private var gifImageData: Data? private let attachment: NSItemProvider - init?(conversation: Conversation, sharingSession: SharingSession, attachment: NSItemProvider) { + init?(conversation: WireShareEngine.Conversation, sharingSession: SharingSession, attachment: NSItemProvider) { guard attachment.hasItemConformingToTypeIdentifier(UTType.gif.identifier) else { return nil } self.attachment = attachment super.init(conversation: conversation, sharingSession: sharingSession) @@ -56,8 +57,23 @@ final class UnsentGifImageSendable: UnsentSendableBase, UnsentSendable { func send(completion: @escaping (Sendable?) -> Void) { sharingSession.enqueue { [weak self] in - guard let self else { return } - completion(gifImageData.flatMap(conversation.appendImage)) + guard let self else { + return + } + + guard let gifImageData else { + return completion(nil) + } + + let message = conversation.appendImage( + SendableImage( + name: nil, + utType: .gif, + data: gifImageData + ) + ) + + completion(message) } } } diff --git a/wire-ios/Wire-iOS Share Extension/Sources/Content/UnsentSendable.swift b/wire-ios/Wire-iOS Share Extension/Sources/Content/UnsentSendable.swift index 4e7d1f392cf..1d2ef876b75 100644 --- a/wire-ios/Wire-iOS Share Extension/Sources/Content/UnsentSendable.swift +++ b/wire-ios/Wire-iOS Share Extension/Sources/Content/UnsentSendable.swift @@ -211,8 +211,23 @@ final class UnsentImageSendable: UnsentSendableBase, UnsentSendable { func send(completion: @escaping (Sendable?) -> Void) { sharingSession.enqueue { [weak self] in - guard let self else { return } - completion(imageData.flatMap(conversation.appendImage)) + guard let self else { + return + } + + guard let imageData else { + return completion(nil) + } + + let message = conversation.appendImage( + SendableImage( + name: nil, + utType: .jpeg, + data: imageData + ) + ) + + completion(message) } } diff --git a/wire-ios/Wire-iOS Tests/CameraKeyboardViewControllerTests.swift b/wire-ios/Wire-iOS Tests/CameraKeyboardViewControllerTests.swift index 29d3a310bb5..6c79e4d9652 100644 --- a/wire-ios/Wire-iOS Tests/CameraKeyboardViewControllerTests.swift +++ b/wire-ios/Wire-iOS Tests/CameraKeyboardViewControllerTests.swift @@ -52,10 +52,9 @@ final class CameraKeyboardViewControllerDelegateMock: CameraKeyboardViewControll var cameraKeyboardViewControllerDidSelectImageDataHitCount: UInt = 0 func cameraKeyboardViewController( - _ controller: CameraKeyboardViewController, - didSelectImageData: Data, - isFromCamera: Bool, - uti: String? + _ controller: Wire.CameraKeyboardViewController, + didSelectImage image: WireDataModel.SendableImage, + isFromCamera: Bool ) { cameraKeyboardViewControllerDidSelectImageDataHitCount += 1 } diff --git a/wire-ios/Wire-iOS Tests/ConversationImagesViewControllerTests.swift b/wire-ios/Wire-iOS Tests/ConversationImagesViewControllerTests.swift index 26791d1a90c..0ca747208a9 100644 --- a/wire-ios/Wire-iOS Tests/ConversationImagesViewControllerTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationImagesViewControllerTests.swift @@ -57,8 +57,9 @@ final class ConversationImagesViewControllerTests: CoreDataSnapshotTestCase { userSession = UserSessionMock() snapshotBackgroundColor = UIColor.white - let image = image(inTestBundleNamed: "unsplash_matterhorn.jpg") - let initialMessage = try! otherUserConversation.appendImage(from: image.imageData!) + let imageData = try XCTUnwrap(image(inTestBundleNamed: "unsplash_matterhorn.jpg").imageData) + let image = SendableImage(name: "picture.jpg", utType: .jpeg, data: imageData) + let initialMessage = try otherUserConversation.appendImage(image, nonce: UUID()) let imagesCategoryMatch = CategoryMatch(including: .image, excluding: .none) let collection = MockCollection(messages: [imagesCategoryMatch: [initialMessage]]) let delegate = AssetCollectionMulticastDelegate() diff --git a/wire-ios/Wire-iOS Tests/ConversationList/ConversationListCell/CoreDataSnapshotTestCase+appendMessage.swift b/wire-ios/Wire-iOS Tests/ConversationList/ConversationListCell/CoreDataSnapshotTestCase+appendMessage.swift index 90b30ec5caf..b3529e5014b 100644 --- a/wire-ios/Wire-iOS Tests/ConversationList/ConversationListCell/CoreDataSnapshotTestCase+appendMessage.swift +++ b/wire-ios/Wire-iOS Tests/ConversationList/ConversationListCell/CoreDataSnapshotTestCase+appendMessage.swift @@ -27,13 +27,13 @@ extension CoreDataSnapshotTestCase { } func appendImage(to conversation: ZMConversation) { - ( - try! conversation - .appendImage( - from: image(inTestBundleNamed: "unsplash_burger.jpg") - .jpegData(compressionQuality: 1.0)! - ) as! ZMMessage - ).sender = otherUser + let image = SendableImage( + name: "burger.jpg", + utType: .jpeg, + data: image(inTestBundleNamed: "unsplash_burger.jpg").jpegData(compressionQuality: 1.0)! + ) + let message = try! conversation.appendImage(image, nonce: UUID()) as! ZMMessage + message.sender = otherUser conversation.lastReadServerTimeStamp = Date.distantPast } diff --git a/wire-ios/Wire-iOS Tests/ConversationStatusTests.swift b/wire-ios/Wire-iOS Tests/ConversationStatusTests.swift index bb9ed6a631e..9804bf4133a 100644 --- a/wire-ios/Wire-iOS Tests/ConversationStatusTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationStatusTests.swift @@ -82,13 +82,11 @@ final class ConversationStatusTests: CoreDataSnapshotTestCase { func testThatItReturnsStatusForConversationWithUnreadOneImage() { // GIVEN let sut = otherUserConversation! - ( - try! sut - .appendImage( - from: image(inTestBundleNamed: "unsplash_burger.jpg") - .jpegData(compressionQuality: 1.0)! - ) as! ZMMessage - ).sender = otherUser + let imageData = image(inTestBundleNamed: "unsplash_burger.jpg") + .jpegData(compressionQuality: 1.0)! + let image = SendableImage(name: "burger.jpg", utType: .jpeg, data: imageData) + let message = try! sut.appendImage(image, nonce: UUID()) as! ZMMessage + message.sender = otherUser markAllMessagesAsUnread(in: sut) // WHEN @@ -106,13 +104,12 @@ final class ConversationStatusTests: CoreDataSnapshotTestCase { let sut = otherUserConversation! try (sut.appendKnock() as! ZMMessage).sender = otherUser (try! sut.appendText(content: "test") as! ZMMessage).sender = otherUser - ( - try! sut - .appendImage( - from: image(inTestBundleNamed: "unsplash_burger.jpg") - .jpegData(compressionQuality: 1.0)! - ) as! ZMMessage - ).sender = otherUser + + let imageData = image(inTestBundleNamed: "unsplash_burger.jpg") + .jpegData(compressionQuality: 1.0)! + let image = SendableImage(name: "burger.jpg", utType: .jpeg, data: imageData) + let imageMessage = try! sut.appendImage(image, nonce: UUID()) as! ZMMessage + imageMessage.sender = otherUser markAllMessagesAsUnread(in: sut) // WHEN @@ -167,27 +164,19 @@ final class ConversationStatusTests: CoreDataSnapshotTestCase { func testThatItReturnsStatusForConversationWithUnreadManyImages() { // GIVEN let sut = otherUserConversation! - ( - try! sut - .appendImage( - from: image(inTestBundleNamed: "unsplash_burger.jpg") - .jpegData(compressionQuality: 1.0)! - ) as! ZMMessage - ).sender = otherUser - ( - try! sut - .appendImage( - from: image(inTestBundleNamed: "unsplash_burger.jpg") - .jpegData(compressionQuality: 1.0)! - ) as! ZMMessage - ).sender = otherUser - ( - try! sut - .appendImage( - from: image(inTestBundleNamed: "unsplash_burger.jpg") - .jpegData(compressionQuality: 1.0)! - ) as! ZMMessage - ).sender = otherUser + let imageData = image(inTestBundleNamed: "unsplash_burger.jpg") + .jpegData(compressionQuality: 1.0)! + let image = SendableImage(name: "burger.jpg", utType: .jpeg, data: imageData) + + let message1 = try! sut.appendImage(image, nonce: UUID()) as! ZMMessage + message1.sender = otherUser + + let message2 = try! sut.appendImage(image, nonce: UUID()) as! ZMMessage + message2.sender = otherUser + + let message3 = try! sut.appendImage(image, nonce: UUID()) as! ZMMessage + message3.sender = otherUser + markAllMessagesAsUnread(in: sut) // WHEN diff --git a/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/AnalyticsTrackingAvailabilityChecker.swift b/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/AnalyticsTrackingAvailabilityChecker.swift index e031bb8a892..277d5adcfc8 100644 --- a/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/AnalyticsTrackingAvailabilityChecker.swift +++ b/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/AnalyticsTrackingAvailabilityChecker.swift @@ -22,23 +22,12 @@ import WireNetwork struct AnalyticsTrackingAvailabilityChecker: AnalyticsTrackingAvailabilityCheckerProtocol { func isAnalyticsTrackingAvailable(for domain: String) -> Bool { - let whitelistedDomains = [ - "wire.com", - "staging.zinfra.io" - ] - return whitelistedDomains.contains(domain) + BackendEnvironment2.isCloudDomain(domain) || BackendEnvironment2.isStagingDomain(domain) } func isAnalyticsTrackingAvailable(for environment: BackendEnvironment2) -> Bool { - guard let backendConfigHost = environment.config.endpoints.restAPIURL.host() else { return false } + environment.isCloudEnvironment || environment.isStagingEnvironment - let whitelistedHosts = [ - // prod - "prod-nginz-https.wire.com", - // staging - "staging-nginz-https.zinfra.io" - ] - return whitelistedHosts.contains(backendConfigHost) } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+CanvasViewControllerDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+CanvasViewControllerDelegate.swift index f085d6f181c..e51b0927000 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+CanvasViewControllerDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+CanvasViewControllerDelegate.swift @@ -31,7 +31,15 @@ extension ConversationContentViewController: CanvasViewControllerDelegate { self.userSession.enqueue { do { let useCase = self.userSession.makeAppendImageMessageUseCase() - try useCase.invoke(withImageData: imageData, in: self.conversation) + let image = SendableImage( + name: nil, + utType: nil, + data: imageData + ) + try useCase.invoke( + image: image, + in: self.conversation + ) } catch { WireLogger.messageProcessing .warn("Failed to append image message from canvas. Reason: \(error.localizedDescription)") diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+Forward.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+Forward.swift index 9b87aa7646d..9b097f4742f 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+Forward.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+Forward.swift @@ -97,11 +97,16 @@ extension ZMMessage: Shareable { } } } - } else if isImage, let imageData = imageMessageData?.imageData { + } else if isImage, let imageMessageData, let data = imageMessageData.imageData { ZMUserSession.shared()?.perform { conversations.forEachNonEphemeral { do { - try $0.appendImage(from: imageData) + let image = SendableImage( + name: imageMessageData.name, + utType: nil, + data: data + ) + try $0.appendImage(image, nonce: UUID()) } catch { WireLogger.messageProcessing .warn("Failed to append image message. Reason: \(error.localizedDescription)") diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/CameraKeyboard/CameraCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/CameraKeyboard/CameraCell.swift index 4cb7fe0734d..a53209b4c8f 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/CameraKeyboard/CameraCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/CameraKeyboard/CameraCell.swift @@ -18,10 +18,11 @@ import avs import Foundation +import WireDataModel protocol CameraCellDelegate: AnyObject { func cameraCellWantsToOpenFullCamera(_ cameraCell: CameraCell) - func cameraCell(_ cameraCell: CameraCell, didPickImageData imageData: Data, type: UTType) + func cameraCell(_ cameraCell: CameraCell, didPickImage image: SendableImage) } final class CameraCell: UICollectionViewCell { @@ -160,8 +161,15 @@ final class CameraCell: UICollectionViewCell { func shutterButtonPressed(_ sender: AnyObject) { cameraController?.capturePhoto { data, error in if error == nil, let data { - // The capture photo method always returns jpeg data - self.delegate?.cameraCell(self, didPickImageData: data, type: .jpeg) + let image = SendableImage( + name: nil, + utType: .jpeg, + data: data + ) + self.delegate?.cameraCell( + self, + didPickImage: image, + ) } } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/CameraKeyboard/CameraKeyboardViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/CameraKeyboard/CameraKeyboardViewController.swift index 89f95e9a616..50714970a0c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/CameraKeyboard/CameraKeyboardViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/CameraKeyboard/CameraKeyboardViewController.swift @@ -36,9 +36,8 @@ protocol CameraKeyboardViewControllerDelegate: AnyObject { ) func cameraKeyboardViewController( _ controller: CameraKeyboardViewController, - didSelectImageData: Data, - isFromCamera: Bool, - uti: String? + didSelectImage image: SendableImage, + isFromCamera: Bool ) func cameraKeyboardViewControllerWantsToOpenFullScreenCamera(_ controller: CameraKeyboardViewController) func cameraKeyboardViewControllerWantsToOpenCameraRoll(_ controller: CameraKeyboardViewController) @@ -313,12 +312,19 @@ class CameraKeyboardViewController: UIViewController { data } + let name = PHAssetResource.assetResources(for: asset).first?.originalFilename + + let image = SendableImage( + name: name, + utType: .jpeg, + data: returnData + ) + DispatchQueue.main.async { self.delegate?.cameraKeyboardViewController( self, - didSelectImageData: returnData, - isFromCamera: false, - uti: uti + didSelectImage: image, + isFromCamera: false ) } } @@ -629,12 +635,14 @@ extension CameraKeyboardViewController: CameraCellDelegate { delegate?.cameraKeyboardViewControllerWantsToOpenFullScreenCamera(self) } - func cameraCell(_ cameraCell: CameraCell, didPickImageData imageData: Data, type: UTType) { + func cameraCell( + _ cameraCell: CameraCell, + didPickImage image: SendableImage + ) { delegate?.cameraKeyboardViewController( self, - didSelectImageData: imageData, - isFromCamera: true, - uti: type.identifier + didSelectImage: image, + isFromCamera: true ) } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarSendController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarSendController.swift index 2f7ab196d66..e69864df5b9 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarSendController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarSendController.swift @@ -30,7 +30,7 @@ final class ConversationInputBarSendController: NSObject { } func sendMessage( - withImageData imageData: Data, + image: SendableImage, userSession: UserSession, completion completionHandler: Completion? = nil ) { @@ -41,7 +41,10 @@ final class ConversationInputBarSendController: NSObject { userSession.enqueue({ do { let useCase = userSession.makeAppendImageMessageUseCase() - try useCase.invoke(withImageData: imageData, in: conversation) + try useCase.invoke( + image: image, + in: conversation + ) self.feedbackGenerator.impactOccurred() } catch { Logging.messageProcessing.warn("Failed to append image message. Reason: \(error.localizedDescription)") @@ -95,7 +98,7 @@ final class ConversationInputBarSendController: NSObject { _ text: String, mentions: [Mention], userSession: UserSession, - withImageData data: Data + withGIFImageData data: Data ) { guard let conversation = conversation as? ZMConversation else { return } @@ -112,7 +115,15 @@ final class ConversationInputBarSendController: NSObject { in: conversation, fetchLinkPreview: shouldFetchLinkPreview ) - try imageMessageUseCase.invoke(withImageData: data, in: conversation) + let image = SendableImage( + name: nil, + utType: nil, + data: data + ) + try imageMessageUseCase.invoke( + image: image, + in: conversation + ) } catch { Logging.messageProcessing .warn("Failed to append text message with image data. Reason: \(error.localizedDescription)") diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+Camera.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+Camera.swift index 20df0b58ebc..3b80925427b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+Camera.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+Camera.swift @@ -102,11 +102,13 @@ extension ConversationInputBarViewController: CameraKeyboardViewControllerDelega func cameraKeyboardViewController( _ controller: CameraKeyboardViewController, - didSelectImageData imageData: Data, - isFromCamera: Bool, - uti: String? + didSelectImage image: SendableImage, + isFromCamera: Bool ) { - showConfirmationForImage(imageData, isFromCamera: isFromCamera, uti: uti) + showConfirmationForImage( + image, + isFromCamera: isFromCamera + ) } @objc @@ -150,16 +152,16 @@ extension ConversationInputBarViewController: CameraKeyboardViewControllerDelega } func showConfirmationForImage( - _ imageData: Data, - isFromCamera: Bool, - uti: String? + _ image: SendableImage, + isFromCamera: Bool ) { - let mediaAsset: MediaAsset = if uti == UTType.gif.identifier, - let gifImage = FLAnimatedImage(animatedGIFData: imageData), - gifImage.frameCount > 1 { + let mediaAsset: MediaAsset = if + image.utType == .gif, + let gifImage = FLAnimatedImage(animatedGIFData: image.data), + gifImage.frameCount > 1 { gifImage } else { - UIImage(data: imageData) ?? UIImage() + UIImage(data: image.data) ?? UIImage() } let context = ConfirmAssetViewController.Context( @@ -168,22 +170,25 @@ extension ConversationInputBarViewController: CameraKeyboardViewControllerDelega guard let self else { return } dismiss(animated: true) { self.writeToSavedPhotoAlbumIfNecessary( - imageData: imageData, + imageData: image.data, isFromCamera: isFromCamera ) - let dataToSend = editedImage?.pngData() ?? imageData + let dataToSend = editedImage?.pngData() ?? image.data + let utType: UTType = if editedImage != nil { + .png + } else { + image.utType ?? .image + } if DeveloperFlag.wireCells.isOn, self.conversation.isCellsEnabled { - let type: UTType = if editedImage != nil { - .png - } else if let uti, let utType = UTType(uti) { - utType - } else { - .image - } - self.uploadDraft(data: dataToSend, type: type) + self.uploadDraft(data: dataToSend, type: utType) } else { + let image = SendableImage( + name: nil, + utType: utType, + data: dataToSend + ) self.sendController.sendMessage( - withImageData: dataToSend, + image: image, userSession: self.userSession ) } @@ -290,7 +295,10 @@ extension ConversationInputBarViewController: UIVideoEditorControllerDelegate { extension ConversationInputBarViewController: CanvasViewControllerDelegate { - func canvasViewController(_ canvasViewController: CanvasViewController, didExportImage image: UIImage) { + func canvasViewController( + _ canvasViewController: CanvasViewController, + didExportImage image: UIImage + ) { hideCameraKeyboardViewController { [weak self] in guard let self else { return } @@ -299,7 +307,15 @@ extension ConversationInputBarViewController: CanvasViewControllerDelegate { if DeveloperFlag.wireCells.isOn, self.conversation.isCellsEnabled { self.uploadDraft(data: imageData, type: .png) } else { - self.sendController.sendMessage(withImageData: imageData, userSession: self.userSession) + let image = SendableImage( + name: nil, + utType: .png, + data: imageData + ) + self.sendController.sendMessage( + image: image, + userSession: self.userSession + ) } } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+DragAndDrop.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+DragAndDrop.swift index 8ee81a80818..9311c2b7bc4 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+DragAndDrop.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+DragAndDrop.swift @@ -48,8 +48,13 @@ extension ConversationInputBarViewController: UIDropInteractionDelegate { onConfirm: { [unowned self] _ in dismiss(animated: true) { if let draggedImageData = draggedImage.pngData() { + let image = SendableImage( + name: nil, + utType: .png, + data: draggedImageData + ) self.sendController.sendMessage( - withImageData: draggedImageData, + image: image, userSession: self.userSession ) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController/ConversationInputBarViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController/ConversationInputBarViewController.swift index 896df161849..eec618e1a00 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController/ConversationInputBarViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController/ConversationInputBarViewController.swift @@ -659,8 +659,20 @@ final class ConversationInputBarViewController: UIViewController, } func postImage(_ image: MediaAsset) { - guard let data = image.imageData else { return } - sendController.sendMessage(withImageData: data, userSession: userSession) + guard let data = image.imageData else { + return + } + // The image is a `UIImage` instances that came from + // the clipboard, so we don't have a name or UTType. + let image = SendableImage( + name: nil, + utType: nil, + data: data + ) + sendController.sendMessage( + image: image, + userSession: userSession + ) } func deallocateUnusedInputControllers() { @@ -905,7 +917,7 @@ extension ConversationInputBarViewController: GiphySearchViewControllerDelegate messageText, mentions: [], userSession: self.userSession, - withImageData: imageData + withGIFImageData: imageData ) } } @@ -959,15 +971,28 @@ extension ConversationInputBarViewController: UIImagePickerControllerDelegate { } // In case of picking from the camera, the iOS controller is showing it's own confirmation screen. parent?.dismiss(animated: true) { + let image = SendableImage( + name: nil, + utType: .jpeg, + data: jpegData + ) self.sendController.sendMessage( - withImageData: jpegData, + image: image, userSession: self.userSession, completion: nil ) } } else { parent?.dismiss(animated: true) { - self.showConfirmationForImage(jpegData, isFromCamera: false, uti: UTType.jpeg.identifier) + let image = SendableImage( + name: nil, + utType: .jpeg, + data: jpegData + ) + self.showConfirmationForImage( + image, + isFromCamera: false + ) } }