Cleaned up some config sync logic and allowed migrations to trigger them

Updated the migrations so they can specify whether a configuration sync is required
Moved the config sync logic into a MessageSender extension (makes far more sense than AppDelegate)
Fixed a bug where the ShareVC was triggering the 'versionMigrationsDidComplete' twice
Removed a couple of imports for files that had been deleted
This commit is contained in:
Morgan Pretty 2022-03-24 10:03:51 +11:00
parent 78c0d000be
commit 5bb3bd7bc1
29 changed files with 131 additions and 136 deletions

View File

@ -622,7 +622,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
// complete, they'll be run the next time the app launches.
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
dispatch_async(dispatch_get_main_queue(), ^{
[[[OWSDatabaseMigrationRunner alloc] init] runAllOutstandingWithCompletion:^{
[[[OWSDatabaseMigrationRunner alloc] init] runAllOutstandingWithCompletion:^(BOOL successful, BOOL needsConfigSync){
resolve(@(1));
}];
});

View File

@ -174,8 +174,7 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
promise = MessageSender.createClosedGroup(name: name, members: selectedContacts, transaction: transaction)
}
let _ = promise.done(on: DispatchQueue.main) { thread in
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside createClosedGroup(...)
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
self?.presentingViewController?.dismiss(animated: true, completion: nil)
SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)
}

View File

@ -54,11 +54,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
Storage.shared.setContact(contact, using: transaction)
},
completion: {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
}
}
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
)
}
@ -1188,9 +1184,7 @@ extension ConversationVC {
}
// Send a sync message with the details of the contact
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.forceSyncConfigurationNowIfNeeded(with: transaction).retainUntilComplete()
}
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
}
}
@ -1254,11 +1248,9 @@ extension ConversationVC {
},
completion: { [weak self] in
// Force a config sync and pop to the previous screen (both must run on the main thread)
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
}
self?.navigationController?.popViewController(animated: true)
}
}

View File

@ -883,10 +883,7 @@ CGFloat kIconViewLength = 24;
// If we successfully blocked then force a config sync
if (isBlocked) {
if ([[UIApplication sharedApplication].delegate isKindOfClass:[AppDelegate class]]) {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate forceSyncConfigurationNowIfNeeded];
}
[SNMessageSender forceSyncConfigurationNow];
}
[weakSelf updateTableContents];
@ -905,10 +902,7 @@ CGFloat kIconViewLength = 24;
// If we successfully unblocked then force a config sync
if (!isBlocked) {
if ([[UIApplication sharedApplication].delegate isKindOfClass:[AppDelegate class]]) {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate forceSyncConfigurationNowIfNeeded];
}
[SNMessageSender forceSyncConfigurationNow];
}
[weakSelf updateTableContents];

View File

@ -75,11 +75,7 @@ final class BlockedModal : Modal {
Storage.shared.setContact(contact, using: transaction)
},
completion: {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
}
}
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
)

View File

@ -72,8 +72,7 @@ final class JoinOpenGroupModal : Modal {
Storage.shared.write { [presentingViewController = self.presentingViewController!] transaction in
OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction)
.done(on: DispatchQueue.main) { _ in
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
.catch(on: DispatchQueue.main) { error in
let alert = UIAlertController(title: "Couldn't Join", message: error.localizedDescription, preferredStyle: .alert)

View File

@ -521,11 +521,9 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
completion: {
DispatchQueue.main.async {
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
}
}
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
)
}
@ -543,11 +541,9 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
completion: {
DispatchQueue.main.async {
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
}
}
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
)
}

View File

@ -362,13 +362,9 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
}
},
completion: {
// Force a config sync (must run on the main thread)
// Force a config sync
if needsSync {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
}
}
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
}
)
@ -398,12 +394,8 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
}
},
completion: {
// Force a config sync (must run on the main thread)
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
}
}
// Force a config sync
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
)
})

View File

@ -173,10 +173,10 @@ static NSTimeInterval launchStartedAt;
[AppEnvironment.shared setup];
[SignalApp.sharedApp setup];
}
migrationCompletion:^{
migrationCompletion:^(BOOL successful, BOOL needsConfigSync){
OWSAssertIsOnMainThread();
[self versionMigrationsDidComplete];
[self versionMigrationsDidCompleteNeedingConfigSync:needsConfigSync];
}];
[SNConfiguration performMainSetup];
@ -411,11 +411,16 @@ static NSTimeInterval launchStartedAt;
}
}
- (void)versionMigrationsDidComplete
- (void)versionMigrationsDidCompleteNeedingConfigSync:(BOOL)needsConfigSync
{
OWSAssertIsOnMainThread();
self.areVersionMigrationsComplete = YES;
// If we need a config sync then trigger it now
if (needsConfigSync) {
[SNMessageSender forceSyncConfigurationNow];
}
[self checkIfAppIsReady];
}

