Merge pull request #472 from oxen-io/performance

Performance & Clock Handling
This commit is contained in:
Niels Andriesse 2021-07-23 14:46:14 +10:00 committed by GitHub
commit 70aa709892
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 210 additions and 1082 deletions

View File

@ -5063,7 +5063,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 276;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5132,7 +5132,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 276;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -5193,7 +5193,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 276;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5263,7 +5263,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 276;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -6148,7 +6148,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 276;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -6216,7 +6216,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 276;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",

View File

@ -445,9 +445,6 @@ static NSTimeInterval launchStartedAt;
[SNConfiguration performMainSetup];
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
[self.profileManager ensureLocalProfileCached];
// Note that this does much more than set a flag;
// it will also run all deferred blocks.
[AppReadiness setAppIsReady];
@ -533,7 +530,6 @@ static NSTimeInterval launchStartedAt;
// Start running the disappearing messages job in case the newly registered user
// enables this feature
[self.disappearingMessagesJob startIfNecessary];
[self.profileManager ensureLocalProfileCached];
// For non-legacy users, read receipts are on by default.
[self.readReceiptManager setAreReadReceiptsEnabled:YES];

View File

@ -8,7 +8,12 @@ enum Onboarding {
func preregister(with seed: Data, ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) {
let userDefaults = UserDefaults.standard
KeyPairUtilities.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519KeyPair.hexEncodedPublicKey
let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519PublicKey
Storage.writeSync { transaction in
let user = Contact(sessionID: x25519PublicKey)
Storage.shared.setContact(user, using: transaction)
}
switch self {
case .register:
userDefaults[.hasViewedSeed] = false

View File

@ -6,8 +6,8 @@ public class Contact : NSObject, NSCoding { // NSObject/NSCoding conformance is
@objc public var profilePictureURL: String?
/// The file name of the contact's profile picture on local storage.
@objc public var profilePictureFileName: String?
/// The key with which the profile picture is encrypted.
@objc public var profilePictureEncryptionKey: OWSAES256Key?
/// The key with which the profile is encrypted.
@objc public var profileEncryptionKey: OWSAES256Key?
/// The ID of the thread associated with this contact.
@objc public var threadID: String?
/// This flag is used to determine whether we should auto-download files sent by this contact.
@ -49,8 +49,8 @@ public class Contact : NSObject, NSCoding { // NSObject/NSCoding conformance is
// MARK: Validation
public var isValid: Bool {
if profilePictureURL != nil { return (profilePictureEncryptionKey != nil) }
if profilePictureEncryptionKey != nil { return (profilePictureURL != nil) }
if profilePictureURL != nil { return (profileEncryptionKey != nil) }
if profileEncryptionKey != nil { return (profilePictureURL != nil) }
return true
}
@ -63,7 +63,7 @@ public class Contact : NSObject, NSCoding { // NSObject/NSCoding conformance is
if let nickname = coder.decodeObject(forKey: "nickname") as! String? { self.nickname = nickname }
if let profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? { self.profilePictureURL = profilePictureURL }
if let profilePictureFileName = coder.decodeObject(forKey: "profilePictureFileName") as! String? { self.profilePictureFileName = profilePictureFileName }
if let profilePictureEncryptionKey = coder.decodeObject(forKey: "profilePictureEncryptionKey") as! OWSAES256Key? { self.profilePictureEncryptionKey = profilePictureEncryptionKey }
if let profileEncryptionKey = coder.decodeObject(forKey: "profilePictureEncryptionKey") as! OWSAES256Key? { self.profileEncryptionKey = profileEncryptionKey }
if let threadID = coder.decodeObject(forKey: "threadID") as! String? { self.threadID = threadID }
}
@ -73,7 +73,7 @@ public class Contact : NSObject, NSCoding { // NSObject/NSCoding conformance is
coder.encode(nickname, forKey: "nickname")
coder.encode(profilePictureURL, forKey: "profilePictureURL")
coder.encode(profilePictureFileName, forKey: "profilePictureFileName")
coder.encode(profilePictureEncryptionKey, forKey: "profilePictureEncryptionKey")
coder.encode(profileEncryptionKey, forKey: "profilePictureEncryptionKey")
coder.encode(threadID, forKey: "threadID")
coder.encode(isTrusted, forKey: "isTrusted")
}

View File

@ -17,12 +17,22 @@ extension Storage {
@objc(setContact:usingTransaction:)
public func setContact(_ contact: Contact, using transaction: Any) {
let oldContact = getContact(with: contact.sessionID)
let transaction = transaction as! YapDatabaseReadWriteTransaction
if contact.sessionID == getUserHexEncodedPublicKey() {
contact.isTrusted = true // Always trust ourselves
}
transaction.setObject(contact, forKey: contact.sessionID, inCollection: Storage.contactCollection)
transaction.addCompletionQueue(DispatchQueue.main) {
// Delete old profile picture if needed
if let oldProfilePictureFileName = oldContact?.profilePictureFileName,
oldProfilePictureFileName != contact.profilePictureFileName {
let path = OWSUserProfile.profileAvatarFilepath(withFilename: oldProfilePictureFileName)
DispatchQueue.global(qos: .default).async {
OWSFileSystem.deleteFileIfExists(path)
}
}
// Post notification
let notificationCenter = NotificationCenter.default
notificationCenter.post(name: .contactUpdated, object: contact.sessionID)
if contact.sessionID == getUserHexEncodedPublicKey() {
@ -34,7 +44,7 @@ extension Storage {
}
}
public func getAllContacts() -> Set<Contact> {
@objc public func getAllContacts() -> Set<Contact> {
var result: Set<Contact> = []
Storage.read { transaction in
transaction.enumerateRows(inCollection: Storage.contactCollection) { _, object, _, _ in

View File

@ -40,16 +40,6 @@ extension Storage {
var result: Contact?
Storage.read { transaction in
result = Storage.shared.getContact(with: userPublicKey)
// HACK: Apparently it's still possible for the user's contact info to be missing
if result == nil, let profile = OWSUserProfile.fetch(uniqueId: kLocalProfileUniqueId, transaction: transaction),
let userPublicKey = Storage.shared.getUserPublicKey() {
let user = Contact(sessionID: userPublicKey)
user.name = profile.profileName
user.profilePictureURL = profile.avatarUrlPath
user.profilePictureFileName = profile.avatarFileName
user.profilePictureEncryptionKey = profile.profileKey
result = user
}
}
return result
}

View File

@ -181,24 +181,20 @@ extension MessageReceiver {
let storage = SNMessagingKitConfiguration.shared.storage
let transaction = transaction as! YapDatabaseReadWriteTransaction
// Profile
let userProfile = SNMessagingKitConfiguration.shared.storage.getUserProfile(using: transaction)
updateProfileIfNeeded(publicKey: userPublicKey, name: message.displayName, profilePictureURL: message.profilePictureURL,
profileKey: given(message.profileKey) { OWSAES256Key(data: $0)! }, sentTimestamp: message.sentTimestamp!, transaction: transaction)
transaction.addCompletionQueue(DispatchQueue.main) {
SSKEnvironment.shared.profileManager.downloadAvatar(for: userProfile)
}
// Initial configuration sync
if !UserDefaults.standard[.hasSyncedInitialConfiguration] {
UserDefaults.standard[.hasSyncedInitialConfiguration] = true
NotificationCenter.default.post(name: .initialConfigurationMessageReceived, object: nil)
// Contacts
for contact in message.contacts {
let sessionID = contact.publicKey!
let userProfile = OWSUserProfile.getOrBuild(forRecipientId: sessionID, transaction: transaction)
userProfile.profileKey = given(contact.profileKey) { OWSAES256Key(data: $0)! }
userProfile.avatarUrlPath = contact.profilePictureURL
userProfile.profileName = contact.displayName
userProfile.save(with: transaction)
for contactInfo in message.contacts {
let sessionID = contactInfo.publicKey!
let contact = Contact(sessionID: sessionID)
contact.profileEncryptionKey = given(contactInfo.profileKey) { OWSAES256Key(data: $0)! }
contact.profilePictureURL = contactInfo.profilePictureURL
contact.name = contactInfo.displayName
Storage.shared.setContact(contact, using: transaction)
let thread = TSContactThread.getOrCreateThread(withContactSessionID: sessionID, transaction: transaction)
thread.shouldBeVisible = true
thread.save(with: transaction)
@ -302,13 +298,10 @@ extension MessageReceiver {
private static func updateProfileIfNeeded(publicKey: String, name: String?, profilePictureURL: String?,
profileKey: OWSAES256Key?, sentTimestamp: UInt64, transaction: YapDatabaseReadWriteTransaction) {
let isCurrentUser = (publicKey == getUserHexEncodedPublicKey())
let profileManager = SSKEnvironment.shared.profileManager
let userDefaults = UserDefaults.standard
let owsProfile = isCurrentUser ? SNMessagingKitConfiguration.shared.storage.getUserProfile(using: transaction)
: OWSUserProfile.fetch(uniqueId: publicKey, transaction: transaction) // Old API
let contact = Storage.shared.getContact(with: publicKey) ?? Contact(sessionID: publicKey) // New API
// Name
if let name = name, (name != owsProfile?.profileName || contact.name != owsProfile?.profileName) {
if let name = name, name != contact.name {
let shouldUpdate: Bool
if isCurrentUser {
shouldUpdate = given(userDefaults[.lastDisplayNameUpdate]) { sentTimestamp > UInt64($0.timeIntervalSince1970 * 1000) } ?? true
@ -317,17 +310,14 @@ extension MessageReceiver {
}
if shouldUpdate {
if isCurrentUser {
owsProfile?.profileName = name
userDefaults[.lastDisplayNameUpdate] = Date(timeIntervalSince1970: TimeInterval(sentTimestamp / 1000))
} else {
profileManager.updateProfileForContact(withID: publicKey, displayName: name, with: transaction)
}
contact.name = name
}
}
// Profile picture & profile key
if let profileKey = profileKey, let profilePictureURL = profilePictureURL, profileKey.keyData.count == kAES256_KeyByteLength,
(profileKey != owsProfile?.profileKey || contact.profilePictureEncryptionKey != owsProfile?.profileKey) {
if let profileKey = profileKey, let profilePictureURL = profilePictureURL,
profileKey.keyData.count == kAES256_KeyByteLength, profileKey != contact.profileEncryptionKey {
let shouldUpdate: Bool
if isCurrentUser {
shouldUpdate = given(userDefaults[.lastProfilePictureUpdate]) { sentTimestamp > UInt64($0.timeIntervalSince1970 * 1000) } ?? true
@ -336,21 +326,18 @@ extension MessageReceiver {
}
if shouldUpdate {
if isCurrentUser {
owsProfile?.avatarUrlPath = profilePictureURL
owsProfile?.profileKey = profileKey
userDefaults[.lastProfilePictureUpdate] = Date(timeIntervalSince1970: TimeInterval(sentTimestamp / 1000))
} else {
profileManager.setProfileKeyData(profileKey.keyData, forRecipientId: publicKey, avatarURL: profilePictureURL)
}
contact.profilePictureURL = profilePictureURL
contact.profilePictureEncryptionKey = profileKey
contact.profileEncryptionKey = profileKey
}
}
// Persist changes
if isCurrentUser { // In the case where it's someone else the profile will already be saved (updateProfileForContact and setProfileKeyData to that internally)
owsProfile?.save(with: transaction)
}
Storage.shared.setContact(contact, using: transaction)
// Download the profile picture if needed
transaction.addCompletionQueue(DispatchQueue.main) {
SSKEnvironment.shared.profileManager.downloadAvatar(forUserProfile: contact)
}
}

View File

@ -145,7 +145,7 @@ public final class MessageSender : NSObject {
// Attach the user's profile if needed
if let message = message as? VisibleMessage {
guard let name = storage.getUser()?.name else { handleFailure(with: Error.noUsername, using: transaction); return promise }
if let profileKey = storage.getUser()?.profilePictureEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL {
if let profileKey = storage.getUser()?.profileEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL {
message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL)
} else {
message.profile = VisibleMessage.Profile(displayName: name)
@ -200,7 +200,8 @@ public final class MessageSender : NSObject {
}
// Send the result
let base64EncodedData = wrappedMessage.base64EncodedString()
let snodeMessage = SnodeMessage(recipient: message.recipient!, data: base64EncodedData, ttl: message.ttl, timestamp: message.sentTimestamp!)
let timestamp = UInt64(Int64(message.sentTimestamp!) + SnodeAPI.clockOffset)
let snodeMessage = SnodeMessage(recipient: message.recipient!, data: base64EncodedData, ttl: message.ttl, timestamp: timestamp)
SnodeAPI.sendMessage(snodeMessage).done(on: DispatchQueue.global(qos: .userInitiated)) { promises in
var isSuccess = false
let promiseCount = promises.count
@ -285,7 +286,7 @@ public final class MessageSender : NSObject {
guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise }
// Attach the user's profile
guard let name = storage.getUser()?.name else { handleFailure(with: Error.noUsername, using: transaction); return promise }
if let profileKey = storage.getUser()?.profilePictureEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL {
if let profileKey = storage.getUser()?.profileEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL {
message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL)
} else {
message.profile = VisibleMessage.Profile(displayName: name)

View File

@ -17,7 +17,7 @@ public protocol SessionMessagingKitStorageProtocol {
func getUserKeyPair() -> ECKeyPair?
func getUserED25519KeyPair() -> Box.KeyPair?
func getUser() -> Contact?
func getUserProfile(using transaction: Any) -> OWSUserProfile
func getAllContacts() -> Set<Contact>
// MARK: - Closed Groups

View File

@ -6,83 +6,12 @@
NS_ASSUME_NONNULL_BEGIN
typedef void (^OWSUserProfileCompletion)(void);
@class OWSAES256Key;
extern NSString *const kNSNotificationName_LocalProfileDidChange;
extern NSString *const kNSNotificationName_OtherUsersProfileWillChange;
extern NSString *const kNSNotificationName_OtherUsersProfileDidChange;
extern NSString *const kNSNotificationKey_ProfileRecipientId;
extern NSString *const kNSNotificationKey_ProfileGroupId;
extern NSString *const kLocalProfileUniqueId;
// This class should be completely thread-safe.
//
// It is critical for coherency that all DB operations for this
// class should be done on OWSProfileManager's dbConnection.
@interface OWSUserProfile : TSYapDatabaseObject
@property (atomic, readonly) NSString *recipientId;
@property (atomic, nullable) OWSAES256Key *profileKey;
@property (atomic, nullable) NSString *profileName;
@property (atomic, nullable) NSString *avatarUrlPath;
// This filename is relative to OWSProfileManager.profileAvatarsDirPath.
@property (atomic, readonly, nullable) NSString *avatarFileName;
- (instancetype)init NS_UNAVAILABLE;
+ (OWSUserProfile *)getOrBuildUserProfileForRecipientId:(NSString *)recipientId
dbConnection:(YapDatabaseConnection *)dbConnection;
+ (OWSUserProfile *)getOrBuildUserProfileForRecipientId:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction;
+ (BOOL)localUserProfileExists:(YapDatabaseConnection *)dbConnection;
#pragma mark - Update With... Methods
- (void)updateWithProfileName:(nullable NSString *)profileName
avatarUrlPath:(nullable NSString *)avatarUrlPath
avatarFileName:(nullable NSString *)avatarFileName
transaction:(YapDatabaseReadWriteTransaction *)transaction
completion:(nullable OWSUserProfileCompletion)completion;
- (void)updateWithProfileName:(nullable NSString *)profileName
avatarUrlPath:(nullable NSString *)avatarUrlPath
avatarFileName:(nullable NSString *)avatarFileName
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion;
- (void)updateWithProfileName:(nullable NSString *)profileName
avatarUrlPath:(nullable NSString *)avatarUrlPath
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion;
- (void)updateWithAvatarUrlPath:(nullable NSString *)avatarUrlPath
avatarFileName:(nullable NSString *)avatarFileName
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion;
- (void)updateWithAvatarFileName:(nullable NSString *)avatarFileName
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion;
- (void)updateWithProfileKey:(OWSAES256Key *)profileKey
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion;
- (void)updateWithProfileKey:(OWSAES256Key *)profileKey
transaction:(YapDatabaseReadWriteTransaction *)transaction
completion:(nullable OWSUserProfileCompletion)completion;
- (void)clearWithProfileKey:(OWSAES256Key *)profileKey
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion;
#pragma mark - Profile Avatars Directory
+ (NSString *)profileAvatarFilepathWithFilename:(NSString *)filename;
+ (nullable NSError *)migrateToSharedData;
+ (NSString *)legacyProfileAvatarsDirPath;

View File

@ -19,456 +19,15 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationName_LocalProfileDidChange";
NSString *const kNSNotificationName_OtherUsersProfileWillChange = @"kNSNotificationName_OtherUsersProfileWillChange";
NSString *const kNSNotificationName_OtherUsersProfileDidChange = @"kNSNotificationName_OtherUsersProfileDidChange";
NSString *const kNSNotificationKey_ProfileRecipientId = @"kNSNotificationKey_ProfileRecipientId";
NSString *const kNSNotificationKey_ProfileGroupId = @"kNSNotificationKey_ProfileGroupId";
NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId";
@interface OWSUserProfile ()
@property (atomic, nullable) NSString *avatarFileName;
@end
#pragma mark -
@implementation OWSUserProfile
@synthesize avatarUrlPath = _avatarUrlPath;
@synthesize avatarFileName = _avatarFileName;
@synthesize profileName = _profileName;
+ (NSString *)collection
{
// Legacy class name.
return @"UserProfile";
}
+ (OWSUserProfile *)getOrBuildUserProfileForRecipientId:(NSString *)recipientId
dbConnection:(YapDatabaseConnection *)dbConnection
{
__block OWSUserProfile *userProfile;
[dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
userProfile = [OWSUserProfile fetchObjectWithUniqueID:recipientId transaction:transaction];
}];
if (userProfile != nil) {
return userProfile;
}
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId transaction:transaction];
}];
return userProfile;
}
+ (OWSUserProfile *)getOrBuildUserProfileForRecipientId:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSUserProfile *userProfile = [OWSUserProfile fetchObjectWithUniqueID:recipientId transaction:transaction];
if (!userProfile) {
userProfile = [[OWSUserProfile alloc] initWithRecipientId:recipientId];
if ([recipientId isEqualToString:kLocalProfileUniqueId]) {
[userProfile updateWithProfileKey:[OWSAES256Key generateRandomKey]
transaction:transaction
completion:nil];
}
}
return userProfile;
}
+ (BOOL)localUserProfileExists:(YapDatabaseConnection *)dbConnection
{
__block BOOL result = NO;
[dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
result = [OWSUserProfile fetchObjectWithUniqueID:kLocalProfileUniqueId transaction:transaction] != nil;
}];
return result;
}
- (instancetype)initWithRecipientId:(NSString *)recipientId
{
self = [super initWithUniqueId:recipientId];
if (!self) {
return self;
}
_recipientId = recipientId;
return self;
}
#pragma mark - Dependencies
- (TSAccountManager *)tsAccountManager
{
return SSKEnvironment.shared.tsAccountManager;
}
#pragma mark -
- (NSString *)sessionID
{
if ([self.recipientId isEqual:kLocalProfileUniqueId]) {
return [SNGeneralUtilities getUserPublicKey];
} else {
return self.recipientId;
}
}
- (nullable NSString *)avatarUrlPath
{
@synchronized(self)
{
return _avatarUrlPath;
}
}
- (void)setAvatarUrlPath:(nullable NSString *)avatarUrlPath
{
@synchronized(self)
{
BOOL didChange = ![NSObject isNullableObject:_avatarUrlPath equalTo:avatarUrlPath];
_avatarUrlPath = avatarUrlPath;
if (didChange) {
// If the avatarURL changed, the avatarFileName can't be valid.
// Clear it.
self.avatarFileName = nil;
}
}
}
- (nullable NSString *)avatarFileName
{
@synchronized(self) {
return _avatarFileName;
}
}
- (void)setAvatarFileName:(nullable NSString *)avatarFileName
{
@synchronized(self) {
BOOL didChange = ![NSObject isNullableObject:_avatarFileName equalTo:avatarFileName];
if (!didChange) {
return;
}
if (_avatarFileName) {
NSString *oldAvatarFilePath = [OWSUserProfile profileAvatarFilepathWithFilename:_avatarFileName];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[OWSFileSystem deleteFileIfExists:oldAvatarFilePath];
});
}
_avatarFileName = avatarFileName;
}
}
#pragma mark - Update With... Methods
// Similar in spirit to [TSYapDatabaseObject applyChangeToSelfAndLatestCopy],
// but with significant differences.
//
// * We save if this entity is not in the database.
// * We skip redundant saves by diffing.
// * We kick off multi-device synchronization.
// * We fire "did change" notifications.
- (void)applyChanges:(void (^)(id))changeBlock
functionName:(const char *)functionName
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion
{
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self applyChanges:changeBlock functionName:functionName transaction:transaction completion:completion];
}];
}
- (void)applyChanges:(void (^)(id))changeBlock
functionName:(const char *)functionName
transaction:(YapDatabaseReadWriteTransaction *)transaction
completion:(nullable OWSUserProfileCompletion)completion
{
// self might be the latest instance, so take a "before" snapshot
// before any changes have been made.
__block NSDictionary *beforeSnapshot = [self.dictionaryValue copy];
changeBlock(self);
BOOL didChange = YES;
NSString *collection = [[self class] collection];
OWSUserProfile *_Nullable latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection];
if (latestInstance) {
// If self is NOT the latest instance, take a new "before" snapshot
// before updating.
if (self != latestInstance) {
beforeSnapshot = [latestInstance.dictionaryValue copy];
}
changeBlock(latestInstance);
NSDictionary *afterSnapshot = [latestInstance.dictionaryValue copy];
if ([beforeSnapshot isEqual:afterSnapshot]) {
didChange = NO;
} else {
[latestInstance saveWithTransaction:transaction];
}
} else {
[self saveWithTransaction:transaction];
}
if (completion) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), completion);
}
if (!didChange) {
return;
}
NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"];
BOOL isLocalUserProfile = [self.recipientId isEqualToString:kLocalProfileUniqueId] || (masterDeviceHexEncodedPublicKey != nil && [self.recipientId isEqualToString:masterDeviceHexEncodedPublicKey]);
dispatch_async(dispatch_get_main_queue(), ^{
if (isLocalUserProfile) {
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_LocalProfileDidChange
object:nil
userInfo:nil];
} else {
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:kNSNotificationName_OtherUsersProfileWillChange
object:nil
userInfo:@{
kNSNotificationKey_ProfileRecipientId : self.recipientId,
}];
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:kNSNotificationName_OtherUsersProfileDidChange
object:nil
userInfo:@{
kNSNotificationKey_ProfileRecipientId : self.recipientId,
}];
}
});
}
- (void)updateWithProfileName:(nullable NSString *)profileName
avatarUrlPath:(nullable NSString *)avatarUrlPath
avatarFileName:(nullable NSString *)avatarFileName
transaction:(YapDatabaseReadWriteTransaction *)transaction
completion:(nullable OWSUserProfileCompletion)completion
{
[self applyChanges:^(OWSUserProfile *userProfile) {
[userProfile setProfileName:[profileName ows_stripped]];
// Always setAvatarUrlPath: before you setAvatarFileName: since
// setAvatarUrlPath: may clear the avatar filename.
[userProfile setAvatarUrlPath:avatarUrlPath];
[userProfile setAvatarFileName:avatarFileName];
}
functionName:__PRETTY_FUNCTION__
transaction:transaction
completion:completion];
SNContact *contact = [LKStorage.shared getContactWithSessionID:self.sessionID] ?: [[SNContact alloc] initWithSessionID:self.sessionID];
contact.name = [profileName ows_stripped];
contact.profilePictureURL = avatarUrlPath;
contact.profilePictureFileName = avatarFileName;
[LKStorage.shared setContact:contact usingTransaction:transaction];
}
- (void)updateWithProfileName:(nullable NSString *)profileName
avatarUrlPath:(nullable NSString *)avatarUrlPath
avatarFileName:(nullable NSString *)avatarFileName
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion
{
[self applyChanges:^(OWSUserProfile *userProfile) {
[userProfile setProfileName:[profileName ows_stripped]];
// Always setAvatarUrlPath: before you setAvatarFileName: since
// setAvatarUrlPath: may clear the avatar filename.
[userProfile setAvatarUrlPath:avatarUrlPath];
[userProfile setAvatarFileName:avatarFileName];
}
functionName:__PRETTY_FUNCTION__
dbConnection:dbConnection
completion:completion];
SNContact *contact = [LKStorage.shared getContactWithSessionID:self.sessionID] ?: [[SNContact alloc] initWithSessionID:self.sessionID];
contact.name = [profileName ows_stripped];
contact.profilePictureURL = avatarUrlPath;
contact.profilePictureFileName = avatarFileName;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:contact usingTransaction:transaction];
}];
}
- (void)updateWithProfileName:(nullable NSString *)profileName
avatarUrlPath:(nullable NSString *)avatarUrlPath
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion
{
[self applyChanges:^(OWSUserProfile *userProfile) {
[userProfile setProfileName:[profileName ows_stripped]];
[userProfile setAvatarUrlPath:avatarUrlPath];
}
functionName:__PRETTY_FUNCTION__
dbConnection:dbConnection
completion:completion];
SNContact *contact = [LKStorage.shared getContactWithSessionID:self.sessionID] ?: [[SNContact alloc] initWithSessionID:self.sessionID];
contact.name = [profileName ows_stripped];
contact.profilePictureURL = avatarUrlPath;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:contact usingTransaction:transaction];
}];
}
- (void)updateWithAvatarUrlPath:(nullable NSString *)avatarUrlPath
avatarFileName:(nullable NSString *)avatarFileName
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion
{
[self applyChanges:^(OWSUserProfile *userProfile) {
// Always setAvatarUrlPath: before you setAvatarFileName: since
// setAvatarUrlPath: may clear the avatar filename.
[userProfile setAvatarUrlPath:avatarUrlPath];
[userProfile setAvatarFileName:avatarFileName];
}
functionName:__PRETTY_FUNCTION__
dbConnection:dbConnection
completion:completion];
SNContact *contact = [LKStorage.shared getContactWithSessionID:self.sessionID] ?: [[SNContact alloc] initWithSessionID:self.sessionID];
contact.profilePictureURL = avatarUrlPath;
contact.profilePictureFileName = avatarFileName;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:contact usingTransaction:transaction];
}];
}
- (void)updateWithAvatarFileName:(nullable NSString *)avatarFileName
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion
{
[self applyChanges:^(OWSUserProfile *userProfile) {
[userProfile setAvatarFileName:avatarFileName];
}
functionName:__PRETTY_FUNCTION__
dbConnection:dbConnection
completion:completion];
SNContact *contact = [LKStorage.shared getContactWithSessionID:self.sessionID] ?: [[SNContact alloc] initWithSessionID:self.sessionID];
contact.profilePictureFileName = avatarFileName;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:contact usingTransaction:transaction];
}];
}
- (void)clearWithProfileKey:(OWSAES256Key *)profileKey
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion
{
[self applyChanges:^(OWSUserProfile *userProfile) {
[userProfile setProfileKey:profileKey];
// [userProfile setProfileName:nil]; - Loki disabled until we include profile name inside the encrypted profile from the url
// Always setAvatarUrlPath: before you setAvatarFileName: since
// setAvatarUrlPath: may clear the avatar filename.
[userProfile setAvatarUrlPath:nil];
[userProfile setAvatarFileName:nil];
}
functionName:__PRETTY_FUNCTION__
dbConnection:dbConnection
completion:completion];
}
- (void)updateWithProfileKey:(OWSAES256Key *)profileKey
dbConnection:(YapDatabaseConnection *)dbConnection
completion:(nullable OWSUserProfileCompletion)completion
{
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self updateWithProfileKey:profileKey transaction:transaction completion:completion];
}];
}
- (void)updateWithProfileKey:(OWSAES256Key *)profileKey
transaction:(YapDatabaseReadWriteTransaction *)transaction
completion:(nullable OWSUserProfileCompletion)completion
{
[self applyChanges:^(OWSUserProfile *userProfile) {
[userProfile setProfileKey:profileKey];
}
functionName:__PRETTY_FUNCTION__
transaction:transaction
completion:completion];
SNContact *contact = [LKStorage.shared getContactWithSessionID:self.sessionID] ?: [[SNContact alloc] initWithSessionID:self.sessionID];
contact.profilePictureEncryptionKey = profileKey;
[LKStorage.shared setContact:contact usingTransaction:transaction];
}
#pragma mark - Database Connection Accessors
- (YapDatabaseConnection *)dbReadConnection
{
return TSYapDatabaseObject.dbReadConnection;
}
+ (YapDatabaseConnection *)dbReadConnection
{
return TSYapDatabaseObject.dbReadConnection;
}
- (YapDatabaseConnection *)dbReadWriteConnection
{
return TSYapDatabaseObject.dbReadWriteConnection;
}
+ (YapDatabaseConnection *)dbReadWriteConnection
{
return TSYapDatabaseObject.dbReadWriteConnection;
}
// This should only be used in verbose, developer-only logs.
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@"%@ %p %@ %lu %@ %@ %@",
@"OWSUserProfile",
self,
self.recipientId,
(unsigned long)self.profileKey.keyData.length,
self.profileName,
self.avatarUrlPath,
self.avatarFileName];
}
- (nullable NSString *)profileName
{
@synchronized(self)
{
return _profileName.filterStringForDisplay;
}
}
- (void)setProfileName:(nullable NSString *)profileName
{
@synchronized(self)
{
_profileName = profileName.filterStringForDisplay;
}
}
#pragma mark - Profile Avatars Directory
+ (NSString *)profileAvatarFilepathWithFilename:(NSString *)filename
{
if (filename.length <= 0) { return @""; };
@ -504,8 +63,6 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId";
return profileAvatarsDirPath;
}
// TODO: We may want to clean up this directory in the "orphan cleanup" logic.
+ (void)resetProfileStorage
{
NSError *error;
@ -517,22 +74,14 @@ NSString *const kLocalProfileUniqueId = @"kLocalProfileUniqueId";
NSString *profileAvatarsDirPath = self.profileAvatarsDirPath;
NSMutableSet<NSString *> *profileAvatarFilePaths = [NSMutableSet new];
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[OWSUserProfile
enumerateCollectionObjectsWithTransaction:transaction
usingBlock:^(id object, BOOL *stop) {
if (![object isKindOfClass:[OWSUserProfile class]]) {
return;
}
OWSUserProfile *userProfile = object;
if (!userProfile.avatarFileName) {
return;
}
NSString *filePath = [profileAvatarsDirPath
stringByAppendingPathComponent:userProfile.avatarFileName];
[profileAvatarFilePaths addObject:filePath];
}];
}];
NSSet<SNContact *> *allContacts = [LKStorage.shared getAllContacts];
for (SNContact *contact in allContacts) {
if (contact.profilePictureFileName == nil) { continue; }
NSString *filePath = [profileAvatarsDirPath stringByAppendingPathComponent:contact.profilePictureFileName];
[profileAvatarFilePaths addObject:filePath];
}
return [profileAvatarFilePaths copy];
}

