diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 624e464424..6941e6a985 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -310,7 +310,8 @@ 45360B901F9527DA00FA666C /* SearcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8F1F9527DA00FA666C /* SearcherTest.swift */; }; 45360B911F952AA900FA666C /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; }; - 4542DF52208B82E9007B4E76 /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadModel.swift */; }; + 4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */; }; + 4542DF52208B82E9007B4E76 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; }; 4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */; }; 45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; }; 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454A84032059C787008B8C75 /* MediaTileViewController.swift */; }; @@ -939,7 +940,8 @@ 45360B8F1F9527DA00FA666C /* SearcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearcherTest.swift; sourceTree = ""; }; 4539B5851F79348F007141FF /* PushRegistrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = ""; }; 453CC0361D08E1A60040EBA3 /* sn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sn; path = translations/sn.lproj/Localizable.strings; sourceTree = ""; }; - 4542DF51208B82E9007B4E76 /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = ""; }; + 4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactShareViewModel.swift; sourceTree = ""; }; + 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadViewModel.swift; sourceTree = ""; }; 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = ""; }; 454A84032059C787008B8C75 /* MediaTileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaTileViewController.swift; sourceTree = ""; }; @@ -1806,6 +1808,7 @@ 450DF2061E0DD28D003D14BE /* UserInterface */ = { isa = PBXGroup; children = ( + 4541B719209D2D860008608F /* ViewModels */, 450DF2071E0DD29E003D14BE /* Notifications */, 34FD936E1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.h */, 34FD936F1E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m */, @@ -1827,8 +1830,6 @@ 45194F911FD7214600333B2C /* Models */ = { isa = PBXGroup; children = ( - 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */, - 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */, 34C42D621F4734ED0072EC04 /* OWSContactOffersInteraction.h */, 34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */, 34C42D641F4734ED0072EC04 /* TSUnreadIndicatorInteraction.h */, @@ -1869,6 +1870,7 @@ 453518931FC63DBF00210559 /* SignalMessaging */ = { isa = PBXGroup; children = ( + 4541B71C209D3B4F0008608F /* ViewModels */, 45194F911FD7214600333B2C /* Models */, 451F8A361FD7115D005CB9DA /* ViewControllers */, 454A96571FD600B4008D2A0E /* attachments */, @@ -1886,6 +1888,24 @@ path = SignalMessaging; sourceTree = ""; }; + 4541B719209D2D860008608F /* ViewModels */ = { + isa = PBXGroup; + children = ( + 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 4541B71C209D3B4F0008608F /* ViewModels */ = { + isa = PBXGroup; + children = ( + 4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */, + 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */, + 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */, + ); + path = ViewModels; + sourceTree = ""; + }; 45464DB81DFA03D8001D3FD6 /* Signaling */ = { isa = PBXGroup; children = ( @@ -1931,7 +1951,6 @@ 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */, 458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */, 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */, - 4542DF51208B82E9007B4E76 /* ThreadModel.swift */, ); path = Models; sourceTree = ""; @@ -3107,6 +3126,7 @@ 4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */, 3438226A209B63500094FEB7 /* EditContactShareNameViewController.swift in Sources */, 346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */, + 4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */, 4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */, 346129D21FD2085A00532771 /* CommonStrings.swift in Sources */, 45F59A082028E4FB00E8D2B0 /* OWSAudioSession.swift in Sources */, @@ -3147,7 +3167,7 @@ 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, 34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */, 340FC8B8204DAC8D007AEB0F /* AddToGroupViewController.m in Sources */, - 4542DF52208B82E9007B4E76 /* ThreadModel.swift in Sources */, + 4542DF52208B82E9007B4E76 /* ThreadViewModel.swift in Sources */, 341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */, 340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */, 34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */, diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index 8c03decff0..b8d51e1a88 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -65,7 +65,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate var reachability: Reachability? - private let contact: OWSContact + private let contactShare: ContactShareViewModel // MARK: - Initializers @@ -74,9 +74,9 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate fatalError("Unimplemented") } - required init(contact: OWSContact) { + required init(contactShare: ContactShareViewModel) { contactsManager = Environment.current().contactsManager - self.contact = contact + self.contactShare = contactShare super.init(nibName: nil, bundle: nil) @@ -140,7 +140,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func updateMode() { SwiftAssertIsOnMainThread(#function) - guard phoneNumbersForContact().count > 0 else { + guard contactShare.phoneNumberStrings.count > 0 else { viewMode = .noPhoneNumber return } @@ -159,7 +159,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func systemContactsWithSignalAccountsForContact() -> [String] { SwiftAssertIsOnMainThread(#function) - return phoneNumbersForContact().filter({ (phoneNumber) -> Bool in + return contactShare.phoneNumberStrings.filter({ (phoneNumber) -> Bool in return contactsManager.hasSignalAccount(forRecipientId: phoneNumber) }) } @@ -167,21 +167,11 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate private func systemContactsForContact() -> [String] { SwiftAssertIsOnMainThread(#function) - return phoneNumbersForContact().filter({ (phoneNumber) -> Bool in + return contactShare.phoneNumberStrings.filter({ (phoneNumber) -> Bool in return contactsManager.allContactsMap[phoneNumber] != nil }) } - private func phoneNumbersForContact() -> [String] { - SwiftAssertIsOnMainThread(#function) - - var result = [String]() - for phoneNumber in contact.phoneNumbers { - result.append(phoneNumber.phoneNumber) - } - return result - } - private func updateContent() { SwiftAssertIsOnMainThread(#function) @@ -259,16 +249,9 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate backButton.addSubview(backIconView) backIconView.autoCenterInSuperview() - // TODO: Use actual avatar. - let avatarSize = CGFloat(100) - + let avatarSize: CGFloat = 100 let avatarView = AvatarImageView() - // TODO: What's the best colorSeed value to use? - let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contact.displayName, - colorSeed: contact.displayName, - diameter: UInt(avatarSize), - contactsManager: contactsManager) - avatarView.image = avatarBuilder.build() + avatarView.image = contactShare.getAvatarImage(diameter: avatarSize, contactsManager: contactsManager) topView.addSubview(avatarView) avatarView.autoPin(toTopLayoutGuideOf: self, withInset: 20) avatarView.autoHCenterInSuperview() @@ -276,7 +259,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate avatarView.autoSetDimension(.height, toSize: avatarSize) let nameLabel = UILabel() - nameLabel.text = contact.displayName + nameLabel.text = contactShare.displayName nameLabel.font = UIFont.ows_dynamicTypeTitle2.ows_bold() nameLabel.textColor = UIColor.black nameLabel.lineBreakMode = .byTruncatingTail @@ -288,7 +271,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate var lastView: UIView = nameLabel - if let firstPhoneNumber = contact.phoneNumbers.first { + if let firstPhoneNumber = contactShare.phoneNumbers.first { let phoneNumberLabel = UILabel() phoneNumberLabel.text = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: firstPhoneNumber.phoneNumber) phoneNumberLabel.font = UIFont.ows_dynamicTypeCaption2 @@ -387,7 +370,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate // action:#selector(didPressShareContact))) // } - for phoneNumber in contact.phoneNumbers { + for phoneNumber in contactShare.phoneNumbers { let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber.phoneNumber) rows.append(createNameValueRow(name: phoneNumber.localizedLabel(), @@ -401,7 +384,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate })) } - for email in contact.emails { + for email in contactShare.emails { rows.append(createNameValueRow(name: email.localizedLabel(), value: email.email, actionBlock: { @@ -599,7 +582,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate OWSAlerts.showErrorAlert(message: NSLocalizedString("UNSUPPORTED_FEATURE_ERROR", comment: "")) return } - let phoneNumbers = phoneNumbersForContact() + let phoneNumbers = contactShare.phoneNumberStrings guard phoneNumbers.count > 0 else { owsFail("\(logTag) no phone numbers.") return @@ -639,8 +622,8 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return } - guard let systemContact = OWSContacts.systemContact(for: contact) else { - owsFail("\(logTag) Could not derive system contact.") + guard let systemContact = OWSContacts.systemContact(for: contactShare.dbRecord) else { + owsFail("\(logTag) Could not derive system contactShare.") return } @@ -678,7 +661,7 @@ class ContactViewController: OWSViewController, CNContactViewControllerDelegate return } - guard let firstPhoneNumber = contact.phoneNumbers.first else { + guard let firstPhoneNumber = contactShare.phoneNumbers.first else { owsFail("\(logTag) Missing phone number.") return } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.h index 3a62a08135..c374c9fcfc 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.h @@ -4,11 +4,11 @@ NS_ASSUME_NONNULL_BEGIN -@class OWSContact; +@class ContactShareViewModel; @interface OWSContactShareView : UIView -- (instancetype)initWithContactShare:(OWSContact *)contactShare isIncoming:(BOOL)isIncoming; +- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare isIncoming:(BOOL)isIncoming; - (void)createContents; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m index f56f63bb58..cfd9a4c473 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSContactShareView.m @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSContactShareView () -@property (nonatomic) OWSContact *contactShare; +@property (nonatomic) ContactShareViewModel *contactShare; @property (nonatomic) BOOL isIncoming; @end @@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN @implementation OWSContactShareView -- (instancetype)initWithContactShare:(OWSContact *)contactShare isIncoming:(BOOL)isIncoming +- (instancetype)initWithContactShare:(ContactShareViewModel *)contactShare isIncoming:(BOOL)isIncoming { self = [super init]; @@ -38,6 +38,11 @@ NS_ASSUME_NONNULL_BEGIN return self; } +- (OWSContactsManager *)contactsManager +{ + return [Environment current].contactsManager; +} + #pragma mark - - (CGFloat)iconHMargin @@ -105,15 +110,10 @@ NS_ASSUME_NONNULL_BEGIN [contentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.vMargin]; [contentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.vMargin]; - // TODO: Use the contact's avatar if present and downloaded. AvatarImageView *avatarView = [AvatarImageView new]; - // TODO: What's the best colorSeed value to use? - OWSAvatarBuilder *avatarBuilder = - [[OWSContactAvatarBuilder alloc] initWithNonSignalName:self.contactShare.displayName - colorSeed:self.contactShare.displayName - diameter:(NSUInteger)self.iconSize - contactsManager:[Environment current].contactsManager]; - avatarView.image = [avatarBuilder build]; + avatarView.image = + [self.contactShare getAvatarImageWithDiameter:self.iconSize contactsManager:self.contactsManager]; + [avatarView autoSetDimension:ALDimensionWidth toSize:self.iconSize]; [avatarView autoSetDimension:ALDimensionHeight toSize:self.iconSize]; [avatarView setCompressionResistanceHigh]; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index b1357f4773..619acde572 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -65,6 +65,7 @@ #import #import #import +#import #import #import #import @@ -2087,7 +2088,7 @@ typedef enum : NSUInteger { OWSAssert(conversationItem.contactShare); OWSAssert([conversationItem.interaction isKindOfClass:[TSMessage class]]); - ContactViewController *view = [[ContactViewController alloc] initWithContact:conversationItem.contactShare]; + ContactViewController *view = [[ContactViewController alloc] initWithContactShare:conversationItem.contactShare]; [self.navigationController pushViewController:view animated:YES]; } @@ -2948,7 +2949,7 @@ typedef enum : NSUInteger { } } -- (void)sendContactShare:(OWSContact *)contactShare +- (void)sendContactShare:(ContactShareViewModel *)contactShare { OWSAssertIsOnMainThread(); OWSAssert(contactShare); @@ -2956,16 +2957,23 @@ typedef enum : NSUInteger { DDLogVerbose(@"%@ Sending contact share.", self.logTag); BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; - TSOutgoingMessage *message = [ThreadUtil sendMessageWithContactShare:contactShare - inThread:self.thread - messageSender:self.messageSender - completion:nil]; - [self messageWasSent:message]; - - if (didAddToProfileWhitelist) { - [self ensureDynamicInteractions]; + [self.editingDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + if (contactShare.avatarImage) { + [contactShare.dbRecord saveAvatarImage:contactShare.avatarImage transaction:transaction]; + } } + completionBlock:^{ + TSOutgoingMessage *message = [ThreadUtil sendMessageWithContactShare:contactShare.dbRecord + inThread:self.thread + messageSender:self.messageSender + completion:nil]; + [self messageWasSent:message]; + + if (didAddToProfileWhitelist) { + [self ensureDynamicInteractions]; + } + }]; } - (NSURL *)videoTempFolder @@ -4942,12 +4950,28 @@ interactionControllerForAnimationController:(id *)allSendContactShareActions:(TSThread *)thread includeLabels:(BOOL)includeLabels @@ -3128,7 +3126,7 @@ typedef OWSContact * (^OWSContactBlock)(void); [actions addObject:[self sendContactShareMessageAction:thread label:@"Name & Number" - contactBlock:^{ + contactBlock:^(YapDatabaseReadWriteTransaction *transaction) { OWSContact *contact = [OWSContact new]; contact.givenName = @"Alice"; OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new]; @@ -3141,7 +3139,7 @@ typedef OWSContact * (^OWSContactBlock)(void); }]]; [actions addObject:[self sendContactShareMessageAction:thread label:@"Name & Email" - contactBlock:^{ + contactBlock:^(YapDatabaseReadWriteTransaction *transaction) { OWSContact *contact = [OWSContact new]; contact.givenName = @"Bob"; OWSContactEmail *email = [OWSContactEmail new]; @@ -3154,7 +3152,7 @@ typedef OWSContact * (^OWSContactBlock)(void); }]]; [actions addObject:[self sendContactShareMessageAction:thread label:@"Complicated" - contactBlock:^{ + contactBlock:^(YapDatabaseReadWriteTransaction *transaction){ OWSContact *contact = [OWSContact new]; contact.givenName = @"Alice"; contact.familyName = @"Carol"; @@ -3206,12 +3204,15 @@ typedef OWSContact * (^OWSContactBlock)(void); address2, ]; - // TODO: Avatar + UIImage *avatarImage = + [OWSAvatarBuilder buildRandomAvatarWithDiameter:200]; + [contact saveAvatarImage:avatarImage transaction:transaction]; + return contact; }]]; [actions addObject:[self sendContactShareMessageAction:thread label:@"Long values" - contactBlock:^{ + contactBlock:^(YapDatabaseReadWriteTransaction *transaction) { OWSContact *contact = [OWSContact new]; contact.givenName = @"Bobasdjasdlkjasldkjas"; contact.familyName = @"Bobasdjasdlkjasldkjas"; @@ -3225,7 +3226,7 @@ typedef OWSContact * (^OWSContactBlock)(void); }]]; [actions addObject:[self sendContactShareMessageAction:thread label:@"System Contact w/o Signal" - contactBlock:^{ + contactBlock:^(YapDatabaseReadWriteTransaction *transaction) { OWSContact *contact = [OWSContact new]; contact.givenName = @"Add Me To Your Contacts"; OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new]; @@ -3238,7 +3239,7 @@ typedef OWSContact * (^OWSContactBlock)(void); }]]; [actions addObject:[self sendContactShareMessageAction:thread label:@"System Contact w. Signal" - contactBlock:^{ + contactBlock:^(YapDatabaseReadWriteTransaction *transaction) { OWSContact *contact = [OWSContact new]; contact.givenName = @"Add Me To Your Contacts"; OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new]; @@ -3583,9 +3584,9 @@ typedef OWSContact * (^OWSContactBlock)(void); TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:phoneNumber.toE164]; [self sendFakeMessages:messageCount thread:contactThread]; - DDLogError(@"Create fake thread: %@, interactions: %tu", + DDLogError(@"Create fake thread: %@, interactions: %lu", phoneNumber.toE164, - contactThread.numberOfInteractions); + (unsigned long)contactThread.numberOfInteractions); }]; } @@ -3611,7 +3612,10 @@ typedef OWSContact * (^OWSContactBlock)(void); [self sendFakeMessages:batchSize thread:thread isTextOnly:isTextOnly transaction:transaction]; }]; remainder -= batchSize; - DDLogInfo(@"%@ sendFakeMessages %td / %tu", self.logTag, counter - remainder, counter); + DDLogInfo(@"%@ sendFakeMessages %lu / %lu", + self.logTag, + (unsigned long)(counter - remainder), + (unsigned long)counter); } }); } @@ -3623,7 +3627,7 @@ typedef OWSContact * (^OWSContactBlock)(void); isTextOnly:(BOOL)isTextOnly transaction:(YapDatabaseReadWriteTransaction *)transaction { - DDLogInfo(@"%@ sendFakeMessages: %tu", self.logTag, counter); + DDLogInfo(@"%@ sendFakeMessages: %lu", self.logTag, (unsigned long)counter); for (NSUInteger i = 0; i < counter; i++) { NSString *randomText = [self randomText]; @@ -3775,7 +3779,7 @@ typedef OWSContact * (^OWSContactBlock)(void); { OWSAssert(thread); - DDLogInfo(@"%@ injectIncomingMessageInThread: %tu", self.logTag, counter); + DDLogInfo(@"%@ injectIncomingMessageInThread: %lu", self.logTag, (unsigned long)counter); NSString *randomText = [self randomText]; NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText]; @@ -4167,7 +4171,8 @@ typedef OWSContact * (^OWSContactBlock)(void); inThread:thread authorId:thread.recipientIdentifiers.firstObject sourceDeviceId:0 - messageBody:[NSString stringWithFormat:@"Should disappear 60s after %tu", now] + messageBody:[NSString + stringWithFormat:@"Should disappear 60s after %lu", (unsigned long)now] attachmentIds:[NSMutableArray new] expiresInSeconds:60 quotedMessage:nil diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index f9c42ba7c4..9c479ca231 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -613,11 +613,11 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele } func didTapContactShare(_ viewItem: ConversationViewItem) { - guard let contact = viewItem.contactShare() else { + guard let contactShare = viewItem.contactShare else { owsFail("\(logTag) missing contact.") return } - let contactViewController = ContactViewController(contact: contact) + let contactViewController = ContactViewController(contactShare: contactShare) self.navigationController?.pushViewController(contactViewController, animated: true) } diff --git a/Signal/src/Models/ThreadModel.swift b/Signal/src/ViewModels/ThreadViewModel.swift similarity index 100% rename from Signal/src/Models/ThreadModel.swift rename to Signal/src/ViewModels/ThreadViewModel.swift diff --git a/SignalMessaging/ViewModels/ContactShareViewModel.swift b/SignalMessaging/ViewModels/ContactShareViewModel.swift new file mode 100644 index 0000000000..5ba8471483 --- /dev/null +++ b/SignalMessaging/ViewModels/ContactShareViewModel.swift @@ -0,0 +1,132 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public class ContactShareViewModel: NSObject { + + public let dbRecord: OWSContact + public let avatarImage: UIImage? + + public required init(contactShareRecord: OWSContact, avatarImage: UIImage?) { + self.dbRecord = contactShareRecord + self.avatarImage = avatarImage + } + + public convenience init(contactShareRecord: OWSContact, transaction: YapDatabaseReadTransaction) { + if let avatarAttachment = contactShareRecord.avatarAttachment(with: transaction) as? TSAttachmentStream { + self.init(contactShareRecord: contactShareRecord, avatarImage: avatarAttachment.image()) + } else { + self.init(contactShareRecord: contactShareRecord, avatarImage: nil) + } + } + + public func getAvatarImage(diameter: CGFloat, contactsManager: OWSContactsManager) -> UIImage { + if let avatarImage = avatarImage { + return avatarImage + } + + // TODO: What's the best colorSeed value to use? + let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: displayName, + colorSeed: displayName, + diameter: UInt(diameter), + contactsManager: contactsManager) + return avatarBuilder.build() + } + + // MARK: Delegated -> dbRecord + + public var addresses: [OWSContactAddress] { + get { + return dbRecord.addresses + } + set { + return dbRecord.addresses = newValue + } + } + + public var emails: [OWSContactEmail] { + get { + return dbRecord.emails + } + set { + dbRecord.emails = newValue + } + } + + public var phoneNumbers: [OWSContactPhoneNumber] { + get { + return dbRecord.phoneNumbers + } + set { + dbRecord.phoneNumbers = newValue + } + } + + public var phoneNumberStrings: [String] { + return phoneNumbers.map { $0.phoneNumber } + } + + public var displayName: String { + return dbRecord.displayName + } + + public var ows_isValid: Bool { + return dbRecord.ows_isValid() + } + + public var namePrefix: String? { + return dbRecord.namePrefix + } + + public var givenName: String? { + return dbRecord.givenName + } + + public var middleName: String? { + return dbRecord.middleName + } + + public var familyName: String? { + return dbRecord.familyName + } + + public var nameSuffix: String? { + return dbRecord.nameSuffix + } + + public var isProfileAvatar: Bool { + return dbRecord.isProfileAvatar + } + + public func copy(withNamePrefix namePrefix: String?, + givenName: String?, + middleName: String?, + familyName: String?, + nameSuffix: String?) -> ContactShareViewModel { + + // TODO move the `copy` logic into the view model? + let newDbRecord = dbRecord.copy(withNamePrefix: namePrefix, givenName: givenName, middleName: middleName, familyName: familyName, nameSuffix: nameSuffix) + + return ContactShareViewModel(contactShareRecord: newDbRecord, avatarImage: self.avatarImage) + } + + public func newContact(withNamePrefix namePrefix: String?, + givenName: String?, + middleName: String?, + familyName: String?, + nameSuffix: String?) -> ContactShareViewModel { + + // TODO move the `newContact` logic into the view model? + let newDbRecord = dbRecord.newContact(withNamePrefix: namePrefix, + givenName: givenName, + middleName: middleName, + familyName: familyName, + nameSuffix: nameSuffix) + + return ContactShareViewModel(contactShareRecord: newDbRecord, avatarImage: self.avatarImage) + } + +} diff --git a/SignalMessaging/Models/OWSQuotedReplyModel.h b/SignalMessaging/ViewModels/OWSQuotedReplyModel.h similarity index 100% rename from SignalMessaging/Models/OWSQuotedReplyModel.h rename to SignalMessaging/ViewModels/OWSQuotedReplyModel.h diff --git a/SignalMessaging/Models/OWSQuotedReplyModel.m b/SignalMessaging/ViewModels/OWSQuotedReplyModel.m similarity index 98% rename from SignalMessaging/Models/OWSQuotedReplyModel.m rename to SignalMessaging/ViewModels/OWSQuotedReplyModel.m index 82ba9f59f9..57ed325c0a 100644 --- a/SignalMessaging/Models/OWSQuotedReplyModel.m +++ b/SignalMessaging/ViewModels/OWSQuotedReplyModel.m @@ -53,8 +53,9 @@ } } else if (attachmentInfo.thumbnailAttachmentPointerId) { // download failed, or hasn't completed yet. - TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentInfo.thumbnailAttachmentPointerId transaction:transaction]; - + TSAttachment *attachment = + [TSAttachment fetchObjectWithUniqueID:attachmentInfo.thumbnailAttachmentPointerId transaction:transaction]; + if ([attachment isKindOfClass:[TSAttachmentPointer class]]) { attachmentPointer = (TSAttachmentPointer *)attachment; if (attachmentPointer.state == TSAttachmentPointerStateFailed) { diff --git a/SignalMessaging/attachments/ApproveContactShareViewController.swift b/SignalMessaging/attachments/ApproveContactShareViewController.swift index 5bddec249c..2b79a29f2e 100644 --- a/SignalMessaging/attachments/ApproveContactShareViewController.swift +++ b/SignalMessaging/attachments/ApproveContactShareViewController.swift @@ -7,8 +7,8 @@ import SignalServiceKit @objc public protocol ApproveContactShareViewControllerDelegate: class { - func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didApproveContactShare contactShare: OWSContact) - func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didCancelContactShare contactShare: OWSContact) + func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didApproveContactShare contactShare: ContactShareViewModel) + func approveContactShare(_ approveContactShare: ApproveContactShareViewController, didCancelContactShare contactShare: ContactShareViewModel) } protocol ContactShareField: class { @@ -19,7 +19,7 @@ protocol ContactShareField: class { func setIsIncluded(_ isIncluded: Bool) - func applyToContact(contact: OWSContact) + func applyToContact(contact: ContactShareViewModel) } // MARK: - @@ -48,7 +48,7 @@ class ContactShareFieldBase: NSObject, Contac isIncludedFlag = isIncluded } - func applyToContact(contact: OWSContact) { + func applyToContact(contact: ContactShareViewModel) { preconditionFailure("This method must be overridden") } } @@ -57,7 +57,7 @@ class ContactShareFieldBase: NSObject, Contac class ContactSharePhoneNumber: ContactShareFieldBase { - override func applyToContact(contact: OWSContact) { + override func applyToContact(contact: ContactShareViewModel) { assert(isIncluded()) var values = [OWSContactPhoneNumber]() @@ -71,7 +71,7 @@ class ContactSharePhoneNumber: ContactShareFieldBase { class ContactShareEmail: ContactShareFieldBase { - override func applyToContact(contact: OWSContact) { + override func applyToContact(contact: ContactShareViewModel) { assert(isIncluded()) var values = [OWSContactEmail]() @@ -85,7 +85,7 @@ class ContactShareEmail: ContactShareFieldBase { class ContactShareAddress: ContactShareFieldBase { - override func applyToContact(contact: OWSContact) { + override func applyToContact(contact: ContactShareViewModel) { assert(isIncluded()) var values = [OWSContactAddress]() @@ -185,7 +185,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh let contactsManager: OWSContactsManager - var contactShare: OWSContact + var contactShare: ContactShareViewModel var fieldViews = [ContactShareFieldView]() @@ -199,7 +199,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh } @objc - required public init(contactShare: OWSContact, contactsManager: OWSContactsManager, delegate: ApproveContactShareViewControllerDelegate) { + required public init(contactShare: ContactShareViewModel, contactsManager: OWSContactsManager, delegate: ApproveContactShareViewControllerDelegate) { self.contactsManager = contactsManager self.contactShare = contactShare self.delegate = delegate @@ -277,7 +277,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh // TODO: Surface error with resolution to user if not. func canShareContact() -> Bool { - return contactShare.ows_isValid() + return contactShare.ows_isValid } func updateNavigationBar() { @@ -471,7 +471,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh // MARK: - - func filteredContactShare() -> OWSContact { + func filteredContactShare() -> ContactShareViewModel { let result = self.contactShare.newContact(withNamePrefix: self.contactShare.namePrefix, givenName: self.contactShare.givenName, middleName: self.contactShare.middleName, @@ -498,7 +498,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh } let filteredContactShare = self.filteredContactShare() - assert(filteredContactShare.ows_isValid()) + assert(filteredContactShare.ows_isValid) delegate.approveContactShare(self, didApproveContactShare: filteredContactShare) } @@ -523,7 +523,7 @@ public class ApproveContactShareViewController: OWSViewController, EditContactSh // MARK: - EditContactShareNameViewControllerDelegate - public func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: OWSContact) { + public func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: ContactShareViewModel) { self.contactShare = contactShare nameLabel.text = contactShare.displayName diff --git a/SignalMessaging/attachments/EditContactShareNameViewController.swift b/SignalMessaging/attachments/EditContactShareNameViewController.swift index b943b60c99..edf6db9fe0 100644 --- a/SignalMessaging/attachments/EditContactShareNameViewController.swift +++ b/SignalMessaging/attachments/EditContactShareNameViewController.swift @@ -109,7 +109,7 @@ class ContactNameFieldView: UIView { @objc public protocol EditContactShareNameViewControllerDelegate: class { - func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: OWSContact) + func editContactShareNameView(_ editContactShareNameView: EditContactShareNameViewController, didEditContactShare contactShare: ContactShareViewModel) } // MARK: - @@ -118,7 +118,7 @@ public protocol EditContactShareNameViewControllerDelegate: class { public class EditContactShareNameViewController: OWSViewController, ContactNameFieldViewDelegate { weak var delegate: EditContactShareNameViewControllerDelegate? - let contactShare: OWSContact + let contactShare: ContactShareViewModel var namePrefixView: ContactNameFieldView! var givenNameView: ContactNameFieldView! @@ -136,7 +136,7 @@ public class EditContactShareNameViewController: OWSViewController, ContactNameF } @objc - required public init(contactShare: OWSContact, delegate: EditContactShareNameViewControllerDelegate) { + required public init(contactShare: ContactShareViewModel, delegate: EditContactShareNameViewControllerDelegate) { self.contactShare = contactShare self.delegate = delegate diff --git a/SignalMessaging/contacts/OWSContactsManager.h b/SignalMessaging/contacts/OWSContactsManager.h index 53c3c34b52..a26ca8b3df 100644 --- a/SignalMessaging/contacts/OWSContactsManager.h +++ b/SignalMessaging/contacts/OWSContactsManager.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import @@ -77,6 +77,9 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; - (nullable NSString *)nameFromSystemContactsForRecipientId:(NSString *)recipientId; - (NSString *)stringForConversationTitleWithPhoneIdentifier:(NSString *)recipientId; +- (nullable UIImage *)systemContactImageForPhoneIdentifier:(nullable NSString *)identifier; +- (nullable UIImage *)profileImageForPhoneIdentifier:(nullable NSString *)identifier; + - (nullable UIImage *)imageForPhoneIdentifier:(nullable NSString *)identifier; - (NSAttributedString *)formattedDisplayNameForSignalAccount:(SignalAccount *)signalAccount font:(UIFont *_Nonnull)font; - (NSAttributedString *)formattedFullNameForRecipientId:(NSString *)recipientId font:(UIFont *)font; diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 7cda1c4faa..27b998592f 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -707,19 +707,32 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification return [self signalAccountForRecipientId:recipientId] != nil; } -- (UIImage *_Nullable)imageForPhoneIdentifier:(NSString *_Nullable)identifier + +- (UIImage *_Nullable)systemContactImageForPhoneIdentifier:(NSString *_Nullable)identifier { Contact *contact = self.allContactsMap[identifier]; if (!contact) { + // If we haven't loaded system contacts yet, we may have a cached + // copy in the db contact = [self signalAccountForRecipientId:identifier].contact; } + return contact.image; +} + +- (nullable UIImage *)profileImageForPhoneIdentifier:(nullable NSString *)identifier +{ + return [self.profileManager profileAvatarForRecipientId:identifier]; +} + +- (UIImage *_Nullable)imageForPhoneIdentifier:(NSString *_Nullable)identifier +{ // Prefer the contact image from the local address book if available - UIImage *_Nullable image = contact.image; + UIImage *_Nullable image = [self systemContactImageForPhoneIdentifier:identifier]; // Else try to use the image from their profile if (image == nil) { - image = [self.profileManager profileAvatarForRecipientId:identifier]; + image = [self profileImageForPhoneIdentifier:identifier]; } return image; diff --git a/SignalMessaging/utils/OWSAvatarBuilder.m b/SignalMessaging/utils/OWSAvatarBuilder.m index 55987acd74..49433c15c7 100644 --- a/SignalMessaging/utils/OWSAvatarBuilder.m +++ b/SignalMessaging/utils/OWSAvatarBuilder.m @@ -89,14 +89,14 @@ NS_ASSUME_NONNULL_BEGIN - (nullable UIImage *)buildSavedImage { - OWSRaiseException( - NSInternalInconsistencyException, @"You must override %@ in a subclass", NSStringFromSelector(_cmd)); + OWS_ABSTRACT_METHOD(); + return nil; } - (UIImage *)buildDefaultImage { - OWSRaiseException( - NSInternalInconsistencyException, @"You must override %@ in a subclass", NSStringFromSelector(_cmd)); + OWS_ABSTRACT_METHOD(); + return [UIImage new]; } @end diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h index 07b795bd98..fa616048e1 100644 --- a/SignalServiceKit/src/Contacts/TSThread.h +++ b/SignalServiceKit/src/Contacts/TSThread.h @@ -43,16 +43,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) NSArray *recipientIdentifiers; -#if TARGET_OS_IOS - -/** - * Returns the image representing the thread. Nil if not available. - * - * @return UIImage of the thread, or nil. - */ -- (nullable UIImage *)image; -#endif - #pragma mark Interactions /** diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m index 074dc7c0d1..7b4f17e733 100644 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ b/SignalServiceKit/src/Contacts/TSThread.m @@ -112,11 +112,6 @@ NS_ASSUME_NONNULL_BEGIN return @[]; } -- (nullable UIImage *)image -{ - return nil; -} - - (BOOL)hasSafetyNumbers { return NO; @@ -256,7 +251,9 @@ NS_ASSUME_NONNULL_BEGIN // who's test devices are constantly reinstalled. We could add a purpose-built DB view, // but I think in the real world this is rare to be a hotspot. if (missedCount > 50) { - DDLogWarn(@"%@ found last interaction for inbox after skipping %tu items", self.logTag, missedCount); + DDLogWarn(@"%@ found last interaction for inbox after skipping %lu items", + self.logTag, + (unsigned long)missedCount); } *stop = YES; } diff --git a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m index 9b4de20ed2..2570a0c4ef 100644 --- a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m +++ b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m @@ -120,15 +120,6 @@ NS_ASSUME_NONNULL_BEGIN return [[TextSecureKitEnv sharedEnv].contactsManager displayNameForPhoneIdentifier:self.contactIdentifier]; } -#if TARGET_OS_IPHONE - -- (nullable UIImage *)image -{ - UIImage *image = [[TextSecureKitEnv sharedEnv].contactsManager imageForPhoneIdentifier:self.contactIdentifier]; - return image; -} - -#endif + (NSString *)threadIdFromContactId:(NSString *)contactId { return [TSContactThreadPrefix stringByAppendingString:contactId]; diff --git a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m index 586993d714..22f338c2d8 100644 --- a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m +++ b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m @@ -110,7 +110,8 @@ NS_ASSUME_NONNULL_BEGIN [[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:attachmentPointer networkManager:self.networkManager]; - DDLogDebug(@"%@ downloading thumbnail for transcript: %tu", self.logTag, transcript.timestamp); + DDLogDebug( + @"%@ downloading thumbnail for transcript: %lu", self.logTag, (unsigned long)transcript.timestamp); [attachmentProcessor fetchAttachmentsForMessage:outgoingMessage transaction:transaction success:^(TSAttachmentStream *_Nonnull attachmentStream) { @@ -121,9 +122,9 @@ NS_ASSUME_NONNULL_BEGIN }]; } failure:^(NSError *_Nonnull error) { - DDLogWarn(@"%@ failed to fetch thumbnail for transcript: %tu with error: %@", + DDLogWarn(@"%@ failed to fetch thumbnail for transcript: %lu with error: %@", self.logTag, - transcript.timestamp, + (unsigned long)transcript.timestamp, error); }]; } diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h index 9bf4304e95..161058545f 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h @@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN +@class OWSSignalServiceProtosAttachmentPointer; @class TSAttachmentPointer; @class YapDatabaseReadWriteTransaction; @@ -79,6 +80,14 @@ NS_ASSUME_NONNULL_BEGIN - (nullable TSAttachmentStream *)cloneAsThumbnail; +#pragma mark - Protobuf + ++ (nullable OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(nullable NSString *)filename + isVoiceMessage:(BOOL)isVoiceMessage; + +- (OWSSignalServiceProtosAttachmentPointer *)buildProtoWithFilename:(nullable NSString *)filename + isVoiceMessage:(BOOL)isVoiceMessage; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index 432189565f..e1036cc439 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -703,6 +703,76 @@ NS_ASSUME_NONNULL_BEGIN return thumbnailAttachment; } +// MARK: Protobuf serialization + ++ (nullable OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(nullable NSString *)attachmentId + isVoiceMessage:(BOOL)isVoiceMessage +{ + OWSAssert(attachmentId.length > 0); + + // TODO we should past in a transaction, rather than sneakily generate one in `fetch...` to make sure we're + // getting a consistent view in the message sending process. A brief glance shows it touches quite a bit of code, + // but should be straight forward. + TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId]; + if (![attachment isKindOfClass:[TSAttachmentStream class]]) { + DDLogError(@"Unexpected type for attachment builder: %@", attachment); + return nil; + } + + TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; + return [attachmentStream buildProtoWithFilename:attachmentStream.sourceFilename isVoiceMessage:isVoiceMessage]; +} + +// MJK can we get rid of the filename / isVoiceMessage pararms? +// They seem to live (redundantly) on attachmentStream... ++ (nullable OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(NSString *)attachmentId + filename:(nullable NSString *)filename + isVoiceMessage:(BOOL)isVoiceMessage +{ + OWSAssert(attachmentId.length > 0); + + TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId]; + if (![attachment isKindOfClass:[TSAttachmentStream class]]) { + DDLogError(@"Unexpected type for attachment builder: %@", attachment); + return nil; + } + TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; + return [attachmentStream buildProtoWithFilename:filename isVoiceMessage:isVoiceMessage]; +} + +- (OWSSignalServiceProtosAttachmentPointer *)buildProtoWithFilename:(nullable NSString *)filename + isVoiceMessage:(BOOL)isVoiceMessage +{ + OWSSignalServiceProtosAttachmentPointerBuilder *builder = [OWSSignalServiceProtosAttachmentPointerBuilder new]; + + builder.id = self.serverId; + + OWSAssert(self.contentType.length > 0); + builder.contentType = self.contentType; + + DDLogVerbose(@"%@ Sending attachment with filename: '%@'", self.logTag, filename); + builder.fileName = filename; + + builder.size = self.byteCount; + builder.key = self.encryptionKey; + builder.digest = self.digest; + builder.flags = isVoiceMessage ? OWSSignalServiceProtosAttachmentPointerFlagsVoiceMessage : 0; + + if (self.shouldHaveImageSize) { + CGSize imageSize = self.imageSize; + if (imageSize.width < NSIntegerMax && imageSize.height < NSIntegerMax) { + NSInteger imageWidth = (NSInteger)round(imageSize.width); + NSInteger imageHeight = (NSInteger)round(imageSize.height); + if (imageWidth > 0 && imageHeight > 0) { + builder.width = (UInt32)imageWidth; + builder.height = (UInt32)imageHeight; + } + } + } + + return [builder build]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m index 0d99dcf14b..01490fc492 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m @@ -47,7 +47,7 @@ NS_ASSUME_NONNULL_BEGIN _quotedMessage = [TSQuotedMessage quotedMessageForDataMessage:_dataMessage thread:_thread relay:relay transaction:transaction]; - _contact = [OWSContacts contactForDataMessage:_dataMessage]; + _contact = [OWSContacts contactForDataMessage:_dataMessage relay:relay transaction:transaction]; return self; } diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h b/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h index e8f37c6cb5..913100f2f3 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h @@ -61,7 +61,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) NSArray *emails; @property (nonatomic) NSArray *addresses; -@property (nonatomic, nullable) TSAttachment *avatar; @property (nonatomic) BOOL isProfileAvatar; @end diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h index 91f4176347..0d4109f7d5 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.h @@ -7,10 +7,13 @@ NS_ASSUME_NONNULL_BEGIN @class CNContact; +@class OWSAttachmentInfo; @class OWSSignalServiceProtosDataMessage; @class OWSSignalServiceProtosDataMessage; @class OWSSignalServiceProtosDataMessageContact; @class TSAttachment; +@class TSAttachmentStream; +@class YapDatabaseReadTransaction; @class YapDatabaseReadWriteTransaction; extern BOOL kIsSendingContactSharesEnabled; @@ -109,8 +112,9 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value); @property (nonatomic, readonly) NSArray *emails; @property (nonatomic, readonly) NSArray *addresses; -// TODO: This is provisional. -@property (nonatomic, readonly, nullable) TSAttachment *avatar; +@property (nonatomic, readonly, nullable) NSString *avatarAttachmentId; +- (nullable TSAttachment *)avatarAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; +- (void)saveAvatarImage:(UIImage *)image transaction:(YapDatabaseReadWriteTransaction *)transaction; // "Profile" avatars should _not_ be saved to device contacts. @property (nonatomic, readonly) BOOL isProfileAvatar; @@ -160,7 +164,10 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value); #pragma mark - Proto Serialization + (nullable OWSSignalServiceProtosDataMessageContact *)protoForContact:(OWSContact *)contact; -+ (OWSContact *_Nullable)contactForDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage; + ++ (nullable OWSContact *)contactForDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage + relay:(nullable NSString *)relay + transaction:(YapDatabaseReadWriteTransaction *)transaction; @end diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m index 5e1130e550..fe979a0080 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContact.m @@ -3,13 +3,14 @@ // #import "OWSContact.h" +#import "MimeTypeUtil.h" #import "NSString+SSK.h" #import "OWSContact+Private.h" #import "OWSSignalServiceProtos.pb.h" #import "PhoneNumber.h" - -//#import "Contact.h" #import "TSAttachment.h" +#import "TSAttachmentPointer.h" +#import "TSAttachmentStream.h" #import @import Contacts; @@ -273,7 +274,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) @property (nonatomic) NSArray *emails; @property (nonatomic) NSArray *addresses; -@property (nonatomic, nullable) TSAttachment *avatar; +@property (nonatomic, nullable) NSString *avatarAttachmentId; @property (nonatomic) BOOL isProfileAvatar; @end @@ -456,6 +457,30 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) [self updateDisplayName]; } +#pragma mark - Avatar + +- (nullable TSAttachment *)avatarAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + return [TSAttachment fetchObjectWithUniqueID:self.avatarAttachmentId transaction:transaction]; +} + + +- (void)saveAvatarImage:(UIImage *)image transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + NSData *imageData = UIImageJPEGRepresentation(image, (CGFloat)0.9); + + TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:OWSMimeTypeImageJpeg + byteCount:imageData.length + sourceFilename:nil]; + + NSError *error; + BOOL success = [attachmentStream writeData:imageData error:&error]; + OWSAssert(success && !error); + + [attachmentStream saveWithTransaction:transaction]; + self.avatarAttachmentId = attachmentStream.uniqueId; +} + @end #pragma mark - @@ -829,7 +854,13 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) [contactBuilder addAddress:addressBuilder.build]; } - // TODO: avatar + if (contact.avatarAttachmentId != nil) { + OWSSignalServiceProtosDataMessageContactAvatarBuilder *avatarBuilder = + [OWSSignalServiceProtosDataMessageContactAvatarBuilder new]; + avatarBuilder.avatar = + [TSAttachmentStream buildProtoForAttachmentId:contact.avatarAttachmentId isVoiceMessage:NO]; + contactBuilder.avatar = [avatarBuilder build]; + } OWSSignalServiceProtosDataMessageContact *contactProto = [contactBuilder build]; if (contactProto.number.count < 1 && contactProto.email.count < 1 && contactProto.address.count < 1) { @@ -839,7 +870,9 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) return contactProto; } -+ (OWSContact *_Nullable)contactForDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage ++ (nullable OWSContact *)contactForDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage + relay:(nullable NSString *)relay + transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssert(dataMessage); @@ -905,10 +938,26 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value) } contact.addresses = [addresses copy]; - // TODO: Avatar - [contact ensureDisplayName]; + if (contactProto.hasAvatar) { + OWSSignalServiceProtosDataMessageContactAvatar *avatarInfo = contactProto.avatar; + + if (avatarInfo.hasAvatar) { + OWSSignalServiceProtosAttachmentPointer *avatarAttachment = avatarInfo.avatar; + + TSAttachmentPointer *attachmentPointer = + [TSAttachmentPointer attachmentPointerFromProto:avatarAttachment relay:relay]; + [attachmentPointer saveWithTransaction:transaction]; + + contact.avatarAttachmentId = attachmentPointer.uniqueId; + contact.isProfileAvatar = avatarInfo.isProfile; + } else { + OWSFail(@"%@ in %s avatarInfo.hasAvatar was unexpectedly false", self.logTag, __PRETTY_FUNCTION__); + } + } + + return contact; } diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m index bd6a53662e..babff7e05c 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m +++ b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m @@ -129,8 +129,10 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)description { - return [NSString - stringWithFormat:@"%@ in thread: %@ timestamp: %tu", [super description], self.uniqueThreadId, self.timestamp]; + return [NSString stringWithFormat:@"%@ in thread: %@ timestamp: %lu", + [super description], + self.uniqueThreadId, + (unsigned long)self.timestamp]; } - (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.h b/SignalServiceKit/src/Messages/Interactions/TSMessage.h index 9d2fc81d44..4c672c7e21 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.h @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)hasAttachments; - (nullable TSAttachment *)attachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; + - (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream; - (BOOL)shouldStartExpireTimer; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 46dc5539ca..1e2c0d96f1 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -738,9 +738,9 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt OWSFail(@"%@ message body length too long.", self.logTag); NSString *truncatedBody = [self.body copy]; while ([truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kOversizeTextMessageSizeThreshold) { - DDLogError(@"%@ truncating body which is too long: %tu", + DDLogError(@"%@ truncating body which is too long: %lu", self.logTag, - [truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); + (unsigned long)[truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); truncatedBody = [truncatedBody substringToIndex:truncatedBody.length / 2]; } [builder setBody:truncatedBody]; @@ -799,6 +799,8 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt [OWSContacts protoForContact:self.contactShare]; if (contactProto) { [builder addContact:contactProto]; + } else { + OWSFail(@"%@ in %s contactProto was unexpectedly nil", self.logTag, __PRETTY_FUNCTION__); } } @@ -834,7 +836,8 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt quotedAttachmentBuilder.fileName = attachment.sourceFilename; if (attachment.thumbnailAttachmentStreamId) { quotedAttachmentBuilder.thumbnail = - [self buildProtoForAttachmentId:attachment.thumbnailAttachmentStreamId]; + [TSAttachmentStream buildProtoForAttachmentId:attachment.thumbnailAttachmentStreamId + isVoiceMessage:NO]; } [quoteBuilder addAttachments:[quotedAttachmentBuilder build]]; @@ -871,62 +874,6 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt return !self.hasSyncedTranscript; } -- (OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(NSString *)attachmentId -{ - OWSAssert(attachmentId.length > 0); - - TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId]; - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - DDLogError(@"Unexpected type for attachment builder: %@", attachment); - return nil; - } - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - return [self buildProtoForAttachmentStream:attachmentStream filename:attachmentStream.sourceFilename]; -} - -- (OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(NSString *)attachmentId - filename:(nullable NSString *)filename -{ - OWSAssert(attachmentId.length > 0); - - TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId]; - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - DDLogError(@"Unexpected type for attachment builder: %@", attachment); - return nil; - } - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - return [self buildProtoForAttachmentStream:attachmentStream filename:filename]; -} - -- (OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentStream:(TSAttachmentStream *)attachmentStream - filename:(nullable NSString *)filename -{ - OWSSignalServiceProtosAttachmentPointerBuilder *builder = [OWSSignalServiceProtosAttachmentPointerBuilder new]; - [builder setId:attachmentStream.serverId]; - OWSAssert(attachmentStream.contentType.length > 0); - [builder setContentType:attachmentStream.contentType]; - DDLogVerbose(@"%@ Sending attachment with filename: '%@'", self.logTag, filename); - [builder setFileName:filename]; - [builder setSize:attachmentStream.byteCount]; - [builder setKey:attachmentStream.encryptionKey]; - [builder setDigest:attachmentStream.digest]; - [builder setFlags:(self.isVoiceMessage ? OWSSignalServiceProtosAttachmentPointerFlagsVoiceMessage : 0)]; - - if ([attachmentStream shouldHaveImageSize]) { - CGSize imageSize = [attachmentStream imageSize]; - if (imageSize.width < NSIntegerMax && imageSize.height < NSIntegerMax) { - NSInteger imageWidth = (NSInteger)round(imageSize.width); - NSInteger imageHeight = (NSInteger)round(imageSize.height); - if (imageWidth > 0 && imageHeight > 0) { - [builder setWidth:(UInt32)imageWidth]; - [builder setHeight:(UInt32)imageHeight]; - } - } - } - - return [builder build]; -} - - (NSString *)statusDescription { NSMutableString *result = [NSMutableString new]; diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h index e8505df2b5..202f8f1ec5 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @class TSThread; @class YapDatabaseReadWriteTransaction; -@interface OWSAttachmentInfo: MTLModel +@interface OWSAttachmentInfo : MTLModel @property (nonatomic, readonly, nullable) NSString *contentType; @property (nonatomic, readonly, nullable) NSString *sourceFilename; @@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN contentType:(NSString *)contentType sourceFilename:(NSString *)sourceFilename NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithAttachment:(TSAttachment *)attachment; +- (instancetype)initWithAttachmentStream:(TSAttachmentStream *)attachmentStream; @end diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m index 5e9544bacf..a846b5e6a6 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m @@ -18,14 +18,15 @@ NS_ASSUME_NONNULL_BEGIN @implementation OWSAttachmentInfo -- (instancetype)initWithAttachment:(TSAttachment *)attachment +- (instancetype)initWithAttachmentStream:(TSAttachmentStream *)attachmentStream; { - OWSAssert(attachment.uniqueId); - OWSAssert(attachment.contentType); + OWSAssert([attachmentStream isKindOfClass:[TSAttachmentStream class]]); + OWSAssert(attachmentStream.uniqueId); + OWSAssert(attachmentStream.contentType); - return [self initWithAttachmentId:attachment.uniqueId - contentType:attachment.contentType - sourceFilename:attachment.sourceFilename]; + return [self initWithAttachmentId:attachmentStream.uniqueId + contentType:attachmentStream.contentType + sourceFilename:attachmentStream.sourceFilename]; } - (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId @@ -94,8 +95,8 @@ NS_ASSUME_NONNULL_BEGIN _body = body; NSMutableArray *attachmentInfos = [NSMutableArray new]; - for (TSAttachment *attachment in attachments) { - [attachmentInfos addObject:[[OWSAttachmentInfo alloc] initWithAttachment:attachment]]; + for (TSAttachmentStream *attachmentStream in attachments) { + [attachmentInfos addObject:[[OWSAttachmentInfo alloc] initWithAttachmentStream:attachmentStream]]; } _quotedAttachments = [attachmentInfos copy]; @@ -152,19 +153,19 @@ NS_ASSUME_NONNULL_BEGIN transaction:transaction]; if (thumbnailStream) { - DDLogDebug(@"%@ Generated local thumbnail for quoted quoted message: %@:%tu", + DDLogDebug(@"%@ Generated local thumbnail for quoted quoted message: %@:%lu", self.logTag, thread.uniqueId, - timestamp); + (unsigned long)timestamp); [thumbnailStream saveWithTransaction:transaction]; attachmentInfo.thumbnailAttachmentStreamId = thumbnailStream.uniqueId; } else if (quotedAttachment.hasThumbnail) { - DDLogDebug(@"%@ Saving reference for fetching remote thumbnail for quoted message: %@:%tu", + DDLogDebug(@"%@ Saving reference for fetching remote thumbnail for quoted message: %@:%lu", self.logTag, thread.uniqueId, - timestamp); + (unsigned long)timestamp); OWSSignalServiceProtosAttachmentPointer *thumbnailAttachmentProto = quotedAttachment.thumbnail; TSAttachmentPointer *thumbnailPointer = @@ -173,7 +174,8 @@ NS_ASSUME_NONNULL_BEGIN attachmentInfo.thumbnailAttachmentPointerId = thumbnailPointer.uniqueId; } else { - DDLogDebug(@"%@ No thumbnail for quoted message: %@:%tu", self.logTag, thread.uniqueId, timestamp); + DDLogDebug( + @"%@ No thumbnail for quoted message: %@:%lu", self.logTag, thread.uniqueId, (unsigned long)timestamp); } [attachmentInfos addObject:attachmentInfo]; diff --git a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m index 5b5c96b0fa..d33fc96070 100644 --- a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m +++ b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m @@ -356,10 +356,10 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo backgroundTask = nil; - DDLogVerbose(@"%@ completed %zu/%zu jobs. %zd jobs left.", + DDLogVerbose(@"%@ completed %lu/%lu jobs. %zd jobs left.", self.logTag, - processedJobs.count, - batchJobs.count, + (unsigned long)processedJobs.count, + (unsigned long)batchJobs.count, [OWSMessageContentJob numberOfKeysInCollection]); // Wait a bit in hopes of increasing the batch size. diff --git a/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.m b/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.m index e5215e286d..e173e15dfd 100644 --- a/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.m +++ b/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.m @@ -135,7 +135,7 @@ void AssertIsOnDisappearingMessagesQueue() transaction:transaction]; }]; - DDLogDebug(@"%@ Removed %tu expired messages", self.logTag, expirationCount); + DDLogDebug(@"%@ Removed %lu expired messages", self.logTag, (unsigned long)expirationCount); backgroundTask = nil; return expirationCount; @@ -398,7 +398,7 @@ void AssertIsOnDisappearingMessagesQueue() { [self.disappearingMessagesFinder enumerateMessagesWhichFailedToStartExpiringWithBlock:^( TSMessage *_Nonnull message) { - DDLogWarn(@"%@ starting old timer for message timestamp: %tu", self.logTag, message.timestamp); + DDLogWarn(@"%@ starting old timer for message timestamp: %lu", self.logTag, (unsigned long)message.timestamp); [self setExpirationForMessage:message expirationStartedAt:message.timestampForSorting transaction:transaction]; } transaction:transaction]; diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 27b4fccfa9..67097cfc12 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -909,7 +909,8 @@ NS_ASSUME_NONNULL_BEGIN uint64_t timestamp = envelope.timestamp; NSString *body = dataMessage.body; NSData *groupId = dataMessage.hasGroup ? dataMessage.group.id : nil; - OWSContact *_Nullable contact = [OWSContacts contactForDataMessage:dataMessage]; + OWSContact *_Nullable contact = + [OWSContacts contactForDataMessage:dataMessage relay:envelope.relay transaction:transaction]; if (dataMessage.group.type == OWSSignalServiceProtosGroupContextTypeRequestInfo) { [self handleGroupInfoRequest:envelope dataMessage:dataMessage transaction:transaction]; @@ -1108,7 +1109,8 @@ NS_ASSUME_NONNULL_BEGIN [[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:attachmentPointer networkManager:self.networkManager]; - DDLogDebug(@"%@ downloading thumbnail for message: %tu", self.logTag, incomingMessage.timestamp); + DDLogDebug( + @"%@ downloading thumbnail for message: %lu", self.logTag, (unsigned long)incomingMessage.timestamp); [attachmentProcessor fetchAttachmentsForMessage:incomingMessage transaction:transaction success:^(TSAttachmentStream *_Nonnull attachmentStream) { @@ -1119,14 +1121,45 @@ NS_ASSUME_NONNULL_BEGIN }]; } failure:^(NSError *_Nonnull error) { - DDLogWarn(@"%@ failed to fetch thumbnail for message: %tu with error: %@", + DDLogWarn(@"%@ failed to fetch thumbnail for message: %lu with error: %@", self.logTag, - incomingMessage.timestamp, + (unsigned long)incomingMessage.timestamp, error); }]; } } + OWSContact *_Nullable contact = incomingMessage.contactShare; + if (contact && contact.avatarAttachmentId) { + TSAttachmentPointer *attachmentPointer = + [TSAttachmentPointer fetchObjectWithUniqueID:contact.avatarAttachmentId transaction:transaction]; + + if (![attachmentPointer isKindOfClass:[TSAttachmentPointer class]]) { + OWSFail(@"%@ in %s avatar attachmentPointer was unexpectedly nil", self.logTag, __PRETTY_FUNCTION__); + } else { + OWSAttachmentsProcessor *attachmentProcessor = + [[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:attachmentPointer + networkManager:self.networkManager]; + + DDLogDebug(@"%@ downloading contact avatar for message: %lu", + self.logTag, + (unsigned long)incomingMessage.timestamp); + [attachmentProcessor fetchAttachmentsForMessage:incomingMessage + transaction:transaction + success:^(TSAttachmentStream *_Nonnull attachmentStream) { + [self.dbConnection + asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [incomingMessage touchWithTransaction:transaction]; + }]; + } + failure:^(NSError *_Nonnull error) { + DDLogWarn(@"%@ failed to fetch contact avatar for message: %lu with error: %@", + self.logTag, + (unsigned long)incomingMessage.timestamp, + error); + }]; + } + } // In case we already have a read receipt for this new message (this happens sometimes). [OWSReadReceiptManager.sharedManager applyEarlyReadReceiptsForIncomingMessage:incomingMessage transaction:transaction]; diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 4e0ec2275b..5fc1d82309 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -11,6 +11,7 @@ #import "NSError+MessageSending.h" #import "OWSBackgroundTask.h" #import "OWSBlockingManager.h" +#import "OWSContact.h" #import "OWSDevice.h" #import "OWSDisappearingMessagesJob.h" #import "OWSError.h" @@ -293,6 +294,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __block NSArray *quotedThumbnailAttachments = @[]; + __block TSAttachmentStream *_Nullable contactShareAvatarAttachment; // This method will use a read/write transaction. This transaction // will block until any open read/write transactions are complete. @@ -312,6 +314,18 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [message.quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction:transaction]; } + if (message.contactShare.avatarAttachmentId != nil) { + TSAttachment *avatarAttachment = [message.contactShare avatarAttachmentWithTransaction:transaction]; + if ([avatarAttachment isKindOfClass:[TSAttachmentStream class]]) { + contactShareAvatarAttachment = (TSAttachmentStream *)avatarAttachment; + } else { + OWSFail(@"%@ in %s unexpected avatarAttachment: %@", + self.logTag, + __PRETTY_FUNCTION__, + avatarAttachment); + } + } + // All outgoing messages should be saved at the time they are enqueued. [message saveWithTransaction:transaction]; // When we start a message send, all "failed" recipients should be marked as "sending". @@ -325,6 +339,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; dbConnection:self.dbConnection success:successHandler failure:failureHandler]; + + // TODO de-dupe attachment enque logic. if (message.hasAttachments) { OWSUploadOperation *uploadAttachmentOperation = [[OWSUploadOperation alloc] initWithAttachmentId:message.attachmentIds.firstObject @@ -349,6 +365,17 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [sendingQueue addOperation:uploadQuoteThumbnailOperation]; } + if (contactShareAvatarAttachment != nil) { + OWSAssert(message.contactShare); + OWSUploadOperation *uploadAvatarOperation = + [[OWSUploadOperation alloc] initWithAttachmentId:contactShareAvatarAttachment.uniqueId + dbConnection:self.dbConnection]; + + // TODO put attachment uploads on a (lowly) concurrent queue + [sendMessageOperation addDependency:uploadAvatarOperation]; + [sendingQueue addOperation:uploadAvatarOperation]; + } + [sendingQueue addOperation:sendMessageOperation]; }); } diff --git a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m index ca54eae1db..8c5b1c2cb5 100644 --- a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m +++ b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m @@ -559,9 +559,9 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE } if (wasLocal) { - DDLogError(@"Marking %zu messages as read locally.", newlyReadList.count); + DDLogError(@"Marking %lu messages as read locally.", (unsigned long)newlyReadList.count); } else { - DDLogError(@"Marking %zu messages as read by linked device.", newlyReadList.count); + DDLogError(@"Marking %lu messages as read by linked device.", (unsigned long)newlyReadList.count); } for (id readItem in newlyReadList) { [readItem markAsReadAtTimestamp:readTimestamp sendReadReceipt:wasLocal transaction:transaction]; diff --git a/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h b/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h index 26af046da5..c20015d9f7 100644 --- a/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h +++ b/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // @class Contact; @@ -12,8 +12,4 @@ - (NSString * _Nonnull)displayNameForPhoneIdentifier:(NSString * _Nullable)phoneNumber; - (NSArray * _Nonnull)signalAccounts; -#if TARGET_OS_IPHONE -- (UIImage * _Nullable)imageForPhoneIdentifier:(NSString * _Nullable)phoneNumber; -#endif - @end diff --git a/SignalServiceKit/src/Storage/OWSOrphanedDataCleaner.m b/SignalServiceKit/src/Storage/OWSOrphanedDataCleaner.m index 2d0b79539d..de92950e49 100644 --- a/SignalServiceKit/src/Storage/OWSOrphanedDataCleaner.m +++ b/SignalServiceKit/src/Storage/OWSOrphanedDataCleaner.m @@ -4,6 +4,7 @@ #import "OWSOrphanedDataCleaner.h" #import "NSDate+OWS.h" +#import "OWSContact.h" #import "OWSPrimaryStorage.h" #import "TSAttachmentStream.h" #import "TSInteraction.h" @@ -82,18 +83,20 @@ NS_ASSUME_NONNULL_BEGIN }]; }]; - CleanupLogDebug(@"%@ fileCount: %zd", self.logTag, fileCount); + CleanupLogDebug(@"%@ fileCount: %lu", self.logTag, (unsigned long)fileCount); CleanupLogDebug(@"%@ totalFileSize: %lld", self.logTag, totalFileSize); CleanupLogDebug(@"%@ attachmentStreams: %d", self.logTag, attachmentStreamCount); - CleanupLogDebug(@"%@ attachmentStreams with file paths: %zd", self.logTag, attachmentFilePaths.count); + CleanupLogDebug( + @"%@ attachmentStreams with file paths: %lu", self.logTag, (unsigned long)attachmentFilePaths.count); NSMutableSet *orphanDiskFilePaths = [diskFilePaths mutableCopy]; [orphanDiskFilePaths minusSet:attachmentFilePaths]; NSMutableSet *missingAttachmentFilePaths = [attachmentFilePaths mutableCopy]; [missingAttachmentFilePaths minusSet:diskFilePaths]; - CleanupLogDebug(@"%@ orphan disk file paths: %zd", self.logTag, orphanDiskFilePaths.count); - CleanupLogDebug(@"%@ missing attachment file paths: %zd", self.logTag, missingAttachmentFilePaths.count); + CleanupLogDebug(@"%@ orphan disk file paths: %lu", self.logTag, (unsigned long)orphanDiskFilePaths.count); + CleanupLogDebug( + @"%@ missing attachment file paths: %lu", self.logTag, (unsigned long)missingAttachmentFilePaths.count); [self printPaths:orphanDiskFilePaths.allObjects label:@"orphan disk file paths"]; [self printPaths:missingAttachmentFilePaths.allObjects label:@"missing attachment file paths"]; @@ -106,6 +109,7 @@ NS_ASSUME_NONNULL_BEGIN NSMutableSet *orphanInteractionIds = [NSMutableSet new]; NSMutableSet *messageAttachmentIds = [NSMutableSet new]; NSMutableSet *quotedReplyThumbnailAttachmentIds = [NSMutableSet new]; + NSMutableSet *contactShareAvatarAttachmentIds = [NSMutableSet new]; [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { [transaction enumerateKeysAndObjectsInCollection:TSMessage.collection @@ -129,22 +133,33 @@ NS_ASSUME_NONNULL_BEGIN addObjectsFromArray:quotedMessage .thumbnailAttachmentStreamIds]; } + + OWSContact *_Nullable contactShare = message.contactShare; + if (contactShare && contactShare.avatarAttachmentId) { + [contactShareAvatarAttachmentIds + addObject:contactShare.avatarAttachmentId]; + } }]; }]; - CleanupLogDebug(@"%@ attachmentIds: %zd", self.logTag, attachmentIds.count); - CleanupLogDebug(@"%@ messageAttachmentIds: %zd", self.logTag, messageAttachmentIds.count); - CleanupLogDebug(@"%@ quotedReplyThumbnailAttachmentIds: %zd", self.logTag, quotedReplyThumbnailAttachmentIds.count); + CleanupLogDebug(@"%@ attachmentIds: %lu", self.logTag, (unsigned long)attachmentIds.count); + CleanupLogDebug(@"%@ messageAttachmentIds: %lu", self.logTag, (unsigned long)messageAttachmentIds.count); + CleanupLogDebug(@"%@ quotedReplyThumbnailAttachmentIds: %lu", + self.logTag, + (unsigned long)quotedReplyThumbnailAttachmentIds.count); + CleanupLogDebug( + @"%@ contactShareAvatarAttachmentIds: %lu", self.logTag, (unsigned long)contactShareAvatarAttachmentIds.count); NSMutableSet *orphanAttachmentIds = [attachmentIds mutableCopy]; [orphanAttachmentIds minusSet:messageAttachmentIds]; [orphanAttachmentIds minusSet:quotedReplyThumbnailAttachmentIds]; + [orphanAttachmentIds minusSet:contactShareAvatarAttachmentIds]; NSMutableSet *missingAttachmentIds = [messageAttachmentIds mutableCopy]; [missingAttachmentIds minusSet:attachmentIds]; - CleanupLogDebug(@"%@ orphan attachmentIds: %zd", self.logTag, orphanAttachmentIds.count); - CleanupLogDebug(@"%@ missing attachmentIds: %zd", self.logTag, missingAttachmentIds.count); - CleanupLogDebug(@"%@ orphan interactions: %zd", self.logTag, orphanInteractionIds.count); + CleanupLogDebug(@"%@ orphan attachmentIds: %lu", self.logTag, (unsigned long)orphanAttachmentIds.count); + CleanupLogDebug(@"%@ missing attachmentIds: %lu", self.logTag, (unsigned long)missingAttachmentIds.count); + CleanupLogDebug(@"%@ orphan interactions: %lu", self.logTag, (unsigned long)orphanInteractionIds.count); // We need to avoid cleaning up new attachments and files that are still in the process of // being created/written, so we don't clean up anything recent.