From d6ca969c62e0b330073a6c9ab5a4997747f223e8 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 28 Nov 2018 12:55:01 -0500 Subject: [PATCH] Backup local profile. --- Signal/src/util/Backup/OWSBackupAPI.swift | 6 +-- Signal/src/util/Backup/OWSBackupExportJob.m | 55 ++++++++++++++++++-- Signal/src/util/Backup/OWSBackupImportJob.m | 20 +++++-- Signal/src/util/Backup/OWSBackupJob.h | 4 ++ Signal/src/util/Backup/OWSBackupJob.m | 34 ++++++++++-- SignalMessaging/profiles/OWSProfileManager.h | 1 + SignalMessaging/profiles/OWSProfileManager.m | 9 ++++ 7 files changed, 116 insertions(+), 13 deletions(-) diff --git a/Signal/src/util/Backup/OWSBackupAPI.swift b/Signal/src/util/Backup/OWSBackupAPI.swift index 93ecdb806..e9faba619 100644 --- a/Signal/src/util/Backup/OWSBackupAPI.swift +++ b/Signal/src/util/Backup/OWSBackupAPI.swift @@ -62,13 +62,13 @@ import PromiseKit // We wouldn't want to overwrite previous images until the entire backup export is // complete. @objc - public class func saveEphemeralDatabaseFileToCloudObjc(recipientId: String, + public class func saveEphemeralFileToCloudObjc(recipientId: String, fileUrl: URL) -> AnyPromise { - return AnyPromise(saveEphemeralDatabaseFileToCloud(recipientId: recipientId, + return AnyPromise(saveEphemeralFileToCloud(recipientId: recipientId, fileUrl: fileUrl)) } - public class func saveEphemeralDatabaseFileToCloud(recipientId: String, + public class func saveEphemeralFileToCloud(recipientId: String, fileUrl: URL) -> Promise { let recordName = "\(recordNamePrefix(forRecipientId: recipientId))ephemeralFile-\(NSUUID().uuidString)" return saveFileToCloud(fileUrl: fileUrl, diff --git a/Signal/src/util/Backup/OWSBackupExportJob.m b/Signal/src/util/Backup/OWSBackupExportJob.m index 6ff6ed111..021aa2bef 100644 --- a/Signal/src/util/Backup/OWSBackupExportJob.m +++ b/Signal/src/util/Backup/OWSBackupExportJob.m @@ -308,6 +308,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) NSMutableArray *savedAttachmentItems; +@property (nonatomic, nullable) OWSBackupExportItem *localProfileAvatarItem; + @property (nonatomic, nullable) OWSBackupExportItem *manifestItem; // If we are replacing an existing backup, we use some of its contents for continuity. @@ -335,6 +337,11 @@ NS_ASSUME_NONNULL_BEGIN return AppEnvironment.shared.backup; } +- (OWSProfileManager *)profileManager +{ + return [OWSProfileManager sharedManager]; +} + #pragma mark - - (void)start @@ -722,6 +729,9 @@ NS_ASSUME_NONNULL_BEGIN .thenInBackground(^{ return [self saveDatabaseFilesToCloud]; }) + .thenInBackground(^{ + return [self saveLocalProfileAvatarToCloud]; + }) .thenInBackground(^{ return [self saveManifestFileToCloud]; }); @@ -745,7 +755,7 @@ NS_ASSUME_NONNULL_BEGIN } return [OWSBackupAPI - saveEphemeralDatabaseFileToCloudObjcWithRecipientId:self.recipientId + saveEphemeralFileToCloudObjcWithRecipientId:self.recipientId fileUrl:[NSURL fileURLWithPath:item.encryptedItem .filePath]]; }) @@ -876,6 +886,36 @@ NS_ASSUME_NONNULL_BEGIN }); } +- (AnyPromise *)saveLocalProfileAvatarToCloud +{ + if (self.isComplete) { + return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")]; + } + + NSData *_Nullable localProfileAvatarData = self.profileManager.localProfileAvatarData; + if (localProfileAvatarData.length < 1) { + // No profile avatar to backup. + return [AnyPromise promiseWithValue:@(1)]; + } + OWSBackupEncryptedItem *_Nullable encryptedItem = + [self.backupIO encryptDataAsTempFile:localProfileAvatarData encryptionKey:self.delegate.backupEncryptionKey]; + if (!encryptedItem) { + return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not encrypt local profile avatar.")]; + } + + OWSBackupExportItem *exportItem = [OWSBackupExportItem new]; + exportItem.encryptedItem = encryptedItem; + + return [OWSBackupAPI saveEphemeralFileToCloudObjcWithRecipientId:self.recipientId + fileUrl:[NSURL fileURLWithPath:encryptedItem.filePath]] + .thenInBackground(^(NSString *recordName) { + exportItem.recordName = recordName; + self.localProfileAvatarItem = exportItem; + + return [AnyPromise promiseWithValue:@(1)]; + }); +} + - (AnyPromise *)saveManifestFileToCloud { if (self.isComplete) { @@ -907,10 +947,19 @@ NS_ASSUME_NONNULL_BEGIN OWSAssertDebug(self.jobTempDirPath.length > 0); OWSAssertDebug(self.backupIO); - NSDictionary *json = @{ + NSMutableDictionary *json = [@{ kOWSBackup_ManifestKey_DatabaseFiles : [self jsonForItems:self.savedDatabaseItems], kOWSBackup_ManifestKey_AttachmentFiles : [self jsonForItems:self.savedAttachmentItems], - }; + } mutableCopy]; + + NSString *_Nullable localProfileName = self.profileManager.localProfileName; + if (localProfileName.length > 0) { + json[kOWSBackup_ManifestKey_LocalProfileName] = localProfileName; + } + + if (self.localProfileAvatarItem) { + json[kOWSBackup_ManifestKey_LocalProfileAvatar] = [self jsonForItems:@[ self.localProfileAvatarItem ]]; + } OWSLogVerbose(@"json: %@", json); diff --git a/Signal/src/util/Backup/OWSBackupImportJob.m b/Signal/src/util/Backup/OWSBackupImportJob.m index 736fbab31..35f817773 100644 --- a/Signal/src/util/Backup/OWSBackupImportJob.m +++ b/Signal/src/util/Backup/OWSBackupImportJob.m @@ -27,8 +27,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe @property (nonatomic) OWSBackupIO *backupIO; -@property (nonatomic) NSArray *databaseItems; -@property (nonatomic) NSArray *attachmentsItems; +@property (nonatomic) OWSBackupManifestContents *manifest; @end @@ -66,6 +65,20 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe #pragma mark - +- (NSArray *)databaseItems +{ + OWSAssertDebug(self.manifest); + + return self.manifest.databaseItems; +} + +- (NSArray *)attachmentsItems +{ + OWSAssertDebug(self.manifest); + + return self.manifest.attachmentsItems; +} + - (void)startAsync { OWSAssertIsOnMainThread(); @@ -120,8 +133,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe } OWSCAssertDebug(manifest.databaseItems.count > 0); OWSCAssertDebug(manifest.attachmentsItems); - strongSelf.databaseItems = manifest.databaseItems; - strongSelf.attachmentsItems = manifest.attachmentsItems; + strongSelf.manifest = manifest; [strongSelf downloadAndProcessImport]; } failure:^(NSError *manifestError) { diff --git a/Signal/src/util/Backup/OWSBackupJob.h b/Signal/src/util/Backup/OWSBackupJob.h index ac42def1d..70a70909b 100644 --- a/Signal/src/util/Backup/OWSBackupJob.h +++ b/Signal/src/util/Backup/OWSBackupJob.h @@ -14,6 +14,8 @@ extern NSString *const kOWSBackup_ManifestKey_EncryptionKey; extern NSString *const kOWSBackup_ManifestKey_RelativeFilePath; extern NSString *const kOWSBackup_ManifestKey_AttachmentId; extern NSString *const kOWSBackup_ManifestKey_DataSize; +extern NSString *const kOWSBackup_ManifestKey_LocalProfileAvatar; +extern NSString *const kOWSBackup_ManifestKey_LocalProfileName; @class OWSBackupIO; @class OWSBackupJob; @@ -28,6 +30,8 @@ typedef void (^OWSBackupJobManifestFailure)(NSError *error); @property (nonatomic) NSArray *databaseItems; @property (nonatomic) NSArray *attachmentsItems; +@property (nonatomic, nullable) OWSBackupFragment *localProfileAvatarItem; +@property (nonatomic, nullable) NSString *localProfileName; @end diff --git a/Signal/src/util/Backup/OWSBackupJob.m b/Signal/src/util/Backup/OWSBackupJob.m index 94617cae7..4b8ffd95b 100644 --- a/Signal/src/util/Backup/OWSBackupJob.m +++ b/Signal/src/util/Backup/OWSBackupJob.m @@ -17,6 +17,8 @@ NSString *const kOWSBackup_ManifestKey_EncryptionKey = @"encryption_key"; NSString *const kOWSBackup_ManifestKey_RelativeFilePath = @"relative_file_path"; NSString *const kOWSBackup_ManifestKey_AttachmentId = @"attachment_id"; NSString *const kOWSBackup_ManifestKey_DataSize = @"data_size"; +NSString *const kOWSBackup_ManifestKey_LocalProfileAvatar = @"local_profile_avatar"; +NSString *const kOWSBackup_ManifestKey_LocalProfileName = @"local_profile_name"; NSString *const kOWSBackup_KeychainService = @"kOWSBackup_KeychainService"; @@ -219,24 +221,50 @@ NSString *const kOWSBackup_KeychainService = @"kOWSBackup_KeychainService"; OWSLogVerbose(@"json: %@", json); NSArray *_Nullable databaseItems = - [self parseItems:json key:kOWSBackup_ManifestKey_DatabaseFiles]; + [self parseManifestItems:json key:kOWSBackup_ManifestKey_DatabaseFiles]; if (!databaseItems) { return failure(); } NSArray *_Nullable attachmentsItems = - [self parseItems:json key:kOWSBackup_ManifestKey_AttachmentFiles]; + [self parseManifestItems:json key:kOWSBackup_ManifestKey_AttachmentFiles]; if (!attachmentsItems) { return failure(); } + NSArray *_Nullable localProfileAvatarItems; + if ([self parseManifestItem:json key:kOWSBackup_ManifestKey_LocalProfileAvatar]) { + localProfileAvatarItems = [self parseManifestItems:json key:kOWSBackup_ManifestKey_LocalProfileAvatar]; + } + + NSString *_Nullable localProfileName = [self parseManifestItem:json key:kOWSBackup_ManifestKey_LocalProfileName]; + OWSBackupManifestContents *contents = [OWSBackupManifestContents new]; contents.databaseItems = databaseItems; contents.attachmentsItems = attachmentsItems; + contents.localProfileAvatarItem = localProfileAvatarItems.firstObject; + if ([localProfileName isKindOfClass:[NSString class]]) { + contents.localProfileName = localProfileName; + } else { + OWSFailDebug(@"Invalid localProfileName: %@", [localProfileName class]); + } return success(contents); } -- (nullable NSArray *)parseItems:(id)json key:(NSString *)key +- (nullable id)parseManifestItem:(id)json key:(NSString *)key +{ + OWSAssertDebug(json); + OWSAssertDebug(key.length); + + if (![json isKindOfClass:[NSDictionary class]]) { + OWSFailDebug(@"manifest has invalid data."); + return nil; + } + id _Nullable value = json[key]; + return value; +} + +- (nullable NSArray *)parseManifestItems:(id)json key:(NSString *)key { OWSAssertDebug(json); OWSAssertDebug(key.length); diff --git a/SignalMessaging/profiles/OWSProfileManager.h b/SignalMessaging/profiles/OWSProfileManager.h index a35193ab9..0f1122220 100644 --- a/SignalMessaging/profiles/OWSProfileManager.h +++ b/SignalMessaging/profiles/OWSProfileManager.h @@ -37,6 +37,7 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter; - (BOOL)hasLocalProfile; - (nullable NSString *)localProfileName; - (nullable UIImage *)localProfileAvatarImage; +- (nullable NSData *)localProfileAvatarData; - (void)ensureLocalProfileCached; // This method is used to update the "local profile" state on the client diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 87648e85c..0766e0e0e 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -217,6 +217,15 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); 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