View File

@ -5,7 +5,7 @@
@class OWSAES256Key;
@class TSThread;
@class YapDatabaseReadWriteTransaction;
@class OWSUserProfile;
@class SNContact;
NS_ASSUME_NONNULL_BEGIN
@ -13,7 +13,6 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Local Profile
- (void)ensureLocalProfileCached;
- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarURL:(nullable NSString *)avatarURL;
#pragma mark - Other User's Profiles
@ -21,12 +20,10 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId;
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId;
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId avatarURL:(nullable NSString *)avatarURL;
- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction;
- (void)ensureProfileCachedForContactWithID:(NSString *)contactID with:(YapDatabaseReadWriteTransaction *)transaction;
#pragma mark - Other
- (void)downloadAvatarForUserProfile:(OWSUserProfile *)userProfile;
- (void)downloadAvatarForUserProfile:(SNContact *)userProfile;
@end

View File

@ -177,7 +177,7 @@ public class FullTextSearchFinder: NSObject {
}
private static let recipientIndexer: SearchIndexer<String> = SearchIndexer { (recipientId: String, transaction: YapDatabaseReadTransaction) in
let displayName = OWSUserProfile.fetch(uniqueId: recipientId, transaction: transaction)?.profileName ?? recipientId
let displayName = Storage.shared.getContact(with: recipientId)?.displayName(for: Contact.Context.regular) ?? recipientId
return "\(recipientId) \(displayName)"
}

