|
|
@ -30,9 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
// These properties may be accessed only from the main thread.
|
|
|
|
// These properties may be accessed only from the main thread.
|
|
|
|
@property (nonatomic, nullable) NSString *profileName;
|
|
|
|
@property (nonatomic, nullable) NSString *profileName;
|
|
|
|
|
|
|
|
|
|
|
|
// TODO This isn't really a URL, since it doesn't contain the host.
|
|
|
|
@property (nonatomic, nullable) NSString *avatarUrlPath;
|
|
|
|
// rename to "avatarPath" or "avatarKey"
|
|
|
|
|
|
|
|
@property (nonatomic, nullable) NSString *avatarUrl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This filename is relative to OWSProfileManager.profileAvatarsDirPath.
|
|
|
|
// This filename is relative to OWSProfileManager.profileAvatarsDirPath.
|
|
|
|
@property (nonatomic, nullable) NSString *avatarFileName;
|
|
|
|
@property (nonatomic, nullable) NSString *avatarFileName;
|
|
|
@ -72,13 +70,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
- (BOOL)isEqual:(UserProfile *)other
|
|
|
|
- (BOOL)isEqual:(UserProfile *)other
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return ([other isKindOfClass:[UserProfile class]] && [self.recipientId isEqualToString:other.recipientId] &&
|
|
|
|
return ([other isKindOfClass:[UserProfile class]] && [self.recipientId isEqualToString:other.recipientId] &&
|
|
|
|
[self.profileName isEqualToString:other.profileName] && [self.avatarUrl isEqualToString:other.avatarUrl] &&
|
|
|
|
[self.profileName isEqualToString:other.profileName] && [self.avatarUrlPath isEqualToString:other.avatarUrlPath] &&
|
|
|
|
[self.avatarFileName isEqualToString:other.avatarFileName]);
|
|
|
|
[self.avatarFileName isEqualToString:other.avatarFileName]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSUInteger)hash
|
|
|
|
- (NSUInteger)hash
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return self.recipientId.hash ^ self.profileName.hash ^ self.avatarUrl.hash ^ self.avatarFileName.hash;
|
|
|
|
return self.recipientId.hash ^ self.profileName.hash ^ self.avatarUrlPath.hash ^ self.avatarFileName.hash;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
@end
|
|
|
@ -306,7 +304,7 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
// * Try to update the service.
|
|
|
|
// * Try to update the service.
|
|
|
|
// * Update client state on success.
|
|
|
|
// * Update client state on success.
|
|
|
|
void (^tryToUpdateService)(NSString *_Nullable, NSString *_Nullable) = ^(
|
|
|
|
void (^tryToUpdateService)(NSString *_Nullable, NSString *_Nullable) = ^(
|
|
|
|
NSString *_Nullable avatarUrl, NSString *_Nullable avatarFileName) {
|
|
|
|
NSString *_Nullable avatarUrlPath, NSString *_Nullable avatarFileName) {
|
|
|
|
[self updateServiceWithProfileName:profileName
|
|
|
|
[self updateServiceWithProfileName:profileName
|
|
|
|
success:^{
|
|
|
|
success:^{
|
|
|
|
// All reads and writes to user profiles should happen on the main thread.
|
|
|
|
// All reads and writes to user profiles should happen on the main thread.
|
|
|
@ -315,9 +313,9 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
OWSAssert(userProfile);
|
|
|
|
OWSAssert(userProfile);
|
|
|
|
userProfile.profileName = profileName;
|
|
|
|
userProfile.profileName = profileName;
|
|
|
|
|
|
|
|
|
|
|
|
// TODO remote avatarUrl changes as result of fetching form -
|
|
|
|
// TODO remote avatarUrlPath changes as result of fetching form -
|
|
|
|
// we should probably invalidate it at that point, and refresh again when uploading file completes.
|
|
|
|
// we should probably invalidate it at that point, and refresh again when uploading file completes.
|
|
|
|
userProfile.avatarUrl = avatarUrl;
|
|
|
|
userProfile.avatarUrlPath = avatarUrlPath;
|
|
|
|
userProfile.avatarFileName = avatarFileName;
|
|
|
|
userProfile.avatarFileName = avatarFileName;
|
|
|
|
|
|
|
|
|
|
|
|
[self saveUserProfile:userProfile];
|
|
|
|
[self saveUserProfile:userProfile];
|
|
|
@ -345,19 +343,19 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
// * Upload it to asset service
|
|
|
|
// * Upload it to asset service
|
|
|
|
// * Send asset service info to Signal Service
|
|
|
|
// * Send asset service info to Signal Service
|
|
|
|
if (self.localCachedAvatarImage == avatarImage) {
|
|
|
|
if (self.localCachedAvatarImage == avatarImage) {
|
|
|
|
OWSAssert(userProfile.avatarUrl.length > 0);
|
|
|
|
OWSAssert(userProfile.avatarUrlPath.length > 0);
|
|
|
|
OWSAssert(userProfile.avatarFileName.length > 0);
|
|
|
|
OWSAssert(userProfile.avatarFileName.length > 0);
|
|
|
|
|
|
|
|
|
|
|
|
DDLogVerbose(@"%@ Updating local profile on service with unchanged avatar.", self.tag);
|
|
|
|
DDLogVerbose(@"%@ Updating local profile on service with unchanged avatar.", self.tag);
|
|
|
|
// If the avatar hasn't changed, reuse the existing metadata.
|
|
|
|
// If the avatar hasn't changed, reuse the existing metadata.
|
|
|
|
tryToUpdateService(userProfile.avatarUrl, userProfile.avatarFileName);
|
|
|
|
tryToUpdateService(userProfile.avatarUrlPath, userProfile.avatarFileName);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
DDLogVerbose(@"%@ Updating local profile on service with new avatar.", self.tag);
|
|
|
|
DDLogVerbose(@"%@ Updating local profile on service with new avatar.", self.tag);
|
|
|
|
[self writeAvatarToDisk:avatarImage
|
|
|
|
[self writeAvatarToDisk:avatarImage
|
|
|
|
success:^(NSData *data, NSString *fileName) {
|
|
|
|
success:^(NSData *data, NSString *fileName) {
|
|
|
|
[self uploadAvatarToService:data
|
|
|
|
[self uploadAvatarToService:data
|
|
|
|
success:^(NSString *avatarUrl) {
|
|
|
|
success:^(NSString *avatarUrlPath) {
|
|
|
|
tryToUpdateService(avatarUrl, fileName);
|
|
|
|
tryToUpdateService(avatarUrlPath, fileName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
failure:^{
|
|
|
|
failure:^{
|
|
|
|
failureBlock();
|
|
|
|
failureBlock();
|
|
|
@ -401,7 +399,7 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)uploadAvatarToService:(NSData *)avatarData
|
|
|
|
- (void)uploadAvatarToService:(NSData *)avatarData
|
|
|
|
success:(void (^)(NSString *avatarUrl))successBlock
|
|
|
|
success:(void (^)(NSString *avatarUrlPath))successBlock
|
|
|
|
failure:(void (^)())failureBlock
|
|
|
|
failure:(void (^)())failureBlock
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssert(avatarData.length > 0);
|
|
|
|
OWSAssert(avatarData.length > 0);
|
|
|
@ -509,15 +507,15 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
// but the approach of getting it from the remote provider seems a more
|
|
|
|
// but the approach of getting it from the remote provider seems a more
|
|
|
|
// robust way to ensure we've actually created the resource where we
|
|
|
|
// robust way to ensure we've actually created the resource where we
|
|
|
|
// think we have.
|
|
|
|
// think we have.
|
|
|
|
NSString *avatarURL = response.allHeaderFields[@"Location"];
|
|
|
|
NSString *avatarUrlPath = response.allHeaderFields[@"Location"];
|
|
|
|
if (avatarURL.length == 0) {
|
|
|
|
if (avatarUrlPath.length == 0) {
|
|
|
|
OWSProdFail(@"profile_manager_error_avatar_upload_no_location_in_response");
|
|
|
|
OWSProdFail(@"profile_manager_error_avatar_upload_no_location_in_response");
|
|
|
|
failureBlock();
|
|
|
|
failureBlock();
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DDLogVerbose(@"%@ successfully uploaded avatar url: %@", self.tag, avatarURL);
|
|
|
|
DDLogVerbose(@"%@ successfully uploaded avatar url: %@", self.tag, avatarUrlPath);
|
|
|
|
successBlock(avatarURL);
|
|
|
|
successBlock(avatarUrlPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
failure:^(NSURLSessionDataTask *_Nullable uploadTask, NSError *_Nonnull error) {
|
|
|
|
failure:^(NSURLSessionDataTask *_Nullable uploadTask, NSError *_Nonnull error) {
|
|
|
|
DDLogVerbose(@"%@ uploading avatar failed with error: %@", self.tag, error);
|
|
|
|
DDLogVerbose(@"%@ uploading avatar failed with error: %@", self.tag, error);
|
|
|
@ -691,7 +689,7 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
|
|
|
|
|
|
|
|
// Clear profile state.
|
|
|
|
// Clear profile state.
|
|
|
|
userProfile.profileName = nil;
|
|
|
|
userProfile.profileName = nil;
|
|
|
|
userProfile.avatarUrl = nil;
|
|
|
|
userProfile.avatarUrlPath = nil;
|
|
|
|
userProfile.avatarFileName = nil;
|
|
|
|
userProfile.avatarFileName = nil;
|
|
|
|
|
|
|
|
|
|
|
|
[self saveUserProfile:userProfile];
|
|
|
|
[self saveUserProfile:userProfile];
|
|
|
@ -738,7 +736,7 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
if (image) {
|
|
|
|
if (image) {
|
|
|
|
[self.otherUsersProfileAvatarImageCache setObject:image forKey:recipientId];
|
|
|
|
[self.otherUsersProfileAvatarImageCache setObject:image forKey:recipientId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (userProfile.avatarUrl.length > 0) {
|
|
|
|
} else if (userProfile.avatarUrlPath.length > 0) {
|
|
|
|
[self downloadAvatarForUserProfile:userProfile];
|
|
|
|
[self downloadAvatarForUserProfile:userProfile];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -750,12 +748,12 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
OWSAssert([NSThread isMainThread]);
|
|
|
|
OWSAssert([NSThread isMainThread]);
|
|
|
|
OWSAssert(userProfile);
|
|
|
|
OWSAssert(userProfile);
|
|
|
|
|
|
|
|
|
|
|
|
if (userProfile.avatarUrl.length < 1) {
|
|
|
|
if (userProfile.avatarUrlPath.length < 1) {
|
|
|
|
OWSFail(@"%@ Malformed avatar URL: %@", self.tag, userProfile.avatarUrl);
|
|
|
|
OWSFail(@"%@ Malformed avatar URL: %@", self.tag, userProfile.avatarUrlPath);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (userProfile.profileKey.keyData.length < 1 || userProfile.avatarUrl.length < 1) {
|
|
|
|
if (userProfile.profileKey.keyData.length < 1 || userProfile.avatarUrlPath.length < 1) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -773,8 +771,8 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
NSString *tempDirectory = NSTemporaryDirectory();
|
|
|
|
NSString *tempDirectory = NSTemporaryDirectory();
|
|
|
|
NSString *tempFilePath = [tempDirectory stringByAppendingPathComponent:fileName];
|
|
|
|
NSString *tempFilePath = [tempDirectory stringByAppendingPathComponent:fileName];
|
|
|
|
|
|
|
|
|
|
|
|
NSURL *avatarUrl = [NSURL URLWithString:userProfile.avatarUrl relativeToURL:self.avatarHTTPManager.baseURL];
|
|
|
|
NSURL *avatarUrlPath = [NSURL URLWithString:userProfile.avatarUrlPath relativeToURL:self.avatarHTTPManager.baseURL];
|
|
|
|
NSURLRequest *request = [NSURLRequest requestWithURL:avatarUrl];
|
|
|
|
NSURLRequest *request = [NSURLRequest requestWithURL:avatarUrlPath];
|
|
|
|
NSURLSessionDownloadTask *downloadTask = [self.avatarHTTPManager downloadTaskWithRequest:request
|
|
|
|
NSURLSessionDownloadTask *downloadTask = [self.avatarHTTPManager downloadTaskWithRequest:request
|
|
|
|
progress:^(NSProgress *_Nonnull downloadProgress) {
|
|
|
|
progress:^(NSProgress *_Nonnull downloadProgress) {
|
|
|
|
DDLogVerbose(@"%@ Downloading avatar for %@", self.tag, userProfile.recipientId);
|
|
|
|
DDLogVerbose(@"%@ Downloading avatar for %@", self.tag, userProfile.recipientId);
|
|
|
@ -862,7 +860,7 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
|
|
|
|
|
|
|
|
- (void)updateProfileForRecipientId:(NSString *)recipientId
|
|
|
|
- (void)updateProfileForRecipientId:(NSString *)recipientId
|
|
|
|
profileNameEncrypted:(nullable NSData *)profileNameEncrypted
|
|
|
|
profileNameEncrypted:(nullable NSData *)profileNameEncrypted
|
|
|
|
avatarUrl:(nullable NSString *)avatarUrl;
|
|
|
|
avatarUrlPath:(nullable NSString *)avatarUrlPath;
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssert(recipientId.length > 0);
|
|
|
|
OWSAssert(recipientId.length > 0);
|
|
|
|
|
|
|
|
|
|
|
@ -877,17 +875,17 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
NSString *_Nullable profileName =
|
|
|
|
NSString *_Nullable profileName =
|
|
|
|
[self decryptProfileNameData:profileNameEncrypted profileKey:userProfile.profileKey];
|
|
|
|
[self decryptProfileNameData:profileNameEncrypted profileKey:userProfile.profileKey];
|
|
|
|
|
|
|
|
|
|
|
|
BOOL isAvatarSame = [self isNullableStringEqual:userProfile.avatarUrl toString:avatarUrl];
|
|
|
|
BOOL isAvatarSame = [self isNullableStringEqual:userProfile.avatarUrlPath toString:avatarUrlPath];
|
|
|
|
|
|
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
userProfile.profileName = profileName;
|
|
|
|
userProfile.profileName = profileName;
|
|
|
|
userProfile.avatarUrl = avatarUrl;
|
|
|
|
userProfile.avatarUrlPath = avatarUrlPath;
|
|
|
|
|
|
|
|
|
|
|
|
if (!isAvatarSame) {
|
|
|
|
if (!isAvatarSame) {
|
|
|
|
// Evacuate avatar image cache.
|
|
|
|
// Evacuate avatar image cache.
|
|
|
|
[self.otherUsersProfileAvatarImageCache removeObjectForKey:recipientId];
|
|
|
|
[self.otherUsersProfileAvatarImageCache removeObjectForKey:recipientId];
|
|
|
|
|
|
|
|
|
|
|
|
if (avatarUrl) {
|
|
|
|
if (avatarUrlPath) {
|
|
|
|
[self downloadAvatarForUserProfile:userProfile];
|
|
|
|
[self downloadAvatarForUserProfile:userProfile];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -993,6 +991,8 @@ static const NSUInteger kOWSProfileManager_NameDataLength = 26;
|
|
|
|
NSUInteger paddingByteCount = kOWSProfileManager_NameDataLength - nameData.length;
|
|
|
|
NSUInteger paddingByteCount = kOWSProfileManager_NameDataLength - nameData.length;
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableData *paddedNameData = [nameData mutableCopy];
|
|
|
|
NSMutableData *paddedNameData = [nameData mutableCopy];
|
|
|
|
|
|
|
|
// Since we want all encrypted profile names to be the same length on the server, we use `increaseLengthBy`
|
|
|
|
|
|
|
|
// to pad out any remaining length with 0 bytes.
|
|
|
|
[paddedNameData increaseLengthBy:paddingByteCount];
|
|
|
|
[paddedNameData increaseLengthBy:paddingByteCount];
|
|
|
|
OWSAssert(paddedNameData.length == kOWSProfileManager_NameDataLength);
|
|
|
|
OWSAssert(paddedNameData.length == kOWSProfileManager_NameDataLength);
|
|
|
|
|
|
|
|
|
|
|
|