Rework "export backup" UI.
This commit is contained in:
parent
cb4b44b8f4
commit
980b3d25a7
|
@ -42,6 +42,7 @@
|
|||
344F248720069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248620069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift */; };
|
||||
344F248A20069F0600CFB4F4 /* ViewControllerUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 344F248820069F0600CFB4F4 /* ViewControllerUtils.h */; };
|
||||
344F248B20069F0600CFB4F4 /* ViewControllerUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 344F248920069F0600CFB4F4 /* ViewControllerUtils.m */; };
|
||||
3456D2C41FFFCC70001EA55D /* OWSBackupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3456D2C21FFFCC6F001EA55D /* OWSBackupViewController.m */; };
|
||||
3461284B1FD0B94000532771 /* SAELoadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */; };
|
||||
346129341FD1A88700532771 /* OWSSwiftUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346129331FD1A88700532771 /* OWSSwiftUtils.swift */; };
|
||||
346129391FD1B47300532771 /* OWSPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129371FD1B47200532771 /* OWSPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -523,6 +524,8 @@
|
|||
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>"; };
|
||||
|
@ -1323,6 +1326,8 @@
|
|||
34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */,
|
||||
34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */,
|
||||
34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */,
|
||||
3456D2C31FFFCC6F001EA55D /* OWSBackupViewController.h */,
|
||||
3456D2C21FFFCC6F001EA55D /* OWSBackupViewController.m */,
|
||||
34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsViewController.h */,
|
||||
34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */,
|
||||
34D5CCAB1EAE7136005515DB /* OWSConversationSettingsViewDelegate.h */,
|
||||
|
@ -2923,6 +2928,7 @@
|
|||
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 */,
|
||||
|
@ -3114,7 +3120,11 @@
|
|||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1";
|
||||
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
"SSK_BUILDING_FOR_TESTS=1",
|
||||
);
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#import "DebugUIMisc.h"
|
||||
#import "OWSBackup.h"
|
||||
#import "OWSBackupViewController.h"
|
||||
#import "OWSCountryMetadata.h"
|
||||
#import "OWSTableViewController.h"
|
||||
#import "RegistrationViewController.h"
|
||||
|
@ -94,10 +95,22 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[DebugUIMisc sendUnencryptedDatabase:thread];
|
||||
}]];
|
||||
}
|
||||
[items addObject:[OWSTableItem itemWithTitle:@"Export Backup"
|
||||
actionBlock:^{
|
||||
[OWSBackup exportBackup];
|
||||
}]];
|
||||
[items addObject:[OWSTableItem
|
||||
subPageItemWithText:@"Export Backup w/ Password"
|
||||
actionBlock:^(UIViewController *viewController) {
|
||||
OWSBackupViewController *backupViewController = [OWSBackupViewController new];
|
||||
[backupViewController exportBackup:thread skipPassword:NO];
|
||||
[viewController.navigationController pushViewController:backupViewController
|
||||
animated:YES];
|
||||
}]];
|
||||
[items addObject:[OWSTableItem
|
||||
subPageItemWithText:@"Export Backup w/o Password"
|
||||
actionBlock:^(UIViewController *viewController) {
|
||||
OWSBackupViewController *backupViewController = [OWSBackupViewController new];
|
||||
[backupViewController exportBackup:thread skipPassword:YES];
|
||||
[viewController.navigationController pushViewController:backupViewController
|
||||
animated:YES];
|
||||
}]];
|
||||
|
||||
return [OWSTableSection sectionWithTitle:self.name items:items];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TSThread;
|
||||
|
||||
@interface OWSBackupViewController : OWSViewController
|
||||
|
||||
// If currentThread is non-nil, we should offer to let users send the
|
||||
// backup in that thread.
|
||||
- (void)exportBackup:(TSThread *_Nullable)currentThread skipPassword:(BOOL)skipPassword;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,269 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSBackupViewController.h"
|
||||
#import "OWSBackup.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 OWSBackupViewController () <OWSBackupDelegate>
|
||||
|
||||
@property (nonatomic) OWSBackup *backup;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSBackupViewController
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
self.navigationItem.title = NSLocalizedString(@"BACKUP_EXPORT_VIEW_TITLE", @"Title for the 'backup export' view.");
|
||||
self.navigationItem.leftBarButtonItem =
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
|
||||
target:self
|
||||
action:@selector(dismissWasPressed:)];
|
||||
|
||||
self.backup.delegate = self;
|
||||
|
||||
[self updateUI];
|
||||
}
|
||||
|
||||
- (void)exportBackup:(TSThread *_Nullable)currentThread skipPassword:(BOOL)skipPassword
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
// We set ourselves as the delegate of the backup later,
|
||||
// after we've loaded our view.
|
||||
self.backup = [OWSBackup new];
|
||||
[self.backup exportBackup:currentThread skipPassword:skipPassword];
|
||||
}
|
||||
|
||||
- (void)updateUI
|
||||
{
|
||||
for (UIView *subview in self.view.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
switch (self.backup.backupState) {
|
||||
case OWSBackupState_InProgress:
|
||||
[self showInProgressUI];
|
||||
break;
|
||||
case OWSBackupState_Cancelled:
|
||||
[self showCancelledUI];
|
||||
break;
|
||||
case OWSBackupState_Complete:
|
||||
[self showCompleteUI];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showInProgressUI
|
||||
{
|
||||
|
||||
UIView *container = [UIView new];
|
||||
[self.view addSubview:container];
|
||||
[container autoCenterInSuperview];
|
||||
|
||||
UIActivityIndicatorView *activityIndicatorView =
|
||||
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
[container addSubview:activityIndicatorView];
|
||||
[activityIndicatorView autoPinEdgeToSuperviewEdge:ALEdgeTop];
|
||||
[activityIndicatorView autoHCenterInSuperview];
|
||||
[activityIndicatorView startAnimating];
|
||||
|
||||
UILabel *label = [UILabel new];
|
||||
label.text = NSLocalizedString(
|
||||
@"BACKUP_EXPORT_IN_PROGRESS_MESSAGE", @"Message indicating that backup export is in progress.");
|
||||
label.textColor = [UIColor blackColor];
|
||||
label.font = [UIFont ows_regularFontWithSize:18.f];
|
||||
[container addSubview:label];
|
||||
[label autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:activityIndicatorView withOffset:20.f];
|
||||
[label autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
[label autoPinWidthToSuperview];
|
||||
}
|
||||
|
||||
- (void)showCancelledUI
|
||||
{
|
||||
// Show nothing.
|
||||
}
|
||||
|
||||
- (void)showCompleteUI
|
||||
{
|
||||
NSMutableArray<UIView *> *subviews = [NSMutableArray new];
|
||||
|
||||
{
|
||||
NSString *message = NSLocalizedString(
|
||||
@"BACKUP_EXPORT_COMPLETE_MESSAGE", @"Message indicating that backup export without password 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_EXPORT_PASSWORD_MESSAGE_FORMAT",
|
||||
@"Format for message indicating that backup export with "
|
||||
@"password 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_EXPORT_COPY_PASSWORD_BUTTON",
|
||||
@"Label for button that copies backup password to the pasteboard.")
|
||||
selector:@selector(copyPassword)]];
|
||||
}
|
||||
|
||||
[subviews addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_EXPORT_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_EXPORT_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];
|
||||
}
|
||||
|
||||
- (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)copyPassword
|
||||
{
|
||||
OWSAssert(self.backup.backupPassword.length > 0);
|
||||
|
||||
[UIPasteboard.generalPasteboard setString:self.backup.backupPassword];
|
||||
}
|
||||
|
||||
- (void)shareBackup
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(self.backup.backupZipPath.length > 0);
|
||||
|
||||
[AttachmentSharing showShareUIForURL:[NSURL fileURLWithPath:self.backup.backupZipPath]];
|
||||
}
|
||||
|
||||
- (void)sendBackup
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(self.backup.backupZipPath.length > 0);
|
||||
OWSAssert(self.backup.currentThread);
|
||||
|
||||
[ModalActivityIndicatorViewController
|
||||
presentFromViewController:self
|
||||
canCancel:NO
|
||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||
NSString *fileName = [self.backup.backupZipPath lastPathComponent];
|
||||
|
||||
OWSMessageSender *messageSender = [Environment current].messageSender;
|
||||
NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:fileName.pathExtension];
|
||||
DataSource *_Nullable dataSource =
|
||||
[DataSourcePath dataSourceWithFilePath:self.backup.backupZipPath];
|
||||
[dataSource setSourceFilename:fileName];
|
||||
SignalAttachment *attachment =
|
||||
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType];
|
||||
if (!attachment || [attachment hasError]) {
|
||||
OWSFail(@"%@ attachment[%@]: %@",
|
||||
self.logTag,
|
||||
[attachment sourceFilename],
|
||||
[attachment errorName]);
|
||||
return;
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ThreadUtil
|
||||
sendMessageWithAttachment:attachment
|
||||
inThread:self.backup.currentThread
|
||||
messageSender:messageSender
|
||||
completion:^(NSError *_Nullable error) {
|
||||
|
||||
OWSAssertIsOnMainThread();
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
if (error) {
|
||||
DDLogError(@"%@ send backup failed: %@", self.logTag, error);
|
||||
[OWSAlerts
|
||||
showAlertWithTitle:NSLocalizedString(
|
||||
@"BACKUP_EXPORT_SEND_BACKUP_FAILED",
|
||||
@"Message indicating that sending "
|
||||
@"the backup failed.")];
|
||||
} else {
|
||||
[OWSAlerts
|
||||
showAlertWithTitle:
|
||||
NSLocalizedString(@"BACKUP_EXPORT_SEND_BACKUP_SUCCESS",
|
||||
@"Message indicating that sending the backup "
|
||||
@"succeeded.")];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dismissWasPressed:(id)sender
|
||||
{
|
||||
[self.backup cancel];
|
||||
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - OWSBackupDelegate
|
||||
|
||||
- (void)backupStateDidChange
|
||||
{
|
||||
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
[self updateUI];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -4,9 +4,38 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol OWSBackupDelegate <NSObject>
|
||||
|
||||
- (void)backupStateDidChange;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
typedef NS_ENUM(NSUInteger, OWSBackupState) {
|
||||
OWSBackupState_InProgress,
|
||||
OWSBackupState_Cancelled,
|
||||
OWSBackupState_Complete,
|
||||
};
|
||||
|
||||
@class TSThread;
|
||||
|
||||
@interface OWSBackup : NSObject
|
||||
|
||||
+ (void)exportBackup;
|
||||
@property (nonatomic, weak) id<OWSBackupDelegate> delegate;
|
||||
|
||||
@property (nonatomic) OWSBackupState backupState;
|
||||
|
||||
// If non-nil, backup is encrypted.
|
||||
@property (nonatomic, nullable, readonly) NSString *backupPassword;
|
||||
|
||||
@property (nonatomic, nullable, readonly) TSThread *currentThread;
|
||||
|
||||
@property (nonatomic, readonly) NSString *backupZipPath;
|
||||
|
||||
- (void)exportBackup:(nullable TSThread *)currentThread skipPassword:(BOOL)skipPassword;
|
||||
|
||||
- (void)cancel;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#import "Signal-Swift.h"
|
||||
#import "zlib.h"
|
||||
#import <SSZipArchive/SSZipArchive.h>
|
||||
#import <SignalMessaging/AttachmentSharing.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SignalServiceKit/OWSFileSystem.h>
|
||||
#import <SignalServiceKit/TSStorageManager.h>
|
||||
|
@ -24,10 +23,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface OWSBackup ()
|
||||
|
||||
@property (nonatomic) NSString *password;
|
||||
@property (nonatomic, nullable) TSThread *currentThread;
|
||||
|
||||
@property (nonatomic, nullable) NSString *backupPassword;
|
||||
|
||||
@property (nonatomic) NSString *backupDirPath;
|
||||
@property (nonatomic) NSString *backupZipPath;
|
||||
@property (atomic) BOOL cancelled;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -43,107 +44,58 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[OWSFileSystem deleteFileIfExists:self.backupDirPath];
|
||||
}
|
||||
|
||||
+ (void)exportBackup
|
||||
- (void)setBackupState:(OWSBackupState)backupState
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[OWSBackup new] exportBackup];
|
||||
});
|
||||
_backupState = backupState;
|
||||
|
||||
[self.delegate backupStateDidChange];
|
||||
}
|
||||
|
||||
- (void)exportBackup
|
||||
- (void)cancel
|
||||
{
|
||||
self.backupState = OWSBackupState_Cancelled;
|
||||
}
|
||||
|
||||
- (BOOL)isCancelled
|
||||
{
|
||||
return self.backupState == OWSBackupState_Cancelled;
|
||||
}
|
||||
|
||||
- (void)exportBackup:(nullable TSThread *)currentThread skipPassword:(BOOL)skipPassword
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
// TODO: Should the user pick a password?
|
||||
// If not, should probably generate something more user-friendly,
|
||||
// e.g. case-insensitive set of hexadecimal?
|
||||
NSString *password = [NSUUID UUID].UUIDString;
|
||||
self.password = password;
|
||||
DDLogVerbose(@"%@ backup export complete; password: %@", self.logTag, password);
|
||||
self.currentThread = currentThread;
|
||||
self.backupState = OWSBackupState_InProgress;
|
||||
|
||||
[self showExportProgressUI:^(UIAlertController *exportProgressAlert) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self exportToFilesAndZip];
|
||||
if (skipPassword) {
|
||||
DDLogVerbose(@"%@ backup export without password", self.logTag);
|
||||
} else {
|
||||
// TODO: Should the user pick a password?
|
||||
// If not, should probably generate something more user-friendly,
|
||||
// e.g. case-insensitive set of hexadecimal?
|
||||
NSString *backupPassword = [NSUUID UUID].UUIDString;
|
||||
self.backupPassword = backupPassword;
|
||||
DDLogVerbose(@"%@ backup export with password: %@", self.logTag, backupPassword);
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[exportProgressAlert dismissViewControllerAnimated:YES
|
||||
completion:^{
|
||||
[self showExportCompleteUI:^{
|
||||
[self showShareUI];
|
||||
}];
|
||||
}];
|
||||
});
|
||||
[self startExport];
|
||||
}
|
||||
|
||||
- (void)startExport
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self exportToFilesAndZip];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!self.isCancelled) {
|
||||
self.backupState = OWSBackupState_Complete;
|
||||
}
|
||||
[self.delegate backupStateDidChange];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showExportProgressUI:(void (^_Nonnull)(UIAlertController *))completion
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(completion);
|
||||
|
||||
NSString *title = NSLocalizedString(
|
||||
@"BACKUP_EXPORT_IN_PROGRESS_ALERT_TITLE", @"Title for the 'backup export in progress' alert.");
|
||||
UIAlertController *alert =
|
||||
[UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[CommonStrings cancelButton]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
self.cancelled = YES;
|
||||
}];
|
||||
[alert addAction:cancelAction];
|
||||
|
||||
__weak UIAlertController *weakAlert = alert;
|
||||
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
|
||||
[fromViewController presentViewController:alert
|
||||
animated:YES
|
||||
completion:^(void) {
|
||||
UIAlertController *strongAlert = weakAlert;
|
||||
if (!strongAlert) {
|
||||
OWSFail(@"%@ missing alert.", self.logTag);
|
||||
return;
|
||||
}
|
||||
completion(strongAlert);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showExportCompleteUI:(void (^_Nonnull)(void))completion
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(completion);
|
||||
OWSAssert(self.password.length > 0);
|
||||
|
||||
// TODO: We probably want to offer an option that lets users copy
|
||||
// the password to the pasteboard.
|
||||
NSString *title
|
||||
= NSLocalizedString(@"BACKUP_EXPORT_COMPLETE_ALERT_TITLE", @"Title for the 'backup export complete' alert.");
|
||||
NSString *message = [NSString
|
||||
stringWithFormat:
|
||||
NSLocalizedString(@"BACKUP_EXPORT_COMPLETE_ALERT_MESSAGE_FORMAT",
|
||||
@"Format for message for the 'backup export complete' alert. Embeds: {{the backup password}}."),
|
||||
self.password];
|
||||
UIAlertController *alert =
|
||||
[UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", comment
|
||||
: nil)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
completion();
|
||||
}];
|
||||
[alert addAction:okAction];
|
||||
|
||||
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
|
||||
[fromViewController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)showShareUI
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(self.backupZipPath.length > 0);
|
||||
|
||||
[AttachmentSharing showShareUIForURL:[NSURL fileURLWithPath:self.backupZipPath]];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)exportToFilesAndZip
|
||||
|
@ -173,21 +125,34 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[OWSFileSystem protectFolderAtPath:rootDirPath];
|
||||
[OWSFileSystem ensureDirectoryExists:backupDirPath];
|
||||
|
||||
if (self.isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *databasePassword = [TSStorageManager sharedManager].databasePassword;
|
||||
|
||||
if (![self writeData:databasePassword fileName:@"databasePassword" backupDirPath:backupDirPath]) {
|
||||
return;
|
||||
}
|
||||
if (self.isCancelled) {
|
||||
return;
|
||||
}
|
||||
if (![self writeUserDefaults:NSUserDefaults.standardUserDefaults
|
||||
fileName:@"standardUserDefaults"
|
||||
backupDirPath:backupDirPath]) {
|
||||
return;
|
||||
}
|
||||
if (self.isCancelled) {
|
||||
return;
|
||||
}
|
||||
if (![self writeUserDefaults:NSUserDefaults.appUserDefaults
|
||||
fileName:@"appUserDefaults"
|
||||
backupDirPath:backupDirPath]) {
|
||||
return;
|
||||
}
|
||||
if (self.isCancelled) {
|
||||
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.
|
||||
|
@ -198,12 +163,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
backupDirPath:backupDirPath]) {
|
||||
return;
|
||||
}
|
||||
if (self.isCancelled) {
|
||||
return;
|
||||
}
|
||||
if (![self copyDirectory:OWSFileSystem.appSharedDataDirectoryPath
|
||||
dstDirName:@"appSharedDataDirectoryPath"
|
||||
backupDirPath:backupDirPath]) {
|
||||
return;
|
||||
}
|
||||
}];
|
||||
if (self.isCancelled) {
|
||||
return;
|
||||
}
|
||||
if (![self zipDirectory:backupDirPath dstFilePath:backupZipPath]) {
|
||||
return;
|
||||
}
|
||||
|
@ -297,14 +268,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
OWSAssert(srcDirPath.length > 0);
|
||||
OWSAssert(dstFilePath.length > 0);
|
||||
OWSAssert(self.password.length > 0);
|
||||
|
||||
BOOL success = [SSZipArchive createZipFileAtPath:dstFilePath
|
||||
withContentsOfDirectory:srcDirPath
|
||||
keepParentDirectory:NO
|
||||
compressionLevel:Z_DEFAULT_COMPRESSION
|
||||
password:self.password
|
||||
AES:YES
|
||||
password:self.backupPassword
|
||||
AES:self.backupPassword != nil
|
||||
progressHandler:^(NSUInteger entryNumber, NSUInteger total) {
|
||||
DDLogVerbose(@"%@ Zip progress: %zd / %zd = %f",
|
||||
self.logTag,
|
||||
|
|
|
@ -145,16 +145,34 @@
|
|||
/* action sheet button title to enable built in speaker during a call */
|
||||
"AUDIO_ROUTE_BUILT_IN_SPEAKER" = "Speaker";
|
||||
|
||||
/* Format for message for the 'backup export complete' alert. Embeds: {{the backup password}} */
|
||||
"BACKUP_EXPORT_COMPLETE_ALERT_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.";
|
||||
/* Message indicating that backup export without password is complete. */
|
||||
"BACKUP_EXPORT_COMPLETE_MESSAGE" = "Backup complete.";
|
||||
|
||||
/* Title for the 'backup export complete' alert. */
|
||||
"BACKUP_EXPORT_COMPLETE_ALERT_TITLE" = "Backup Complete";
|
||||
/* Label for button that copies backup password to the pasteboard. */
|
||||
"BACKUP_EXPORT_COPY_PASSWORD_BUTTON" = "Copy Password";
|
||||
|
||||
/* Title for the 'backup export in progress' alert. */
|
||||
"BACKUP_EXPORT_IN_PROGRESS_ALERT_TITLE" = "Exporting Backup...";
|
||||
/* Message indicating that backup export is in progress. */
|
||||
"BACKUP_EXPORT_IN_PROGRESS_MESSAGE" = "Exporting Backup...";
|
||||
|
||||
/* Format for backup filenames. Embeds: {{the date and time of the backup}} */
|
||||
/* Format for message indicating that backup export with password 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. */
|
||||
"BACKUP_EXPORT_SEND_BACKUP_BUTTON" = "Send Backup as Message";
|
||||
|
||||
/* Message indicating that sending the backup failed. */
|
||||
"BACKUP_EXPORT_SEND_BACKUP_FAILED" = "Sending Backup Failed.";
|
||||
|
||||
/* Message indicating that sending the backup succeeded. */
|
||||
"BACKUP_EXPORT_SEND_BACKUP_SUCCESS" = "Backup Sent.";
|
||||
|
||||
/* Label for button that opens share UI for backup. */
|
||||
"BACKUP_EXPORT_SHARE_BACKUP_BUTTON" = "Share Backup";
|
||||
|
||||
/* Title for the 'backup export' view. */
|
||||
"BACKUP_EXPORT_VIEW_TITLE" = "Backup";
|
||||
|
||||
/* 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 %@";
|
||||
|
||||
/* An explanation of the consequences of blocking another user. */
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSViewController.h"
|
||||
|
@ -50,10 +50,13 @@ typedef NS_ENUM(NSInteger, OWSTableItemType) {
|
|||
};
|
||||
|
||||
typedef void (^OWSTableActionBlock)(void);
|
||||
typedef void (^OWSTableSubPageBlock)(UIViewController *viewController);
|
||||
typedef UITableViewCell *_Nonnull (^OWSTableCustomCellBlock)(void);
|
||||
|
||||
@interface OWSTableItem : NSObject
|
||||
|
||||
@property (nonatomic, weak) UIViewController *tableViewController;
|
||||
|
||||
+ (OWSTableItem *)itemWithTitle:(NSString *)title actionBlock:(nullable OWSTableActionBlock)actionBlock;
|
||||
|
||||
+ (OWSTableItem *)itemWithCustomCell:(UITableViewCell *)customCell
|
||||
|
@ -73,6 +76,12 @@ typedef UITableViewCell *_Nonnull (^OWSTableCustomCellBlock)(void);
|
|||
customRowHeight:(CGFloat)customRowHeight
|
||||
actionBlock:(nullable OWSTableActionBlock)actionBlock;
|
||||
|
||||
+ (OWSTableItem *)subPageItemWithText:(NSString *)text actionBlock:(nullable OWSTableSubPageBlock)actionBlock;
|
||||
|
||||
+ (OWSTableItem *)subPageItemWithText:(NSString *)text
|
||||
customRowHeight:(CGFloat)customRowHeight
|
||||
actionBlock:(nullable OWSTableSubPageBlock)actionBlock;
|
||||
|
||||
+ (OWSTableItem *)actionItemWithText:(NSString *)text actionBlock:(nullable OWSTableActionBlock)actionBlock;
|
||||
|
||||
+ (OWSTableItem *)softCenterLabelItemWithText:(NSString *)text;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSTableViewController.h"
|
||||
|
@ -153,6 +153,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
|
|||
|
||||
OWSTableItem *item = [OWSTableItem new];
|
||||
item.itemType = OWSTableItemTypeAction;
|
||||
__weak OWSTableItem *weakItem = item;
|
||||
item.actionBlock = actionBlock;
|
||||
item.customCellBlock = ^{
|
||||
UITableViewCell *cell = [UITableViewCell new];
|
||||
|
@ -176,6 +177,45 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
|
|||
return item;
|
||||
}
|
||||
|
||||
+ (OWSTableItem *)subPageItemWithText:(NSString *)text actionBlock:(nullable OWSTableSubPageBlock)actionBlock
|
||||
{
|
||||
OWSAssert(text.length > 0);
|
||||
OWSAssert(actionBlock);
|
||||
|
||||
OWSTableItem *item = [OWSTableItem new];
|
||||
item.itemType = OWSTableItemTypeAction;
|
||||
__weak OWSTableItem *weakItem = item;
|
||||
item.actionBlock = ^{
|
||||
OWSTableItem *strongItem = weakItem;
|
||||
OWSAssert(strongItem);
|
||||
OWSAssert(strongItem.tableViewController);
|
||||
|
||||
if (actionBlock) {
|
||||
actionBlock(strongItem.tableViewController);
|
||||
}
|
||||
};
|
||||
item.customCellBlock = ^{
|
||||
UITableViewCell *cell = [UITableViewCell new];
|
||||
cell.textLabel.text = text;
|
||||
cell.textLabel.font = [UIFont ows_regularFontWithSize:18.f];
|
||||
cell.textLabel.textColor = [UIColor blackColor];
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
return cell;
|
||||
};
|
||||
return item;
|
||||
}
|
||||
|
||||
+ (OWSTableItem *)subPageItemWithText:(NSString *)text
|
||||
customRowHeight:(CGFloat)customRowHeight
|
||||
actionBlock:(nullable OWSTableSubPageBlock)actionBlock
|
||||
{
|
||||
OWSAssert(customRowHeight > 0);
|
||||
|
||||
OWSTableItem *item = [self subPageItemWithText:text actionBlock:actionBlock];
|
||||
item.customRowHeight = @(customRowHeight);
|
||||
return item;
|
||||
}
|
||||
|
||||
+ (OWSTableItem *)actionItemWithText:(NSString *)text actionBlock:(nullable OWSTableActionBlock)actionBlock
|
||||
{
|
||||
OWSAssert(text.length > 0);
|
||||
|
@ -481,6 +521,8 @@ NSString *const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
|
|||
{
|
||||
OWSTableItem *item = [self itemForIndexPath:indexPath];
|
||||
|
||||
item.tableViewController = self;
|
||||
|
||||
UITableViewCell *customCell = [item customCell];
|
||||
if (customCell) {
|
||||
return customCell;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <PureLayout/PureLayout.h>
|
||||
|
@ -95,9 +95,14 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value);
|
|||
- (NSTextAlignment)textAlignmentUnnatural;
|
||||
// Leading and trailing anchors honor layout margins.
|
||||
// When using a UIView as a "div" to structure layout, we don't want it to have margins.
|
||||
+ (UIView *)containerView;
|
||||
- (void)setHLayoutMargins:(CGFloat)value;
|
||||
|
||||
#pragma mark - Containers
|
||||
|
||||
+ (UIView *)containerView;
|
||||
|
||||
+ (UIView *)verticalStackWithSubviews:(NSArray<UIView *> *)subviews spacing:(int)spacing;
|
||||
|
||||
#pragma mark - Debugging
|
||||
|
||||
- (void)addBorderWithColor:(UIColor *)color;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSMath.h"
|
||||
|
@ -388,6 +388,16 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
|
|||
return (self.isRTL ? NSTextAlignmentLeft : NSTextAlignmentRight);
|
||||
}
|
||||
|
||||
- (void)setHLayoutMargins:(CGFloat)value
|
||||
{
|
||||
UIEdgeInsets layoutMargins = self.layoutMargins;
|
||||
layoutMargins.left = value;
|
||||
layoutMargins.right = value;
|
||||
self.layoutMargins = layoutMargins;
|
||||
}
|
||||
|
||||
#pragma mark - Containers
|
||||
|
||||
+ (UIView *)containerView
|
||||
{
|
||||
UIView *view = [UIView new];
|
||||
|
@ -397,12 +407,22 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
|
|||
return view;
|
||||
}
|
||||
|
||||
- (void)setHLayoutMargins:(CGFloat)value
|
||||
+ (UIView *)verticalStackWithSubviews:(NSArray<UIView *> *)subviews spacing:(int)spacing
|
||||
{
|
||||
UIEdgeInsets layoutMargins = self.layoutMargins;
|
||||
layoutMargins.left = value;
|
||||
layoutMargins.right = value;
|
||||
self.layoutMargins = layoutMargins;
|
||||
UIView *container = [UIView containerView];
|
||||
UIView *_Nullable lastSubview = nil;
|
||||
for (UIView *subview in subviews) {
|
||||
[container addSubview:subview];
|
||||
[subview autoPinWidthToSuperview];
|
||||
if (lastSubview) {
|
||||
[subview autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview withOffset:spacing];
|
||||
} else {
|
||||
[subview autoPinEdgeToSuperviewEdge:ALEdgeTop];
|
||||
}
|
||||
lastSubview = subview;
|
||||
}
|
||||
[lastSubview autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
return container;
|
||||
}
|
||||
|
||||
#pragma mark - Debugging
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MIMETypeUtil.h"
|
||||
|
@ -461,7 +461,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
|
|||
|
||||
+ (NSSet<NSString *> *)supportedVideoUTITypes
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
static NSSet<NSString *> *result = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
@ -472,7 +471,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
|
|||
|
||||
+ (NSSet<NSString *> *)supportedAudioUTITypes
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
static NSSet<NSString *> *result = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
@ -483,7 +481,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
|
|||
|
||||
+ (NSSet<NSString *> *)supportedImageUTITypes
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
static NSSet<NSString *> *result = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
@ -494,7 +491,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
|
|||
|
||||
+ (NSSet<NSString *> *)supportedAnimatedImageUTITypes
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
static NSSet<NSString *> *result = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
|
Loading…
Reference in New Issue