Add import back up UI. Begin work on import backup logic.

pull/1/head
Matthew Chen 7 years ago
parent 857ca56ab6
commit 272a90d269

@ -118,6 +118,8 @@
3478506C1FD9B78A007B8332 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */; };
347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 3478506F1FDAEB16007B8332 /* OWSUserProfile.m */; };
347850721FDAEB17007B8332 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 347850701FDAEB16007B8332 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; };
347E0B7A2003CD7500BC2F76 /* OWSBackupExportViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 347E0B762003CD7400BC2F76 /* OWSBackupExportViewController.m */; };
347E0B7B2003CD7500BC2F76 /* OWSBackupImportViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 347E0B782003CD7400BC2F76 /* OWSBackupImportViewController.m */; };
3497DBEC1ECE257500DB2605 /* OWSCountryMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = 3497DBEB1ECE257500DB2605 /* OWSCountryMetadata.m */; };
3497DBEF1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3497DBEE1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m */; };
34A910601FFEB114000C4745 /* OWSBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A9105F1FFEB114000C4745 /* OWSBackup.m */; };
@ -524,8 +526,6 @@
344F248920069F0600CFB4F4 /* ViewControllerUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ViewControllerUtils.m; path = SignalMessaging/contacts/ViewControllerUtils.m; sourceTree = SOURCE_ROOT; };
34533F161EA8D2070006114F /* OWSAudioAttachmentPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAudioAttachmentPlayer.h; sourceTree = "<group>"; };
34533F171EA8D2070006114F /* OWSAudioAttachmentPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAudioAttachmentPlayer.m; sourceTree = "<group>"; };
3456D2C21FFFCC6F001EA55D /* OWSBackupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupViewController.m; sourceTree = "<group>"; };
3456D2C31FFFCC6F001EA55D /* OWSBackupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupViewController.h; sourceTree = "<group>"; };
3461284A1FD0B93F00532771 /* SAELoadViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAELoadViewController.swift; sourceTree = "<group>"; };
346129331FD1A88700532771 /* OWSSwiftUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSSwiftUtils.swift; sourceTree = "<group>"; };
346129371FD1B47200532771 /* OWSPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSPreferences.h; sourceTree = "<group>"; };
@ -601,6 +601,10 @@
347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = "<group>"; };
3478506F1FDAEB16007B8332 /* OWSUserProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUserProfile.m; sourceTree = "<group>"; };
347850701FDAEB16007B8332 /* OWSUserProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUserProfile.h; sourceTree = "<group>"; };
347E0B762003CD7400BC2F76 /* OWSBackupExportViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupExportViewController.m; sourceTree = "<group>"; };
347E0B772003CD7400BC2F76 /* OWSBackupExportViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupExportViewController.h; sourceTree = "<group>"; };
347E0B782003CD7400BC2F76 /* OWSBackupImportViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupImportViewController.m; sourceTree = "<group>"; };
347E0B792003CD7500BC2F76 /* OWSBackupImportViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupImportViewController.h; sourceTree = "<group>"; };
348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceSleepManager.swift; sourceTree = "<group>"; };
3495BC911F1426B800B478F5 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = translations/ar.lproj/Localizable.strings; sourceTree = "<group>"; };
3497DBEA1ECE257500DB2605 /* OWSCountryMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSCountryMetadata.h; sourceTree = "<group>"; };
@ -1326,8 +1330,10 @@
34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */,
34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */,
34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */,
3456D2C31FFFCC6F001EA55D /* OWSBackupViewController.h */,
3456D2C21FFFCC6F001EA55D /* OWSBackupViewController.m */,
347E0B772003CD7400BC2F76 /* OWSBackupExportViewController.h */,
347E0B762003CD7400BC2F76 /* OWSBackupExportViewController.m */,
347E0B792003CD7500BC2F76 /* OWSBackupImportViewController.h */,
347E0B782003CD7400BC2F76 /* OWSBackupImportViewController.m */,
34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsViewController.h */,
34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */,
34D5CCAB1EAE7136005515DB /* OWSConversationSettingsViewDelegate.h */,
@ -2905,6 +2911,7 @@
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
34B3F8871E8DF1700035BE1A /* NotificationSettingsViewController.m in Sources */,
347E0B7B2003CD7500BC2F76 /* OWSBackupImportViewController.m in Sources */,
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */,
4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */,
34B3F8751E8DF1700035BE1A /* CallViewController.swift in Sources */,
@ -2928,7 +2935,6 @@
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */,
34B3F8711E8DF1700035BE1A /* AboutTableViewController.m in Sources */,
34B3F88D1E8DF1700035BE1A /* OWSQRCodeScanningViewController.m in Sources */,
3456D2C41FFFCC70001EA55D /* OWSBackupViewController.m in Sources */,
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */,
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
@ -2940,6 +2946,7 @@
FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */,
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */,
347E0B7A2003CD7500BC2F76 /* OWSBackupExportViewController.m in Sources */,
34D1F0B11F867BFC0066283D /* OWSUnreadIndicatorCell.m in Sources */,
34B3F89C1E8DF3270035BE1A /* BlockListViewController.m in Sources */,
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */,

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "AppSettingsViewController.h"
@ -89,6 +89,10 @@
self.title = NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for settings activity");
[self updateTableContents];
dispatch_async(dispatch_get_main_queue(), ^{
[self showDebugUI];
});
}
- (void)viewWillAppear:(BOOL)animated

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSMessageCell.h"
@ -8,6 +8,8 @@
#import "ConversationViewItem.h"
#import "NSAttributedString+OWS.h"
#import "OWSAudioMessageView.h"
#import "OWSBackup.h"
#import "OWSBackupImportViewController.h"
#import "OWSExpirationTimerView.h"
#import "OWSGenericAttachmentView.h"
#import "Signal-Swift.h"
@ -1314,7 +1316,16 @@ NS_ASSUME_NONNULL_BEGIN
[self.delegate didTapVideoViewItem:self.viewItem attachmentStream:self.attachmentStream];
return;
case OWSMessageCellType_GenericAttachment:
#ifdef DEBUG
if ([self.attachmentStream.filePath.lastPathComponent hasSuffix:OWSBackup_FileExtension]) {
[self showBackupImportConfirmAlert:self.attachmentStream.filePath];
} else {
[AttachmentSharing showShareUIForAttachment:self.attachmentStream];
}
#else
[AttachmentSharing showShareUIForAttachment:self.attachmentStream];
#endif
break;
case OWSMessageCellType_DownloadingAttachment: {
OWSAssert(self.attachmentPointer);
@ -1326,6 +1337,40 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (void)showBackupImportConfirmAlert:(NSString *)backupZipPath
{
OWSAssert(backupZipPath.length > 0);
UIAlertController *controller =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"BACKUP_IMPORT_CONFIRM_ALERT_TITLE",
@"Title for alert confirming backup import.")
message:NSLocalizedString(@"BACKUP_IMPORT_CONFIRM_ALERT_MESSAGE",
@"Message for alert confirming backup import.")
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BACKUP_IMPORT_CONFIRM_ALERT_BUTTON",
@"Label for button confirming backup import.")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[self showBackupImportUI:backupZipPath];
}]];
[controller addAction:OWSAlerts.cancelAction];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:controller animated:YES completion:nil];
}
- (void)showBackupImportUI:(NSString *)backupZipPath
{
OWSAssert(backupZipPath.length > 0);
OWSBackupImportViewController *backupViewController = [OWSBackupImportViewController new];
// TODO: Add support for restoring password-protected backups.
[backupViewController importBackup:backupZipPath password:nil];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:backupViewController animated:YES completion:nil];
}
- (void)handleTextLongPressGesture:(UILongPressGestureRecognizer *)sender
{
OWSAssert(self.delegate);

@ -4,7 +4,7 @@
#import "DebugUIMisc.h"
#import "OWSBackup.h"
#import "OWSBackupViewController.h"
#import "OWSBackupExportViewController.h"
#import "OWSCountryMetadata.h"
#import "OWSTableViewController.h"
#import "RegistrationViewController.h"
@ -98,7 +98,8 @@ NS_ASSUME_NONNULL_BEGIN
[items addObject:[OWSTableItem
subPageItemWithText:@"Export Backup w/ Password"
actionBlock:^(UIViewController *viewController) {
OWSBackupViewController *backupViewController = [OWSBackupViewController new];
OWSBackupExportViewController *backupViewController =
[OWSBackupExportViewController new];
[backupViewController exportBackup:thread skipPassword:NO];
[viewController.navigationController pushViewController:backupViewController
animated:YES];
@ -106,7 +107,8 @@ NS_ASSUME_NONNULL_BEGIN
[items addObject:[OWSTableItem
subPageItemWithText:@"Export Backup w/o Password"
actionBlock:^(UIViewController *viewController) {
OWSBackupViewController *backupViewController = [OWSBackupViewController new];
OWSBackupExportViewController *backupViewController =
[OWSBackupExportViewController new];
[backupViewController exportBackup:thread skipPassword:YES];
[viewController.navigationController pushViewController:backupViewController
animated:YES];

@ -286,6 +286,10 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
}
[self updateBarButtonItems];
dispatch_async(dispatch_get_main_queue(), ^{
[self settingsButtonPressed:nil];
});
}
- (void)updateBarButtonItems

@ -8,7 +8,7 @@ NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface OWSBackupViewController : OWSViewController
@interface OWSBackupExportViewController : OWSViewController
// If currentThread is non-nil, we should offer to let users send the
// backup in that thread.

@ -2,7 +2,7 @@
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackupViewController.h"
#import "OWSBackupExportViewController.h"
#import "OWSBackup.h"
#import "OWSProgressView.h"
#import "Signal-Swift.h"
@ -17,7 +17,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface OWSBackupViewController () <OWSBackupDelegate>
@interface OWSBackupExportViewController () <OWSBackupDelegate>
@property (nonatomic) OWSBackup *backup;
@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@implementation OWSBackupViewController
@implementation OWSBackupExportViewController
- (void)loadView
{
@ -299,7 +299,6 @@ NS_ASSUME_NONNULL_BEGIN
[self updateUI];
}
- (void)backupProgressDidChange
{
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);

@ -0,0 +1,17 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalMessaging/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface OWSBackupImportViewController : OWSViewController
- (void)importBackup:(NSString *)backupZipPath password:(NSString *_Nullable)password;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,242 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackupImportViewController.h"
#import "OWSBackup.h"
#import "OWSProgressView.h"
#import "Signal-Swift.h"
#import "ThreadUtil.h"
#import <SignalMessaging/AttachmentSharing.h>
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalMessaging/UIColor+OWS.h>
#import <SignalMessaging/UIFont+OWS.h>
#import <SignalMessaging/UIView+OWS.h>
#import <SignalServiceKit/MIMETypeUtil.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSBackupImportViewController () <OWSBackupDelegate>
@property (nonatomic) OWSBackup *backup;
@property (nonatomic, nullable) OWSProgressView *progressView;
@end
#pragma mark -
@implementation OWSBackupImportViewController
- (void)loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationItem.title = NSLocalizedString(@"BACKUP_IMPORT_VIEW_TITLE", @"Title for the 'backup import' view.");
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(dismissWasPressed:)];
self.backup.delegate = self;
[self updateUI];
}
- (void)importBackup:(NSString *)backupZipPath password:(NSString *_Nullable)password
{
OWSAssertIsOnMainThread();
OWSAssert(backupZipPath.length > 0);
// We set ourselves as the delegate of the backup later,
// after we've loaded our view.
self.backup = [OWSBackup new];
[self.backup importBackup:backupZipPath password:password];
}
- (void)updateUI
{
for (UIView *subview in self.view.subviews) {
[subview removeFromSuperview];
}
self.progressView = nil;
switch (self.backup.backupState) {
case OWSBackupState_InProgress:
[self showInProgressUI];
break;
case OWSBackupState_Cancelled:
[self showCancelledUI];
break;
case OWSBackupState_Complete:
[self showCompleteUI];
break;
case OWSBackupState_Failed:
[self showFailedUI];
break;
}
}
- (void)showInProgressUI
{
self.progressView = [OWSProgressView new];
self.progressView.color = [UIColor ows_materialBlueColor];
self.progressView.progress = self.backup.backupProgress;
[self.progressView autoSetDimension:ALDimensionWidth toSize:300];
[self.progressView autoSetDimension:ALDimensionHeight toSize:20];
UILabel *label = [UILabel new];
label.text = NSLocalizedString(
@"BACKUP_IMPORT_IN_PROGRESS_MESSAGE", @"Message indicating that backup import is in progress.");
label.textColor = [UIColor blackColor];
label.font = [UIFont ows_regularFontWithSize:18.f];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
UIView *container = [UIView verticalStackWithSubviews:@[
label,
self.progressView,
]
spacing:10];
[self.view addSubview:container];
[container autoVCenterInSuperview];
[container autoPinWidthToSuperviewWithMargin:25.f];
}
- (void)showCancelledUI
{
// Show nothing.
}
- (void)showCompleteUI
{
NSMutableArray<UIView *> *subviews = [NSMutableArray new];
{
NSString *message = NSLocalizedString(
@"BACKUP_IMPORT_COMPLETE_MESSAGE", @"Message indicating that backup import is complete.");
UILabel *label = [UILabel new];
label.text = message;
label.textColor = [UIColor blackColor];
label.font = [UIFont ows_regularFontWithSize:18.f];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
[subviews addObject:label];
}
if (self.backup.backupPassword) {
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"BACKUP_IMPORT_PASSWORD_MESSAGE_FORMAT",
@"Format for message indicating that backup import "
@"is complete. Embeds: {{the backup password}}."),
self.backup.backupPassword];
UILabel *label = [UILabel new];
label.text = message;
label.textColor = [UIColor blackColor];
label.font = [UIFont ows_regularFontWithSize:14.f];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
[subviews addObject:label];
}
[subviews addObject:[UIView new]];
if (self.backup.backupPassword) {
[subviews
addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_IMPORT_COPY_PASSWORD_BUTTON",
@"Label for button that copies backup password to the pasteboard.")
selector:@selector(copyPassword)]];
}
[subviews addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_IMPORT_SHARE_BACKUP_BUTTON",
@"Label for button that opens share UI for backup.")
selector:@selector(shareBackup)]];
if (self.backup.currentThread) {
[subviews
addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_IMPORT_SEND_BACKUP_BUTTON",
@"Label for button that 'send backup' in the current conversation.")
selector:@selector(sendBackup)]];
}
// TODO: We should offer the option to save the backup to "Files", iCloud, Dropbox, etc.
UIView *container = [UIView verticalStackWithSubviews:subviews spacing:10];
[self.view addSubview:container];
[container autoVCenterInSuperview];
[container autoPinWidthToSuperviewWithMargin:25.f];
}
- (void)showFailedUI
{
NSMutableArray<UIView *> *subviews = [NSMutableArray new];
{
NSString *message
= NSLocalizedString(@"BACKUP_IMPORT_FAILED_MESSAGE", @"Message indicating that backup import failed.");
UILabel *label = [UILabel new];
label.text = message;
label.textColor = [UIColor blackColor];
label.font = [UIFont ows_regularFontWithSize:18.f];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
[subviews addObject:label];
}
// TODO: We should offer the option to save the backup to "Files", iCloud, Dropbox, etc.
UIView *container = [UIView verticalStackWithSubviews:subviews spacing:10];
[self.view addSubview:container];
[container autoVCenterInSuperview];
[container autoPinWidthToSuperviewWithMargin:25.f];
}
- (UIView *)makeButtonWithTitle:(NSString *)title selector:(SEL)selector
{
const CGFloat kButtonHeight = 40;
OWSFlatButton *button = [OWSFlatButton buttonWithTitle:title
font:[OWSFlatButton fontForHeight:kButtonHeight]
titleColor:[UIColor whiteColor]
backgroundColor:[UIColor ows_materialBlueColor]
target:self
selector:selector];
[button autoSetDimension:ALDimensionWidth toSize:140];
[button autoSetDimension:ALDimensionHeight toSize:kButtonHeight];
return button;
}
- (void)dismissWasPressed:(id)sender
{
[self.backup cancel];
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark - OWSBackupDelegate
- (void)backupStateDidChange
{
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
[self updateUI];
}
- (void)backupProgressDidChange
{
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
self.progressView.progress = self.backup.backupProgress;
}
@end
NS_ASSUME_NONNULL_END

@ -4,6 +4,8 @@
NS_ASSUME_NONNULL_BEGIN
extern NSString *const OWSBackup_FileExtension;
@protocol OWSBackupDelegate <NSObject>
- (void)backupStateDidChange;
@ -40,6 +42,8 @@ typedef NS_ENUM(NSUInteger, OWSBackupState) {
- (void)exportBackup:(nullable TSThread *)currentThread skipPassword:(BOOL)skipPassword;
- (void)importBackup:(NSString *)backupZipPath password:(NSString *_Nullable)password;
- (void)cancel;
@end

@ -8,11 +8,15 @@
#import "zlib.h"
#import <SSZipArchive/SSZipArchive.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalServiceKit/Cryptography.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/TSStorageManager.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const OWSBackup_FileExtension = @".signalbackup";
NSString *const OWSBackup_EncryptionKeyFilename = @"OWSBackup_EncryptionKeyFilename";
@interface OWSStorage (OWSBackup)
- (NSData *)databasePassword;
@ -64,7 +68,16 @@ NS_ASSUME_NONNULL_BEGIN
- (void)cancel
{
if (!self.isCancelledOrFailed) {
self.backupState = OWSBackupState_Cancelled;
}
}
- (void)complete
{
if (!self.isCancelledOrFailed) {
self.backupState = OWSBackupState_Complete;
}
}
- (BOOL)isCancelledOrFailed
@ -72,9 +85,12 @@ NS_ASSUME_NONNULL_BEGIN
return (self.backupState == OWSBackupState_Cancelled || self.backupState == OWSBackupState_Failed);
}
#pragma mark - Export Backup
- (void)exportBackup:(nullable TSThread *)currentThread skipPassword:(BOOL)skipPassword
{
OWSAssertIsOnMainThread();
OWSAssert(CurrentAppContext().isMainApp);
self.currentThread = currentThread;
self.backupState = OWSBackupState_InProgress;
@ -101,10 +117,7 @@ NS_ASSUME_NONNULL_BEGIN
[self exportToFilesAndZip];
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.isCancelledOrFailed) {
self.backupState = OWSBackupState_Complete;
}
[self.delegate backupStateDidChange];
[self complete];
});
});
}
@ -125,7 +138,7 @@ NS_ASSUME_NONNULL_BEGIN
@"Should not include characters like slash (/ or \\) or colon (:)."),
backupDateTime];
NSString *backupZipPath =
[rootDirPath stringByAppendingPathComponent:[backupName stringByAppendingString:@".signalbackup"]];
[rootDirPath stringByAppendingPathComponent:[backupName stringByAppendingString:OWSBackup_FileExtension]];
self.backupDirPath = backupDirPath;
self.backupZipPath = backupZipPath;
DDLogInfo(@"%@ rootDirPath: %@", self.logTag, rootDirPath);
@ -140,29 +153,35 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
OWSAES256Key *encryptionKey = [OWSAES256Key generateRandomKey];
NSData *databasePassword = [TSStorageManager sharedManager].databasePassword;
if (![self writeData:databasePassword fileName:@"databasePassword" backupDirPath:backupDirPath]) {
[self fail];
return;
// TODO: We don't want this to reside unencrypted on disk even temporarily.
// We need to encrypt this with a key that we hide in the keychain.
if (![self writeData:databasePassword
fileName:@"databasePassword"
backupDirPath:backupDirPath
encryptionKey:encryptionKey]) {
return [self fail];
}
if (self.isCancelledOrFailed) {
return;
}
if (![self writeUserDefaults:NSUserDefaults.standardUserDefaults
fileName:@"standardUserDefaults"
backupDirPath:backupDirPath]) {
[self fail];
return;
backupDirPath:backupDirPath
encryptionKey:encryptionKey]) {
return [self fail];
}
if (self.isCancelledOrFailed) {
return;
}
if (![self writeUserDefaults:NSUserDefaults.appUserDefaults
fileName:@"appUserDefaults"
backupDirPath:backupDirPath]) {
[self fail];
return;
backupDirPath:backupDirPath
encryptionKey:encryptionKey]) {
return [self fail];
}
if (self.isCancelledOrFailed) {
return;
@ -191,9 +210,8 @@ NS_ASSUME_NONNULL_BEGIN
if (self.isCancelledOrFailed) {
return;
}
if (![self zipDirectory:backupDirPath dstFilePath:backupZipPath]) {
[self fail];
return;
if (![self zipDirectory:backupDirPath dstFilePath:backupZipPath encryptionKey:encryptionKey]) {
return [self fail];
}
[OWSFileSystem protectFolderAtPath:backupZipPath];
@ -201,18 +219,26 @@ NS_ASSUME_NONNULL_BEGIN
[OWSFileSystem deleteFileIfExists:self.backupDirPath];
}
- (BOOL)writeData:(NSData *)data fileName:(NSString *)fileName backupDirPath:(NSString *)backupDirPath
// TODO: We
- (BOOL)writeData:(NSData *)data
fileName:(NSString *)fileName
backupDirPath:(NSString *)backupDirPath
encryptionKey:(OWSAES256Key *)encryptionKey
{
OWSAssert(data);
OWSAssert(fileName.length > 0);
OWSAssert(backupDirPath.length > 0);
OWSAssert(encryptionKey);
NSData *encryptedData = [Cryptography encryptAESGCMWithData:data key:encryptionKey];
OWSAssert(encryptedData);
NSString *filePath = [backupDirPath stringByAppendingPathComponent:fileName];
DDLogVerbose(@"%@ writeData: %@", self.logTag, filePath);
NSError *error;
BOOL success = [data writeToFile:filePath options:NSDataWritingAtomic error:&error];
BOOL success = [encryptedData writeToFile:filePath options:NSDataWritingAtomic error:&error];
if (!success || error) {
OWSFail(@"%@ failed to write user defaults: %@", self.logTag, error);
return NO;
@ -260,10 +286,12 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)writeUserDefaults:(NSUserDefaults *)userDefaults
fileName:(NSString *)fileName
backupDirPath:(NSString *)backupDirPath
encryptionKey:(OWSAES256Key *)encryptionKey
{
OWSAssert(userDefaults);
OWSAssert(fileName.length > 0);
OWSAssert(backupDirPath.length > 0);
OWSAssert(encryptionKey);
DDLogVerbose(@"%@ writeUserDefaults: %@", self.logTag, fileName);
@ -278,38 +306,91 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
return [self writeData:data fileName:fileName backupDirPath:backupDirPath];
return [self writeData:data fileName:fileName backupDirPath:backupDirPath encryptionKey:encryptionKey];
}
- (BOOL)zipDirectory:(NSString *)srcDirPath dstFilePath:(NSString *)dstFilePath
- (BOOL)zipDirectory:(NSString *)srcDirPath
dstFilePath:(NSString *)dstFilePath
encryptionKey:(OWSAES256Key *)encryptionKey
{
OWSAssert(srcDirPath.length > 0);
OWSAssert(dstFilePath.length > 0);
OWSAssert(encryptionKey);
srcDirPath = [srcDirPath stringByStandardizingPath];
OWSAssert(srcDirPath.length > 0);
BOOL success = [SSZipArchive createZipFileAtPath:dstFilePath
withContentsOfDirectory:srcDirPath
keepParentDirectory:NO
// BOOL success = [SSZipArchive createZipFileAtPath:dstFilePath
// withContentsOfDirectory:srcDirPath
// keepParentDirectory:NO
// compressionLevel:Z_DEFAULT_COMPRESSION
// password:self.backupPassword
// AES:self.backupPassword != nil
// progressHandler:^(NSUInteger entryNumber, NSUInteger total) {
// DDLogVerbose(@"%@ Zip progress: %zd / %zd = %f",
// self.logTag,
// entryNumber,
// total,
// entryNumber / (CGFloat)total);
//
// CGFloat progress = entryNumber / (CGFloat)total;
// self.backupProgress = progress;
// [self.delegate backupProgressDidChange];
// }];
// if (!success) {
// OWSFail(@"%@ failed to write zip backup", self.logTag);
// return NO;
// }
NSError *error;
NSArray<NSString *> *_Nullable srcFilePaths = [OWSFileSystem allFilesInDirectoryRecursive:srcDirPath error:&error];
if (!srcFilePaths || error) {
OWSFail(@"%@ failed to find files to zip: %@", self.logTag, error);
return NO;
}
SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:dstFilePath];
if (![zipArchive open]) {
OWSFail(@"%@ failed to open zip file.", self.logTag);
return NO;
}
for (NSString *srcFilePath in srcFilePaths) {
OWSAssert(srcFilePath.stringByStandardizingPath.length > 0);
OWSAssert([srcFilePath.stringByStandardizingPath hasPrefix:srcDirPath]);
NSString *relativePath = [srcFilePath.stringByStandardizingPath substringFromIndex:srcDirPath.length];
NSString *separator = @"/";
if ([relativePath hasPrefix:separator]) {
relativePath = [relativePath substringFromIndex:separator.length];
}
OWSAssert(relativePath.length > 0);
BOOL success = [zipArchive writeFileAtPath:srcFilePath
withFileName:relativePath
compressionLevel:Z_DEFAULT_COMPRESSION
password:self.backupPassword
AES:self.backupPassword != nil
progressHandler:^(NSUInteger entryNumber, NSUInteger total) {
DDLogVerbose(@"%@ Zip progress: %zd / %zd = %f",
self.logTag,
entryNumber,
total,
entryNumber / (CGFloat)total);
CGFloat progress = entryNumber / (CGFloat)total;
self.backupProgress = progress;
[self.delegate backupProgressDidChange];
}];
AES:self.backupPassword != nil];
if (!success) {
OWSFail(@"%@ failed to write file to zip file.", self.logTag);
return NO;
}
}
// Write the encryption key directly into the zip so that it never
// resides in plaintext on disk.
BOOL success = [zipArchive writeData:encryptionKey.keyData
filename:OWSBackup_EncryptionKeyFilename
compressionLevel:Z_DEFAULT_COMPRESSION
password:self.backupPassword
AES:self.backupPassword != nil];
if (!success) {
OWSFail(@"%@ failed to write zip backup", self.logTag);
OWSFail(@"%@ failed to write file to zip file.", self.logTag);
return NO;
}
if (![zipArchive close]) {
OWSFail(@"%@ failed to close zip file.", self.logTag);
return NO;
}
NSError *error;
NSNumber *fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:dstFilePath error:&error][NSFileSize];
if (error) {
OWSFail(@"%@ failed to get zip file size: %@", self.logTag, error);
@ -320,6 +401,129 @@ NS_ASSUME_NONNULL_BEGIN
return YES;
}
#pragma mark - Import Backup
- (void)importBackup:(NSString *)srcZipPath password:(NSString *_Nullable)password
{
OWSAssertIsOnMainThread();
OWSAssert(srcZipPath.length > 0);
OWSAssert(CurrentAppContext().isMainApp);
self.backupPassword = password;
self.backupState = OWSBackupState_InProgress;
if (password.length == 0) {
DDLogVerbose(@"%@ backup import without password", self.logTag);
} else {
DDLogVerbose(@"%@ backup import with password: %@", self.logTag, password);
}
[self startExport];
}
- (void)startExport:(NSString *)srcZipPath
{
OWSAssertIsOnMainThread();
OWSAssert(srcZipPath.length > 0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self unpackFiles:srcZipPath];
dispatch_async(dispatch_get_main_queue(), ^{
[self complete];
});
});
}
- (void)unpackFiles:(NSString *)srcZipPath
{
OWSAssert(srcZipPath.length > 0);
NSString *documentDirectoryPath = OWSFileSystem.appDocumentDirectoryPath;
// Hide the "import" directory from exports, etc. by prefixing with a period.
NSString *rootDirName = [@"." stringByAppendingString:[NSUUID UUID].UUIDString];
NSString *rootDirPath = [documentDirectoryPath stringByAppendingPathComponent:rootDirName];
NSString *backupDirPath = [rootDirPath stringByAppendingPathComponent:@"Contents"];
NSString *backupZipPath = [rootDirPath stringByAppendingPathComponent:srcZipPath.lastPathComponent];
self.backupDirPath = backupDirPath;
self.backupZipPath = backupZipPath;
DDLogInfo(@"%@ rootDirPath: %@", self.logTag, rootDirPath);
DDLogInfo(@"%@ backupDirPath: %@", self.logTag, backupDirPath);
DDLogInfo(@"%@ backupZipPath: %@", self.logTag, backupZipPath);
[OWSFileSystem ensureDirectoryExists:rootDirPath];
[OWSFileSystem protectFolderAtPath:rootDirPath];
[OWSFileSystem ensureDirectoryExists:backupDirPath];
NSError *error = nil;
BOOL success = [[NSFileManager defaultManager] copyItemAtPath:srcZipPath toPath:backupZipPath error:&error];
if (!success || error) {
OWSFail(@"%@ failed to copy backup zip: %@, %@", self.logTag, srcZipPath, error);
return [self fail];
}
if (self.isCancelledOrFailed) {
return;
}
//// NSData *databasePassword = [TSStorageManager sharedManager].databasePassword;
//
// if (![self writeData:databasePassword fileName:@"databasePassword" backupDirPath:backupDirPath]) {
// return [self fail];
// }
// if (self.isCancelledOrFailed) {
// return;
// }
// if (![self writeUserDefaults:NSUserDefaults.standardUserDefaults
// fileName:@"standardUserDefaults"
// backupDirPath:backupDirPath]) {
// return [self fail];
// }
// if (self.isCancelledOrFailed) {
// return;
// }
// if (![self writeUserDefaults:NSUserDefaults.appUserDefaults
// fileName:@"appUserDefaults"
// backupDirPath:backupDirPath]) {
// return [self fail];
// }
// if (self.isCancelledOrFailed) {
// return;
// }
// // Use a read/write transaction to acquire a file lock on the database files.
// //
// // TODO: If we use multiple database files, lock them too.
// [TSStorageManager.sharedManager.newDatabaseConnection
// readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
// if (![self copyDirectory:OWSFileSystem.appDocumentDirectoryPath
// dstDirName:@"appDocumentDirectoryPath"
// backupDirPath:backupDirPath]) {
// [self fail];
// return;
// }
// if (self.isCancelledOrFailed) {
// return;
// }
// if (![self copyDirectory:OWSFileSystem.appSharedDataDirectoryPath
// dstDirName:@"appSharedDataDirectoryPath"
// backupDirPath:backupDirPath]) {
// [self fail];
// return;
// }
// }];
// if (self.isCancelledOrFailed) {
// return;
// }
// if (![self zipDirectory:backupDirPath dstFilePath:backupZipPath]) {
// return [self fail];
// }
//
// [OWSFileSystem protectFolderAtPath:backupZipPath];
//
// [OWSFileSystem deleteFileIfExists:self.backupDirPath];
}
@end
NS_ASSUME_NONNULL_END