View File

@ -1,4 +1,5 @@
import PromiseKit
import SessionMessagingKit
extension AppDelegate {
@ -7,36 +8,17 @@ extension AppDelegate {
guard Storage.shared.getUser()?.name != nil else { return }
let userDefaults = UserDefaults.standard
let lastSync = userDefaults[.lastConfigurationSync] ?? .distantPast
guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60,
let configurationMessage = ConfigurationMessage.getCurrent() else { return } // Sync every 2 days
let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
Storage.shared.write { transaction in
let job = MessageSendJob(message: configurationMessage, destination: destination)
JobQueue.shared.add(job, using: transaction)
}
guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60 else { return } // Sync every 2 days
// Only update the 'lastConfigurationSync' timestamp if we have done the first sync (Don't want
// a new device config sync to override config syncs from other devices)
if userDefaults[.hasSyncedInitialConfiguration] {
userDefaults[.lastConfigurationSync] = Date()
}
}
func forceSyncConfigurationNowIfNeeded(with transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise<Void> {
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
return Promise.value(())
}
let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
let (promise, seal) = Promise<Void>.pending()
Storage.writeSync { transaction in
MessageSender.send(configurationMessage, to: destination, using: transaction).done {
seal.fulfill(())
}.catch { _ in
seal.fulfill(()) // Fulfill even if this failed; the configuration in the swarm should be at most 2 days old
}.retainUntilComplete()
}
return promise
MessageSender.syncConfiguration(forceSyncNow: false)
.done {
// Only update the 'lastConfigurationSync' timestamp if we have done the first sync (Don't want
// a new device config sync to override config syncs from other devices)
if userDefaults[.hasSyncedInitialConfiguration] {
userDefaults[.lastConfigurationSync] = Date()
}
}
.retainUntilComplete()
}
@objc func startClosedGroupPoller() {
@ -48,10 +30,3 @@ extension AppDelegate {
ClosedGroupPoller.shared.stop()
}
}
extension AppDelegate {
@objc(forceSyncConfigurationNowIfNeeded)
func objc_forceSyncConfigurationNowIfNeeded() {
return forceSyncConfigurationNowIfNeeded(with: nil).retainUntilComplete()
}
}

View File

@ -144,8 +144,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction)
.done(on: DispatchQueue.main) { [weak self] _ in
self?.presentingViewController?.dismiss(animated: true, completion: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
.catch(on: DispatchQueue.main) { [weak self] error in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader

View File

@ -123,9 +123,8 @@ final class NukeDataModal : Modal {
}
@objc private func clearDeviceOnly() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in
appDelegate.forceSyncConfigurationNowIfNeeded().ensure(on: DispatchQueue.main) {
MessageSender.syncConfiguration(forceSyncNow: true).ensure(on: DispatchQueue.main) {
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
General.Cache.cachedEncodedPublicKey = nil // Remove the cached key so it gets re-cached on next access

View File

@ -1,4 +1,5 @@
import UIKit
import SessionMessagingKit
final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
private var profilePictureToBeUploaded: UIImage?
@ -367,8 +368,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
if profilePictureToBeUploaded != nil {
userDefaults[.lastProfilePictureUpdate] = Date()
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
DispatchQueue.main.async {
modalActivityIndicator.dismiss {
guard let self = self else { return }

View File

@ -800,21 +800,10 @@ extension MessageReceiver {
Storage.shared.setContact(contact, using: transaction)
}
// Force a config sync to ensure all devices know the contact approval state if desired (Note: This logic
// should match the behaviour in AppDelegate.forceSyncConfigurationNowIfNeeded())
// Force a config sync to ensure all devices know the contact approval state if desired
guard forceConfigSync else { return }
// Note: We MUST run this async as we need to ensure the database `transaction` has finished before we generate
// a new configuration message (otherwise the `contact` will be loaded direct from the database and the
// `didApproveMe` value won't have been updated)
DispatchQueue.global(qos: .background).async {
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent() else {
return
}
let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey)
MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete()
}
MessageSender.syncConfiguration(forceSyncNow: true, with: transaction).retainUntilComplete()
}
public static func handleMessageRequestResponse(_ message: MessageRequestResponse, using transaction: Any) {

View File

@ -109,8 +109,8 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
appSpecificSingletonBlock: {
SSKEnvironment.shared.notificationsManager = NSENotificationPresenter()
},
migrationCompletion: { [weak self] in
self?.versionMigrationsDidComplete()
migrationCompletion: { [weak self] _, needsConfigSync in
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
completion()
}
)
@ -119,10 +119,15 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
}
@objc
private func versionMigrationsDidComplete() {
private func versionMigrationsDidComplete(needsConfigSync: Bool) {
AssertIsOnMainThread()
areVersionMigrationsComplete = true
// If we need a config sync then trigger it now
if needsConfigSync {
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
checkIsAppReady()
}

View File

@ -46,14 +46,12 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
appSpecificSingletonBlock: {
SSKEnvironment.shared.notificationsManager = NoopNotificationsManager()
},
migrationCompletion: { [weak self] in
migrationCompletion: { [weak self] _, needsConfigSync in
AssertIsOnMainThread()
self?.versionMigrationsDidComplete()
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
self?.versionMigrationsDidComplete()
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
}
)
@ -74,12 +72,17 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
}
@objc
func versionMigrationsDidComplete() {
func versionMigrationsDidComplete(needsConfigSync: Bool) {
AssertIsOnMainThread()
Logger.debug("")
areVersionMigrationsComplete = true
// If we need a config sync then trigger it now
if needsConfigSync {
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
}
checkIsAppReady()
}

View File

@ -40,8 +40,7 @@ public class BlockingManagerRemovalMigration: OWSDatabaseMigration {
self.save(with: transaction) // Intentionally capture self
},
completion: {
// FIXME: Need to force a config sync after this migration
completion()
completion(true, true)
}
)
}

View File

@ -27,7 +27,7 @@ public class ContactsMigration : OWSDatabaseMigration {
}
self.save(with: transaction) // Intentionally capture self
}, completion: {
completion()
completion(true, false)
})
}
}

View File

@ -57,7 +57,7 @@ public class MessageRequestsMigration : OWSDatabaseMigration {
}
self.save(with: transaction) // Intentionally capture self
}, completion: {
completion()
completion(true, true)
})
}
}