View File

@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (OWSAES256Key *)localProfileKey
{
return [[LKStorage.shared getUser] profilePictureEncryptionKey];
return [[LKStorage.shared getUser] profileEncryptionKey];
}
#pragma mark -

View File

@ -103,9 +103,6 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
Logger.debug("")
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
OWSProfileManager.shared().ensureLocalProfileCached()
// Note that this does much more than set a flag;
// it will also run all deferred blocks.
AppReadiness.setAppIsReady()
@ -123,8 +120,6 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
// We don't need to use OWSMessageReceiver in the SAE.
// We don't need to use OWSBatchMessageProcessor in the SAE.
OWSProfileManager.shared().ensureLocalProfileCached()
// We don't need to use OWSOrphanDataCleaner in the SAE.
// We don't need to fetch the local profile in the SAE

View File

@ -310,7 +310,7 @@ public enum OnionRequestAPI {
}
/// Sends an onion request to `server`. Builds new paths as needed.
public static func sendOnionRequest(_ request: NSURLRequest, to server: String, target: String = "/loki/v3/lsrpc", using x25519PublicKey: String, isJSONRequired: Bool = true) -> Promise<JSON> {
public static func sendOnionRequest(_ request: NSURLRequest, to server: String, target: String = "/loki/v3/lsrpc", using x25519PublicKey: String) -> Promise<JSON> {
var rawHeaders = request.allHTTPHeaderFields ?? [:]
rawHeaders.removeValue(forKey: "User-Agent")
var headers: JSON = rawHeaders.mapValues { value in
@ -352,14 +352,14 @@ public enum OnionRequestAPI {
"headers" : headers
]
let destination = Destination.server(host: host, target: target, x25519PublicKey: x25519PublicKey, scheme: scheme, port: port)
let promise = sendOnionRequest(with: payload, to: destination, isJSONRequired: isJSONRequired)
let promise = sendOnionRequest(with: payload, to: destination)
promise.catch2 { error in
SNLog("Couldn't reach server: \(url) due to error: \(error).")
}
return promise
}
public static func sendOnionRequest(with payload: JSON, to destination: Destination, isJSONRequired: Bool = true) -> Promise<JSON> {
public static func sendOnionRequest(with payload: JSON, to destination: Destination) -> Promise<JSON> {
let (promise, seal) = Promise<JSON>.pending()
var guardSnode: Snode?
Threading.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths`
@ -386,28 +386,26 @@ public enum OnionRequestAPI {
let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= AESGCM.ivSize else { return seal.reject(HTTP.Error.invalidJSON) }
do {
let data = try AESGCM.decrypt(ivAndCiphertext, with: destinationSymmetricKey)
// The old open group server and file server implementations put the status code in the JSON under the "status"
// key, whereas the new implementations put it under the "status_code" key
guard let json = try JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON,
let statusCode = json["status_code"] as? Int ?? json["status"] as? Int else { return seal.reject(HTTP.Error.invalidJSON) }
if statusCode == 406 { // Clock out of sync
SNLog("The user's clock is out of sync with the service node network.")
seal.reject(SnodeAPI.Error.clockOutOfSync)
} else if let bodyAsString = json["body"] as? String {
// This clause is only used by the old open group and file server implementations. The new implementations will
// always go to the next clause.
let body: JSON
if !isJSONRequired {
body = [ "result" : bodyAsString ]
} else {
guard let bodyAsData = bodyAsString.data(using: .utf8),
let b = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) }
body = b
guard let bodyAsData = bodyAsString.data(using: .utf8),
let body = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) }
if let timestamp = body["t"] as? Int64 {
let offset = timestamp - Int64(NSDate.millisecondTimestamp())
SnodeAPI.clockOffset = offset
}
guard 200...299 ~= statusCode else {
return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: body, destination: destination))
}
guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: body, destination: destination)) }
seal.fulfill(body)
} else {
guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: json, destination: destination)) }
guard 200...299 ~= statusCode else {
return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: json, destination: destination))
}
seal.fulfill(json)
}
} catch {

View File

@ -13,6 +13,11 @@ public final class SnodeAPI : NSObject {
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
internal static var snodePool: Set<Snode> = []
/// The offset between the user's clock and the Service Node's clock. Used in cases where the
/// user's clock is incorrect.
///
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
public static var clockOffset: Int64 = 0
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
public static var swarmCache: [String:Set<Snode>] = [:]

View File

@ -5,8 +5,4 @@ extension Storage : SessionMessagingKitStorageProtocol, SessionSnodeKitStoragePr
let transaction = transaction as! YapDatabaseReadWriteTransaction
OWSPrimaryStorage.shared().updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, in: transaction)
}
public func getUserProfile(using transaction: Any) -> OWSUserProfile {
return OWSProfileManager.shared().getLocalUserProfile(with: transaction as! YapDatabaseReadWriteTransaction)
}
}

View File

@ -6,7 +6,7 @@ extension ConfigurationMessage {
guard let user = storage.getUser() else { return nil }
let displayName = user.name
let profilePictureURL = user.profilePictureURL
let profileKey = user.profilePictureEncryptionKey?.keyData
let profileKey = user.profileEncryptionKey?.keyData
var closedGroups: Set<ClosedGroup> = []
var openGroups: Set<String> = []
var contacts: Set<Contact> = []
@ -31,18 +31,18 @@ extension ConfigurationMessage {
default: break
}
}
OWSUserProfile.enumerateCollectionObjects(with: transaction) { object, stop in
guard let profile = object as? OWSUserProfile, let displayName = profile.profileName else { return }
let publicKey = profile.recipientId
var truncatedContacts = storage.getAllContacts()
if truncatedContacts.count > 200 { truncatedContacts = Set(Array(truncatedContacts)[0..<200]) }
truncatedContacts.forEach { contact in
let publicKey = contact.sessionID
let threadID = TSContactThread.threadID(fromContactSessionID: publicKey)
guard let thread = TSContactThread.fetch(uniqueId: threadID, transaction: transaction), thread.shouldBeVisible
&& !SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(publicKey) else { return }
let profilePictureURL = profile.avatarUrlPath
let profileKey = profile.profileKey?.keyData
let contact = ConfigurationMessage.Contact(publicKey: publicKey, displayName: displayName,
let profilePictureURL = contact.profilePictureURL
let profileKey = contact.profileEncryptionKey?.keyData
let contact = ConfigurationMessage.Contact(publicKey: publicKey, displayName: contact.name ?? publicKey,
profilePictureURL: profilePictureURL, profileKey: profileKey)
contacts.insert(contact)
guard contactCount < 200 else { stop.pointee = true; return }
contactCount += 1
}
}

View File

@ -35,8 +35,6 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter;
// hasLocalProfile is true if there is a local profile with a name or avatar.
- (BOOL)hasLocalProfile;
- (OWSUserProfile *)getLocalUserProfileWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
// 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
// successfully updated.
@ -61,11 +59,9 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter;
profileNameEncrypted:(nullable NSData *)profileNameEncrypted
avatarUrlPath:(nullable NSString *)avatarUrlPath;
- (void)ensureProfileCachedForContactWithID:(NSString *)contactID with:(YapDatabaseReadWriteTransaction *)transaction;
#pragma mark - Other
- (void)downloadAvatarForUserProfile:(OWSUserProfile *)userProfile;
- (void)downloadAvatarForUserProfile:(SNContact *)contact;
@end

View File

@ -45,9 +45,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
// This property can be accessed on any thread, while synchronized on self.
@property (atomic, readonly) OWSUserProfile *localUserProfile;
// This property can be accessed on any thread, while synchronized on self.
@property (atomic, readonly) NSCache<NSString *, UIImage *> *profileAvatarImageCache;
@ -62,8 +59,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
// Writes should happen off the main thread, wherever possible.
@implementation OWSProfileManager
@synthesize localUserProfile = _localUserProfile;
+ (instancetype)sharedManager
{
return SSKEnvironment.shared.profileManager;
@ -87,12 +82,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
OWSSingletonAssert();
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[self rotateLocalProfileKeyIfNecessary];
}];
[self observeNotifications];
return self;
}
@ -101,14 +90,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(blockListDidChange:)
name:kNSNotificationName_BlockListDidChange
object:nil];
}
#pragma mark - Dependencies
- (TSAccountManager *)tsAccountManager
@ -126,78 +107,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
return SSKEnvironment.shared.blockingManager;
}
#pragma mark - User Profile Accessor
- (void)ensureLocalProfileCached
{
// Since localUserProfile can create a transaction, we want to make sure it's not called for the first
// time unexpectedly (e.g. in a nested transaction.)
__unused OWSUserProfile *profile = [self localUserProfile];
}
#pragma mark - Local Profile
- (OWSUserProfile *)localUserProfile
{
if (_localUserProfile) { return _localUserProfile; }
__block OWSUserProfile *userProfile;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
userProfile = [self getLocalUserProfileWithTransaction:transaction];
}];
return userProfile;
}
- (OWSUserProfile *)getLocalUserProfileWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
@synchronized(self)
{
if (!_localUserProfile) {
_localUserProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:kLocalProfileUniqueId transaction:transaction];
}
}
OWSAssertDebug(_localUserProfile.profileKey);
return _localUserProfile;
}
- (BOOL)localProfileExists
{
return [OWSUserProfile localUserProfileExists:self.dbConnection];
}
- (OWSAES256Key *)localProfileKey
{
OWSAssertDebug(self.localUserProfile.profileKey.keyData.length == kAES256_KeyByteLength);
return self.localUserProfile.profileKey;
}
- (BOOL)hasLocalProfile
{
return (self.localProfileName.length > 0 || self.localProfileAvatarImage != nil);
}
- (nullable NSString *)localProfileName
{
return self.localUserProfile.profileName;
}
- (nullable UIImage *)localProfileAvatarImage
{
return [self loadProfileAvatarWithFilename:self.localUserProfile.avatarFileName];
}
- (nullable NSData *)localProfileAvatarData
{
NSString *_Nullable filename = self.localUserProfile.avatarFileName;
if (filename.length < 1) {
return nil;
}
return [self loadProfileDataWithFilename:filename];
}
- (void)updateLocalProfileName:(nullable NSString *)profileName
avatarImage:(nullable UIImage *)avatarImage
success:(void (^)(void))successBlockParameter
@ -232,27 +141,29 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
[self updateServiceWithProfileName:profileName
avatarUrl:avatarUrlPath
success:^{
OWSUserProfile *userProfile = self.localUserProfile;
SNContact *userProfile = [LKStorage.shared getUser];
OWSAssertDebug(userProfile);
[userProfile updateWithProfileName:profileName
avatarUrlPath:avatarUrlPath
avatarFileName:avatarFileName
dbConnection:self.dbConnection
completion:^{
if (avatarFileName) {
[self updateProfileAvatarCache:avatarImage filename:avatarFileName];
}
successBlock();
}];
userProfile.name = profileName;
userProfile.profilePictureURL = avatarUrlPath;
userProfile.profilePictureFileName = avatarFileName;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:userProfile usingTransaction:transaction];
} completion:^{
if (avatarFileName != nil) {
[self updateProfileAvatarCache:avatarImage filename:avatarFileName];
}
successBlock();
}];
}
failure:^(NSError *error) {
failureBlock(error);
}];
};
OWSUserProfile *userProfile = self.localUserProfile;
SNContact *userProfile = [LKStorage.shared getUser];
OWSAssertDebug(userProfile);
if (avatarImage) {
@ -277,7 +188,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
failure:^(NSError *error) {
failureBlock(error);
}];
} else if (userProfile.avatarUrlPath) {
} else if (userProfile.profilePictureURL) {
OWSLogVerbose(@"Updating local profile on service with cleared avatar.");
[self uploadAvatarToService:nil
success:^(NSString *_Nullable avatarUrlPath) {
@ -292,11 +203,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}
}
- (nullable NSString *)profilePictureURL
{
return self.localUserProfile.avatarUrlPath;
}
- (void)writeAvatarToDisk:(UIImage *)avatar
success:(void (^)(NSData *data, NSString *fileName))successBlock
failure:(ProfileManagerFailureBlock)failureBlock {
@ -367,8 +273,13 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
[promise.thenOn(dispatch_get_main_queue(), ^(NSString *fileID) {
NSString *downloadURL = [NSString stringWithFormat:@"%@/files/%@", SNFileServerAPIV2.server, fileID];
[NSUserDefaults.standardUserDefaults setObject:[NSDate new] forKey:@"lastProfilePictureUpload"];
[self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{
successBlock(downloadURL);
SNContact *user = [LKStorage.shared getUser];
user.profileEncryptionKey = newProfileKey;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:user usingTransaction:transaction];
} completion:^{
successBlock(downloadURL);
}];
})
.catchOn(dispatch_get_main_queue(), ^(id result) {
@ -376,7 +287,11 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
// to be invoked with the fulfilled promise's value as the error. The below
// is a quick and dirty workaround.
if ([result isKindOfClass:NSString.class]) {
[self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{
SNContact *user = [LKStorage.shared getUser];
user.profileEncryptionKey = newProfileKey;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:user usingTransaction:transaction];
} completion:^{
successBlock(result);
}];
} else {
@ -385,7 +300,13 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}) retainUntilComplete];
} else {
// Update our profile key and set the url to nil if avatar data is nil
[self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{
SNContact *user = [LKStorage.shared getUser];
user.profileEncryptionKey = newProfileKey;
user.profilePictureURL = nil;
user.profilePictureFileName = nil;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:user usingTransaction:transaction];
} completion:^{
successBlock(nil);
}];
}
@ -438,217 +359,18 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
return [groupId copy];
}
- (void)rotateLocalProfileKeyIfNecessary {
[self rotateLocalProfileKeyIfNecessaryWithSuccess:^{ } failure:^(NSError *error) { }];
}
- (void)rotateLocalProfileKeyIfNecessaryWithSuccess:(dispatch_block_t)success
failure:(ProfileManagerFailureBlock)failure {
OWSAssertDebug(AppReadiness.isAppReady);
if (!self.tsAccountManager.isRegistered) {
success();
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableSet<NSString *> *whitelistedRecipientIds = [NSMutableSet new];
NSMutableSet<NSData *> *whitelistedGroupIds = [NSMutableSet new];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[whitelistedRecipientIds
addObjectsFromArray:[transaction allKeysInCollection:kOWSProfileManager_UserWhitelistCollection]];
NSArray<NSString *> *whitelistedGroupKeys =
[transaction allKeysInCollection:kOWSProfileManager_GroupWhitelistCollection];
for (NSString *groupKey in whitelistedGroupKeys) {
NSData *_Nullable groupId = [self groupIdForGroupKey:groupKey];
if (!groupId) {
OWSFailDebug(@"Couldn't parse group key: %@.", groupKey);
continue;
}
[whitelistedGroupIds addObject:groupId];
// Note we don't add `group.recipientIds` to the `whitelistedRecipientIds`.
//
// Whenever we message a contact, be it in a 1:1 thread or in a group thread,
// we add them to the contact whitelist, so there's no reason to redundnatly
// add them here.
//
// Furthermore, doing so would cause the following problem:
// - Alice is in group Book Club
// - Add Book Club to your profile white list
// - Message Book Club, which also adds Alice to your profile whitelist.
// - Block Alice, but not Book Club
//
// Now, at this point we'd want to rotate our profile key once, since Alice has
// it via BookClub.
//
// However, after we did. The next time we check if we should rotate our profile
// key, adding all `group.recipientIds` to `whitelistedRecipientIds` here, would
// include Alice, and we'd rotate our profile key every time this method is called.
}
}];
NSString *_Nullable localNumber = [TSAccountManager localNumber];
if (localNumber) {
[whitelistedRecipientIds removeObject:localNumber];
} else {
OWSFailDebug(@"Missing localNumber");
}
NSSet<NSString *> *blockedRecipientIds = [NSSet setWithArray:self.blockingManager.blockedPhoneNumbers];
NSSet<NSData *> *blockedGroupIds = [NSSet setWithArray:self.blockingManager.blockedGroupIds];
// Find the users and groups which are both a) blocked b) may have our current profile key.
NSMutableSet<NSString *> *intersectingRecipientIds = [blockedRecipientIds mutableCopy];
[intersectingRecipientIds intersectSet:whitelistedRecipientIds];
NSMutableSet<NSData *> *intersectingGroupIds = [blockedGroupIds mutableCopy];
[intersectingGroupIds intersectSet:whitelistedGroupIds];
BOOL isProfileKeySharedWithBlocked = (intersectingRecipientIds.count > 0 || intersectingGroupIds.count > 0);
if (!isProfileKeySharedWithBlocked) {
// No need to rotate the profile key.
return success();
}
[self rotateProfileKeyWithIntersectingRecipientIds:intersectingRecipientIds
intersectingGroupIds:intersectingGroupIds
success:success
failure:failure];
});
}
- (void)rotateProfileKeyWithIntersectingRecipientIds:(NSSet<NSString *> *)intersectingRecipientIds
intersectingGroupIds:(NSSet<NSData *> *)intersectingGroupIds
success:(dispatch_block_t)success
failure:(ProfileManagerFailureBlock)failure {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Rotate the profile key
OWSLogInfo(@"Rotating the profile key.");
// Make copies of the current local profile state.
OWSUserProfile *localUserProfile = self.localUserProfile;
NSString *_Nullable oldProfileName = localUserProfile.profileName;
NSString *_Nullable oldAvatarFileName = localUserProfile.avatarFileName;
NSData *_Nullable oldAvatarData = [self profileAvatarDataForRecipientId:self.tsAccountManager.localNumber];
// Rotate the stored profile key.
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self.localUserProfile updateWithProfileKey:[OWSAES256Key generateRandomKey]
dbConnection:self.dbConnection
completion:^{
// The value doesn't matter, we just need any non-NSError value.
resolve(@(1));
}];
}];
// Try to re-upload our profile name, if any.
//
// This may fail.
promise = promise.then(^(id value) {
if (oldProfileName.length < 1) {
return [AnyPromise promiseWithValue:@(1)];
}
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self updateServiceWithProfileName:oldProfileName
avatarUrl:localUserProfile.avatarUrlPath
success:^{
OWSLogInfo(@"Update to profile name succeeded.");
// The value doesn't matter, we just need any non-NSError value.
resolve(@(1));
}
failure:^(NSError *error) {
resolve(error);
}];
}];
});
// Try to re-upload our profile avatar, if any.
//
// This may fail.
promise = promise.then(^(id value) {
if (oldAvatarData.length < 1 || oldAvatarFileName.length < 1) {
return [AnyPromise promiseWithValue:@(1)];
}
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self uploadAvatarToService:oldAvatarData
success:^(NSString *_Nullable avatarUrlPath) {
OWSLogInfo(@"Update to profile avatar after profile key rotation succeeded.");
// The profile manager deletes the underlying file when updating a profile URL
// So we need to copy the underlying file to a new location.
NSString *oldPath = [OWSUserProfile profileAvatarFilepathWithFilename:oldAvatarFileName];
NSString *newAvatarFilename = [self generateAvatarFilename];
NSString *newPath = [OWSUserProfile profileAvatarFilepathWithFilename:newAvatarFilename];
NSError *error;
[NSFileManager.defaultManager copyItemAtPath:oldPath toPath:newPath error:&error];
OWSAssertDebug(!error);
[self.localUserProfile updateWithAvatarUrlPath:avatarUrlPath
avatarFileName:newAvatarFilename
dbConnection:self.dbConnection
completion:^{
// The value doesn't matter, we just need any
// non-NSError value.
resolve(@(1));
}];
}
failure:^(NSError *error) {
OWSLogInfo(@"Update to profile avatar after profile key rotation failed.");
resolve(error);
}];
}];
});
// Try to re-upload our profile avatar, if any.
//
// This may fail.
promise = promise.then(^(id value) {
// Remove blocked users and groups from profile whitelist.
//
// This will always succeed.
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeObjectsForKeys:intersectingRecipientIds.allObjects
inCollection:kOWSProfileManager_UserWhitelistCollection];
for (NSData *groupId in intersectingGroupIds) {
NSString *groupIdKey = [self groupKeyForGroupId:groupId];
[transaction removeObjectForKey:groupIdKey
inCollection:kOWSProfileManager_GroupWhitelistCollection];
}
}];
return @(1);
});
// Update account attributes.
//
// This may fail.
promise = promise.then(^(id value) {
return [self.tsAccountManager updateAccountAttributes];
});
promise = promise.then(^(id value) {
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_ProfileKeyDidChange
object:nil
userInfo:nil];
success();
});
promise = promise.catch(^(NSError *error) {
if ([error isKindOfClass:[NSError class]]) {
failure(error);
} else {
failure(OWSErrorMakeAssertionError(@"Profile key rotation failure missing error."));
}
});
[promise retainUntilComplete];
});
}
- (void)regenerateLocalProfile
{
OWSUserProfile *userProfile = self.localUserProfile;
[userProfile clearWithProfileKey:[OWSAES256Key generateRandomKey] dbConnection:self.dbConnection completion:nil];
[[self.tsAccountManager updateAccountAttributes] retainUntilComplete];
NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey];
SNContact *contact = [LKStorage.shared getContactWithSessionID:userPublicKey];
contact.profileEncryptionKey = [OWSAES256Key generateRandomKey];
contact.profilePictureURL = nil;
contact.profilePictureFileName = nil;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:contact usingTransaction:transaction];
} completion:^{
[[self.tsAccountManager updateAccountAttributes] retainUntilComplete];
}];
}
#pragma mark - Other Users' Profiles
@ -662,22 +384,27 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
return;
}
OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection];
SNContact *contact = [LKStorage.shared getContactWithSessionID:recipientId];
OWSAssertDebug(userProfile);
if (userProfile.profileKey && [userProfile.profileKey.keyData isEqual:profileKey.keyData]) {
OWSAssertDebug(contact);
if (contact.profileEncryptionKey != nil && [contact.profileEncryptionKey.keyData isEqual:profileKey.keyData]) {
// Ignore redundant update.
return;
}
[userProfile clearWithProfileKey:profileKey
dbConnection:self.dbConnection
completion:^{
dispatch_async(dispatch_get_main_queue(), ^{
[userProfile updateWithAvatarUrlPath:avatarURL avatarFileName:nil dbConnection:self.dbConnection completion:^{
[self downloadAvatarForUserProfile:userProfile];
}];
});
contact.profileEncryptionKey = profileKey;
contact.profilePictureURL = nil;
contact.profilePictureFileName = nil;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:contact usingTransaction:transaction];
} completion:^{
contact.profilePictureURL = avatarURL;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:contact usingTransaction:transaction];
} completion:^{
[self downloadAvatarForUserProfile:contact];
}];
}];
});
}
@ -696,42 +423,24 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
{
OWSAssertDebug(recipientId.length > 0);
// For "local reads", use the local user profile.
OWSUserProfile *userProfile = ([self.tsAccountManager.localNumber isEqualToString:recipientId]
? self.localUserProfile
: [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection]);
OWSAssertDebug(userProfile);
SNContact *contact = [LKStorage.shared getContactWithSessionID:recipientId];
OWSAssertDebug(contact);
return userProfile.profileKey;
}
- (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(recipientID.length > 0);
// For "local reads", use the local user profile.
OWSUserProfile *userProfile = [self.tsAccountManager.localNumber isEqualToString:recipientID]
? [self getLocalUserProfileWithTransaction:transaction]
: [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientID transaction:transaction];
return userProfile.profileName;
return contact.profileEncryptionKey;
}
- (nullable UIImage *)profileAvatarForRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
// For "local reads", use the local user profile.
OWSUserProfile *userProfile = ([self.tsAccountManager.localNumber isEqualToString:recipientId]
? self.localUserProfile
: [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection]);
if (userProfile.avatarFileName.length > 0) {
return [self loadProfileAvatarWithFilename:userProfile.avatarFileName];
SNContact *contact = [LKStorage.shared getContactWithSessionID:recipientId];
if (contact.profilePictureFileName != nil && contact.profilePictureFileName.length > 0) {
return [self loadProfileAvatarWithFilename:contact.profilePictureFileName];
}
if (userProfile.avatarUrlPath.length > 0) {
[self downloadAvatarForUserProfile:userProfile];
if (contact.profilePictureURL != nil && contact.profilePictureURL.length > 0) {
[self downloadAvatarForUserProfile:contact];
}
return nil;
@ -741,13 +450,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
{
OWSAssertDebug(recipientId.length > 0);
// For "local reads", use the local user profile.
OWSUserProfile *userProfile = ([self.tsAccountManager.localNumber isEqualToString:recipientId]
? self.localUserProfile
: [OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection]);
SNContact *contact = [LKStorage.shared getContactWithSessionID:recipientId];
if (userProfile.avatarFileName.length > 0) {
return [self loadProfileDataWithFilename:userProfile.avatarFileName];
if (contact.profilePictureFileName != nil && contact.profilePictureFileName.length > 0) {
return [self loadProfileDataWithFilename:contact.profilePictureFileName];
}
return nil;
@ -758,40 +464,42 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
return [[NSUUID UUID].UUIDString stringByAppendingPathExtension:@"jpg"];
}
- (void)downloadAvatarForUserProfile:(OWSUserProfile *)userProfile
- (void)downloadAvatarForUserProfile:(SNContact *)contact
{
OWSAssertDebug(userProfile);
OWSAssertDebug(contact);
__block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (userProfile.avatarUrlPath.length < 1) {
OWSLogDebug(@"Skipping downloading avatar for %@ because url is not set", userProfile.recipientId);
BOOL hasProfilePictureURL = (contact.profilePictureURL != nil && contact.profilePictureURL.length > 0);
if (!hasProfilePictureURL) {
OWSLogDebug(@"Skipping downloading avatar for %@ because url is not set", contact.sessionID);
return;
}
NSString *_Nullable avatarUrlPathAtStart = userProfile.avatarUrlPath;
NSString *_Nullable avatarUrlPathAtStart = contact.profilePictureURL;
if (userProfile.profileKey.keyData.length < 1 || userProfile.avatarUrlPath.length < 1) {
BOOL hasProfileEncryptionKey = (contact.profileEncryptionKey != nil && contact.profileEncryptionKey.keyData.length > 0);
if (!hasProfileEncryptionKey || !hasProfilePictureURL) {
return;
}
OWSAES256Key *profileKeyAtStart = userProfile.profileKey;
OWSAES256Key *profileKeyAtStart = contact.profileEncryptionKey;
NSString *fileName = [self generateAvatarFilename];
NSString *filePath = [OWSUserProfile profileAvatarFilepathWithFilename:fileName];
@synchronized(self.currentAvatarDownloads)
{
if ([self.currentAvatarDownloads containsObject:userProfile.recipientId]) {
if ([self.currentAvatarDownloads containsObject:contact.sessionID]) {
// Download already in flight; ignore.
return;
}
[self.currentAvatarDownloads addObject:userProfile.recipientId];
[self.currentAvatarDownloads addObject:contact.sessionID];
}
OWSLogVerbose(@"downloading profile avatar: %@", userProfile.uniqueId);
OWSLogVerbose(@"downloading profile avatar: %@", contact.sessionID);
NSString *profilePictureURL = userProfile.avatarUrlPath;
NSString *profilePictureURL = contact.profilePictureURL;
NSString *file = [profilePictureURL lastPathComponent];
BOOL useOldServer = [profilePictureURL containsString:SNFileServerAPIV2.oldServer];
@ -800,7 +508,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
[promise.then(^(NSData *data) {
@synchronized(self.currentAvatarDownloads)
{
[self.currentAvatarDownloads removeObject:userProfile.recipientId];
[self.currentAvatarDownloads removeObject:contact.sessionID];
}
NSData *_Nullable encryptedData = data;
NSData *_Nullable decryptedData = [self decryptProfileData:encryptedData profileKey:profileKeyAtStart];
@ -812,24 +520,28 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}
}
OWSUserProfile *latestUserProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:userProfile.recipientId dbConnection:self.dbConnection];
SNContact *latestContact = [LKStorage.shared getContactWithSessionID:contact.sessionID];
if (latestUserProfile.profileKey.keyData.length < 1
|| ![latestUserProfile.profileKey isEqual:userProfile.profileKey]) {
BOOL hasProfileEncryptionKey = (latestContact.profileEncryptionKey != nil
&& latestContact.profileEncryptionKey.keyData.length > 0);
if (!hasProfileEncryptionKey || ![latestContact.profileEncryptionKey isEqual:contact.profileEncryptionKey]) {
OWSLogWarn(@"Ignoring avatar download for obsolete user profile.");
} else if (![avatarUrlPathAtStart isEqualToString:latestUserProfile.avatarUrlPath]) {
} else if (![avatarUrlPathAtStart isEqualToString:latestContact.profilePictureURL]) {
OWSLogInfo(@"avatar url has changed during download");
if (latestUserProfile.avatarUrlPath.length > 0) {
[self downloadAvatarForUserProfile:latestUserProfile];
if (latestContact.profilePictureURL != nil && latestContact.profilePictureURL.length > 0) {
[self downloadAvatarForUserProfile:latestContact];
}
} else if (!encryptedData) {
OWSLogError(@"avatar encrypted data for %@ could not be read.", userProfile.recipientId);
OWSLogError(@"avatar encrypted data for %@ could not be read.", contact.sessionID);
} else if (!decryptedData) {
OWSLogError(@"avatar data for %@ could not be decrypted.", userProfile.recipientId);
OWSLogError(@"avatar data for %@ could not be decrypted.", contact.sessionID);
} else if (!image) {
OWSLogError(@"avatar image for %@ could not be loaded.", userProfile.recipientId);
OWSLogError(@"avatar image for %@ could not be loaded.", contact.sessionID);
} else {
[latestUserProfile updateWithAvatarFileName:fileName dbConnection:self.dbConnection completion:nil];
latestContact.profilePictureFileName = fileName;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:latestContact usingTransaction:transaction];
}];
[self updateProfileAvatarCache:image filename:fileName];
}
@ -849,64 +561,32 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
// Ensure decryption, etc. off main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSUserProfile *userProfile =
[OWSUserProfile getOrBuildUserProfileForRecipientId:recipientId dbConnection:self.dbConnection];
SNContact *contact = [LKStorage.shared getContactWithSessionID:recipientId];
NSString *_Nullable localNumber = self.tsAccountManager.localNumber;
// If we're updating the profile that corresponds to our local number,
// make sure we're using the latest key.
if (localNumber && [localNumber isEqualToString:recipientId]) {
[userProfile updateWithProfileKey:self.localUserProfile.profileKey
dbConnection:self.dbConnection
completion:nil];
}
if (!userProfile.profileKey) {
return;
}
if (!contact.profileEncryptionKey) { return; }
NSString *_Nullable profileName =
[self decryptProfileNameData:profileNameEncrypted profileKey:userProfile.profileKey];
[userProfile updateWithProfileName:profileName
avatarUrlPath:avatarUrlPath
dbConnection:self.dbConnection
completion:nil];
// If we're updating the profile that corresponds to our local number,
// update the local profile as well.
if (localNumber && [localNumber isEqualToString:recipientId]) {
OWSUserProfile *localUserProfile = self.localUserProfile;
OWSAssertDebug(localUserProfile);
[localUserProfile updateWithProfileName:profileName
avatarUrlPath:avatarUrlPath
dbConnection:self.dbConnection
completion:nil];
}
[self decryptProfileNameData:profileNameEncrypted profileKey:contact.profileEncryptionKey];
contact.name = profileName;
contact.profilePictureURL = avatarUrlPath;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKStorage.shared setContact:contact usingTransaction:transaction];
}];
// Whenever we change avatarUrlPath, OWSUserProfile clears avatarFileName.
// So if avatarUrlPath is set and avatarFileName is not set, we should to
// download this avatar. downloadAvatarForUserProfile will de-bounce
// downloads.
if (userProfile.avatarUrlPath.length > 0 && userProfile.avatarFileName.length < 1) {
[self downloadAvatarForUserProfile:userProfile];
BOOL hasProfilePictureURL = (contact.profilePictureURL != nil && contact.profilePictureURL.length > 0);
BOOL hasProfilePictureFileName = (contact.profilePictureFileName != nil && contact.profilePictureFileName.length > 0);
if (hasProfilePictureURL && !hasProfilePictureFileName) {
[self downloadAvatarForUserProfile:contact];
}
});
}
- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction
{
OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:contactID transaction:transaction];
[userProfile updateWithProfileName:displayName avatarUrlPath:userProfile.avatarUrlPath avatarFileName:userProfile.avatarFileName transaction:transaction completion:nil];
}
- (void)ensureProfileCachedForContactWithID:(NSString *)contactID with:(YapDatabaseReadWriteTransaction *)transaction
{
OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForRecipientId:contactID transaction:transaction];
[userProfile saveWithTransaction:transaction];
}
- (BOOL)isNullableDataEqual:(NSData *_Nullable)left toData:(NSData *_Nullable)right
{
if (left == nil && right == nil) {
@ -983,7 +663,9 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
- (nullable NSData *)encryptProfileData:(nullable NSData *)data
{
return [self encryptProfileData:data profileKey:self.localProfileKey];
OWSAES256Key *localProfileKey = [LKStorage.shared getUser].profileEncryptionKey;
return [self encryptProfileData:data profileKey:localProfileKey];
}
- (BOOL)isProfileNameTooLong:(nullable NSString *)profileName
@ -1010,7 +692,9 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
[paddedNameData increaseLengthBy:paddingByteCount];
OWSAssertDebug(paddedNameData.length == kOWSProfileManager_NameDataLength);
return [self encryptProfileData:[paddedNameData copy] profileKey:self.localProfileKey];
OWSAES256Key *localProfileKey = [LKStorage.shared getUser].profileEncryptionKey;
return [self encryptProfileData:[paddedNameData copy] profileKey:localProfileKey];
}
#pragma mark - Avatar Disk Cache
@ -1061,16 +745,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
}
}
#pragma mark - Notifications
- (void)blockListDidChange:(NSNotification *)notification {
OWSAssertIsOnMainThread();
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[self rotateLocalProfileKeyIfNecessary];
}];
}
@end
NS_ASSUME_NONNULL_END