@ -145,16 +145,19 @@
/* action sheet button title to enable built in speaker during a call */
"AUDIO_ROUTE_BUILT_IN_SPEAKER" = "Speaker";
/* Message indicating that backup export without password is complete. */
/* Message indicating that backup export is complete. */
"BACKUP_EXPORT_COMPLETE_MESSAGE" = "Backup complete.";
/* Label for button that copies backup password to the pasteboard. */
"BACKUP_EXPORT_COPY_PASSWORD_BUTTON" = "Copy Password";
/* Message indicating that backup export failed. */
"BACKUP_EXPORT_FAILED_MESSAGE" = "BACKUP_EXPORT_FAILED_MESSAGE";
/* Message indicating that backup export is in progress. */
"BACKUP_EXPORT_IN_PROGRESS_MESSAGE" = "Exporting Backup...";
/* Format for message indicating that backup export with password is complete. Embeds: {{the backup password}}. */
/* Format for message indicating that backup export is complete. Embeds: {{the backup password}}. */
"BACKUP_EXPORT_PASSWORD_MESSAGE_FORMAT" = "Your backup password is: %@. Make sure to keep a copy of this password or you won't be able to restore from this backup.";
/* Label for button that 'send backup' in the current conversation. */
@ -175,6 +178,39 @@
/* Format for backup filenames. Embeds: {{the date and time of the backup}}. Should not include characters like slash (/ or \\) or colon (:). */
"BACKUP_FILENAME_FORMAT" = "Signal Backup %@";
/* Message indicating that backup import is complete. */
"BACKUP_IMPORT_COMPLETE_MESSAGE" = "BACKUP_IMPORT_COMPLETE_MESSAGE";
/* Label for button confirming backup import. */
"BACKUP_IMPORT_CONFIRM_ALERT_BUTTON" = "Restore";
/* Message for alert confirming backup import. */
"BACKUP_IMPORT_CONFIRM_ALERT_MESSAGE" = "To restore this backup you must restart the Signal app. Warning: you will lose all of your current Signal data.";
/* Title for alert confirming backup import. */
"BACKUP_IMPORT_CONFIRM_ALERT_TITLE" = "Restore Backup?";
/* Label for button that copies backup password to the pasteboard. */
"BACKUP_IMPORT_COPY_PASSWORD_BUTTON" = "BACKUP_IMPORT_COPY_PASSWORD_BUTTON";
/* Message indicating that backup import failed. */
"BACKUP_IMPORT_FAILED_MESSAGE" = "BACKUP_IMPORT_FAILED_MESSAGE";
/* Message indicating that backup import is in progress. */
"BACKUP_IMPORT_IN_PROGRESS_MESSAGE" = "BACKUP_IMPORT_IN_PROGRESS_MESSAGE";
/* Format for message indicating that backup import is complete. Embeds: {{the backup password}}. */
"BACKUP_IMPORT_PASSWORD_MESSAGE_FORMAT" = "BACKUP_IMPORT_PASSWORD_MESSAGE_FORMAT";
/* Label for button that 'send backup' in the current conversation. */
"BACKUP_IMPORT_SEND_BACKUP_BUTTON" = "BACKUP_IMPORT_SEND_BACKUP_BUTTON";
/* Label for button that opens share UI for backup. */
"BACKUP_IMPORT_SHARE_BACKUP_BUTTON" = "BACKUP_IMPORT_SHARE_BACKUP_BUTTON";
/* Title for the 'backup import' view. */
"BACKUP_IMPORT_VIEW_TITLE" = "BACKUP_IMPORT_VIEW_TITLE";
/* An explanation of the consequences of blocking another user. */
"BLOCK_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages.";

@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)deleteFileIfExists:(NSString *)filePath;
+ (NSArray<NSString *> *_Nullable)allFilesInDirectoryRecursive:(NSString *)dirPath error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END

@ -131,6 +131,37 @@ NS_ASSUME_NONNULL_BEGIN
}
}
+ (NSArray<NSString *> *_Nullable)allFilesInDirectoryRecursive:(NSString *)dirPath error:(NSError **)error
{
OWSAssert(dirPath.length > 0);
*error = nil;
NSArray<NSString *> *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:error];
if (*error) {
return nil;
}
NSMutableArray<NSString *> *filePaths = [NSMutableArray new];
for (NSString *filename in filenames) {
NSString *filePath = [dirPath stringByAppendingPathComponent:filename];
BOOL isDirectory;
[[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
if (isDirectory) {
[filePaths addObjectsFromArray:[self allFilesInDirectoryRecursive:filePath error:error]];
if (*error) {
return nil;
}
} else {
[filePaths addObject:filePath];
}
}
return filePaths;
}
@end
NS_ASSUME_NONNULL_END

Loading…
Cancel
Save