View File

@ -6,7 +6,7 @@
NS_ASSUME_NONNULL_BEGIN
typedef void (^OWSDatabaseMigrationCompletion)(void);
typedef void (^OWSDatabaseMigrationCompletion)(BOOL success, BOOL requiresConfigurationSync);
@class OWSPrimaryStorage;

View File

@ -76,7 +76,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSLogInfo(@"Completed migration %@", self.uniqueId);
[self save];
completion();
completion(true, false);
}];
}

View File

@ -4,7 +4,7 @@
NS_ASSUME_NONNULL_BEGIN
typedef void (^OWSDatabaseMigrationCompletion)(void);
typedef void (^OWSDatabaseMigrationCompletion)(BOOL success, BOOL requiresConfigurationSync);
@interface OWSDatabaseMigrationRunner : NSObject

View File

@ -44,7 +44,10 @@ NS_ASSUME_NONNULL_BEGIN
{
[self removeUnknownMigrations];
[self runMigrations:[self.allMigrations mutableCopy] completion:completion];
[self runMigrations:[self.allMigrations mutableCopy]
prevWasSuccessful: true
prevNeedsConfigSync:false
completion:completion];
}
// Some users (especially internal users) will move back and forth between
@ -77,6 +80,8 @@ NS_ASSUME_NONNULL_BEGIN
// * Ensure predictable ordering.
// * Prevent them from interfering with each other (e.g. deadlock).
- (void)runMigrations:(NSMutableArray<OWSDatabaseMigration *> *)migrations
prevWasSuccessful:(BOOL)prevWasSuccessful
prevNeedsConfigSync:(BOOL)prevNeedsConfigSync
completion:(OWSDatabaseMigrationCompletion)completion
{
OWSAssertDebug(migrations);
@ -85,7 +90,7 @@ NS_ASSUME_NONNULL_BEGIN
// If there are no more migrations to run, complete.
if (migrations.count < 1) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
completion(prevWasSuccessful, prevNeedsConfigSync);
});
return;
}
@ -96,14 +101,20 @@ NS_ASSUME_NONNULL_BEGIN
// If migration has already been run, skip it.
if ([OWSDatabaseMigration fetchObjectWithUniqueID:migration.uniqueId] != nil) {
[self runMigrations:migrations completion:completion];
[self runMigrations:migrations
prevWasSuccessful:prevWasSuccessful
prevNeedsConfigSync:prevNeedsConfigSync
completion:completion];
return;
}
OWSLogInfo(@"Running migration: %@", migration);
[migration runUpWithCompletion:^{
[migration runUpWithCompletion:^(BOOL successful, BOOL needsConfigSync){
OWSLogInfo(@"Migration complete: %@", migration);
[self runMigrations:migrations completion:completion];
[self runMigrations:migrations
prevWasSuccessful:(prevWasSuccessful && successful)
prevNeedsConfigSync:(prevNeedsConfigSync || needsConfigSync)
completion:completion];
}];
}

View File

@ -48,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSLogVerbose(@"%lu", (unsigned long)recordIds.count);
if (recordIds.count < 1) {
completion();
completion(true, false);
return;
}

View File

@ -104,4 +104,46 @@ extension MessageSender {
}
return promise
}
public static func syncConfiguration(forceSyncNow: Bool = true, with transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise<Void> {
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
return Promise.value(())
}
let (promise, seal) = Promise<Void>.pending()
let sendMessage: (YapDatabaseReadTransaction) -> () = { transaction in
let destination: Message.Destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
if forceSyncNow {
MessageSender.send(configurationMessage, to: destination, using: transaction).done {
seal.fulfill(())
}.catch { _ in
seal.fulfill(()) // Fulfill even if this failed; the configuration in the swarm should be at most 2 days old
}.retainUntilComplete()
}
else {
let job = MessageSendJob(message: configurationMessage, destination: destination)
JobQueue.shared.add(job, using: transaction)
seal.fulfill(())
}
}
// If we are provided with a transaction then read the data based on the state of the database
// from within the transaction rather than the state in disk
if let transaction: YapDatabaseReadWriteTransaction = transaction {
sendMessage(transaction)
}
else {
Storage.writeSync { transaction in sendMessage(transaction) }
}
return promise
}
}
extension MessageSender {
@objc(forceSyncConfigurationNow)
public static func objc_forceSyncConfigurationNow() {
return syncConfiguration(forceSyncNow: true, with: nil).retainUntilComplete()
}
}

