Merge branch 'mkirk/contact-share-avatars'

pull/1/head
Michael Kirk 7 years ago
commit b7634c650c

@ -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 = "<group>"; };
4539B5851F79348F007141FF /* PushRegistrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = "<group>"; };
453CC0361D08E1A60040EBA3 /* sn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sn; path = translations/sn.lproj/Localizable.strings; sourceTree = "<group>"; };
4542DF51208B82E9007B4E76 /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = "<group>"; };
4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactShareViewModel.swift; sourceTree = "<group>"; };
4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadViewModel.swift; sourceTree = "<group>"; };
4542DF53208D40AC007B4E76 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = "<group>"; };
454A84032059C787008B8C75 /* MediaTileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaTileViewController.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
};
4541B719209D2D860008608F /* ViewModels */ = {
isa = PBXGroup;
children = (
4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
4541B71C209D3B4F0008608F /* ViewModels */ = {
isa = PBXGroup;
children = (
4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */,
459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */,
459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */,
);
path = ViewModels;
sourceTree = "<group>";
};
45464DB81DFA03D8001D3FD6 /* Signaling */ = {
isa = PBXGroup;
children = (
@ -1931,7 +1951,6 @@
45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */,
458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */,
458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */,
4542DF51208B82E9007B4E76 /* ThreadModel.swift */,
);
path = Models;
sourceTree = "<group>";
@ -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 */,

@ -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
}

@ -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;

@ -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];

@ -65,6 +65,7 @@
#import <SignalMessaging/ThreadUtil.h>
#import <SignalMessaging/UIUtil.h>
#import <SignalMessaging/UIViewController+OWS.h>
#import <SignalServiceKit/Contact.h>
#import <SignalServiceKit/ContactsUpdater.h>
#import <SignalServiceKit/MimeTypeUtil.h>
#import <SignalServiceKit/NSDate+OWS.h>
@ -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<UIViewControllerAnimatedTransiti
DDLogDebug(@"%@ in %s with contact: %@", self.logTag, __PRETTY_FUNCTION__, contact);
OWSContact *_Nullable contactShare = [OWSContacts contactForSystemContact:contact.cnContact];
if (!contactShare) {
OWSContact *_Nullable contactShareRecord = [OWSContacts contactForSystemContact:contact.cnContact];
if (!contactShareRecord) {
DDLogError(@"%@ Could not convert system contact.", self.logTag);
return;
}
BOOL isProfileAvatar = NO;
UIImage *_Nullable avatarImage = contact.image;
if (!avatarImage) {
NSString *firstSignalId = contact.textSecureIdentifiers.firstObject;
if (firstSignalId) {
avatarImage = [self.contactsManager profileImageForPhoneIdentifier:firstSignalId];
if (avatarImage) {
isProfileAvatar = YES;
}
}
}
ContactShareViewModel *contactShare =
[[ContactShareViewModel alloc] initWithContactShareRecord:contactShareRecord avatarImage:avatarImage];
contactShareRecord.isProfileAvatar = isProfileAvatar;
// TODO: We should probably show this in the same navigation view controller.
ApproveContactShareViewController *approveContactShare =
[[ApproveContactShareViewController alloc] initWithContactShare:contactShare
@ -4974,7 +4998,7 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
#pragma mark - ApproveContactShareViewControllerDelegate
- (void)approveContactShare:(ApproveContactShareViewController *)approveContactShare
didApproveContactShare:(OWSContact *)contactShare
didApproveContactShare:(ContactShareViewModel *)contactShare
{
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
@ -4985,7 +5009,7 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
}
- (void)approveContactShare:(ApproveContactShareViewController *)approveContactShare
didCancelContactShare:(OWSContact *)contactShare
didCancelContactShare:(ContactShareViewModel *)contactShare
{
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);

@ -24,10 +24,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
#pragma mark -
@class ContactShareViewModel;
@class ConversationViewCell;
@class DisplayableText;
@class OWSAudioMessageView;
@class OWSContact;
@class OWSQuotedReplyModel;
@class TSAttachmentPointer;
@class TSAttachmentStream;
@ -103,7 +103,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
// if a load has previously failed.
@property (nonatomic) BOOL didCellMediaFailToLoad;
- (nullable OWSContact *)contactShare;
@property (nonatomic, readonly, nullable) ContactShareViewModel *contactShare;
#pragma mark - UIMenuController

@ -65,7 +65,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
@property (nonatomic, readonly, nullable) NSString *quotedRecipientId;
@property (nonatomic, nullable) TSAttachmentStream *attachmentStream;
@property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer;
@property (nonatomic, nullable) OWSContact *contactShare;
@property (nonatomic, nullable) ContactShareViewModel *contactShare;
@property (nonatomic) CGSize mediaSize;
@end
@ -406,7 +406,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
TSMessage *message = (TSMessage *)self.interaction;
if (message.contactShare) {
self.contactShare = message.contactShare;
self.contactShare =
[[ContactShareViewModel alloc] initWithContactShareRecord:message.contactShare transaction:transaction];
self.messageCellType = OWSMessageCellType_ContactShare;
return;
}

@ -84,8 +84,12 @@ NS_ASSUME_NONNULL_BEGIN
actionBlock:^{
[DebugUIMessages deleteAllMessagesInThread:thread];
}]];
[items addObject:[OWSTableItem itemWithTitle:@"👷 Send All Contact Shares"
actionBlock:^{
[DebugUIMessages sendAllContacts:thread];
}]];
[items addObjectsFromArray:[self itemsForActions:@[
[DebugUIMessages fakeAllContactShareAction:thread],
[DebugUIMessages sendMessageVariationsAction:thread],
// Send Media
[DebugUIMessages sendAllMediaAction:thread],
@ -103,7 +107,6 @@ NS_ASSUME_NONNULL_BEGIN
// Exemplary
[DebugUIMessages allFakeAction:thread],
[DebugUIMessages allFakeBackDatedAction:thread],
[DebugUIMessages allContactShareAction:thread],
]]];
[items addObjectsFromArray:@[
@ -130,10 +133,7 @@ NS_ASSUME_NONNULL_BEGIN
actionBlock:^{
[DebugUIMessages selectBackDatedAction:thread];
}],
[OWSTableItem itemWithTitle:@"Send All Contact Shares"
actionBlock:^{
[DebugUIMessages sendAllContacts:thread];
}],
#pragma mark - Misc.
@ -2914,7 +2914,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Contact Shares
typedef OWSContact * (^OWSContactBlock)(void);
typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transaction);
+ (DebugUIMessagesAction *)fakeContactShareMessageAction:(TSThread *)thread
label:(NSString *)label
@ -2925,7 +2925,7 @@ typedef OWSContact * (^OWSContactBlock)(void);
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Fake Contact Share (%@)", label]
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
OWSContact *contact = contactBlock();
OWSContact *contact = contactBlock(transaction);
TSOutgoingMessage *message = [self createFakeOutgoingMessage:thread
messageBody:nil
fakeAssetLoader:nil
@ -2953,7 +2953,7 @@ typedef OWSContact * (^OWSContactBlock)(void);
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"Name & Number"
contactBlock:^{
contactBlock:^(YapDatabaseReadWriteTransaction *transaction){
OWSContact *contact = [OWSContact new];
contact.givenName = @"Alice";
OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new];
@ -2966,7 +2966,7 @@ typedef OWSContact * (^OWSContactBlock)(void);
}]];
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"Name & Email"
contactBlock:^{
contactBlock:^(YapDatabaseReadWriteTransaction *transaction){
OWSContact *contact = [OWSContact new];
contact.givenName = @"Bob";
OWSContactEmail *email = [OWSContactEmail new];
@ -2979,7 +2979,7 @@ typedef OWSContact * (^OWSContactBlock)(void);
}]];
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"Complicated"
contactBlock:^{
contactBlock:^(YapDatabaseReadWriteTransaction *transaction){
OWSContact *contact = [OWSContact new];
contact.givenName = @"Alice";
contact.familyName = @"Carol";
@ -3031,12 +3031,15 @@ typedef OWSContact * (^OWSContactBlock)(void);
address2,
];
// TODO: Avatar
UIImage *avatarImage =
[OWSAvatarBuilder buildRandomAvatarWithDiameter:200];
[contact saveAvatarImage:avatarImage transaction:transaction];
return contact;
}]];
[actions addObject:[self fakeContactShareMessageAction:thread
label:@"Long values"
contactBlock:^{
contactBlock:^(YapDatabaseReadWriteTransaction *transaction){
OWSContact *contact = [OWSContact new];
contact.givenName = @"Bobasdjasdlkjasldkjas";
contact.familyName = @"Bobasdjasdlkjasldkjas";
@ -3050,7 +3053,7 @@ typedef OWSContact * (^OWSContactBlock)(void);
}]];
[actions addObject:[self fakeContactShareMessageAction: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];
@ -3063,7 +3066,7 @@ typedef OWSContact * (^OWSContactBlock)(void);
}]];
[actions addObject:[self fakeContactShareMessageAction: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];
@ -3078,12 +3081,12 @@ typedef OWSContact * (^OWSContactBlock)(void);
return actions;
}
+ (DebugUIMessagesAction *)allContactShareAction:(TSThread *)thread
+ (DebugUIMessagesAction *)fakeAllContactShareAction:(TSThread *)thread
{
OWSAssert(thread);
return
[DebugUIMessagesGroupAction allGroupActionWithLabel:@"All Fake Contact Shares"
[DebugUIMessagesGroupAction allGroupActionWithLabel:@"👷 All Fake Contact Shares"
subactions:[self allFakeContactShareActions:thread includeLabels:YES]];
}
@ -3094,24 +3097,19 @@ typedef OWSContact * (^OWSContactBlock)(void);
{
OWSAssert(thread);
return
[DebugUIMessagesSingleAction actionWithLabel:[NSString stringWithFormat:@"Send Contact Share (%@)", label]
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
OWSContact *contact = contactBlock();
DDLogVerbose(@"%@ sending contact: %@", self.logTag, contact.debugDescription);
OWSMessageSender *messageSender = [Environment current].messageSender;
[ThreadUtil sendMessageWithContactShare:contact
inThread:thread
messageSender:messageSender
completion:nil];
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Send Contact Share (%@)", label]
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
OWSContact *contact = contactBlock(transaction);
DDLogVerbose(@"%@ sending contact: %@", self.logTag, contact.debugDescription);
OWSMessageSender *messageSender = [Environment current].messageSender;
[ThreadUtil sendMessageWithContactShare:contact inThread:thread messageSender:messageSender completion:nil];
success();
});
}];
success();
}];
}
+ (NSArray<DebugUIMessagesAction *> *)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

