From 540a0a8e484826078c082c31db8984158e631ad2 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 2 Aug 2017 12:03:06 -0400 Subject: [PATCH 1/5] Refine UserProfile class. Move local user properties to UserProfile. // FREEBIE --- Signal/src/ProfileFetcherJob.swift | 10 +- .../ConversationView/MessagesViewController.m | 2 + .../ViewControllers/ProfileViewController.m | 17 +- .../DeviceSyncing/OWSOutgoingSyncMessage.m | 1 - .../src/Messages/OWSMessageSender.h | 3 - .../src/Messages/OWSMessageSender.m | 10 -- .../src/Messages/OWSOutgoingCallMessage.m | 1 - .../src/Messages/OWSOutgoingNullMessage.m | 1 - .../src/Messages/TSMessagesManager.m | 3 +- .../src/Profiles/OWSProfilesManager.h | 11 +- .../src/Profiles/OWSProfilesManager.m | 170 ++++++++---------- 11 files changed, 98 insertions(+), 131 deletions(-) diff --git a/Signal/src/ProfileFetcherJob.swift b/Signal/src/ProfileFetcherJob.swift index aebf6702f..d86fa6eac 100644 --- a/Signal/src/ProfileFetcherJob.swift +++ b/Signal/src/ProfileFetcherJob.swift @@ -17,7 +17,11 @@ class ProfileFetcherJob: NSObject { static var fetchDateMap = [String: Date]() public class func run(thread: TSThread, networkManager: TSNetworkManager) { - ProfileFetcherJob(networkManager: networkManager).run(thread: thread) + ProfileFetcherJob(networkManager: networkManager).run(recipientIds: thread.recipientIdentifiers) + } + + public class func run(recipientId: String, networkManager: TSNetworkManager) { + ProfileFetcherJob(networkManager: networkManager).run(recipientIds: [recipientId]) } init(networkManager: TSNetworkManager) { @@ -25,11 +29,11 @@ class ProfileFetcherJob: NSObject { self.storageManager = TSStorageManager.shared() } - public func run(thread: TSThread) { + public func run(recipientIds: [String]) { AssertIsOnMainThread() DispatchQueue.main.async { - for recipientId in thread.recipientIdentifiers { + for recipientId in recipientIds { self.updateProfile(recipientId: recipientId) } } diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index 657049768..b21059e4b 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -75,6 +75,7 @@ #import #import #import +#import #import #import #import @@ -966,6 +967,7 @@ typedef enum : NSUInteger { } [self updateNavigationBarSubtitleLabel]; [ProfileFetcherJob runWithThread:self.thread networkManager:self.networkManager]; + [OWSProfilesManager.sharedManager refreshProfilesForThread:self.thread]; [self markVisibleMessagesAsRead]; } diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 0e7b5e952..915fa5656 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -212,15 +212,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateProfile { __weak ProfileViewController *weakSelf = self; - [OWSProfilesManager.sharedManager - updateLocalProfileName:self.nameTextField.text - avatarImage:self.avatar - success:^{ - [weakSelf.navigationController popViewControllerAnimated:YES]; - } - failure:^{ - // <#code#> - }]; + [OWSProfilesManager.sharedManager updateLocalProfileName:self.nameTextField.text + avatarImage:self.avatar + success:^{ + [weakSelf.navigationController popViewControllerAnimated:YES]; + } + failure:^{ + // TODO: Handle failure. + }]; } #pragma mark - UITextFieldDelegate diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m index 5b177e5fc..c780eea7a 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m @@ -4,7 +4,6 @@ #import "OWSOutgoingSyncMessage.h" #import "Cryptography.h" -#import "OWSProfilesManager.h" #import "OWSSignalServiceProtos.pb.h" NS_ASSUME_NONNULL_BEGIN diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.h b/SignalServiceKit/src/Messages/OWSMessageSender.h index b1cc2a6b1..a45cd6fcb 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.h +++ b/SignalServiceKit/src/Messages/OWSMessageSender.h @@ -6,7 +6,6 @@ NS_ASSUME_NONNULL_BEGIN @class ContactsUpdater; @class OWSBlockingManager; -@class OWSProfilesManager; @class OWSUploadingService; @class SignalRecipient; @class TSInvalidIdentityKeySendingErrorMessage; @@ -59,8 +58,6 @@ NS_SWIFT_NAME(MessageSender) - (void)setBlockingManager:(OWSBlockingManager *)blockingManager; -- (void)setProfilesManager:(OWSProfilesManager *)profilesManager; - /** * Send and resend text messages or resend messages with existing attachments. * If you haven't yet created the attachment, see the `sendAttachmentData:` variants. diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 09731c6db..1a192f508 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -14,7 +14,6 @@ #import "OWSMessageServiceParams.h" #import "OWSOutgoingSentMessageTranscript.h" #import "OWSOutgoingSyncMessage.h" -#import "OWSProfilesManager.h" #import "OWSUploadingService.h" #import "PreKeyBundle+jsonDict.h" #import "SignalRecipient.h" @@ -356,7 +355,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; @property (nonatomic, readonly) TSNetworkManager *networkManager; @property (nonatomic, readonly) TSStorageManager *storageManager; @property (nonatomic, readonly) OWSBlockingManager *blockingManager; -@property (nonatomic, readonly) OWSProfilesManager *profilesManager; @property (nonatomic, readonly) OWSUploadingService *uploadingService; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; @property (nonatomic, readonly) id contactsManager; @@ -399,14 +397,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; _blockingManager = blockingManager; } -- (void)setProfilesManager:(OWSProfilesManager *)profilesManager -{ - OWSAssert(profilesManager); - OWSAssert(!_profilesManager); - - _profilesManager = profilesManager; -} - - (NSOperationQueue *)sendingQueueForMessage:(TSOutgoingMessage *)message { OWSAssert(message); diff --git a/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m b/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m index 4989017e3..5e11b23b4 100644 --- a/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m +++ b/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m @@ -9,7 +9,6 @@ #import "OWSCallHangupMessage.h" #import "OWSCallIceUpdateMessage.h" #import "OWSCallOfferMessage.h" -#import "OWSProfilesManager.h" #import "OWSSignalServiceProtos.pb.h" #import "TSContactThread.h" diff --git a/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m b/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m index 08416b816..7b6284165 100644 --- a/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m +++ b/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m @@ -6,7 +6,6 @@ #import "Cryptography.h" #import "NSDate+millisecondTimeStamp.h" #import "TSContactThread.h" -#import "OWSProfilesManager.h" #import "OWSSignalServiceProtos.pb.h" #import "OWSVerificationStateSyncMessage.h" diff --git a/SignalServiceKit/src/Messages/TSMessagesManager.m b/SignalServiceKit/src/Messages/TSMessagesManager.m index e0b154dfa..aae36da91 100644 --- a/SignalServiceKit/src/Messages/TSMessagesManager.m +++ b/SignalServiceKit/src/Messages/TSMessagesManager.m @@ -499,8 +499,7 @@ NS_ASSUME_NONNULL_BEGIN if ([content hasProfileKey]) { NSData *profileKey = [content profileKey]; NSString *recipientId = envelope.source; - [OWSProfilesManager.sharedManager setProfileKey:profileKey - forRecipientId:recipientId]; + [OWSProfilesManager setProfileKey:profileKey forRecipientId:recipientId]; } if (content.hasSyncMessage) { diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.h b/SignalServiceKit/src/Profiles/OWSProfilesManager.h index b991c6bff..c61ec32ec 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.h +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.h @@ -18,11 +18,10 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; #pragma mark - Local Profile -@property (atomic, readonly) NSData *localProfileKey; - // These two methods should only be called from the main thread. -- (NSString *)localProfileName; -- (UIImage *)localProfileAvatarImage; +- (NSData *)localProfileKey; +- (nullable NSString *)localProfileName; +- (nullable UIImage *)localProfileAvatarImage; // This method is used to update the "local profile" state on the client // and the service. Client state is only updated if service state is @@ -51,7 +50,7 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; #pragma mark - Other User's Profiles -- (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId; ++ (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId; - (nullable NSData *)profileKeyForRecipientId:(NSString *)recipientId; @@ -59,7 +58,7 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; - (nullable UIImage *)profileAvatarForRecipientId:(NSString *)recipientId; -- (void)fetchProfileForRecipientId:(NSString *)recipientId; +- (void)refreshProfileForRecipientId:(NSString *)recipientId; @end diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.m b/SignalServiceKit/src/Profiles/OWSProfilesManager.m index f34860be1..3b14981ac 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.m +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.m @@ -7,7 +7,6 @@ #import "NSDate+OWS.h" #import "OWSMessageSender.h" #import "SecurityUtils.h" -#import "TSAccountManager.h" #import "TSGroupThread.h" #import "TSStorageManager.h" #import "TSThread.h" @@ -16,9 +15,14 @@ NS_ASSUME_NONNULL_BEGIN +// UserProfile properties should only be mutated on the main thread. @interface UserProfile : TSYapDatabaseObject -@property (nonatomic, readonly) NSString *recipientId; +// These properties may be accessed from any thread. +@property (atomic, readonly) NSString *recipientId; +@property (atomic, nullable) NSData *profileKey; + +// These properties may be accessed only from the main thread. @property (nonatomic, nullable) NSString *profileName; @property (nonatomic, nullable) NSString *avatarUrl; @property (nonatomic, nullable) NSString *avatarDigest; @@ -28,8 +32,10 @@ NS_ASSUME_NONNULL_BEGIN // This should reflect when either: // -// * The last successful update was started. -// * The in-flight update was started. +// * The last successful update finished. +// * The current in-flight update began. +// +// This property may be accessed from any thread. @property (nonatomic, nullable) NSDate *lastUpdateDate; - (instancetype)init NS_UNAVAILABLE; @@ -41,10 +47,6 @@ NS_ASSUME_NONNULL_BEGIN @implementation UserProfile - (instancetype)initWithRecipientId:(NSString *)recipientId - profileName:(NSString *_Nullable)profileName - avatarUrl:(NSString *_Nullable)avatarUrl - avatarDigest:(NSString *_Nullable)avatarDigest - avatarFileName:(NSString *_Nullable)avatarFileName { self = [super initWithUniqueId:recipientId]; @@ -54,10 +56,6 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(recipientId.length > 0); _recipientId = recipientId; - _profileName = profileName; - _avatarUrl = avatarUrl; - _avatarDigest = avatarDigest; - _avatarFileName = avatarFileName; return self; } @@ -82,19 +80,14 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - +NSString *const kNSNotificationName_LocalProfileUniqueId = @"kNSNotificationName_LocalProfileUniqueId"; + NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationName_LocalProfileDidChange"; NSString *const kNSNotificationName_OtherUsersProfileDidChange = @"kNSNotificationName_OtherUsersProfileDidChange"; -NSString *const kOWSProfilesManager_Collection = @"kOWSProfilesManager_Collection"; -// This key is used to persist the local user's profile key. -NSString *const kOWSProfilesManager_LocalProfileSecretKey = @"kOWSProfilesManager_LocalProfileSecretKey"; - NSString *const kOWSProfilesManager_UserWhitelistCollection = @"kOWSProfilesManager_UserWhitelistCollection"; NSString *const kOWSProfilesManager_GroupWhitelistCollection = @"kOWSProfilesManager_GroupWhitelistCollection"; -NSString *const kOWSProfilesManager_OtherUsersProfileKeysCollection - = @"kOWSProfilesManager_OtherUsersProfileKeysCollection"; - // TODO: static const NSInteger kProfileKeyLength = 16; @@ -103,10 +96,7 @@ static const NSInteger kProfileKeyLength = 16; @property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -// This property should only be mutated on the main thread, -// -// NOTE: Do not access this property directly; use getOrCreateLocalUserProfile instead. -@property (nonatomic, nullable) UserProfile *localUserProfile; +@property (atomic, nullable) UserProfile *localUserProfile; // This property should only be mutated on the main thread, @property (nonatomic, nullable) UIImage *localCachedAvatarImage; @@ -115,7 +105,6 @@ static const NSInteger kProfileKeyLength = 16; // These three properties can be accessed on any thread. @property (atomic, readonly) NSMutableDictionary *userProfileWhitelistCache; @property (atomic, readonly) NSMutableDictionary *groupProfileWhitelistCache; -@property (atomic, readonly) NSMutableDictionary *otherUsersProfileKeyCache; // This property should only be mutated on the main thread, @property (nonatomic, readonly) NSCache *otherUsersProfileAvatarImageCache; @@ -161,27 +150,22 @@ static const NSInteger kProfileKeyLength = 16; _dbConnection = storageManager.newDatabaseConnection; _userProfileWhitelistCache = [NSMutableDictionary new]; _groupProfileWhitelistCache = [NSMutableDictionary new]; - _otherUsersProfileKeyCache = [NSMutableDictionary new]; _otherUsersProfileAvatarImageCache = [NSCache new]; OWSSingletonAssert(); - // Register this manager with the message sender. - // This is a circular dependency. - [messageSender setProfilesManager:self]; - - // Try to load. - _localProfileKey = [self.dbConnection objectForKey:kOWSProfilesManager_LocalProfileSecretKey - inCollection:kOWSProfilesManager_Collection]; - if (!_localProfileKey) { - // Generate - _localProfileKey = [OWSProfilesManager generateLocalProfileKey]; - // Persist - [self.dbConnection setObject:_localProfileKey - forKey:kOWSProfilesManager_LocalProfileSecretKey - inCollection:kOWSProfilesManager_Collection]; + self.localUserProfile = [self getOrCreateUserProfileForRecipientId:kNSNotificationName_LocalProfileUniqueId]; + OWSAssert(self.localUserProfile); + if (!self.localUserProfile.profileKey) { + self.localUserProfile.profileKey = [OWSProfilesManager generateLocalProfileKey]; + // Make sure to save on the local db connection for consistency. + // + // NOTE: we do an async read/write here to avoid blocking during app launch path. + [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [self.localUserProfile saveWithTransaction:transaction]; + }]; } - OWSAssert(_localProfileKey.length == kProfileKeyLength); + OWSAssert(self.localUserProfile.profileKey.length == kProfileKeyLength); return self; } @@ -206,44 +190,36 @@ static const NSInteger kProfileKeyLength = 16; #pragma mark - User Profile Accessor +// This method can be safely called from any thread. - (UserProfile *)getOrCreateUserProfileForRecipientId:(NSString *)recipientId { - OWSAssert([NSThread isMainThread]); OWSAssert(recipientId.length > 0); __block UserProfile *instance; // Make sure to read on the local db connection for consistency. - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { instance = [UserProfile fetchObjectWithUniqueID:recipientId transaction:transaction]; - - if (!instance) { - instance = [[UserProfile alloc] initWithRecipientId:recipientId - profileName:nil - avatarUrl:nil - avatarDigest:nil - avatarFileName:nil]; - [instance saveWithTransaction:transaction]; - } }]; + if (!instance) { + instance = [[UserProfile alloc] initWithRecipientId:recipientId]; + } + OWSAssert(instance); return instance; } -- (nullable UserProfile *)getOrCreateLocalUserProfile +// All writes to user profiles should occur on the main thread. +- (void)saveUserProfile:(UserProfile *)userProfile { OWSAssert([NSThread isMainThread]); + OWSAssert(userProfile); - if (!self.localUserProfile) { - NSString *_Nullable recipientId = [TSAccountManager localNumber]; - if (!recipientId) { - OWSFail(@"Missing local number."); - return nil; - } - self.localUserProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; - } - return self.localUserProfile; + // Make sure to save on the local db connection for consistency. + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [userProfile saveWithTransaction:transaction]; + }]; } #pragma mark - Local Profile Key @@ -257,14 +233,21 @@ static const NSInteger kProfileKeyLength = 16; #pragma mark - Local Profile -- (NSString *)localProfileName +- (NSData *)localProfileKey +{ + OWSAssert(self.localUserProfile.profileKey.length == kProfileKeyLength); + + return self.localUserProfile.profileKey; +} + +- (nullable NSString *)localProfileName { OWSAssert([NSThread isMainThread]); - return [self getOrCreateLocalUserProfile].profileName; + return self.localUserProfile.profileName; } -- (UIImage *)localProfileAvatarImage +- (nullable UIImage *)localProfileAvatarImage { OWSAssert([NSThread isMainThread]); @@ -299,16 +282,15 @@ static const NSInteger kProfileKeyLength = 16; success:^{ // All reads and writes to user profiles should happen on the main thread. dispatch_async(dispatch_get_main_queue(), ^{ - UserProfile *userProfile = [self getOrCreateLocalUserProfile]; + UserProfile *userProfile = self.localUserProfile; OWSAssert(userProfile); userProfile.profileName = profileName; userProfile.avatarUrl = avatarUrl; userProfile.avatarDigest = avatarDigest; userProfile.avatarFileName = avatarFileName; - // Make sure to save on the local db connection for consistency. - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [userProfile saveWithTransaction:transaction]; - }]; + + [self saveUserProfile:userProfile]; + self.localCachedAvatarImage = avatarImage; successBlock(); @@ -323,7 +305,7 @@ static const NSInteger kProfileKeyLength = 16; }]; }; - UserProfile *userProfile = [self getOrCreateLocalUserProfile]; + UserProfile *userProfile = self.localUserProfile; OWSAssert(userProfile); // If we have a new avatar image, we must first: @@ -536,43 +518,41 @@ static const NSInteger kProfileKeyLength = 16; #pragma mark - Other User's Profiles ++ (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [[self sharedManager] setProfileKey:profileKey forRecipientId:recipientId]; + }); +} + - (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId { + OWSAssert([NSThread isMainThread]); OWSAssert(profileKey.length == kProfileKeyLength); OWSAssert(recipientId.length > 0); if (profileKey.length != kProfileKeyLength) { return; } - NSData *_Nullable existingProfileKey = [self profileKeyForRecipientId:recipientId]; - if (existingProfileKey && - [existingProfileKey isEqual:profileKey]) { + UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + OWSAssert(userProfile); + if (userProfile.profileKey && [userProfile.profileKey isEqual:profileKey]) { // Ignore redundant update. return; } - [self.dbConnection setObject:profileKey - forKey:recipientId - inCollection:kOWSProfilesManager_OtherUsersProfileKeysCollection]; - self.otherUsersProfileKeyCache[recipientId] = profileKey; + userProfile.profileKey = profileKey; + + [self saveUserProfile:userProfile]; } - (nullable NSData *)profileKeyForRecipientId:(NSString *)recipientId { OWSAssert(recipientId.length > 0); - NSData *_Nullable profileKey = self.otherUsersProfileKeyCache[recipientId]; - if (profileKey.length > 0) { - return profileKey; - } - - profileKey = - [self.dbConnection dataForKey:recipientId inCollection:kOWSProfilesManager_OtherUsersProfileKeysCollection]; - if (profileKey) { - OWSAssert(profileKey.length == kProfileKeyLength); - self.otherUsersProfileKeyCache[recipientId] = profileKey; - } - return profileKey; + UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + OWSAssert(userProfile); + return userProfile.profileKey; } - (nullable NSString *)profileNameForRecipientId:(NSString *)recipientId @@ -580,7 +560,7 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert([NSThread isMainThread]); OWSAssert(recipientId.length > 0); - [self fetchProfileForRecipientId:recipientId]; + [self refreshProfileForRecipientId:recipientId]; UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; return userProfile.profileName; @@ -591,7 +571,7 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert([NSThread isMainThread]); OWSAssert(recipientId.length > 0); - [self fetchProfileForRecipientId:recipientId]; + [self refreshProfileForRecipientId:recipientId]; UIImage *_Nullable image = [self.otherUsersProfileAvatarImageCache objectForKey:recipientId]; if (image) { @@ -609,8 +589,9 @@ static const NSInteger kProfileKeyLength = 16; return image; } -- (void)fetchProfileForRecipientId:(NSString *)recipientId +- (void)refreshProfileForRecipientId:(NSString *)recipientId { + OWSAssert([NSThread isMainThread]); OWSAssert(recipientId.length > 0); UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; @@ -623,9 +604,8 @@ static const NSInteger kProfileKeyLength = 16; } userProfile.lastUpdateDate = [NSDate new]; - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [userProfile saveWithTransaction:transaction]; - }]; + + [self saveUserProfile:userProfile]; // TODO: Actually update the profile. } From 6ec756de444206cd8942fd06a357c0416498b1d2 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 2 Aug 2017 13:12:26 -0400 Subject: [PATCH 2/5] Move profile manager to Signal. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 30 +++++-- Signal/src/AppDelegate.m | 6 +- .../src/Profiles/OWSProfileManager.h | 9 +- .../src/Profiles/OWSProfileManager.m | 85 +++++++++---------- .../{ => Profiles}/ProfileFetcherJob.swift | 0 .../ConversationView/MessagesViewController.m | 2 - .../OWSConversationSettingsViewController.m | 8 +- .../ViewControllers/ProfileViewController.m | 22 ++--- Signal/src/contact/OWSContactsManager.m | 4 +- Signal/test/util/ExceptionsTest.m | 1 + .../Messages/Interactions/TSOutgoingMessage.m | 14 +-- .../src/Messages/TSMessagesManager.m | 5 +- .../src/Protocols/ProfileManagerProtocol.h | 17 ++++ SignalServiceKit/src/TextSecureKitEnv.h | 5 +- SignalServiceKit/src/TextSecureKitEnv.m | 11 ++- 15 files changed, 129 insertions(+), 90 deletions(-) rename SignalServiceKit/src/Profiles/OWSProfilesManager.h => Signal/src/Profiles/OWSProfileManager.h (90%) rename SignalServiceKit/src/Profiles/OWSProfilesManager.m => Signal/src/Profiles/OWSProfileManager.m (91%) rename Signal/src/{ => Profiles}/ProfileFetcherJob.swift (100%) create mode 100644 SignalServiceKit/src/Protocols/ProfileManagerProtocol.h diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 4213a5b75..3c86df26c 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -77,6 +77,8 @@ 34CCAF381F0C0599004084F4 /* AppUpdateNag.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */; }; 34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */; }; 34CE88E71F2FB9A10098030F /* ProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88E61F2FB9A10098030F /* ProfileViewController.m */; }; + 34CE88EC1F3237260098030F /* OWSProfileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88EA1F3237260098030F /* OWSProfileManager.m */; }; + 34CE88ED1F3237260098030F /* ProfileFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88EB1F3237260098030F /* ProfileFetcherJob.swift */; }; 34D5CC961EA6AFAD005515DB /* OWSContactsSyncing.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */; }; 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; }; 34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */; }; @@ -168,7 +170,6 @@ 45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; }; 45855F381D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; }; 4585C4601ED4FD0400896AEA /* OWS104CreateRecipientIdentities.m in Sources */ = {isa = PBXBuildFile; fileRef = 4585C45F1ED4FD0400896AEA /* OWS104CreateRecipientIdentities.m */; }; - 4585C4661ED5DF7A00896AEA /* ProfileFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585C4651ED5DF7A00896AEA /* ProfileFetcherJob.swift */; }; 4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585C4671ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift */; }; 458967111DC117CC00E9DD21 /* AccountManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458967101DC117CC00E9DD21 /* AccountManagerTest.swift */; }; 458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */; }; @@ -506,6 +507,9 @@ 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAddToContactViewController.m; sourceTree = ""; }; 34CE88E51F2FB9A10098030F /* ProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProfileViewController.h; sourceTree = ""; }; 34CE88E61F2FB9A10098030F /* ProfileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProfileViewController.m; sourceTree = ""; }; + 34CE88E91F3237260098030F /* OWSProfileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProfileManager.h; sourceTree = ""; }; + 34CE88EA1F3237260098030F /* OWSProfileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProfileManager.m; sourceTree = ""; }; + 34CE88EB1F3237260098030F /* ProfileFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileFetcherJob.swift; sourceTree = ""; }; 34D5CC941EA6AFAD005515DB /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = ""; }; 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = ""; }; 34D5CC981EA6EB79005515DB /* OWSMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCollectionViewCell.h; sourceTree = ""; }; @@ -1074,6 +1078,16 @@ path = TSMessageAdapters; sourceTree = ""; }; + 34CE88E81F3237260098030F /* Profiles */ = { + isa = PBXGroup; + children = ( + 34CE88E91F3237260098030F /* OWSProfileManager.h */, + 34CE88EA1F3237260098030F /* OWSProfileManager.m */, + 34CE88EB1F3237260098030F /* ProfileFetcherJob.swift */, + ); + path = Profiles; + sourceTree = ""; + }; 34D8C0221ED3673300188D7C /* DebugUI */ = { isa = PBXGroup; children = ( @@ -1274,21 +1288,22 @@ 76EB03C118170B33006006FC /* src */ = { isa = PBXGroup; children = ( - 45CD81A41DBFF8CF004C9430 /* Storyboards */, 76EB03C218170B33006006FC /* AppDelegate.h */, 76EB03C318170B33006006FC /* AppDelegate.m */, 76EB03FE18170B33006006FC /* call */, 76EB040318170B33006006FC /* contact */, 70DBA29918CFE98500771DAD /* crypto */, 76EB041118170B33006006FC /* environment */, - 76EB041D18170B33006006FC /* network */, - B60959791C2C0FA9004E8797 /* rating */, - 76EB04C818170B33006006FC /* util */, 45D231751DC7E8C50034FA89 /* Jobs */, 457F3AC01D14A0F700C51351 /* Models */, - 450DF2061E0DD28D003D14BE /* UserInterface */, + 76EB041D18170B33006006FC /* network */, 45BFFFA51D898AB8004A12A7 /* Observers */, + 34CE88E81F3237260098030F /* Profiles */, + B60959791C2C0FA9004E8797 /* rating */, 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */, + 45CD81A41DBFF8CF004C9430 /* Storyboards */, + 450DF2061E0DD28D003D14BE /* UserInterface */, + 76EB04C818170B33006006FC /* util */, ); path = src; sourceTree = ""; @@ -2123,7 +2138,6 @@ buildActionMask = 2147483647; files = ( 34B3F8761E8DF1700035BE1A /* CodeVerificationViewController.m in Sources */, - 4585C4661ED5DF7A00896AEA /* ProfileFetcherJob.swift in Sources */, 76EB063E18170B33006006FC /* Operation.m in Sources */, 34B3F8741E8DF1700035BE1A /* AttachmentSharing.m in Sources */, 34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */, @@ -2139,10 +2153,12 @@ 3456710A1E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m in Sources */, 450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */, 340CB2241EAC155C0001CAA1 /* ContactsViewHelper.m in Sources */, + 34CE88EC1F3237260098030F /* OWSProfileManager.m in Sources */, 45CD81F21DC03A22004C9430 /* OWSLogger.m in Sources */, 4542F0941EB9372700C7EE92 /* SystemContactsFetcher.swift in Sources */, B60C16651988999D00E97A6C /* VersionMigrations.m in Sources */, B97940271832BD2400BD66CB /* UIUtil.m in Sources */, + 34CE88ED1F3237260098030F /* ProfileFetcherJob.swift in Sources */, 34B3F8791E8DF1700035BE1A /* CountryCodeViewController.m in Sources */, 4CE0E3771B954546007210CF /* TSAnimatedAdapter.m in Sources */, 4531C9C41DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m in Sources */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 367869aad..ff0321d70 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -11,6 +11,7 @@ #import "NotificationsManager.h" #import "OWSContactsManager.h" #import "OWSContactsSyncing.h" +#import "OWSProfileManager.h" #import "OWSStaleNotificationObserver.h" #import "Pastelog.h" #import "PropertyListPreferences.h" @@ -28,7 +29,6 @@ #import #import #import -#import #import #import #import @@ -161,7 +161,6 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; DDLogInfo(@"%@ application: didFinishLaunchingWithOptions completed.", self.tag); [OWSAnalytics appLaunchDidBegin]; - [OWSProfilesManager.sharedManager appLaunchDidBegin]; return YES; } @@ -236,7 +235,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; [[TextSecureKitEnv alloc] initWithCallMessageHandler:[Environment getCurrent].callMessageHandler contactsManager:[Environment getCurrent].contactsManager messageSender:[Environment getCurrent].messageSender - notificationsManager:[Environment getCurrent].notificationsManager]; + notificationsManager:[Environment getCurrent].notificationsManager + profileManager:OWSProfileManager.sharedManager]; [TextSecureKitEnv setSharedEnv:sharedEnv]; [[TSStorageManager sharedManager] setupDatabaseWithSafeBlockingMigrations:^{ diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.h b/Signal/src/Profiles/OWSProfileManager.h similarity index 90% rename from SignalServiceKit/src/Profiles/OWSProfilesManager.h rename to Signal/src/Profiles/OWSProfileManager.h index c61ec32ec..5503f80a6 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.h +++ b/Signal/src/Profiles/OWSProfileManager.h @@ -2,6 +2,8 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +#import + NS_ASSUME_NONNULL_BEGIN extern NSString *const kNSNotificationName_LocalProfileDidChange; @@ -10,7 +12,7 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; @class TSThread; // This class can be safely accessed and used from any thread. -@interface OWSProfilesManager : NSObject +@interface OWSProfileManager : NSObject - (instancetype)init NS_UNAVAILABLE; @@ -33,9 +35,6 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; success:(void (^)())successBlock failure:(void (^)())failureBlock; -// This method should only be called from the main thread. -- (void)appLaunchDidBegin; - #pragma mark - Profile Whitelist - (void)addUserToProfileWhitelist:(NSString *)recipientId; @@ -50,7 +49,7 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; #pragma mark - Other User's Profiles -+ (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId; +- (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId; - (nullable NSData *)profileKeyForRecipientId:(NSString *)recipientId; diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.m b/Signal/src/Profiles/OWSProfileManager.m similarity index 91% rename from SignalServiceKit/src/Profiles/OWSProfilesManager.m rename to Signal/src/Profiles/OWSProfileManager.m index 3b14981ac..2ff9c417f 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -2,16 +2,18 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // -#import "OWSProfilesManager.h" -#import "NSData+hexString.h" -#import "NSDate+OWS.h" -#import "OWSMessageSender.h" -#import "SecurityUtils.h" -#import "TSGroupThread.h" -#import "TSStorageManager.h" -#import "TSThread.h" -#import "TSYapDatabaseObject.h" -#import "TextSecureKitEnv.h" +#import "OWSProfileManager.h" +#import "Environment.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -27,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, nullable) NSString *avatarUrl; @property (nonatomic, nullable) NSString *avatarDigest; -// This filename is relative to OWSProfilesManager.profileAvatarsDirPath. +// This filename is relative to OWSProfileManager.profileAvatarsDirPath. @property (nonatomic, nullable) NSString *avatarFileName; // This should reflect when either: @@ -85,13 +87,13 @@ NSString *const kNSNotificationName_LocalProfileUniqueId = @"kNSNotificationName NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationName_LocalProfileDidChange"; NSString *const kNSNotificationName_OtherUsersProfileDidChange = @"kNSNotificationName_OtherUsersProfileDidChange"; -NSString *const kOWSProfilesManager_UserWhitelistCollection = @"kOWSProfilesManager_UserWhitelistCollection"; -NSString *const kOWSProfilesManager_GroupWhitelistCollection = @"kOWSProfilesManager_GroupWhitelistCollection"; +NSString *const kOWSProfileManager_UserWhitelistCollection = @"kOWSProfileManager_UserWhitelistCollection"; +NSString *const kOWSProfileManager_GroupWhitelistCollection = @"kOWSProfileManager_GroupWhitelistCollection"; // TODO: static const NSInteger kProfileKeyLength = 16; -@interface OWSProfilesManager () +@interface OWSProfileManager () @property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; @@ -113,11 +115,11 @@ static const NSInteger kProfileKeyLength = 16; #pragma mark - -@implementation OWSProfilesManager +@implementation OWSProfileManager + (instancetype)sharedManager { - static OWSProfilesManager *sharedMyManager = nil; + static OWSProfileManager *sharedMyManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedMyManager = [[self alloc] initDefault]; @@ -128,7 +130,7 @@ static const NSInteger kProfileKeyLength = 16; - (instancetype)initDefault { TSStorageManager *storageManager = [TSStorageManager sharedManager]; - OWSMessageSender *messageSender = [TextSecureKitEnv sharedEnv].messageSender; + OWSMessageSender *messageSender = [Environment getCurrent].messageSender; return [self initWithStorageManager:storageManager messageSender:messageSender]; } @@ -157,7 +159,7 @@ static const NSInteger kProfileKeyLength = 16; self.localUserProfile = [self getOrCreateUserProfileForRecipientId:kNSNotificationName_LocalProfileUniqueId]; OWSAssert(self.localUserProfile); if (!self.localUserProfile.profileKey) { - self.localUserProfile.profileKey = [OWSProfilesManager generateLocalProfileKey]; + self.localUserProfile.profileKey = [OWSProfileManager generateLocalProfileKey]; // Make sure to save on the local db connection for consistency. // // NOTE: we do an async read/write here to avoid blocking during app launch path. @@ -183,11 +185,6 @@ static const NSInteger kProfileKeyLength = 16; object:nil]; } -- (void)appLaunchDidBegin -{ - // Do nothing; we only want to make sure this singleton is created on startup. -} - #pragma mark - User Profile Accessor // This method can be safely called from any thread. @@ -422,7 +419,7 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert([NSThread isMainThread]); OWSAssert(recipientId.length > 0); - [self.dbConnection setBool:YES forKey:recipientId inCollection:kOWSProfilesManager_UserWhitelistCollection]; + [self.dbConnection setBool:YES forKey:recipientId inCollection:kOWSProfileManager_UserWhitelistCollection]; self.userProfileWhitelistCache[recipientId] = @(YES); } @@ -444,7 +441,7 @@ static const NSInteger kProfileKeyLength = 16; [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { for (NSString *recipientId in recipientIds) { - [transaction setObject:@(YES) forKey:recipientId inCollection:kOWSProfilesManager_UserWhitelistCollection]; + [transaction setObject:@(YES) forKey:recipientId inCollection:kOWSProfileManager_UserWhitelistCollection]; self.userProfileWhitelistCache[recipientId] = @(YES); } }]; @@ -459,7 +456,7 @@ static const NSInteger kProfileKeyLength = 16; return [value boolValue]; } - value = @([self.dbConnection hasObjectForKey:recipientId inCollection:kOWSProfilesManager_UserWhitelistCollection]); + value = @([self.dbConnection hasObjectForKey:recipientId inCollection:kOWSProfileManager_UserWhitelistCollection]); self.userProfileWhitelistCache[recipientId] = value; return [value boolValue]; } @@ -469,7 +466,7 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert(groupId.length > 0); NSString *groupIdKey = [groupId hexadecimalString]; - [self.dbConnection setObject:@(1) forKey:groupIdKey inCollection:kOWSProfilesManager_GroupWhitelistCollection]; + [self.dbConnection setObject:@(1) forKey:groupIdKey inCollection:kOWSProfileManager_GroupWhitelistCollection]; self.groupProfileWhitelistCache[groupIdKey] = @(YES); } @@ -484,7 +481,7 @@ static const NSInteger kProfileKeyLength = 16; } value = - @(nil != [self.dbConnection objectForKey:groupIdKey inCollection:kOWSProfilesManager_GroupWhitelistCollection]); + @(nil != [self.dbConnection objectForKey:groupIdKey inCollection:kOWSProfileManager_GroupWhitelistCollection]); self.groupProfileWhitelistCache[groupIdKey] = value; return [value boolValue]; } @@ -496,10 +493,10 @@ static const NSInteger kProfileKeyLength = 16; if (thread.isGroupThread) { TSGroupThread *groupThread = (TSGroupThread *)thread; NSData *groupId = groupThread.groupModel.groupId; - return [OWSProfilesManager.sharedManager isGroupIdInProfileWhitelist:groupId]; + return [self isGroupIdInProfileWhitelist:groupId]; } else { NSString *recipientId = thread.contactIdentifier; - return [OWSProfilesManager.sharedManager isUserInProfileWhitelist:recipientId]; + return [self isUserInProfileWhitelist:recipientId]; } } @@ -507,7 +504,7 @@ static const NSInteger kProfileKeyLength = 16; { OWSAssert([NSThread isMainThread]); OWSAssert(contactRecipientIds); - + // TODO: The persisted whitelist could either be: // // * Just users manually added to the whitelist. @@ -518,32 +515,26 @@ static const NSInteger kProfileKeyLength = 16; #pragma mark - Other User's Profiles -+ (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [[self sharedManager] setProfileKey:profileKey forRecipientId:recipientId]; - }); -} - - (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId { - OWSAssert([NSThread isMainThread]); OWSAssert(profileKey.length == kProfileKeyLength); OWSAssert(recipientId.length > 0); if (profileKey.length != kProfileKeyLength) { return; } - UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; - OWSAssert(userProfile); - if (userProfile.profileKey && [userProfile.profileKey isEqual:profileKey]) { - // Ignore redundant update. - return; - } + dispatch_async(dispatch_get_main_queue(), ^{ + UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + OWSAssert(userProfile); + if (userProfile.profileKey && [userProfile.profileKey isEqual:profileKey]) { + // Ignore redundant update. + return; + } - userProfile.profileKey = profileKey; + userProfile.profileKey = profileKey; - [self saveUserProfile:userProfile]; + [self saveUserProfile:userProfile]; + }); } - (nullable NSData *)profileKeyForRecipientId:(NSString *)recipientId diff --git a/Signal/src/ProfileFetcherJob.swift b/Signal/src/Profiles/ProfileFetcherJob.swift similarity index 100% rename from Signal/src/ProfileFetcherJob.swift rename to Signal/src/Profiles/ProfileFetcherJob.swift diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index b21059e4b..657049768 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -75,7 +75,6 @@ #import #import #import -#import #import #import #import @@ -967,7 +966,6 @@ typedef enum : NSUInteger { } [self updateNavigationBarSubtitleLabel]; [ProfileFetcherJob runWithThread:self.thread networkManager:self.networkManager]; - [OWSProfilesManager.sharedManager refreshProfilesForThread:self.thread]; [self markVisibleMessagesAsRead]; } diff --git a/Signal/src/ViewControllers/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/OWSConversationSettingsViewController.m index d13979576..371aae256 100644 --- a/Signal/src/ViewControllers/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/OWSConversationSettingsViewController.m @@ -11,6 +11,7 @@ #import "OWSAvatarBuilder.h" #import "OWSBlockingManager.h" #import "OWSContactsManager.h" +#import "OWSProfileManager.h" #import "PhoneNumber.h" #import "ShowGroupMembersViewController.h" #import "Signal-Swift.h" @@ -24,7 +25,6 @@ #import #import #import -#import #import #import #import @@ -294,7 +294,7 @@ NS_ASSUME_NONNULL_BEGIN }]]; } - if ([OWSProfilesManager.sharedManager isThreadInProfileWhitelist:self.thread]) { + if ([OWSProfileManager.sharedManager isThreadInProfileWhitelist:self.thread]) { [mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ return [weakSelf labelCellWithName:(self.isGroupThread @@ -804,10 +804,10 @@ NS_ASSUME_NONNULL_BEGIN if (self.isGroupThread) { TSGroupThread *groupThread = (TSGroupThread *)self.thread; NSData *groupId = groupThread.groupModel.groupId; - [OWSProfilesManager.sharedManager addGroupIdToProfileWhitelist:groupId]; + [OWSProfileManager.sharedManager addGroupIdToProfileWhitelist:groupId]; } else { NSString *recipientId = self.thread.contactIdentifier; - [OWSProfilesManager.sharedManager addUserToProfileWhitelist:recipientId]; + [OWSProfileManager.sharedManager addUserToProfileWhitelist:recipientId]; } [self updateTableContents]; diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 915fa5656..14a62076f 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -4,12 +4,12 @@ #import "ProfileViewController.h" #import "AvatarViewHelper.h" +#import "OWSProfileManager.h" #import "Signal-Swift.h" #import "UIColor+OWS.h" #import "UIFont+OWS.h" #import "UIView+OWS.h" #import "UIViewController+OWS.h" -#import NS_ASSUME_NONNULL_BEGIN @@ -46,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN _avatarViewHelper = [AvatarViewHelper new]; _avatarViewHelper.delegate = self; - _avatar = [OWSProfilesManager.sharedManager localProfileAvatarImage]; + _avatar = [OWSProfileManager.sharedManager localProfileAvatarImage]; [self createViews]; } @@ -59,7 +59,7 @@ NS_ASSUME_NONNULL_BEGIN _nameTextField.placeholder = NSLocalizedString( @"PROFILE_VIEW_NAME_DEFAULT_TEXT", @"Default text for the profile name field of the profile view."); _nameTextField.delegate = self; - _nameTextField.text = [OWSProfilesManager.sharedManager localProfileName]; + _nameTextField.text = [OWSProfileManager.sharedManager localProfileName]; [_nameTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; _avatarView = [AvatarImageView new]; @@ -212,14 +212,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateProfile { __weak ProfileViewController *weakSelf = self; - [OWSProfilesManager.sharedManager updateLocalProfileName:self.nameTextField.text - avatarImage:self.avatar - success:^{ - [weakSelf.navigationController popViewControllerAnimated:YES]; - } - failure:^{ - // TODO: Handle failure. - }]; + [OWSProfileManager.sharedManager updateLocalProfileName:self.nameTextField.text + avatarImage:self.avatar + success:^{ + [weakSelf.navigationController popViewControllerAnimated:YES]; + } + failure:^{ + // TODO: Handle failure. + }]; } #pragma mark - UITextFieldDelegate diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index 287443b2c..ae7b641a1 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -4,12 +4,12 @@ #import "OWSContactsManager.h" #import "Environment.h" +#import "OWSProfileManager.h" #import "Signal-Swift.h" #import "Util.h" #import "ViewControllerUtils.h" #import #import -#import #import #import @@ -206,7 +206,7 @@ NSString *const kTSStorageManager_AccountLastNames = @"kTSStorageManager_Account self.signalAccountMap = [signalAccountMap copy]; self.signalAccounts = [signalAccounts copy]; - [OWSProfilesManager.sharedManager setContactRecipientIds:signalAccountMap.allKeys]; + [OWSProfileManager.sharedManager setContactRecipientIds:signalAccountMap.allKeys]; [self updateCachedDisplayNames]; }); diff --git a/Signal/test/util/ExceptionsTest.m b/Signal/test/util/ExceptionsTest.m index cac5a7efb..ce6fc6ee3 100644 --- a/Signal/test/util/ExceptionsTest.m +++ b/Signal/test/util/ExceptionsTest.m @@ -6,6 +6,7 @@ #import "TestUtil.h" @implementation ExceptionsTest + - (void)testContracts { ows_require(1 + 1 == 2); @try { diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 30a53ffd4..80b8682b1 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -5,12 +5,13 @@ #import "TSOutgoingMessage.h" #import "NSDate+millisecondTimeStamp.h" #import "OWSOutgoingSyncMessage.h" -#import "OWSProfilesManager.h" #import "OWSSignalServiceProtos.pb.h" +#import "ProfileManagerProtocol.h" #import "SignalRecipient.h" #import "TSAttachmentStream.h" #import "TSContactThread.h" #import "TSGroupThread.h" +#import "TextSecureKitEnv.h" #import #import @@ -464,7 +465,10 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec OWSAssert(contentBuilder); OWSAssert(recipient); - OWSAssert(OWSProfilesManager.sharedManager.localProfileKey.length > 0); + id profileManager = [TextSecureKitEnv sharedEnv].profileManager; + NSData *localProfileKey = profileManager.localProfileKey; + + OWSAssert(localProfileKey.length > 0); BOOL shouldIncludeProfileKey = NO; if ([self isKindOfClass:[OWSOutgoingSyncMessage class]]) { @@ -478,15 +482,15 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec // // For Group threads, we want to include the profile key IFF the // recipient OR the group is in the whitelist. - if ([OWSProfilesManager.sharedManager isUserInProfileWhitelist:recipient.recipientId]) { + if ([profileManager isUserInProfileWhitelist:recipient.recipientId]) { shouldIncludeProfileKey = YES; - } else if ([OWSProfilesManager.sharedManager isThreadInProfileWhitelist:self.thread]) { + } else if ([profileManager isThreadInProfileWhitelist:self.thread]) { shouldIncludeProfileKey = YES; } } if (shouldIncludeProfileKey) { - [contentBuilder setProfileKey:OWSProfilesManager.sharedManager.localProfileKey]; + [contentBuilder setProfileKey:localProfileKey]; } } diff --git a/SignalServiceKit/src/Messages/TSMessagesManager.m b/SignalServiceKit/src/Messages/TSMessagesManager.m index aae36da91..79164dda7 100644 --- a/SignalServiceKit/src/Messages/TSMessagesManager.m +++ b/SignalServiceKit/src/Messages/TSMessagesManager.m @@ -19,12 +19,12 @@ #import "OWSIncomingMessageFinder.h" #import "OWSIncomingSentMessageTranscript.h" #import "OWSMessageSender.h" -#import "OWSProfilesManager.h" #import "OWSReadReceiptsProcessor.h" #import "OWSRecordTranscriptJob.h" #import "OWSSyncContactsMessage.h" #import "OWSSyncGroupsMessage.h" #import "OWSSyncGroupsRequestMessage.h" +#import "ProfileManagerProtocol.h" #import "TSAccountManager.h" #import "TSAttachmentStream.h" #import "TSCall.h" @@ -499,7 +499,8 @@ NS_ASSUME_NONNULL_BEGIN if ([content hasProfileKey]) { NSData *profileKey = [content profileKey]; NSString *recipientId = envelope.source; - [OWSProfilesManager setProfileKey:profileKey forRecipientId:recipientId]; + id profileManager = [TextSecureKitEnv sharedEnv].profileManager; + [profileManager setProfileKey:profileKey forRecipientId:recipientId]; } if (content.hasSyncMessage) { diff --git a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h new file mode 100644 index 000000000..ac1b750ee --- /dev/null +++ b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h @@ -0,0 +1,17 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +@class TSThread; + +@protocol ProfileManagerProtocol + +- (NSData *)localProfileKey; + +- (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId; + +- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId; + +- (BOOL)isThreadInProfileWhitelist:(TSThread *)thread; + +@end diff --git a/SignalServiceKit/src/TextSecureKitEnv.h b/SignalServiceKit/src/TextSecureKitEnv.h index 424f6daed..11642e215 100644 --- a/SignalServiceKit/src/TextSecureKitEnv.h +++ b/SignalServiceKit/src/TextSecureKitEnv.h @@ -8,13 +8,15 @@ NS_ASSUME_NONNULL_BEGIN @class OWSMessageSender; @protocol NotificationsProtocol; @protocol OWSCallMessageHandler; +@protocol ProfileManagerProtocol; @interface TextSecureKitEnv : NSObject - (instancetype)initWithCallMessageHandler:(id)callMessageHandler contactsManager:(id)contactsManager messageSender:(OWSMessageSender *)messageSender - notificationsManager:(id)notificationsManager NS_DESIGNATED_INITIALIZER; + notificationsManager:(id)notificationsManager + profileManager:(id)profileManager NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; @@ -25,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) id contactsManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) id notificationsManager; +@property (nonatomic, readonly) id profileManager; @end diff --git a/SignalServiceKit/src/TextSecureKitEnv.m b/SignalServiceKit/src/TextSecureKitEnv.m index aba5ed412..8bda6af70 100644 --- a/SignalServiceKit/src/TextSecureKitEnv.m +++ b/SignalServiceKit/src/TextSecureKitEnv.m @@ -11,12 +11,14 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance; @implementation TextSecureKitEnv @synthesize callMessageHandler = _callMessageHandler, contactsManager = _contactsManager, - messageSender = _messageSender, notificationsManager = _notificationsManager; + messageSender = _messageSender, notificationsManager = _notificationsManager, + profileManager = _profileManager; - (instancetype)initWithCallMessageHandler:(id)callMessageHandler contactsManager:(id)contactsManager messageSender:(OWSMessageSender *)messageSender notificationsManager:(id)notificationsManager + profileManager:(id)profileManager { self = [super init]; if (!self) { @@ -27,6 +29,7 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance; _contactsManager = contactsManager; _messageSender = messageSender; _notificationsManager = notificationsManager; + _profileManager = profileManager; return self; } @@ -71,6 +74,12 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance; return _notificationsManager; } +- (id)profileManager +{ + NSAssert(_profileManager, @"Trying to access the profileManager before it's set."); + return _profileManager; +} + @end NS_ASSUME_NONNULL_END From 9c0f94f1c095bddb3a153c3191a6087209280da5 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 3 Aug 2017 11:13:40 -0400 Subject: [PATCH 3/5] Fetch profiles from profile manager. Update profile manager with profile fetch results. // FREEBIE --- Signal/src/Profiles/OWSProfileManager.h | 5 + Signal/src/Profiles/OWSProfileManager.m | 218 ++++++++++++++++++-- Signal/src/Profiles/ProfileFetcherJob.swift | 71 +++++-- Signal/src/Signal-Bridging-Header.h | 1 + 4 files changed, 265 insertions(+), 30 deletions(-) diff --git a/Signal/src/Profiles/OWSProfileManager.h b/Signal/src/Profiles/OWSProfileManager.h index 5503f80a6..2d2530a2a 100644 --- a/Signal/src/Profiles/OWSProfileManager.h +++ b/Signal/src/Profiles/OWSProfileManager.h @@ -59,6 +59,11 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; - (void)refreshProfileForRecipientId:(NSString *)recipientId; +- (void)updateProfileForRecipientId:(NSString *)recipientId + profileNameEncrypted:(NSData *_Nullable)profileNameEncrypted + avatarUrlEncrypted:(NSData *_Nullable)avatarUrlEncrypted + avatarDigestEncrypted:(NSData *_Nullable)avatarDigestEncrypted; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index 2ff9c417f..14a0083d1 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -4,6 +4,7 @@ #import "OWSProfileManager.h" #import "Environment.h" +#import "Signal-Swift.h" #import #import #import @@ -27,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN // These properties may be accessed only from the main thread. @property (nonatomic, nullable) NSString *profileName; @property (nonatomic, nullable) NSString *avatarUrl; -@property (nonatomic, nullable) NSString *avatarDigest; +@property (nonatomic, nullable) NSData *avatarDigest; // This filename is relative to OWSProfileManager.profileAvatarsDirPath. @property (nonatomic, nullable) NSString *avatarFileName; @@ -68,8 +69,7 @@ NS_ASSUME_NONNULL_BEGIN { return ([other isKindOfClass:[UserProfile class]] && [self.recipientId isEqualToString:other.recipientId] && [self.profileName isEqualToString:other.profileName] && [self.avatarUrl isEqualToString:other.avatarUrl] && - [self.avatarDigest isEqualToString:other.avatarDigest] && - [self.avatarFileName isEqualToString:other.avatarFileName]); + [self.avatarDigest isEqual:other.avatarDigest] && [self.avatarFileName isEqualToString:other.avatarFileName]); } - (NSUInteger)hash @@ -97,6 +97,7 @@ static const NSInteger kProfileKeyLength = 16; @property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; +@property (nonatomic, readonly) TSNetworkManager *networkManager; @property (atomic, nullable) UserProfile *localUserProfile; // This property should only be mutated on the main thread, @@ -131,12 +132,14 @@ static const NSInteger kProfileKeyLength = 16; { TSStorageManager *storageManager = [TSStorageManager sharedManager]; OWSMessageSender *messageSender = [Environment getCurrent].messageSender; + TSNetworkManager *networkManager = [Environment getCurrent].networkManager; - return [self initWithStorageManager:storageManager messageSender:messageSender]; + return [self initWithStorageManager:storageManager messageSender:messageSender networkManager:networkManager]; } - (instancetype)initWithStorageManager:(TSStorageManager *)storageManager messageSender:(OWSMessageSender *)messageSender + networkManager:(TSNetworkManager *)networkManager { self = [super init]; @@ -147,9 +150,12 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert([NSThread isMainThread]); OWSAssert(storageManager); OWSAssert(messageSender); + OWSAssert(messageSender); _messageSender = messageSender; _dbConnection = storageManager.newDatabaseConnection; + _networkManager = networkManager; + _userProfileWhitelistCache = [NSMutableDictionary new]; _groupProfileWhitelistCache = [NSMutableDictionary new]; _otherUsersProfileAvatarImageCache = [NSCache new]; @@ -217,6 +223,16 @@ static const NSInteger kProfileKeyLength = 16; [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [userProfile saveWithTransaction:transaction]; }]; + + if (userProfile == self.localUserProfile) { + [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange + object:nil + userInfo:nil]; + } else { + [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_OtherUsersProfileDidChange + object:nil + userInfo:nil]; + } } #pragma mark - Local Profile Key @@ -271,8 +287,8 @@ static const NSInteger kProfileKeyLength = 16; // // * Try to update the service. // * Update client state on success. - void (^tryToUpdateService)(NSString *_Nullable, NSString *_Nullable, NSString *_Nullable) = ^( - NSString *_Nullable avatarUrl, NSString *_Nullable avatarDigest, NSString *_Nullable avatarFileName) { + void (^tryToUpdateService)(NSString *_Nullable, NSData *_Nullable, NSString *_Nullable) = ^( + NSString *_Nullable avatarUrl, NSData *_Nullable avatarDigest, NSString *_Nullable avatarFileName) { [self updateProfileOnService:profileName avatarUrl:avatarUrl avatarDigest:avatarDigest @@ -291,10 +307,6 @@ static const NSInteger kProfileKeyLength = 16; self.localCachedAvatarImage = avatarImage; successBlock(); - - [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange - object:nil - userInfo:nil]; }); } failure:^{ @@ -325,7 +337,7 @@ static const NSInteger kProfileKeyLength = 16; success:^(NSData *data, NSString *fileName) { [self uploadAvatarToService:data fileName:fileName - success:^(NSString *avatarUrl, NSString *avatarDigest) { + success:^(NSString *avatarUrl, NSData *avatarDigest) { tryToUpdateService(avatarUrl, avatarDigest, fileName); } failure:^{ @@ -372,7 +384,7 @@ static const NSInteger kProfileKeyLength = 16; // TODO: The exact API & encryption scheme for avatars is not yet settled. - (void)uploadAvatarToService:(NSData *)data fileName:(NSString *)fileName - success:(void (^)(NSString *avatarUrl, NSString *avatarDigest))successBlock + success:(void (^)(NSString *avatarUrl, NSData *avatarDigest))successBlock failure:(void (^)())failureBlock { OWSAssert(data.length > 0); @@ -383,7 +395,7 @@ static const NSInteger kProfileKeyLength = 16; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // TODO: NSString *avatarUrl = @"avatarUrl"; - NSString *avatarDigest = @"avatarDigest"; + NSData *avatarDigest = [@"avatarDigest" dataUsingEncoding:NSUTF8StringEncoding]; if (YES) { successBlock(avatarUrl, avatarDigest); return; @@ -395,7 +407,7 @@ static const NSInteger kProfileKeyLength = 16; // TODO: The exact API & encryption scheme for profiles is not yet settled. - (void)updateProfileOnService:(nullable NSString *)localProfileName avatarUrl:(nullable NSString *)avatarUrl - avatarDigest:(nullable NSString *)avatarDigest + avatarDigest:(nullable NSData *)avatarDigest success:(void (^)())successBlock failure:(void (^)())failureBlock { @@ -403,6 +415,12 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert(failureBlock); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // TODO: Do we need to use NSDataBase64EncodingOptions? + NSString *_Nullable localProfileNameEncrypted = + [[self encryptProfileString:localProfileName] base64EncodedString]; + NSString *_Nullable avatarUrlEncrypted = [[self encryptProfileString:avatarUrl] base64EncodedString]; + NSString *_Nullable avatarDigestEncrypted = [[self encryptProfileData:avatarDigest] base64EncodedString]; + // TODO: if (YES) { successBlock(); @@ -534,6 +552,8 @@ static const NSInteger kProfileKeyLength = 16; userProfile.profileKey = profileKey; [self saveUserProfile:userProfile]; + + [self refreshProfileForRecipientId:recipientId ignoreThrottling:YES]; }); } @@ -575,18 +595,40 @@ static const NSInteger kProfileKeyLength = 16; if (image) { [self.otherUsersProfileAvatarImageCache setObject:image forKey:recipientId]; } + } else if (userProfile.avatarUrl) { + [self downloadProfileAvatarWithUrl:userProfile.avatarUrl recipientId:recipientId]; } return image; } +- (void)downloadProfileAvatarWithUrl:(NSString *)avatarUrl recipientId:(NSString *)recipientId +{ + OWSAssert(avatarUrl.length > 0); + OWSAssert(recipientId.length > 0); + + // TODO: +} + - (void)refreshProfileForRecipientId:(NSString *)recipientId +{ + [self refreshProfileForRecipientId:recipientId ignoreThrottling:NO]; +} + +- (void)refreshProfileForRecipientId:(NSString *)recipientId ignoreThrottling:(BOOL)ignoreThrottling { OWSAssert([NSThread isMainThread]); OWSAssert(recipientId.length > 0); UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + if (!userProfile.profileKey) { + // There's no point in fetching the profile for a user + // if we don't have their profile key; we won't be able + // to decrypt it. + return; + } + // Throttle and debounce the updates. const NSTimeInterval kMaxRefreshFrequency = 5 * kMinuteInterval; if (userProfile.lastUpdateDate && fabs([userProfile.lastUpdateDate timeIntervalSinceNow]) < kMaxRefreshFrequency) { @@ -598,7 +640,153 @@ static const NSInteger kProfileKeyLength = 16; [self saveUserProfile:userProfile]; - // TODO: Actually update the profile. + [ProfileFetcherJob runWithRecipientId:recipientId + networkManager:self.networkManager + ignoreThrottling:ignoreThrottling]; +} + +- (void)updateProfileForRecipientId:(NSString *)recipientId + profileNameEncrypted:(NSData *_Nullable)profileNameEncrypted + avatarUrlEncrypted:(NSData *_Nullable)avatarUrlEncrypted + avatarDigestEncrypted:(NSData *_Nullable)avatarDigestEncrypted +{ + OWSAssert(recipientId.length > 0); + + UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + if (!userProfile.profileKey) { + return; + } + + NSString *_Nullable profileName = + [self decryptProfileString:profileNameEncrypted profileKey:userProfile.profileKey]; + NSString *_Nullable avatarUrl = [self decryptProfileString:avatarUrlEncrypted profileKey:userProfile.profileKey]; + NSData *_Nullable avatarDigest = [self decryptProfileData:avatarDigestEncrypted profileKey:userProfile.profileKey]; + + if (!avatarUrl || !avatarDigest) { + // If either avatar url or digest is missing, skip both. + avatarUrl = nil; + avatarDigest = nil; + } + + BOOL isProfileNameSame = [self isNullableStringEqual:userProfile.profileName toString:profileName]; + BOOL isAvatarSame = ([self isNullableStringEqual:userProfile.avatarUrl toString:avatarUrl] && + [self isNullableDataEqual:userProfile.avatarDigest toData:avatarDigest]); + if (isProfileNameSame && isAvatarSame) { + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + userProfile.profileName = profileName; + userProfile.avatarUrl = avatarUrl; + userProfile.avatarDigest = avatarDigest; + + if (!isAvatarSame) { + // Evacuate avatar image cache. + [self.otherUsersProfileAvatarImageCache removeObjectForKey:recipientId]; + } + + if (avatarUrl) { + [self downloadProfileAvatarWithUrl:avatarUrl recipientId:recipientId]; + } + + [self saveUserProfile:userProfile]; + }); +} + +- (BOOL)isNullableDataEqual:(NSData *_Nullable)left toData:(NSData *_Nullable)right +{ + if (left == nil && right == nil) { + return YES; + } else if (left == nil || right == nil) { + return YES; + } else { + return [left isEqual:right]; + } +} + +- (BOOL)isNullableStringEqual:(NSString *_Nullable)left toString:(NSString *_Nullable)right +{ + if (left == nil && right == nil) { + return YES; + } else if (left == nil || right == nil) { + return YES; + } else { + return [left isEqualToString:right]; + } +} + +#pragma mark - Profile Encryption + ++ (NSData *_Nullable)decryptProfileData:(NSData *_Nullable)encryptedData profileKey:(NSData *)profileKey +{ + OWSAssert(profileKey.length == kProfileKeyLength); + + if (!encryptedData) { + return nil; + } + + // TODO: Decrypt. + return nil; +} + ++ (NSString *_Nullable)decryptProfileString:(NSData *_Nullable)encryptedData profileKey:(NSData *)profileKey +{ + OWSAssert(profileKey.length == kProfileKeyLength); + + NSData *_Nullable decryptedData = [self decryptProfileData:encryptedData profileKey:profileKey]; + + if (decryptedData) { + return [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; + } else { + return nil; + } +} + ++ (NSData *_Nullable)encryptProfileData:(NSData *_Nullable)data profileKey:(NSData *)profileKey +{ + OWSAssert(profileKey.length == kProfileKeyLength); + + if (!data) { + return nil; + } + + // TODO: Encrypt. + return nil; +} + ++ (NSData *_Nullable)encryptProfileString:(NSString *_Nullable)value profileKey:(NSData *)profileKey +{ + OWSAssert(profileKey.length == kProfileKeyLength); + + if (value) { + NSData *_Nullable data = [value dataUsingEncoding:NSUTF8StringEncoding]; + if (data) { + NSData *_Nullable encryptedData = [self encryptProfileData:data profileKey:profileKey]; + return encryptedData; + } + } + + return nil; +} + +- (NSData *_Nullable)decryptProfileData:(NSData *_Nullable)encryptedData profileKey:(NSData *)profileKey +{ + return [OWSProfileManager decryptProfileData:encryptedData profileKey:profileKey]; +} + +- (NSString *_Nullable)decryptProfileString:(NSData *_Nullable)encryptedData profileKey:(NSData *)profileKey +{ + return [OWSProfileManager decryptProfileString:encryptedData profileKey:profileKey]; +} + +- (NSData *_Nullable)encryptProfileData:(NSData *_Nullable)data +{ + return [OWSProfileManager encryptProfileData:data profileKey:self.localProfileKey]; +} + +- (NSData *_Nullable)encryptProfileString:(NSString *_Nullable)value +{ + return [OWSProfileManager encryptProfileString:value profileKey:self.localProfileKey]; } #pragma mark - Avatar Disk Cache diff --git a/Signal/src/Profiles/ProfileFetcherJob.swift b/Signal/src/Profiles/ProfileFetcherJob.swift index d86fa6eac..ffda36563 100644 --- a/Signal/src/Profiles/ProfileFetcherJob.swift +++ b/Signal/src/Profiles/ProfileFetcherJob.swift @@ -16,17 +16,20 @@ class ProfileFetcherJob: NSObject { // This property is only accessed on the main queue. static var fetchDateMap = [String: Date]() + let ignoreThrottling: Bool + public class func run(thread: TSThread, networkManager: TSNetworkManager) { ProfileFetcherJob(networkManager: networkManager).run(recipientIds: thread.recipientIdentifiers) } - public class func run(recipientId: String, networkManager: TSNetworkManager) { - ProfileFetcherJob(networkManager: networkManager).run(recipientIds: [recipientId]) + public class func run(recipientId: String, networkManager: TSNetworkManager, ignoreThrottling: Bool) { + ProfileFetcherJob(networkManager: networkManager, ignoreThrottling:ignoreThrottling).run(recipientIds: [recipientId]) } - init(networkManager: TSNetworkManager) { + init(networkManager: TSNetworkManager, ignoreThrottling: Bool = false) { self.networkManager = networkManager self.storageManager = TSStorageManager.shared() + self.ignoreThrottling = ignoreThrottling } public func run(recipientIds: [String]) { @@ -65,15 +68,17 @@ class ProfileFetcherJob: NSObject { public func getProfile(recipientId: String) -> Promise { AssertIsOnMainThread() - if let lastDate = ProfileFetcherJob.fetchDateMap[recipientId] { - let lastTimeInterval = fabs(lastDate.timeIntervalSinceNow) - // Don't check a profile more often than every N minutes. - // - // Only throttle profile fetch in production builds in order to - // facilitate debugging. - let kGetProfileMaxFrequencySeconds = _isDebugAssertConfiguration() ? 0 : 60.0 * 5.0 - guard lastTimeInterval > kGetProfileMaxFrequencySeconds else { - return Promise(error: ProfileFetcherJobError.throttled(lastTimeInterval: lastTimeInterval)) + if !ignoreThrottling { + if let lastDate = ProfileFetcherJob.fetchDateMap[recipientId] { + let lastTimeInterval = fabs(lastDate.timeIntervalSinceNow) + // Don't check a profile more often than every N minutes. + // + // Only throttle profile fetch in production builds in order to + // facilitate debugging. + let kGetProfileMaxFrequencySeconds = _isDebugAssertConfiguration() ? 0 : 60.0 * 5.0 + guard lastTimeInterval > kGetProfileMaxFrequencySeconds else { + return Promise(error: ProfileFetcherJobError.throttled(lastTimeInterval: lastTimeInterval)) + } } } ProfileFetcherJob.fetchDateMap[recipientId] = Date() @@ -109,7 +114,10 @@ class ProfileFetcherJob: NSObject { private func updateProfile(signalServiceProfile: SignalServiceProfile) { verifyIdentityUpToDateAsync(recipientId: signalServiceProfile.recipientId, latestIdentityKey: signalServiceProfile.identityKey) - // Eventually we'll want to do more things with new SignalServiceProfile fields here. + OWSProfileManager.shared().updateProfile(forRecipientId : signalServiceProfile.recipientId, + profileNameEncrypted : signalServiceProfile.profileNameEncrypted, + avatarUrlEncrypted : signalServiceProfile.avatarUrlEncrypted, + avatarDigestEncrypted : signalServiceProfile.avatarDigestEncrypted) } private func verifyIdentityUpToDateAsync(recipientId: String, latestIdentityKey: Data) { @@ -130,14 +138,22 @@ struct SignalServiceProfile { enum ValidationError: Error { case invalid(description: String) case invalidIdentityKey(description: String) + case invalidProfileName(description: String) + case invalidAvatarUrl(description: String) + case invalidAvatarDigest(description: String) } public let recipientId: String public let identityKey: Data + public let profileNameEncrypted: Data? + public let avatarUrlEncrypted: Data? + public let avatarDigestEncrypted: Data? init(recipientId: String, rawResponse: Any?) throws { self.recipientId = recipientId + Logger.info("rawResponse: \(rawResponse)") + guard let responseDict = rawResponse as? [String: Any?] else { throw ValidationError.invalid(description: "\(TAG) unexpected type: \(String(describing: rawResponse))") } @@ -145,17 +161,42 @@ struct SignalServiceProfile { guard let identityKeyString = responseDict["identityKey"] as? String else { throw ValidationError.invalidIdentityKey(description: "\(TAG) missing identity key: \(String(describing: rawResponse))") } - guard let identityKeyWithType = Data(base64Encoded: identityKeyString) else { throw ValidationError.invalidIdentityKey(description: "\(TAG) unable to parse identity key: \(identityKeyString)") } - let kIdentityKeyLength = 33 guard identityKeyWithType.count == kIdentityKeyLength else { throw ValidationError.invalidIdentityKey(description: "\(TAG) malformed key \(identityKeyString) with decoded length: \(identityKeyWithType.count)") } + var profileNameEncrypted: Data? = nil + if let profileNameString = responseDict["name"] as? String { + guard let data = Data(base64Encoded: profileNameString) else { + throw ValidationError.invalidProfileName(description: "\(TAG) unable to parse profile name: \(profileNameString)") + } + profileNameEncrypted = data + } + + var avatarUrlEncrypted: Data? = nil + if let avatarUrlString = responseDict["avatar"] as? String { + guard let data = Data(base64Encoded: avatarUrlString) else { + throw ValidationError.invalidAvatarUrl(description: "\(TAG) unable to parse avatar URL: \(avatarUrlString)") + } + avatarUrlEncrypted = data + } + + var avatarDigestEncrypted: Data? = nil + if let avatarDigestString = responseDict["avatarDigest"] as? String { + guard let data = Data(base64Encoded: avatarDigestString) else { + throw ValidationError.invalidAvatarDigest(description: "\(TAG) unable to parse avatar digest: \(avatarDigestString)") + } + avatarDigestEncrypted = data + } + // `removeKeyType` is an objc category method only on NSData, so temporarily cast. self.identityKey = (identityKeyWithType as NSData).removeKeyType() as Data + self.profileNameEncrypted = profileNameEncrypted + self.avatarUrlEncrypted = avatarUrlEncrypted + self.avatarDigestEncrypted = avatarDigestEncrypted } } diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 960986d75..51393b431 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -18,6 +18,7 @@ #import "OWSDatabaseMigration.h" #import "OWSLogger.h" #import "OWSMessageEditing.h" +#import "OWSProfileManager.h" #import "OWSProgressView.h" #import "OWSViewController.h" #import "OWSWebRTCDataProtos.pb.h" From 49e65ba1b1b2aab97832f1affae5a23b701fba66 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 3 Aug 2017 13:43:03 -0400 Subject: [PATCH 4/5] Update user profile update date on successful update. // FREEBIE --- Signal/src/Profiles/OWSProfileManager.m | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index 14a0083d1..a86a4b0fc 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -668,12 +668,8 @@ static const NSInteger kProfileKeyLength = 16; avatarDigest = nil; } - BOOL isProfileNameSame = [self isNullableStringEqual:userProfile.profileName toString:profileName]; BOOL isAvatarSame = ([self isNullableStringEqual:userProfile.avatarUrl toString:avatarUrl] && [self isNullableDataEqual:userProfile.avatarDigest toData:avatarDigest]); - if (isProfileNameSame && isAvatarSame) { - return; - } dispatch_async(dispatch_get_main_queue(), ^{ userProfile.profileName = profileName; @@ -683,12 +679,14 @@ static const NSInteger kProfileKeyLength = 16; if (!isAvatarSame) { // Evacuate avatar image cache. [self.otherUsersProfileAvatarImageCache removeObjectForKey:recipientId]; - } - if (avatarUrl) { - [self downloadProfileAvatarWithUrl:avatarUrl recipientId:recipientId]; + if (avatarUrl) { + [self downloadProfileAvatarWithUrl:avatarUrl recipientId:recipientId]; + } } + userProfile.lastUpdateDate = [NSDate new]; + [self saveUserProfile:userProfile]; }); } From ba506bf09270140f54f0b72bfcd7f9f8d8a5435e Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 4 Aug 2017 10:16:17 -0400 Subject: [PATCH 5/5] Respond to CR. // FREEBIE --- Signal/src/Profiles/OWSProfileManager.m | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index a86a4b0fc..17f9972e5 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -82,7 +82,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - -NSString *const kNSNotificationName_LocalProfileUniqueId = @"kNSNotificationName_LocalProfileUniqueId"; +NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId"; NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationName_LocalProfileDidChange"; NSString *const kNSNotificationName_OtherUsersProfileDidChange = @"kNSNotificationName_OtherUsersProfileDidChange"; @@ -162,7 +162,7 @@ static const NSInteger kProfileKeyLength = 16; OWSSingletonAssert(); - self.localUserProfile = [self getOrCreateUserProfileForRecipientId:kNSNotificationName_LocalProfileUniqueId]; + self.localUserProfile = [self getOrBuildUserProfileForRecipientId:kLocalProfileUniqueId]; OWSAssert(self.localUserProfile); if (!self.localUserProfile.profileKey) { self.localUserProfile.profileKey = [OWSProfileManager generateLocalProfileKey]; @@ -194,7 +194,7 @@ static const NSInteger kProfileKeyLength = 16; #pragma mark - User Profile Accessor // This method can be safely called from any thread. -- (UserProfile *)getOrCreateUserProfileForRecipientId:(NSString *)recipientId +- (UserProfile *)getOrBuildUserProfileForRecipientId:(NSString *)recipientId { OWSAssert(recipientId.length > 0); @@ -239,6 +239,7 @@ static const NSInteger kProfileKeyLength = 16; + (NSData *)generateLocalProfileKey { + DDLogInfo(@"%@ Generating profile key for local user.", self.tag); // TODO: DDLogVerbose(@"%@ Profile key generation is not yet implemented.", self.tag); return [SecurityUtils generateRandomBytes:kProfileKeyLength]; @@ -542,7 +543,7 @@ static const NSInteger kProfileKeyLength = 16; } dispatch_async(dispatch_get_main_queue(), ^{ - UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + UserProfile *userProfile = [self getOrBuildUserProfileForRecipientId:recipientId]; OWSAssert(userProfile); if (userProfile.profileKey && [userProfile.profileKey isEqual:profileKey]) { // Ignore redundant update. @@ -561,7 +562,7 @@ static const NSInteger kProfileKeyLength = 16; { OWSAssert(recipientId.length > 0); - UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + UserProfile *userProfile = [self getOrBuildUserProfileForRecipientId:recipientId]; OWSAssert(userProfile); return userProfile.profileKey; } @@ -573,7 +574,7 @@ static const NSInteger kProfileKeyLength = 16; [self refreshProfileForRecipientId:recipientId]; - UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + UserProfile *userProfile = [self getOrBuildUserProfileForRecipientId:recipientId]; return userProfile.profileName; } @@ -589,7 +590,7 @@ static const NSInteger kProfileKeyLength = 16; return image; } - UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + UserProfile *userProfile = [self getOrBuildUserProfileForRecipientId:recipientId]; if (userProfile.avatarFileName) { image = [self loadProfileAvatarWithFilename:userProfile.avatarFileName]; if (image) { @@ -620,7 +621,7 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert([NSThread isMainThread]); OWSAssert(recipientId.length > 0); - UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + UserProfile *userProfile = [self getOrBuildUserProfileForRecipientId:recipientId]; if (!userProfile.profileKey) { // There's no point in fetching the profile for a user @@ -652,7 +653,7 @@ static const NSInteger kProfileKeyLength = 16; { OWSAssert(recipientId.length > 0); - UserProfile *userProfile = [self getOrCreateUserProfileForRecipientId:recipientId]; + UserProfile *userProfile = [self getOrBuildUserProfileForRecipientId:recipientId]; if (!userProfile.profileKey) { return; }