View File

@ -42,7 +42,6 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
#import <SignalUtilitiesKit/OWSUnreadIndicator.h>
#import <SignalUtilitiesKit/OWSViewController.h>
#import <SignalUtilitiesKit/ScreenLockViewController.h>
#import <SignalUtilitiesKit/SelectRecipientViewController.h>
#import <SignalUtilitiesKit/SignalAccount.h>
#import <SignalUtilitiesKit/SSKAsserts.h>
#import <SignalUtilitiesKit/Theme.h>

View File

@ -4,11 +4,13 @@
NS_ASSUME_NONNULL_BEGIN
typedef void (^OWSDatabaseMigrationCompletion)(BOOL success, BOOL requiresConfigurationSync);
// This is _NOT_ a singleton and will be instantiated each time that the SAE is used.
@interface AppSetup : NSObject
+ (void)setupEnvironmentWithAppSpecificSingletonBlock:(dispatch_block_t)appSpecificSingletonBlock
migrationCompletion:(dispatch_block_t)migrationCompletion;
migrationCompletion:(OWSDatabaseMigrationCompletion)migrationCompletion;
@end

View File

@ -8,7 +8,6 @@
#import <SignalUtilitiesKit/OWSDatabaseMigration.h>
#import <SignalUtilitiesKit/OWSProfileManager.h>
#import <SessionMessagingKit/OWSBackgroundTask.h>
#import <SessionMessagingKit/OWSBlockingManager.h>
#import <SessionMessagingKit/OWSDisappearingMessagesJob.h>
#import <SessionMessagingKit/OWSIdentityManager.h>
#import <SessionMessagingKit/OWSOutgoingReceiptManager.h>
@ -23,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation AppSetup
+ (void)setupEnvironmentWithAppSpecificSingletonBlock:(dispatch_block_t)appSpecificSingletonBlock
migrationCompletion:(dispatch_block_t)migrationCompletion
migrationCompletion:(OWSDatabaseMigrationCompletion)migrationCompletion
{
OWSAssertDebug(appSpecificSingletonBlock);
OWSAssertDebug(migrationCompletion);
@ -97,10 +96,10 @@ NS_ASSUME_NONNULL_BEGIN
[OWSStorage registerExtensionsWithMigrationBlock:^() {
dispatch_async(dispatch_get_main_queue(), ^{
// Don't start database migrations until storage is ready.
[VersionMigrations performUpdateCheckWithCompletion:^() {
[VersionMigrations performUpdateCheckWithCompletion:^(BOOL successful, BOOL needsConfigSync) {
OWSAssertIsOnMainThread();
migrationCompletion();
migrationCompletion(successful, needsConfigSync);
OWSAssertDebug(backgroundTask);
backgroundTask = nil;

View File

@ -6,7 +6,7 @@ NS_ASSUME_NONNULL_BEGIN
#define RECENT_CALLS_DEFAULT_KEY @"RPRecentCallsDefaultKey"
typedef void (^VersionMigrationCompletion)(void);
typedef void (^VersionMigrationCompletion)(BOOL success, BOOL requiresConfigurationSync);
@interface VersionMigrations : NSObject