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