diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 67a731875..d198f2852 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -34,6 +34,10 @@ 340FC8BB204DAC8D007AEB0F /* OWSAddToContactViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8A1204DAC8D007AEB0F /* OWSAddToContactViewController.m */; }; 340FC8BC204DAC8D007AEB0F /* FingerprintViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8A2204DAC8D007AEB0F /* FingerprintViewController.m */; }; 340FC8BD204DAC8D007AEB0F /* ShowGroupMembersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8A6204DAC8D007AEB0F /* ShowGroupMembersViewController.m */; }; + 340FC8C0204DB7D2007AEB0F /* OWSBackupExport.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8BF204DB7D2007AEB0F /* OWSBackupExport.m */; }; + 340FC8C2204DDF67007AEB0F /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 340FC8C1204DDF66007AEB0F /* CloudKit.framework */; }; + 340FC8C5204DE223007AEB0F /* DebugUIBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8C4204DE223007AEB0F /* DebugUIBackup.m */; }; + 340FC8C7204DE64D007AEB0F /* OWSBackupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8C6204DE64D007AEB0F /* OWSBackupAPI.swift */; }; 341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */ = {isa = PBXBuildFile; fileRef = 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */; }; 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430FE171F7751D4000EC51B /* GiphyAPI.swift */; }; 34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; }; @@ -567,6 +571,12 @@ 340FC8A4204DAC8D007AEB0F /* AddToGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToGroupViewController.h; sourceTree = ""; }; 340FC8A5204DAC8D007AEB0F /* FingerprintViewScanController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FingerprintViewScanController.h; sourceTree = ""; }; 340FC8A6204DAC8D007AEB0F /* ShowGroupMembersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShowGroupMembersViewController.m; sourceTree = ""; }; + 340FC8BE204DB7D1007AEB0F /* OWSBackupExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupExport.h; sourceTree = ""; }; + 340FC8BF204DB7D2007AEB0F /* OWSBackupExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupExport.m; sourceTree = ""; }; + 340FC8C1204DDF66007AEB0F /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; + 340FC8C3204DE223007AEB0F /* DebugUIBackup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIBackup.h; sourceTree = ""; }; + 340FC8C4204DE223007AEB0F /* DebugUIBackup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIBackup.m; sourceTree = ""; }; + 340FC8C6204DE64D007AEB0F /* OWSBackupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupAPI.swift; sourceTree = ""; }; 341458471FBE11C4005ABCF9 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = translations/fa.lproj/Localizable.strings; sourceTree = ""; }; 341F2C0D1F2B8AE700D07D6B /* DebugUIMisc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMisc.h; sourceTree = ""; }; 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIMisc.m; sourceTree = ""; }; @@ -1118,6 +1128,7 @@ 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */, B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */, 453518991FC63DBF00210559 /* SignalMessaging.framework in Frameworks */, + 340FC8C2204DDF67007AEB0F /* CloudKit.framework in Frameworks */, 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */, 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */, A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */, @@ -1624,11 +1635,14 @@ 34D8C0221ED3673300188D7C /* DebugUI */ = { isa = PBXGroup; children = ( + 340FC8C3204DE223007AEB0F /* DebugUIBackup.h */, + 340FC8C4204DE223007AEB0F /* DebugUIBackup.m */, 45638BDB1F3DD0D400128435 /* DebugUICalling.swift */, 34D8C0291ED3685800188D7C /* DebugUIContacts.h */, 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */, 34E3EF0B1EFC235B007F6822 /* DebugUIDiskUsage.h */, 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */, + 45B27B852037FFB400A539DF /* DebugUIFileBrowser.swift */, 34D8C0231ED3673300188D7C /* DebugUIMessages.h */, 34D8C0241ED3673300188D7C /* DebugUIMessages.m */, 341F2C0D1F2B8AE700D07D6B /* DebugUIMisc.h */, @@ -1645,7 +1659,6 @@ 343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */, 34D8C0251ED3673300188D7C /* DebugUITableViewController.h */, 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */, - 45B27B852037FFB400A539DF /* DebugUIFileBrowser.swift */, ); path = DebugUI; sourceTree = ""; @@ -1920,6 +1933,9 @@ 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */, 34A9105E1FFEB113000C4745 /* OWSBackup.h */, 34A9105F1FFEB114000C4745 /* OWSBackup.m */, + 340FC8C6204DE64D007AEB0F /* OWSBackupAPI.swift */, + 340FC8BE204DB7D1007AEB0F /* OWSBackupExport.h */, + 340FC8BF204DB7D2007AEB0F /* OWSBackupExport.m */, 4579431C1E7C8CE9008ED0C0 /* Pastelog.h */, 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */, 450DF2041E0D74AC003D14BE /* Platform.swift */, @@ -2119,6 +2135,7 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( + 340FC8C1204DDF66007AEB0F /* CloudKit.framework */, 455A16DB1F1FEA0000F86704 /* Metal.framework */, 455A16DC1F1FEA0000F86704 /* MetalKit.framework */, 45847E861E4283C30080EAB3 /* Intents.framework */, @@ -3066,6 +3083,7 @@ 450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */, 34D1F0821F8678AA0066283D /* ConversationHeaderView.m in Sources */, 34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */, + 340FC8C7204DE64D007AEB0F /* OWSBackupAPI.swift in Sources */, 343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */, 34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */, 4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */, @@ -3147,6 +3165,7 @@ 45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */, 340FC8BB204DAC8D007AEB0F /* OWSAddToContactViewController.m in Sources */, 4523149E1F7E916B003A428C /* SlideOffAnimatedTransition.swift in Sources */, + 340FC8C0204DB7D2007AEB0F /* OWSBackupExport.m in Sources */, 340FC8A7204DAC8D007AEB0F /* RegistrationViewController.m in Sources */, 452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */, 45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */, @@ -3175,6 +3194,7 @@ 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */, 34B3F87E1E8DF1700035BE1A /* InboxTableViewCell.m in Sources */, 340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */, + 340FC8C5204DE223007AEB0F /* DebugUIBackup.m in Sources */, 340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */, 4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */, 34B3F8941E8DF1710035BE1A /* HomeViewController.m in Sources */, diff --git a/Signal/Signal.entitlements b/Signal/Signal.entitlements index 79da5ce67..89b44180f 100644 --- a/Signal/Signal.entitlements +++ b/Signal/Signal.entitlements @@ -12,12 +12,10 @@ com.apple.developer.icloud-services - CloudDocuments - - com.apple.developer.ubiquity-container-identifiers - - iCloud.$(CFBundleIdentifier) + CloudKit + com.apple.developer.ubiquity-kvstore-identifier + $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.application-groups group.org.whispersystems.signal.group diff --git a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m index e60226c28..15dfcd95b 100644 --- a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m @@ -92,7 +92,8 @@ [self updateTableContents]; dispatch_async(dispatch_get_main_queue(), ^{ - [self showBackup]; + // [self showBackup]; + [self showDebugUI]; }); } diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIBackup.h b/Signal/src/ViewControllers/DebugUI/DebugUIBackup.h new file mode 100644 index 000000000..8f437ef41 --- /dev/null +++ b/Signal/src/ViewControllers/DebugUI/DebugUIBackup.h @@ -0,0 +1,13 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "DebugUIPage.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DebugUIBackup : DebugUIPage + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIBackup.m b/Signal/src/ViewControllers/DebugUI/DebugUIBackup.m new file mode 100644 index 000000000..6ecb9155e --- /dev/null +++ b/Signal/src/ViewControllers/DebugUI/DebugUIBackup.m @@ -0,0 +1,76 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "DebugUIBackup.h" +#import "OWSBackup.h" + +//#import "OWSCountryMetadata.h" +#import "OWSTableViewController.h" + +//#import "RegistrationViewController.h" +#import "Signal-Swift.h" + +//#import "ThreadUtil.h" +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +#import + +@import CloudKit; + +NS_ASSUME_NONNULL_BEGIN + +@implementation DebugUIBackup + +#pragma mark - Factory Methods + +- (NSString *)name +{ + return @"Backup"; +} + +- (nullable OWSTableSection *)sectionForThread:(nullable TSThread *)thread +{ + NSMutableArray *items = [NSMutableArray new]; + [items addObject:[OWSTableItem itemWithTitle:@"Backup test file @ CloudKit" + actionBlock:^{ + [DebugUIBackup backupTestFile]; + }]]; + + return [OWSTableSection sectionWithTitle:self.name items:items]; +} + ++ (void)backupTestFile +{ + DDLogInfo(@"%@ backupTestFile.", self.logTag); + + NSData *_Nullable data = [Randomness generateRandomBytes:32]; + OWSAssert(data); + NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:@"pdf"]; + BOOL success = [data writeToFile:filePath atomically:YES]; + OWSAssert(success); + + [OWSBackupAPI checkCloudKitAccessWithCompletion:^(BOOL hasAccess) { + if (hasAccess) { + [OWSBackupAPI saveTestFileToCloudWithFileUrl:[NSURL fileURLWithPath:filePath] + completion:^(NSError *_Nullable error){ + // Do nothing, the API method will log for us. + }]; + } + }]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index e972b3f14..188c667b1 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -693,12 +693,7 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(size % 4 == 0); - NSMutableData *data = [NSMutableData dataWithCapacity:size]; - for (size_t i = 0; i < size / 4; ++i) { - u_int32_t randomBits = arc4random(); - [data appendBytes:(void *)&randomBits length:4]; - } - return data; + return [Randomness generateRandomBytes:size]; } + (void)sendRandomAttachment:(TSThread *)thread uti:(NSString *)uti diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.h b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.h index ba27774b2..c890d2657 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.h +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.h @@ -1,13 +1,11 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "DebugUIPage.h" NS_ASSUME_NONNULL_BEGIN -@class TSThread; - @interface DebugUIMisc : DebugUIPage @end diff --git a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m index 620ecb481..3d5b1bf81 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m @@ -3,6 +3,7 @@ // #import "DebugUITableViewController.h" +#import "DebugUIBackup.h" #import "DebugUIContacts.h" #import "DebugUIDiskUsage.h" #import "DebugUIMessages.h" @@ -110,6 +111,8 @@ NS_ASSUME_NONNULL_BEGIN }]; [subsectionItems addObject:documentsFileBrowserItem]; [subsectionItems addObject:[self itemForSubsection:[DebugUIMisc new] viewController:viewController thread:thread]]; + [subsectionItems + addObject:[self itemForSubsection:[DebugUIBackup new] viewController:viewController thread:thread]]; [contents addSection:[OWSTableSection sectionWithTitle:@"Sections" items:subsectionItems]]; @@ -135,6 +138,7 @@ NS_ASSUME_NONNULL_BEGIN [subsectionItems addObject:[self itemForSubsection:[DebugUISyncMessages new] viewController:viewController thread:nil]]; [subsectionItems addObject:[self itemForSubsection:[DebugUIMisc new] viewController:viewController thread:nil]]; + [subsectionItems addObject:[self itemForSubsection:[DebugUIBackup new] viewController:viewController thread:nil]]; [contents addSection:[OWSTableSection sectionWithTitle:@"Sections" items:subsectionItems]]; viewController.contents = contents; diff --git a/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift b/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift index 3f9f13953..930b8564c 100644 --- a/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift +++ b/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift @@ -188,7 +188,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect bottomBanner.autoPinEdge(toSuperviewEdge: .top) bottomBanner.autoPinWidthToSuperview() - self.autoPinView(toBottomGuideOrKeyboard:bottomBanner) + self.autoPinView(toBottomGuideOrKeyboard: bottomBanner) // The Giphy API requires us to "show their trademark prominently" in our GIF experience. let logoImage = UIImage(named: "giphy_logo") @@ -454,9 +454,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect progressiveSearchTimer = nil guard let text = searchBar.text else { - OWSAlerts.showAlert(title: NSLocalizedString("ALERT_ERROR_TITLE", - comment: ""), - message: NSLocalizedString("GIF_PICKER_VIEW_MISSING_QUERY", + OWSAlerts.showErrorAlert(withMessage: NSLocalizedString("GIF_PICKER_VIEW_MISSING_QUERY", comment: "Alert message shown when user tries to search for GIFs without entering any search terms.")) return } diff --git a/Signal/src/ViewControllers/NewContactThreadViewController.m b/Signal/src/ViewControllers/NewContactThreadViewController.m index ada15b621..c83a7f6f2 100644 --- a/Signal/src/ViewControllers/NewContactThreadViewController.m +++ b/Signal/src/ViewControllers/NewContactThreadViewController.m @@ -728,8 +728,7 @@ NS_ASSUME_NONNULL_BEGIN if ([MFMessageComposeViewController canSendText]) { [inviteFlow sendSMSToPhoneNumbers:@[ phoneNumber ]]; } else { - [OWSAlerts showAlertWithTitle:NSLocalizedString(@"ALERT_ERROR_TITLE", @"") - message:NSLocalizedString(@"UNSUPPORTED_FEATURE_ERROR", @"")]; + [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"UNSUPPORTED_FEATURE_ERROR", @"")]; } }]; diff --git a/Signal/src/ViewControllers/OWS2FASettingsViewController.m b/Signal/src/ViewControllers/OWS2FASettingsViewController.m index 17921f7c4..5de5b9f4f 100644 --- a/Signal/src/ViewControllers/OWS2FASettingsViewController.m +++ b/Signal/src/ViewControllers/OWS2FASettingsViewController.m @@ -322,10 +322,9 @@ NS_ASSUME_NONNULL_BEGIN // Clear the PIN so that the user can try again. self.pinTextfield.text = nil; - [OWSAlerts - showAlertWithTitle:NSLocalizedString(@"ALERT_ERROR_TITLE", @"") - message:NSLocalizedString(@"ENABLE_2FA_VIEW_PIN_DOES_NOT_MATCH", - @"Error indicating that the entered 'two-factor auth PINs' do not match.")]; + [OWSAlerts showErrorAlertWithMessage: + NSLocalizedString(@"ENABLE_2FA_VIEW_PIN_DOES_NOT_MATCH", + @"Error indicating that the entered 'two-factor auth PINs' do not match.")]; } break; } @@ -370,11 +369,10 @@ NS_ASSUME_NONNULL_BEGIN [modalActivityIndicator dismissWithCompletion:^{ [weakSelf updateTableContents]; - [OWSAlerts - showAlertWithTitle:NSLocalizedString(@"ALERT_ERROR_TITLE", @"") - message:NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_DISABLE_2FA", - @"Error indicating that attempt to disable 'two-factor " - @"auth' failed.")]; + [OWSAlerts showErrorAlertWithMessage: + NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_DISABLE_2FA", + @"Error indicating that attempt to disable 'two-factor " + @"auth' failed.")]; }]; }]; }]; @@ -407,11 +405,10 @@ NS_ASSUME_NONNULL_BEGIN [weakSelf updateTableContents]; - [OWSAlerts - showAlertWithTitle:NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA", - @"Error indicating that attempt to enable 'two-factor " - @"auth' failed.") - message:error.localizedDescription]; + [OWSAlerts showErrorAlertWithMessage: + NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA", + @"Error indicating that attempt to enable 'two-factor " + @"auth' failed.")]; }]; }]; }]; diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 6ae576bd4..8bb6c0c31 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -367,8 +367,8 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat NSString *normalizedProfileName = [self normalizedProfileName]; if ([OWSProfileManager.sharedManager isProfileNameTooLong:normalizedProfileName]) { - [OWSAlerts showAlertWithTitle:NSLocalizedString(@"ALERT_ERROR_TITLE", @"") - message:NSLocalizedString(@"PROFILE_VIEW_ERROR_PROFILE_NAME_TOO_LONG", + [OWSAlerts + showErrorAlertWithMessage:NSLocalizedString(@"PROFILE_VIEW_ERROR_PROFILE_NAME_TOO_LONG", @"Error message shown when user tries to update profile with a profile name " @"that is too long.")]; return; @@ -396,14 +396,11 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat [alertController dismissViewControllerAnimated:NO completion:^{ - [OWSAlerts - showAlertWithTitle:NSLocalizedString( - @"ALERT_ERROR_TITLE", @"") - message: - NSLocalizedString( - @"PROFILE_VIEW_ERROR_UPDATE_FAILED", - @"Error message shown when a " - @"profile update fails.")]; + [OWSAlerts showErrorAlertWithMessage: + NSLocalizedString( + @"PROFILE_VIEW_ERROR_UPDATE_FAILED", + @"Error message shown when a " + @"profile update fails.")]; }]; }]; }]; diff --git a/Signal/src/util/OWSBackup.m b/Signal/src/util/OWSBackup.m index d8876be43..6113c295b 100644 --- a/Signal/src/util/OWSBackup.m +++ b/Signal/src/util/OWSBackup.m @@ -6,10 +6,10 @@ //#import "NSUserDefaults+OWS.h" //#import "Signal-Swift.h" -#import "zlib.h" +//#import "zlib.h" //#import -#import +//#import //#import //#import @@ -17,7 +17,10 @@ //#import #import "NSNotificationCenter+OWS.h" +#import "OWSBackupExport.h" +#import #import +#import #import NSString *const NSNotificationNameBackupStateDidChange = @"NSNotificationNameBackupStateDidChange"; @@ -50,15 +53,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -//- (NSData *)databasePassword; -// -//+ (void)storeDatabasePassword:(NSString *)password; +// This property should only be accessed on the main thread. +@property (nonatomic, nullable) OWSBackupExport *backupExport; -@end - -#pragma mark - - -@interface OWSBackup () +// //@property (nonatomic) OWSBackupState backupState; // @@ -114,6 +112,15 @@ NS_ASSUME_NONNULL_BEGIN OWSSingletonAssert(); + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationDidBecomeActive:) + name:OWSApplicationDidBecomeActiveNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(registrationStateDidChange) + name:RegistrationStateDidChangeNotification + object:nil]; + return self; } @@ -124,10 +131,6 @@ NS_ASSUME_NONNULL_BEGIN //- (void)observeNotifications //{ -// [[NSNotificationCenter defaultCenter] addObserver:self -// selector:@selector(applicationDidBecomeActive:) -// name:OWSApplicationDidBecomeActiveNotification -// object:nil]; //} //- (void)dealloc @@ -167,6 +170,56 @@ NS_ASSUME_NONNULL_BEGIN userInfo:nil]; } +- (BOOL)shouldHaveBackupExport +{ + if (!self.isBackupEnabled) { + return NO; + } + if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) { + // Only start backups when app is in the background. + return NO; + } + if (![TSAccountManager isRegistered]) { + return NO; + } + + // TODO: There's probably other conditions that affect this decision. + return YES; +} + +- (void)ensureBackupExportState +{ + OWSAssertIsOnMainThread(); + + if (!self.shouldHaveBackupExport && self.backupExport) { + [self.backupExport cancel]; + self.backupExport = nil; + } else if (self.shouldHaveBackupExport && !self.backupExport) { + self.backupExport = + [[OWSBackupExport alloc] initWithDelegate:self primaryStorage:[OWSPrimaryStorage sharedManager]]; + [self.backupExport start]; + } + + // BOOL shouldHaveBackupExport + // OWSBackupExport *backupExport +} + +#pragma mark - + +- (void)applicationDidBecomeActive:(NSNotification *)notification +{ + OWSAssertIsOnMainThread(); + + [self ensureBackupExportState]; +} + +- (void)registrationStateDidChange +{ + OWSAssertIsOnMainThread(); + + [self ensureBackupExportState]; +} + //- (void)setBackupProgress:(CGFloat)backupProgress //{ // _backupProgress = backupProgress; diff --git a/Signal/src/util/OWSBackupAPI.swift b/Signal/src/util/OWSBackupAPI.swift new file mode 100644 index 000000000..6324c2f46 --- /dev/null +++ b/Signal/src/util/OWSBackupAPI.swift @@ -0,0 +1,98 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import SignalServiceKit +import CloudKit + +//func FormatAnalyticsLocation(file: String, function: String) -> NSString { +// return "\((file as NSString).lastPathComponent):\(function)" as NSString +//} +// +//func OWSProdError(_ eventName: String, file: String, function: String, line: Int32) { +// let location = FormatAnalyticsLocation(file: file, function: function) +// OWSAnalytics +// .logEvent(eventName, severity: .error, parameters: nil, location: location.utf8String!, line:line) +//} +// +//func OWSProdInfo(_ eventName: String, file: String, function: String, line: Int32) { +// let location = FormatAnalyticsLocation(file: file, function: function) +// OWSAnalytics +// .logEvent(eventName, severity: .info, parameters: nil, location: location.utf8String!, line:line) +//} + +@objc public class OWSBackupAPI: NSObject { + @objc + public class func recordIdForTest() -> String { + return "test-\(NSUUID().uuidString)" + } + + @objc + public class func recordIdForAttachmentStream(value: TSAttachmentStream) -> String { + return "attachment-stream-\(value.uniqueId)" + } + + @objc + public class func saveTestFileToCloud(fileUrl: NSURL, + completion: @escaping (Error?) -> Swift.Void) { + saveFileToCloud(fileUrl: fileUrl, + recordId: recordIdForTest(), + recordType: "test", + completion: completion) + } + + @objc + public class func saveFileToCloud(fileUrl: NSURL, + recordId: String, + recordType: String, + completion: @escaping (Error?) -> Swift.Void) { + let recordID = CKRecordID(recordName: recordId) + let record = CKRecord(recordType: recordType, recordID: recordID) +// artworkRecord["title"] = "MacKerricher State Park" as NSString +// artworkRecord["artist"] = "Mei Chen" as NSString +// artworkRecord["address"] = "Fort Bragg, CA" as NSString +// artworkRecord[@"title" ] = @"MacKerricher State Park"; +// artworkRecord[@"artist"] = @"Mei Chen"; +// artworkRecord[@"address"] = @"Fort Bragg, CA"; + + let myContainer = CKContainer.default() + let privateDatabase = myContainer.privateCloudDatabase + privateDatabase.save(record) { + (record, error) in + + if let error = error { + Logger.error("\(self.logTag) error saving record: \(error)") + completion(error) + } else { + Logger.info("\(self.logTag) saved record.") + completion(nil) + } + } + } + + @objc + public class func checkCloudKitAccess(completion: @escaping (Bool) -> Swift.Void) { + CKContainer.default().accountStatus(completionHandler: { (accountStatus, error) in + DispatchQueue.main.async { + switch accountStatus { + case .couldNotDetermine: + Logger.error("\(self.logTag) could not determine CloudKit account status:\(error).") + OWSAlerts.showErrorAlert(withMessage: NSLocalizedString("CLOUDKIT_STATUS_COULD_NOT_DETERMINE", comment: "Error indicating that the app could not determine that user's CloudKit account status")) + completion(false) + case .noAccount: + Logger.error("\(self.logTag) no CloudKit account.") + OWSAlerts.showErrorAlert(withMessage: NSLocalizedString("CLOUDKIT_STATUS_NO_ACCOUNT", comment: "Error indicating that user does not have an iCloud account.")) + completion(false) + case .restricted: + Logger.error("\(self.logTag) restricted CloudKit account.") + OWSAlerts.showErrorAlert(withMessage: NSLocalizedString("CLOUDKIT_STATUS_RESTRICTED", comment: "Error indicating that the app was prevented from accessing the user's CloudKit account.")) + completion(false) + case .available: + Logger.error("\(self.logTag) no CloudKit account.") + completion(true) + } + } + }) + } +} diff --git a/Signal/src/util/OWSBackupExport.h b/Signal/src/util/OWSBackupExport.h new file mode 100644 index 000000000..9008accea --- /dev/null +++ b/Signal/src/util/OWSBackupExport.h @@ -0,0 +1,70 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +// extern NSString *const OWSBackup_FileExtension; + +// extern NSString *const NSNotificationNameBackupStateDidChange; + +@protocol OWSBackupExportDelegate + +- (void)backupExportDidSucceed; + +- (void)backupExportDidFailWithError:(NSError *)error; + +@end + +//#pragma mark - + +// typedef NS_ENUM(NSUInteger, OWSBackupState) { +// OWSBackupState_AtRest = 0, +// OWSBackupState_InProgress, +// // OWSBackupState_Cancelled, +// OWSBackupState_Failed, +//}; + +@class OWSPrimaryStorage; + +@interface OWSBackupExport : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithDelegate:(id)delegate + primaryStorage:(OWSPrimaryStorage *)primaryStorage; + +- (void)start; + +- (void)cancel; + +//@property (nonatomic, readonly) OWSBackupState backupExportState; +// +////@property (nonatomic, readonly) CGFloat backupProgress; +//// +////// If non-nil, backup is encrypted. +////@property (nonatomic, nullable, readonly) NSString *backupPassword; +//// +////// Only applies to "backup export" task. +////@property (nonatomic, nullable, readonly) TSThread *currentThread; +//// +////@property (nonatomic, readonly) NSString *backupZipPath; +//// +// +// +//+ (instancetype)sharedManager; +// +//- (BOOL)isBackupEnabled; +//- (void)setIsBackupEnabled:(BOOL)value; + +//- (void)exportBackup:(nullable TSThread *)currentThread skipPassword:(BOOL)skipPassword; +// +//- (void)importBackup:(NSString *)backupZipPath password:(NSString *_Nullable)password; +// +//- (void)cancel; +// +//+ (void)applicationDidFinishLaunching; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/OWSBackupExport.m b/Signal/src/util/OWSBackupExport.m new file mode 100644 index 000000000..03a4e0556 --- /dev/null +++ b/Signal/src/util/OWSBackupExport.m @@ -0,0 +1,1096 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSBackupExport.h" + +//#import "NSUserDefaults+OWS.h" +//#import "Signal-Swift.h" +#import "zlib.h" + +//#import +#import + +//#import +//#import +#import + +//#import +//#import "NSNotificationCenter+OWS.h" +#import +#import + +// NSString *const NSNotificationNameBackupStateDidChange = @"NSNotificationNameBackupStateDidChange"; +// +// NSString *const OWSPrimaryStorage_OWSBackupCollection = @"OWSPrimaryStorage_OWSBackupCollection"; +// NSString *const OWSBackup_IsBackupEnabledKey = @"OWSBackup_IsBackupEnabledKey"; + +NS_ASSUME_NONNULL_BEGIN + +//// Hide the "import" directories from exports, etc. by prefixing their name with a period. +//// +//// OWSBackup backs up files and directories in the "app documents" and "shared data container", +//// but ignores any top-level files or directories in those locations whose names start with a +//// period ".". +// NSString *const OWSBackup_DirNamePrefix = @".SignalBackup."; +// NSString *const OWSBackup_FileExtension = @".signalbackup"; +// NSString *const OWSBackup_EncryptionKeyFilename = @".encryptionKey"; +// NSString *const OWSBackup_DatabasePasswordFilename = @".databasePassword"; +// NSString *const OWSBackup_StandardUserDefaultsFilename = @".standardUserDefaults"; +// NSString *const OWSBackup_AppUserDefaultsFilename = @".appUserDefaults"; +// NSString *const OWSBackup_AppDocumentDirName = @"appDocumentDirectoryPath"; +// NSString *const OWSBackup_AppSharedDataDirName = @"appSharedDataDirectoryPath"; +// +// NSString *const NSUserDefaults_QueuedBackupPath = @"NSUserDefaults_QueuedBackupPath"; +// +// NSString *const Keychain_ImportBackupService = @"OWSKeychainService"; +// NSString *const Keychain_ImportBackupKey = @"ImportBackupKey"; + +@interface OWSBackupExport () + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; + +// Indicates that the backup succeeded, failed or was cancelled. +@property (atomic) BOOL isComplete; + +//- (NSData *)databasePassword; +// +//+ (void)storeDatabasePassword:(NSString *)password; + +//@end +// +//#pragma mark - +// +//@interface OWSBackupExport () + +//@property (nonatomic) OWSBackupState backupState; +// +//@property (nonatomic) CGFloat backupProgress; +// +//@property (nonatomic, nullable) TSThread *currentThread; +// +//@property (nonatomic, nullable) NSString *backupPassword; +// +//@property (nonatomic) NSString *backupDirPath; +//@property (nonatomic) NSString *backupZipPath; +// +//@property (nonatomic) OWSAES256Key *encryptionKey; + +@end + +#pragma mark - + +@implementation OWSBackupExport + +@synthesize dbConnection = _dbConnection; + +//+ (instancetype)sharedManager +//{ +// static OWSBackup *sharedMyManager = nil; +// static dispatch_once_t onceToken; +// dispatch_once(&onceToken, ^{ +// sharedMyManager = [[self alloc] initDefault]; +// }); +// return sharedMyManager; +//} +// +//- (instancetype)initDefault +//{ +// OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; +// +// return [self initWithPrimaryStorage:primaryStorage]; +//} + +- (instancetype)initWithDelegate:(id)delegate + primaryStorage:(OWSPrimaryStorage *)primaryStorage +{ + self = [super init]; + + if (!self) { + return self; + } + + OWSAssert(primaryStorage); + + self.delegate = delegate; + _dbConnection = primaryStorage.newDatabaseConnection; + + // _backupExportState = OWSBackupState_AtRest; + + OWSSingletonAssert(); + + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)start +{ + // TODO: +} + +- (void)cancel +{ + // TODO: + self.isComplete = YES; +} + +- (void)succeed +{ + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + + self.isComplete = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate backupExportDidSucceed]; + }); + // TODO: +} + +- (void)failWithError:(NSError *)error +{ + DDLogError(@"%@ %s %@", self.logTag, __PRETTY_FUNCTION__, error); + + self.isComplete = YES; + + // TODO: + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate backupExportDidFailWithError:error]; + }); +} + +//- (void)observeNotifications +//{ +// [[NSNotificationCenter defaultCenter] addObserver:self +// selector:@selector(applicationDidBecomeActive:) +// name:OWSApplicationDidBecomeActiveNotification +// object:nil]; +//} + +//- (void)dealloc +//{ +// DDLogInfo(@"%@ Cleaning up: %@", self.logTag, self.backupDirPath); +// [OWSFileSystem deleteFileIfExists:self.backupDirPath]; +// +// dispatch_async(dispatch_get_main_queue(), ^{ +// [OWSBackup cleanupBackupState]; +// }); +//} + +//- (void)setBackupExportState:(OWSBackupState)backupExportState +//{ +// _backupExportState = backupExportState; +// +// [[NSNotificationCenter defaultCenter] postNotificationNameAsync:NSNotificationNameBackupStateDidChange +// object:nil +// userInfo:nil]; +//} +// +//- (BOOL)isBackupEnabled +//{ +// return [self.dbConnection boolForKey:OWSBackup_IsBackupEnabledKey +// inCollection:OWSPrimaryStorage_OWSBackupCollection +// defaultValue:NO]; +//} +// +//- (void)setIsBackupEnabled:(BOOL)value +//{ +// [self.dbConnection setBool:value +// forKey:OWSBackup_IsBackupEnabledKey +// inCollection:OWSPrimaryStorage_OWSBackupCollection]; +// +// [[NSNotificationCenter defaultCenter] postNotificationNameAsync:NSNotificationNameBackupStateDidChange +// object:nil +// userInfo:nil]; +//} + +//- (void)setBackupProgress:(CGFloat)backupProgress +//{ +// _backupProgress = backupProgress; +// +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self.delegate backupProgressDidChange]; +// }); +//} +// +//- (void)fail +//{ +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// if (!self.isCancelledOrFailed) { +// self.backupState = OWSBackupState_Failed; +// } +// +// dispatch_async(dispatch_get_main_queue(), ^{ +// [OWSBackup cleanupBackupState]; +// }); +//} +// +//- (void)cancel +//{ +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// if (!self.isCancelledOrFailed) { +// self.backupState = OWSBackupState_Cancelled; +// } +// +// dispatch_async(dispatch_get_main_queue(), ^{ +// [OWSBackup cleanupBackupState]; +// }); +//} +// +//- (void)complete +//{ +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// if (!self.isCancelledOrFailed) { +// self.backupState = OWSBackupState_Complete; +// } +//} +// +//- (BOOL)isCancelledOrFailed +//{ +// 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); +// +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// self.currentThread = currentThread; +// self.backupState = OWSBackupState_InProgress; +// +// if (skipPassword) { +// DDLogInfo(@"%@ 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; +// DDLogInfo(@"%@ backup export with password: %@", self.logTag, backupPassword); +// } +// +// [self startExport]; +//} +// +//- (void)startExport +//{ +// OWSAssertIsOnMainThread(); +// +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ +// [self exportToFilesAndZip]; +// +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self complete]; +// }); +// }); +//} +// +//- (void)exportToFilesAndZip +//{ +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// // First, clean up any existing backup import/export state. +// [OWSBackup cleanupBackupState]; +// +// NSString *temporaryDirectory = NSTemporaryDirectory(); +// NSString *rootDirName = [OWSBackup_DirNamePrefix stringByAppendingString:[NSUUID UUID].UUIDString]; +// NSString *rootDirPath = [temporaryDirectory stringByAppendingPathComponent:rootDirName]; +// NSString *backupDirPath = [rootDirPath stringByAppendingPathComponent:@"Contents"]; +// +// NSDateFormatter *dateFormatter = [NSDateFormatter new]; +// [dateFormatter setLocale:[NSLocale currentLocale]]; +// [dateFormatter setDateFormat:@"yyyy.MM.dd hh.mm.ss"]; +// NSString *backupDateTime = [dateFormatter stringFromDate:[NSDate new]]; +// NSString *backupName = +// [NSString stringWithFormat:NSLocalizedString(@"BACKUP_FILENAME_FORMAT", +// @"Format for backup filenames. Embeds: {{the date and time of the backup}}. " +// @"Should not include characters like slash (/ or \\) or colon (:)."), +// backupDateTime]; +// NSString *backupZipPath = +// [rootDirPath stringByAppendingPathComponent:[backupName stringByAppendingString:OWSBackup_FileExtension]]; +// 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 protectFileOrFolderAtPath:rootDirPath]; +// [OWSFileSystem ensureDirectoryExists:backupDirPath]; +// +// if (self.isCancelledOrFailed) { +// return; +// } +// +// OWSAES256Key *encryptionKey = [OWSAES256Key generateRandomKey]; +// self.encryptionKey = encryptionKey; +// +// NSData *databasePassword = [OWSPrimaryStorage sharedManager].databasePassword; +// +// // 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:OWSBackup_DatabasePasswordFilename +// backupDirPath:backupDirPath +// encryptionKey:encryptionKey]) { +// return [self fail]; +// } +// if (self.isCancelledOrFailed) { +// return; +// } +// if (![self writeUserDefaults:NSUserDefaults.standardUserDefaults +// fileName:OWSBackup_StandardUserDefaultsFilename +// backupDirPath:backupDirPath +// encryptionKey:encryptionKey]) { +// return [self fail]; +// } +// if (self.isCancelledOrFailed) { +// return; +// } +// if (![self writeUserDefaults:NSUserDefaults.appUserDefaults +// fileName:OWSBackup_AppUserDefaultsFilename +// backupDirPath:backupDirPath +// encryptionKey:encryptionKey]) { +// 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. +// [OWSPrimaryStorage.sharedManager.newDatabaseConnection +// readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { +// if (![self copyDirectory:OWSFileSystem.appDocumentDirectoryPath +// dstDirName:OWSBackup_AppDocumentDirName +// backupDirPath:backupDirPath]) { +// [self fail]; +// return; +// } +// if (self.isCancelledOrFailed) { +// return; +// } +// if (![self copyDirectory:OWSFileSystem.appSharedDataDirectoryPath +// dstDirName:OWSBackup_AppSharedDataDirName +// backupDirPath:backupDirPath]) { +// [self fail]; +// return; +// } +// }]; +// if (self.isCancelledOrFailed) { +// return; +// } +// if (![self zipDirectory:backupDirPath dstFilePath:backupZipPath encryptionKey:encryptionKey]) { +// return [self fail]; +// } +// +// [OWSFileSystem protectFileOrFolderAtPath:backupZipPath]; +// +// [OWSFileSystem deleteFileIfExists:self.backupDirPath]; +//} +// +//- (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 *_Nullable encryptedData = [Cryptography encryptAESGCMWithData:data key:encryptionKey]; +// if (!encryptedData) { +// OWSFail(@"%@ failed to encrypt data: %@", self.logTag, fileName); +// return NO; +// } +// +// NSString *filePath = [backupDirPath stringByAppendingPathComponent:fileName]; +// +// DDLogInfo(@"%@ writeData: %@", self.logTag, filePath); +// +// NSError *error; +// BOOL success = [encryptedData writeToFile:filePath options:NSDataWritingAtomic error:&error]; +// if (!success || error) { +// OWSFail(@"%@ failed to write user defaults: %@", self.logTag, error); +// return NO; +// } +// return YES; +//} +// +//- (BOOL)copyDirectory:(NSString *)srcDirPath dstDirName:(NSString *)dstDirName backupDirPath:(NSString *)backupDirPath +//{ +// OWSAssert(srcDirPath.length > 0); +// OWSAssert(dstDirName.length > 0); +// OWSAssert(backupDirPath.length > 0); +// +// NSString *dstDirPath = [backupDirPath stringByAppendingPathComponent:dstDirName]; +// +// DDLogInfo(@"%@ copyDirectory: %@ -> %@", self.logTag, srcDirPath, dstDirPath); +// +// // We "manually" copy the "root" items in the src directory. +// // Can't just use [NSFileManager copyItemAtPath:...] because the shared data container +// // contains files that the app is not allowed to access. +// [OWSFileSystem ensureDirectoryExists:dstDirPath]; +// NSError *error = nil; +// NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:srcDirPath +// error:&error]; if (error) { +// OWSFail(@"%@ failed to list directory: %@, %@", self.logTag, srcDirPath, error); +// return NO; +// } +// for (NSString *fileName in fileNames) { +// NSString *srcFilePath = [srcDirPath stringByAppendingPathComponent:fileName]; +// NSString *dstFilePath = [dstDirPath stringByAppendingPathComponent:fileName]; +// if ([fileName hasPrefix:@"."]) { +// DDLogInfo(@"%@ ignoring: %@", self.logTag, srcFilePath); +// continue; +// } +// BOOL success = [[NSFileManager defaultManager] copyItemAtPath:srcFilePath toPath:dstFilePath error:&error]; +// if (!success || error) { +// OWSFail(@"%@ failed to copy directory item: %@, %@", self.logTag, srcFilePath, error); +// return NO; +// } +// } +// +// return YES; +//} +// +//- (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); +// +// DDLogInfo(@"%@ writeUserDefaults: %@", self.logTag, fileName); +// +// NSDictionary *dictionary = userDefaults.dictionaryRepresentation; +// if (!dictionary) { +// OWSFail(@"%@ failed to extract user defaults", self.logTag); +// return NO; +// } +// NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary]; +// if (!data) { +// OWSFail(@"%@ failed to archive user defaults", self.logTag); +// return NO; +// } +// +// return [self writeData:data fileName:fileName backupDirPath:backupDirPath encryptionKey:encryptionKey]; +//} +// +//- (BOOL)zipDirectory:(NSString *)srcDirPath +// dstFilePath:(NSString *)dstFilePath +// encryptionKey:(OWSAES256Key *)encryptionKey +//{ +// OWSAssert(srcDirPath.length > 0); +// OWSAssert(dstFilePath.length > 0); +// OWSAssert(encryptionKey); +// +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// srcDirPath = [srcDirPath stringByStandardizingPath]; +// OWSAssert(srcDirPath.length > 0); +// +// NSError *error; +// NSArray *_Nullable srcFilePaths = [OWSFileSystem allFilesInDirectoryRecursive:srcDirPath +// error:&error]; if (!srcFilePaths || error) { +// OWSFail(@"%@ failed to find files to zip: %@", self.logTag, error); +// return NO; +// } +// +// // Don't use the SSZipArchive convenience methods so that we can add the +// // encryption key directly as data. +// SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:dstFilePath]; +// if (![zipArchive open]) { +// OWSFail(@"%@ failed to open zip file.", self.logTag); +// return NO; +// } +// for (NSString *srcFilePath in srcFilePaths) { +// NSString *relativePath = [self relativePathforPath:srcFilePath basePath:srcDirPath]; +// BOOL success = [zipArchive writeFileAtPath:srcFilePath +// withFileName:relativePath +// compressionLevel:Z_DEFAULT_COMPRESSION +// password:self.backupPassword +// 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 file to zip file.", self.logTag); +// return NO; +// } +// +// +// if (![zipArchive close]) { +// OWSFail(@"%@ failed to close zip file.", self.logTag); +// return NO; +// } +// +// NSNumber *fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:dstFilePath error:&error][NSFileSize]; +// if (error) { +// OWSFail(@"%@ failed to get zip file size: %@", self.logTag, error); +// return NO; +// } +// DDLogInfo(@"%@ Zip file size: %@", self.logTag, fileSize); +// +// return YES; +//} +// +//#pragma mark - Import Backup, Part 1 +// +//- (void)importBackup:(NSString *)srcZipPath password:(NSString *_Nullable)password +//{ +// OWSAssertIsOnMainThread(); +// OWSAssert(srcZipPath.length > 0); +// OWSAssert(CurrentAppContext().isMainApp); +// +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// self.backupPassword = password; +// +// self.backupState = OWSBackupState_InProgress; +// +// if (password.length == 0) { +// DDLogInfo(@"%@ backup import without password", self.logTag); +// } else { +// DDLogInfo(@"%@ backup import with password: %@", self.logTag, password); +// } +// +// [self startImport:srcZipPath]; +//} +// +//- (void)startImport:(NSString *)srcZipPath +//{ +// OWSAssertIsOnMainThread(); +// OWSAssert(srcZipPath.length > 0); +// +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ +// [self prepareForImport:srcZipPath]; +// +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self complete]; +// }); +// }); +//} +// +//- (void)prepareForImport:(NSString *)srcZipPath +//{ +// OWSAssert(srcZipPath.length > 0); +// +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// NSString *documentDirectoryPath = OWSFileSystem.appDocumentDirectoryPath; +// NSString *rootDirName = [OWSBackup_DirNamePrefix 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 protectFileOrFolderAtPath: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; +// } +// if (![self unzipFilePath]) { +// return [self fail]; +// } +// if (self.isCancelledOrFailed) { +// return; +// } +// if (![self extractEncryptionKey]) { +// return [self fail]; +// } +// if (self.isCancelledOrFailed) { +// return; +// } +// if (![self isValidBackup]) { +// return [self fail]; +// } +// if (self.isCancelledOrFailed) { +// return; +// } +// if (![self enqueueBackupRestore]) { +// return [self fail]; +// } +//} +// +//- (BOOL)extractEncryptionKey +//{ +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// NSString *encryptionKeyFilePath = +// [self.backupDirPath stringByAppendingPathComponent:OWSBackup_EncryptionKeyFilename]; +// if (![[NSFileManager defaultManager] fileExistsAtPath:encryptionKeyFilePath]) { +// return NO; +// } +// NSData *_Nullable encryptionKeyData = [NSData dataWithContentsOfFile:encryptionKeyFilePath]; +// if (!encryptionKeyData) { +// return NO; +// } +// OWSAES256Key *encryptionKey = [OWSAES256Key keyWithData:encryptionKeyData]; +// if (!encryptionKey) { +// return NO; +// } +// self.encryptionKey = encryptionKey; +// +// NSError *error = nil; +// BOOL success = [[NSFileManager defaultManager] removeItemAtPath:encryptionKeyFilePath error:&error]; +// if (!success || error) { +// OWSFail(@"%@ could not delete encryption key file: %@", self.logTag, error); +// return NO; +// } +// return YES; +//} +// +//- (BOOL)unzipFilePath +//{ +// OWSAssert(self.backupZipPath.length > 0); +// OWSAssert(self.backupDirPath.length > 0); +// +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// // Don't use the SSZipArchive convenience methods so that we can add the +// // encryption key directly as data. +// +// // TODO: Should we use preserveAttributes? +// NSError *error = nil; +// BOOL success = [SSZipArchive unzipFileAtPath:self.backupZipPath +// toDestination:self.backupDirPath +// preserveAttributes:YES +// overwrite:YES +// nestedZipLevel:0 +// password:self.backupPassword +// error:&error +// delegate:self +// progressHandler:^(NSString *entry, unz_file_info zipInfo, long entryNumber, long total) { +// DDLogInfo(@"%@ progressHandler: %ld %ld", self.logTag, entryNumber, total); +// +// CGFloat progress = entryNumber / (CGFloat)total; +// self.backupProgress = progress; +// } +// completionHandler:^(NSString *path, BOOL succeeded, NSError *_Nullable completionError) { +// DDLogInfo(@"%@ completionHandler: %d %@", self.logTag, succeeded, completionError); +// }]; +// if (!success || error) { +// OWSFail(@"%@ failed to unzip file: %@.", self.logTag, error); +// return NO; +// } +// +// return YES; +//} +// +//- (BOOL)isValidBackup +//{ +// NSString *databasePasswordFilePath = +// [self.backupDirPath stringByAppendingPathComponent:OWSBackup_DatabasePasswordFilename]; +// if (![[NSFileManager defaultManager] fileExistsAtPath:databasePasswordFilePath]) { +// return NO; +// } +// NSString *standardUserDefaultsFilePath = +// [self.backupDirPath stringByAppendingPathComponent:OWSBackup_StandardUserDefaultsFilename]; +// if (![[NSFileManager defaultManager] fileExistsAtPath:standardUserDefaultsFilePath]) { +// return NO; +// } +// NSString *appUserDefaultsFilePath = +// [self.backupDirPath stringByAppendingPathComponent:OWSBackup_AppUserDefaultsFilename]; +// if (![[NSFileManager defaultManager] fileExistsAtPath:appUserDefaultsFilePath]) { +// return NO; +// } +// // TODO: Verify that the primary database exists. +// +// return YES; +//} +// +//- (BOOL)enqueueBackupRestore +//{ +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// NSError *error = nil; +// [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; +// BOOL success = [SAMKeychain setPasswordData:self.encryptionKey.keyData +// forService:Keychain_ImportBackupService +// account:Keychain_ImportBackupKey +// error:&error]; +// if (!success || error) { +// OWSFail(@"%@ Could not store encryption key for import backup: %@", self.logTag, error); +// return NO; +// } +// +// NSString *documentDirectoryPath = OWSFileSystem.appDocumentDirectoryPath; +// NSString *relativePath = [self relativePathforPath:self.backupDirPath basePath:documentDirectoryPath]; +// [[NSUserDefaults appUserDefaults] setObject:relativePath forKey:NSUserDefaults_QueuedBackupPath]; +// [[NSUserDefaults appUserDefaults] synchronize]; +// +// return YES; +//} +// +//#pragma mark - Import Backup, Part 2 +// +//- (void)completeImportBackupIfPossible +//{ +// OWSAssertIsOnMainThread(); +// OWSAssert(CurrentAppContext().isMainApp); +// +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// NSString *_Nullable queuedBackupRelativePath = +// [[NSUserDefaults appUserDefaults] stringForKey:NSUserDefaults_QueuedBackupPath]; +// if (queuedBackupRelativePath.length == 0) { +// return; +// } +// NSString *documentDirectoryPath = OWSFileSystem.appDocumentDirectoryPath; +// NSString *_Nullable queuedBackupPath = +// [self joinRelativePath:queuedBackupRelativePath basePath:documentDirectoryPath]; +// if (![[NSFileManager defaultManager] fileExistsAtPath:queuedBackupPath]) { +// OWSFail(@"%@ Missing import backup directory: %@.", self.logTag, queuedBackupPath); +// return; +// } +// self.backupDirPath = queuedBackupPath; +// self.backupState = OWSBackupState_InProgress; +// DDLogInfo(@"%@ queuedBackupPath: %@", self.logTag, queuedBackupPath); +// +// [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; +// NSError *error; +// NSData *_Nullable encryptionKeyData = +// [SAMKeychain passwordDataForService:Keychain_ImportBackupService account:Keychain_ImportBackupKey +// error:&error]; +// if (!encryptionKeyData || error) { +// OWSFail(@"%@ Could not retrieve encryption key for import backup: %@", self.logTag, error); +// return; +// } +// self.encryptionKey = [OWSAES256Key keyWithData:encryptionKeyData]; +// +// if (![self isValidBackup]) { +// return; +// } +// +// NSData *_Nullable databasePassword = [self readDataFromFileName:OWSBackup_DatabasePasswordFilename]; +// if (!databasePassword) { +// OWSFail(@"%@ Could not retrieve database password.", self.logTag); +// return; +// } +// +// // We can't restore a backup atomically, so we: +// // +// // * Ensure the restore consists only of tiny writes, and file moves. +// // * Write the database password last. +// if (![self loadUserDefaults:NSUserDefaults.standardUserDefaults fileName:OWSBackup_StandardUserDefaultsFilename]) +// { +// return; +// } +// if (![self loadUserDefaults:NSUserDefaults.appUserDefaults fileName:OWSBackup_AppUserDefaultsFilename]) { +// return; +// } +// +// if (![self restoreDirectoryContents:OWSFileSystem.appDocumentDirectoryPath +// srcDirName:OWSBackup_AppDocumentDirName]) { +// return; +// } +// if (![self restoreDirectoryContents:OWSFileSystem.appSharedDataDirectoryPath +// srcDirName:OWSBackup_AppSharedDataDirName]) { +// return; +// } +// +// // TODO: Possibly verify database file location? +// +// [OWSStorage storeDatabasePassword:[[NSString alloc] initWithData:databasePassword encoding:NSUTF8StringEncoding]]; +//} +// +//- (nullable NSData *)readDataFromFileName:(NSString *)fileName +//{ +// OWSAssert(fileName.length > 0); +// OWSAssert(self.backupDirPath.length > 0); +// OWSAssert(self.encryptionKey); +// +// NSString *filePath = [self.backupDirPath stringByAppendingPathComponent:fileName]; +// +// DDLogInfo(@"%@ readDataFromFileName: %@", self.logTag, filePath); +// +// NSData *_Nullable encryptedData = [NSData dataWithContentsOfFile:filePath]; +// if (!encryptedData) { +// OWSFail(@"%@ failed to read encrypted data: %@", self.logTag, fileName); +// return nil; +// } +// +// NSData *_Nullable data = [Cryptography decryptAESGCMWithData:encryptedData key:self.encryptionKey]; +// if (!data) { +// OWSFail(@"%@ failed to decrypt data: %@", self.logTag, fileName); +// return nil; +// } +// +// return data; +//} +// +//- (BOOL)loadUserDefaults:(NSUserDefaults *)userDefaults fileName:(NSString *)fileName +//{ +// OWSAssert(userDefaults); +// OWSAssert(fileName.length > 0); +// OWSAssert(self.backupDirPath.length > 0); +// OWSAssert(self.encryptionKey); +// +// DDLogInfo(@"%@ loadUserDefaults: %@", self.logTag, fileName); +// +// NSData *_Nullable data = [self readDataFromFileName:fileName]; +// if (!data) { +// OWSFail(@"%@ Could not retrieve user defaults: %@.", self.logTag, fileName); +// return NO; +// } +// +// NSError *error; +// NSDictionary *_Nullable dictionary = +// [NSKeyedUnarchiver unarchiveTopLevelObjectWithData:data error:&error]; +// if (!dictionary || error) { +// OWSFail(@"%@ Could not unarchive user defaults: %@", self.logTag, error); +// return NO; +// } +// if (![dictionary isKindOfClass:[NSDictionary class]]) { +// OWSFail(@"%@ Unexpected archived user defaults: %@", self.logTag, error); +// return NO; +// } +// +// // Clear out any existing keys in this instance of NSUserDefaults. +// for (NSString *key in userDefaults.dictionaryRepresentation) { +// [userDefaults removeObjectForKey:key]; +// } +// +// // TODO: this doesn't yet remove any keys, so you end up with the "union". +// for (NSString *key in dictionary) { +// id value = dictionary[key]; +// OWSAssert(value); +// [userDefaults setObject:value forKey:key]; +// } +// +// [userDefaults synchronize]; +// +// return YES; +//} +// +//- (BOOL)renameDirectoryContents:(NSString *)dirPath +//{ +// OWSAssert(dirPath.length > 0); +// +// DDLogInfo(@"%@ renameDirectoryContents: %@", self.logTag, dirPath); +// +// NSError *error = nil; +// NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error]; +// if (error) { +// OWSFail(@"%@ failed to list directory: %@, %@", self.logTag, dirPath, error); +// return NO; +// } +// for (NSString *fileName in fileNames) { +// if ([fileName hasPrefix:@"."]) { +// // Ignore hidden files and directories. +// continue; +// } +// NSString *filePath = [dirPath stringByAppendingPathComponent:fileName]; +// +// // To replace an existing file or directory, rename the existing item +// // by adding a date/time suffix. +// NSDateFormatter *dateFormatter = [NSDateFormatter new]; +// [dateFormatter setLocale:[NSLocale currentLocale]]; +// [dateFormatter setDateFormat:@".yyyy.MM.dd hh.mm.ss"]; +// NSString *replacementDateTime = [dateFormatter stringFromDate:[NSDate new]]; +// +// // Prefix with period to prevent subsequent backups from including these old, replaced +// // files and directories. +// NSString *renamedFileName = [NSString stringWithFormat:@".Old.%@.%@", fileName, replacementDateTime]; +// NSString *renamedFilePath = [dirPath stringByAppendingPathComponent:renamedFileName]; +// BOOL success = [[NSFileManager defaultManager] moveItemAtPath:filePath toPath:renamedFilePath error:&error]; +// if (!success || error) { +// OWSFail(@"%@ failed to move directory item: %@, %@", self.logTag, filePath, error); +// return NO; +// } +// if (![OWSFileSystem protectFileOrFolderAtPath:renamedFilePath]) { +// OWSFail(@"%@ failed to protect old directory item: %@, %@", self.logTag, renamedFilePath, error); +// return NO; +// } +// } +// +// return YES; +//} +// +//- (BOOL)restoreDirectoryContents:(NSString *)dstDirPath srcDirName:(NSString *)srcDirName +//{ +// OWSAssert(srcDirName.length > 0); +// OWSAssert(dstDirPath.length > 0); +// OWSAssert(self.backupDirPath.length > 0); +// +// // Rename any existing files and directories in this directory. +// if (![self renameDirectoryContents:dstDirPath]) { +// return NO; +// } +// +// NSString *srcDirPath = [self.backupDirPath stringByAppendingPathComponent:srcDirName]; +// +// DDLogInfo(@"%@ restoreDirectoryContents: %@ -> %@", self.logTag, srcDirPath, dstDirPath); +// +// if (![[NSFileManager defaultManager] fileExistsAtPath:srcDirPath]) { +// // Not all backups will have both a "app documents" and "shared data container" folder. +// // The latter should always be present for "modern" installs, but we are permissive +// // here about what we accept so that we can easily apply this branch to historic +// // (pre-shared data container) versions of the app and restore from them. +// DDLogInfo(@"%@ Skipping restore directory: %@.", self.logTag, srcDirPath); +// return YES; +// } +// +// NSError *error = nil; +// NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:srcDirPath +// error:&error]; if (error) { +// OWSFail(@"%@ failed to list directory: %@, %@", self.logTag, srcDirPath, error); +// return NO; +// } +// for (NSString *fileName in fileNames) { +// if ([fileName hasPrefix:@"."]) { +// // Ignore hidden files and directories. +// OWSFail(@"%@ can't restore hidden file or directory: %@", self.logTag, fileName); +// continue; +// } +// NSString *srcFilePath = [srcDirPath stringByAppendingPathComponent:fileName]; +// NSString *dstFilePath = [dstDirPath stringByAppendingPathComponent:fileName]; +// +// if ([[NSFileManager defaultManager] fileExistsAtPath:dstFilePath]) { +// // All conflicting contents should have already been moved by renameDirectoryContents. +// OWSFail(@"%@ unexpected pre-existing file or directory: %@", self.logTag, fileName); +// continue; +// } +// +// BOOL success = [[NSFileManager defaultManager] moveItemAtPath:srcFilePath toPath:dstFilePath error:&error]; +// if (!success || error) { +// OWSFail(@"%@ failed to move directory item: %@, %@", self.logTag, dstFilePath, error); +// return NO; +// } +// if (![OWSFileSystem protectFileOrFolderAtPath:dstFilePath]) { +// OWSFail(@"%@ failed to protect directory item: %@, %@", self.logTag, dstFilePath, error); +// return NO; +// } +// } +// +// return YES; +//} +// +//#pragma mark - Clean up +// +//+ (void)cleanupBackupState +//{ +// DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__); +// +// [self cleanupBackupDirectoriesInDirectory:NSTemporaryDirectory()]; +// [self cleanupBackupDirectoriesInDirectory:OWSFileSystem.appDocumentDirectoryPath]; +// +// [[NSUserDefaults appUserDefaults] removeObjectForKey:NSUserDefaults_QueuedBackupPath]; +// [[NSUserDefaults appUserDefaults] synchronize]; +//} +// +//+ (void)cleanupBackupDirectoriesInDirectory:(NSString *)dirPath +//{ +// OWSAssert(dirPath.length > 0); +// +// NSError *error; +// NSArray *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error]; +// if (error) { +// OWSFail(@"%@ could not find files in directory: %@", self.logTag, error); +// return; +// } +// +// for (NSString *filename in filenames) { +// if (![filename hasPrefix:OWSBackup_DirNamePrefix]) { +// continue; +// } +// NSString *filePath = [dirPath stringByAppendingPathComponent:filename]; +// BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; +// if (!success || error) { +// OWSFail(@"%@ could not clean up backup directory: %@", self.logTag, error); +// return; +// } +// } +//} +// +//#pragma mark - Utils +// +//- (NSString *)relativePathforPath:(NSString *)filePath basePath:(NSString *)basePath +//{ +// OWSAssert(filePath.stringByStandardizingPath.length > 0); +// OWSAssert([filePath.stringByStandardizingPath hasPrefix:basePath.stringByStandardizingPath]); +// +// NSString *relativePath = +// [filePath.stringByStandardizingPath substringFromIndex:basePath.stringByStandardizingPath.length]; +// NSString *separator = @"/"; +// if ([relativePath hasPrefix:separator]) { +// relativePath = [relativePath substringFromIndex:separator.length]; +// } +// OWSAssert(relativePath.length > 0); +// return relativePath; +//} +// +//- (NSString *)joinRelativePath:(NSString *)relativePath basePath:(NSString *)basePath +//{ +// OWSAssert(basePath.stringByStandardizingPath.length > 0); +// OWSAssert(relativePath.length > 0); +// +// return [basePath stringByAppendingPathComponent:relativePath]; +//} +// +//#pragma mark - App Launch +// +//+ (void)applicationDidFinishLaunching +//{ +// [[OWSBackup new] completeImportBackupIfPossible]; +// +// // Always clean up backup state on disk, but defer so as not to interface with +// // app launch sequence. +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ +// [OWSBackup cleanupBackupState]; +// }); +//} +// +//#pragma mark - SSZipArchiveDelegate +// +//- (void)zipArchiveProgressEvent:(unsigned long long)loaded total:(unsigned long long)total +//{ +// DDLogInfo(@"%@ zipArchiveProgressEvent: %llu %llu", self.logTag, loaded, total); +// +// CGFloat progress = loaded / (CGFloat)total; +// self.backupProgress = progress; +//} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/contacts/SelectRecipientViewController.m b/SignalMessaging/contacts/SelectRecipientViewController.m index 534da5bef..3f6f4c7c0 100644 --- a/SignalMessaging/contacts/SelectRecipientViewController.m +++ b/SignalMessaging/contacts/SelectRecipientViewController.m @@ -338,10 +338,8 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien [modalActivityIndicator dismissViewControllerAnimated:NO completion:^{ - [OWSAlerts showAlertWithTitle: - NSLocalizedString(@"ALERT_ERROR_TITLE", - @"Title for a generic error alert.") - message:error.localizedDescription]; + [OWSAlerts + showErrorAlertWithMessage:error.localizedDescription]; }]; }]; }]; diff --git a/SignalMessaging/utils/OWSAudioPlayer.m b/SignalMessaging/utils/OWSAudioPlayer.m index 4586aea1f..bb146d117 100644 --- a/SignalMessaging/utils/OWSAudioPlayer.m +++ b/SignalMessaging/utils/OWSAudioPlayer.m @@ -132,8 +132,8 @@ NS_ASSUME_NONNULL_BEGIN if ([error.domain isEqualToString:NSOSStatusErrorDomain] && (error.code == kAudioFileInvalidFileError || error.code == kAudioFileStreamError_InvalidFile)) { - [OWSAlerts showAlertWithTitle:NSLocalizedString(@"ALERT_ERROR_TITLE", @"") - message:NSLocalizedString(@"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE", + [OWSAlerts + showErrorAlertWithMessage:NSLocalizedString(@"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE", @"Message for the alert indicating that an audio file is invalid.")]; } diff --git a/SignalMessaging/views/CommonStrings.swift b/SignalMessaging/views/CommonStrings.swift index 799ea97a2..8ee0a1783 100644 --- a/SignalMessaging/views/CommonStrings.swift +++ b/SignalMessaging/views/CommonStrings.swift @@ -12,11 +12,13 @@ import Foundation @objc static public let dismissButton = NSLocalizedString("DISMISS_BUTTON_TEXT", comment: "Short text to dismiss current modal / actionsheet / screen") @objc - static public let cancelButton = NSLocalizedString("TXT_CANCEL_TITLE", comment:"Label for the cancel button in an alert or action sheet.") + static public let cancelButton = NSLocalizedString("TXT_CANCEL_TITLE", comment: "Label for the cancel button in an alert or action sheet.") @objc - static public let retryButton = NSLocalizedString("RETRY_BUTTON_TEXT", comment:"Generic text for button that retries whatever the last action was.") + static public let retryButton = NSLocalizedString("RETRY_BUTTON_TEXT", comment: "Generic text for button that retries whatever the last action was.") @objc static public let openSettingsButton = NSLocalizedString("OPEN_SETTINGS_BUTTON", comment: "Button text which opens the settings app") + @objc + static public let errorAlertTitle = NSLocalizedString("ALERT_ERROR_TITLE", comment: "") } @objc public class MessageStrings: NSObject { diff --git a/SignalMessaging/views/OWSAlerts.swift b/SignalMessaging/views/OWSAlerts.swift index 1d367cfab..b19c9b892 100644 --- a/SignalMessaging/views/OWSAlerts.swift +++ b/SignalMessaging/views/OWSAlerts.swift @@ -54,6 +54,11 @@ import Foundation CurrentAppContext().frontmostViewController()?.present(alert, animated: true, completion: nil) } + @objc + public class func showErrorAlert(withMessage message: String) { + self.showAlert(withTitle: CommonStrings.errorAlertTitle, message: message, buttonTitle: nil) + } + @objc public class var cancelAction: UIAlertAction { let action = UIAlertAction(title: CommonStrings.cancelButton, style: .cancel) { _ in