@ -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)
}

@ -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)
}
}

@ -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) {

@ -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<ContactFieldType: OWSContactField>: NSObject, Contac
isIncludedFlag = isIncluded
}
func applyToContact(contact: OWSContact) {
func applyToContact(contact: ContactShareViewModel) {
preconditionFailure("This method must be overridden")
}
}
@ -57,7 +57,7 @@ class ContactShareFieldBase<ContactFieldType: OWSContactField>: NSObject, Contac
class ContactSharePhoneNumber: ContactShareFieldBase<OWSContactPhoneNumber> {
override func applyToContact(contact: OWSContact) {
override func applyToContact(contact: ContactShareViewModel) {
assert(isIncluded())
var values = [OWSContactPhoneNumber]()
@ -71,7 +71,7 @@ class ContactSharePhoneNumber: ContactShareFieldBase<OWSContactPhoneNumber> {
class ContactShareEmail: ContactShareFieldBase<OWSContactEmail> {
override func applyToContact(contact: OWSContact) {
override func applyToContact(contact: ContactShareViewModel) {
assert(isIncluded())
var values = [OWSContactEmail]()
@ -85,7 +85,7 @@ class ContactShareEmail: ContactShareFieldBase<OWSContactEmail> {
class ContactShareAddress: ContactShareFieldBase<OWSContactAddress> {
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

@ -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

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalServiceKit/Contact.h>
@ -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;

@ -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;

@ -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

@ -43,16 +43,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, readonly) NSArray<NSString *> *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
/**

@ -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;
}

@ -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];

@ -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);
}];
}

@ -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

@ -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

@ -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;
}

@ -61,7 +61,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) NSArray<OWSContactEmail *> *emails;
@property (nonatomic) NSArray<OWSContactAddress *> *addresses;
@property (nonatomic, nullable) TSAttachment *avatar;
@property (nonatomic) BOOL isProfileAvatar;
@end

@ -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<OWSContactEmail *> *emails;
@property (nonatomic, readonly) NSArray<OWSContactAddress *> *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

@ -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 <YapDatabase/YapDatabaseTransaction.h>
@import Contacts;
@ -273,7 +274,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
@property (nonatomic) NSArray<OWSContactEmail *> *emails;
@property (nonatomic) NSArray<OWSContactAddress *> *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;
}

@ -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 {

@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)hasAttachments;
- (nullable TSAttachment *)attachmentWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream;
- (BOOL)shouldStartExpireTimer;

@ -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];

@ -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

@ -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];

@ -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.

@ -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];

@ -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];

@ -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<TSAttachmentStream *> *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];
});
}

@ -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<OWSReadTracking> readItem in newlyReadList) {
[readItem markAsReadAtTimestamp:readTimestamp sendReadReceipt:wasLocal transaction:transaction];

@ -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<SignalAccount *> * _Nonnull)signalAccounts;
#if TARGET_OS_IPHONE
- (UIImage * _Nullable)imageForPhoneIdentifier:(NSString * _Nullable)phoneNumber;
#endif
@end

@ -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<NSString *> *orphanDiskFilePaths = [diskFilePaths mutableCopy];
[orphanDiskFilePaths minusSet:attachmentFilePaths];
NSMutableSet<NSString *> *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<NSString *> *orphanInteractionIds = [NSMutableSet new];
NSMutableSet<NSString *> *messageAttachmentIds = [NSMutableSet new];
NSMutableSet<NSString *> *quotedReplyThumbnailAttachmentIds = [NSMutableSet new];
NSMutableSet<NSString *> *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<NSString *> *orphanAttachmentIds = [attachmentIds mutableCopy];
[orphanAttachmentIds minusSet:messageAttachmentIds];
[orphanAttachmentIds minusSet:quotedReplyThumbnailAttachmentIds];
[orphanAttachmentIds minusSet:contactShareAvatarAttachmentIds];
NSMutableSet<NSString *> *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.

Loading…
Cancel
Save