diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index 27c5db7bb..74bc26eda 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -167,7 +167,7 @@ NS_ASSUME_NONNULL_BEGIN dispatch_once(&onceToken, ^{ NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; - attachmentsFolder = [documentsPath stringByAppendingFormat:@"/Attachments"]; + attachmentsFolder = [documentsPath stringByAppendingPathComponent:@"Attachments"]; BOOL isDirectory; BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:attachmentsFolder isDirectory:&isDirectory]; diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.h b/SignalServiceKit/src/Profiles/OWSProfilesManager.h index 139bb2728..21f4b1446 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.h +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.h @@ -4,6 +4,8 @@ NS_ASSUME_NONNULL_BEGIN +extern NSString *const kNSNotificationName_LocalProfileDidChange; + // This class can be safely accessed and used from any thread. @interface OWSProfilesManager : NSObject @@ -11,6 +13,10 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)sharedManager; +- (nullable NSString *)localProfileName; + +- (nullable UIImage *)localProfileAvatarImage; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.m b/SignalServiceKit/src/Profiles/OWSProfilesManager.m index a46c7f437..720844624 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.m +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.m @@ -10,9 +10,14 @@ NS_ASSUME_NONNULL_BEGIN +NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationName_LocalProfileDidChange"; + NSString *const kOWSProfilesManager_Collection = @"kOWSProfilesManager_Collection"; // This key is used to persist the local user's profile key. NSString *const kOWSProfilesManager_LocalProfileKey = @"kOWSProfilesManager_LocalProfileKey"; +NSString *const kOWSProfilesManager_LocalProfileNameKey = @"kOWSProfilesManager_LocalProfileNameKey"; +NSString *const kOWSProfilesManager_LocalProfileAvatarFilenameKey + = @"kOWSProfilesManager_LocalProfileAvatarFilenameKey"; // TODO: static const NSInteger kProfileKeyLength = 16; @@ -22,7 +27,11 @@ static const NSInteger kProfileKeyLength = 16; @property (nonatomic, readonly) TSStorageManager *storageManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; -@property (nonatomic, readonly, nullable) NSData *localProfileKey; +@property (atomic, readonly, nullable) NSData *localProfileKey; + +@property (atomic, nullable) NSString *localProfileName; +@property (atomic, nullable) UIImage *localProfileAvatarImage; +@property (atomic) BOOL hasLoadedLocalProfile; @end @@ -84,6 +93,8 @@ static const NSInteger kProfileKeyLength = 16; } OWSAssert(_localProfileKey.length == kProfileKeyLength); + [self loadLocalProfileAsync]; + return self; } @@ -109,10 +120,71 @@ static const NSInteger kProfileKeyLength = 16; return [SecurityUtils generateRandomBytes:kProfileKeyLength]; } -- (nullable NSData *)localProfileKey +#pragma mark - Local Profile + +- (void)loadLocalProfileAsync { - OWSAssert(_localProfileKey.length == kProfileKeyLength); - return _localProfileKey; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSString *_Nullable localProfileName = [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileNameKey + inCollection:kOWSProfilesManager_Collection]; + NSString *_Nullable localProfileAvatarFilename = + [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileAvatarFilenameKey + inCollection:kOWSProfilesManager_Collection]; + UIImage *_Nullable localProfileAvatar = nil; + if (localProfileAvatarFilename) { + localProfileAvatar = [self loadProfileAvatarsWithFilename:localProfileAvatarFilename]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + self.localProfileName = localProfileName; + self.localProfileAvatarImage = localProfileAvatar; + self.hasLoadedLocalProfile = YES; + + if (localProfileAvatar || localProfileName) { + [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange + object:nil + userInfo:nil]; + } + }); + }); +} + +#pragma mark - Avatar Disk Cache + +- (nullable UIImage *)loadProfileAvatarsWithFilename:(NSString *)filename +{ + NSString *filePath = [self.profileAvatarsDirPath stringByAppendingPathComponent:filename]; + UIImage *_Nullable image = [UIImage imageWithContentsOfFile:filePath]; + return image; +} + +- (NSString *)profileAvatarsDirPath +{ + static NSString *profileAvatarsDirPath = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *documentsPath = + [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + profileAvatarsDirPath = [documentsPath stringByAppendingPathComponent:@"ProfileAvatars"]; + + BOOL isDirectory; + BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:profileAvatarsDirPath isDirectory:&isDirectory]; + if (exists) { + OWSAssert(isDirectory); + + DDLogInfo(@"Profile avatars directory already exists"); + } else { + NSError *error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:profileAvatarsDirPath + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (error) { + DDLogError(@"Failed to create profile avatars directory: %@", error); + } + } + }); + return profileAvatarsDirPath; } #pragma mark - Notifications diff --git a/SignalServiceKit/src/Storage/TSStorageManager.m b/SignalServiceKit/src/Storage/TSStorageManager.m index c52489fdd..f030b36b9 100644 --- a/SignalServiceKit/src/Storage/TSStorageManager.m +++ b/SignalServiceKit/src/Storage/TSStorageManager.m @@ -277,7 +277,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; #if TARGET_OS_IPHONE NSURL *fileURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; NSString *path = [fileURL path]; - databasePath = [path stringByAppendingFormat:@"/%@", databaseName]; + databasePath = [path stringByAppendingPathComponent:databaseName]; #elif TARGET_OS_MAC NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; @@ -289,7 +289,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; [fileManager createDirectoryAtURL:appDirectory withIntermediateDirectories:NO attributes:nil error:nil]; } - databasePath = [appDirectory.filePathURL.absoluteString stringByAppendingFormat:@"/%@", databaseName]; + databasePath = [appDirectory.filePathURL.absoluteString stringByAppendingPathComponent:databaseName]; #endif return databasePath; diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.m b/SignalServiceKit/src/Util/MIMETypeUtil.m index 5c77a0002..dfde27b72 100644 --- a/SignalServiceKit/src/Util/MIMETypeUtil.m +++ b/SignalServiceKit/src/Util/MIMETypeUtil.m @@ -364,36 +364,41 @@ NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype"; } + (NSString *)filePathForImage:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [[folder stringByAppendingFormat:@"/%@", uniqueId] - stringByAppendingPathExtension:[self getSupportedExtensionFromImageMIMEType:contentType]]; + return [self filePathForData:uniqueId + withFileExtension:[self getSupportedExtensionFromImageMIMEType:contentType] + inFolder:folder]; } + (NSString *)filePathForVideo:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [[folder stringByAppendingFormat:@"/%@", uniqueId] - stringByAppendingPathExtension:[self getSupportedExtensionFromVideoMIMEType:contentType]]; + return [self filePathForData:uniqueId + withFileExtension:[self getSupportedExtensionFromVideoMIMEType:contentType] + inFolder:folder]; } + (NSString *)filePathForAudio:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [[folder stringByAppendingFormat:@"/%@", uniqueId] - stringByAppendingPathExtension:[self getSupportedExtensionFromAudioMIMEType:contentType]]; + return [self filePathForData:uniqueId + withFileExtension:[self getSupportedExtensionFromAudioMIMEType:contentType] + inFolder:folder]; } + (NSString *)filePathForAnimated:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [[folder stringByAppendingFormat:@"/%@", uniqueId] - stringByAppendingPathExtension:[self getSupportedExtensionFromAnimatedMIMEType:contentType]]; + return [self filePathForData:uniqueId + withFileExtension:[self getSupportedExtensionFromAnimatedMIMEType:contentType] + inFolder:folder]; } + (NSString *)filePathForBinaryData:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [[folder stringByAppendingFormat:@"/%@", uniqueId] - stringByAppendingPathExtension:[self getSupportedExtensionFromBinaryDataMIMEType:contentType]]; + return [self filePathForData:uniqueId + withFileExtension:[self getSupportedExtensionFromBinaryDataMIMEType:contentType] + inFolder:folder]; } + (NSString *)filePathForData:(NSString *)uniqueId withFileExtension:(NSString *)fileExtension inFolder:(NSString *)folder { - return [[folder stringByAppendingFormat:@"/%@", uniqueId] stringByAppendingPathExtension:fileExtension]; + return [folder stringByAppendingPathComponent:[uniqueId stringByAppendingPathExtension:fileExtension]]; } + (nullable NSString *)utiTypeForMIMEType:(NSString *)mimeType