Wait during registration for storage service restore and re-populate local profile if available

This commit is contained in:
Nora Trapp 2020-02-11 12:51:32 -08:00
parent 2fde911cab
commit 724b483d00
12 changed files with 121 additions and 51 deletions

View File

@ -174,8 +174,19 @@ public class AccountManager: NSObject {
throw error
}
}
}.done { (_) -> Void in
}.done {
self.completeRegistration()
}.then { _ -> Promise<Void> in
BenchEventStart(title: "waiting for initial storage service restore", eventId: "initial-storage-service-restore")
return StorageServiceManager.shared.restoreOrCreateManifestIfNecessary().asVoid().ensure {
BenchEventComplete(eventId: "initial-storage-service-restore")
}.timeout(seconds: 60)
}.then { _ -> Promise<Void> in
// In the case that we restored our profile from a previous registration,
// re-upload it so that the user does not need to refill in all the details.
// Right now the avatar will always be lost since we do not store avatars in
// the storage service.
self.profileManager.reuploadLocalProfilePromise()
}
registrationPromise.retainUntilComplete()
@ -236,15 +247,32 @@ public class AccountManager: NSObject {
}
}.then {
self.deviceService.updateCapabilities()
}.then { _ -> Promise<Void> in
}.done {
self.completeRegistration()
}.then { _ -> Promise<Void> in
BenchEventStart(title: "waiting for initial storage service restore", eventId: "initial-storage-service-restore")
self.databaseStorage.asyncWrite { transaction in
OWSSyncManager.shared().sendKeysSyncRequestMessage(transaction: transaction)
}
let storageServiceRestorePromise = firstly {
NotificationCenter.default.observe(once: .OWSSyncManagerKeysSyncDidComplete).asVoid()
}.then {
StorageServiceManager.shared.restoreOrCreateManifestIfNecessary().asVoid()
}.ensure {
BenchEventComplete(eventId: "initial-storage-service-restore")
}.timeout(seconds: 60)
// we wait a bit for the initial syncs to come in before proceeding to the inbox
// because we want to present the inbox already populated with groups and contacts,
// rather than have the trickle in moments later.
// TODO: Eventually, we can rely entirely on the storage service and will no longer
// need to do any initial sync beyond the "keys" sync. For now, we try and do both
// operations in parallel.
BenchEventStart(title: "waiting for initial contact and group sync", eventId: "initial-contact-sync")
return firstly {
let initialSyncMessagePromise = firstly {
OWSSyncManager.shared().sendInitialSyncRequestsAwaitingCreatedThreadOrdering(timeoutSeconds: 60)
}.done(on: .global() ) { orderedThreadIds in
Logger.debug("orderedThreadIds: \(orderedThreadIds)")
@ -265,6 +293,8 @@ public class AccountManager: NSObject {
}.ensure {
BenchEventComplete(eventId: "initial-contact-sync")
}
return when(fulfilled: [storageServiceRestorePromise, initialSyncMessagePromise])
}
}

View File

@ -483,6 +483,12 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat
case ProfileViewMode_Registration:
self.navigationItem.hidesBackButton = YES;
self.navigationItem.rightBarButtonItem = nil;
// During registration, if you have a name pre-populatd we want
// to enable the save button even if you haven't edited anything.
if (self.givenNameTextField.text.length > 0) {
forceSaveButtonEnabled = YES;
}
break;
case ProfileViewMode_ExperienceUpgrade:
self.navigationItem.rightBarButtonItem = nil;

View File

@ -1,14 +1,15 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
@objc(OWSStorageServiceManager)
class StorageServiceManager: NSObject, StorageServiceManagerProtocol {
public class StorageServiceManager: NSObject, StorageServiceManagerProtocol {
@objc
static let shared = StorageServiceManager()
public static let shared = StorageServiceManager()
var tsAccountManager: TSAccountManager {
return SSKEnvironment.shared.tsAccountManager
@ -18,35 +19,24 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol {
super.init()
AppReadiness.runNowOrWhenAppDidBecomeReady {
self.registrationStateDidChange()
NotificationCenter.default.addObserver(
self,
selector: #selector(self.registrationStateDidChange),
name: .RegistrationStateDidChange,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.willResignActive),
name: .OWSApplicationWillResignActive,
object: nil
)
guard self.tsAccountManager.isRegisteredAndReady else { return }
// Schedule a restore. This will do nothing unless we've never
// registered a manifest before.
self.restoreOrCreateManifestIfNecessary()
// If we have any pending changes since we last launch, back them up now.
self.backupPendingChanges()
}
}
@objc private func registrationStateDidChange() {
guard self.tsAccountManager.isRegisteredAndReady else { return }
// Schedule a restore. This will do nothing unless we've never
// registered a manifest before.
restoreOrCreateManifestIfNecessary()
// If we have any pending changes since we last launch, back them up now.
backupPendingChanges()
}
@objc private func willResignActive() {
// If we have any pending changes, start a back up immediately
// to try and make sure the service doesn't get stale. If for
@ -58,7 +48,7 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol {
// MARK: -
@objc
func recordPendingDeletions(deletedIds: [AccountId]) {
public func recordPendingDeletions(deletedIds: [AccountId]) {
let operation = StorageServiceOperation.recordPendingDeletions(deletedIds)
StorageServiceOperation.operationQueue.addOperation(operation)
@ -68,7 +58,7 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol {
}
@objc
func recordPendingDeletions(deletedAddresses: [SignalServiceAddress]) {
public func recordPendingDeletions(deletedAddresses: [SignalServiceAddress]) {
let operation = StorageServiceOperation.recordPendingDeletions(deletedAddresses)
StorageServiceOperation.operationQueue.addOperation(operation)
@ -78,7 +68,7 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol {
}
@objc
func recordPendingUpdates(updatedIds: [AccountId]) {
public func recordPendingUpdates(updatedIds: [AccountId]) {
let operation = StorageServiceOperation.recordPendingUpdates(updatedIds)
StorageServiceOperation.operationQueue.addOperation(operation)
@ -88,7 +78,7 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol {
}
@objc
func recordPendingUpdates(updatedAddresses: [SignalServiceAddress]) {
public func recordPendingUpdates(updatedAddresses: [SignalServiceAddress]) {
let operation = StorageServiceOperation.recordPendingUpdates(updatedAddresses)
StorageServiceOperation.operationQueue.addOperation(operation)
@ -98,15 +88,17 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol {
}
@objc
func backupPendingChanges() {
public func backupPendingChanges() {
let operation = StorageServiceOperation(mode: .backup)
StorageServiceOperation.operationQueue.addOperation(operation)
}
@objc
func restoreOrCreateManifestIfNecessary() {
@discardableResult
public func restoreOrCreateManifestIfNecessary() -> AnyPromise {
let operation = StorageServiceOperation(mode: .restoreOrCreate)
StorageServiceOperation.operationQueue.addOperation(operation)
return AnyPromise(operation.promise)
}
// MARK: - Backup Scheduling
@ -183,14 +175,28 @@ class StorageServiceOperation: OWSOperation {
}
private let mode: Mode
let promise: Promise<Void>
private let resolver: Resolver<Void>
fileprivate init(mode: Mode) {
self.mode = mode
(self.promise, self.resolver) = Promise<Void>.pending()
super.init()
self.remainingRetries = 4
}
// MARK: - Run
override func didSucceed() {
super.didSucceed()
resolver.fulfill(())
}
override func didFail(error: Error) {
super.didFail(error: error)
resolver.reject(error)
}
// Called every retry, this is where the bulk of the operation's work should go.
override public func run() {
Logger.info("\(mode)")

View File

@ -148,6 +148,12 @@ extension StorageServiceProtoContactRecord {
mergeState = .needsUpdate(recipient.accountId)
}
// The only thing we currently want to preserve for the local user is profile
// information. Everything else doesn't make sense to store / update and can
// lead to us being in a weird state such as thinking our own safety number changed.
// If more data needs to be restored for the local user it should be done above this line.
guard !address.isLocalAddress else { return mergeState }
// If our local identity differs from the service, use the service's value.
if let identityKey = identity?.key, let identityState = identity?.state?.verificationState,
localIdentityKey != identityKey || localIdentityState != identityState {

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
#import <SignalServiceKit/SignalServiceKit-Swift.h>
@ -7,6 +7,7 @@
NS_ASSUME_NONNULL_BEGIN
extern NSString *const OWSSyncManagerConfigurationSyncDidCompleteNotification;
extern NSString *const OWSSyncManagerKeysSyncDidCompleteNotification;
@class AnyPromise;
@class OWSContactsManager;

View File

@ -32,6 +32,7 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const OWSSyncManagerConfigurationSyncDidCompleteNotification = @"OWSSyncManagerConfigurationSyncDidCompleteNotification";
NSString *const OWSSyncManagerKeysSyncDidCompleteNotification = @"OWSSyncManagerKeysSyncDidCompleteNotification";
NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManagerLastMessageKey";

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -39,7 +39,8 @@ extension OWSSyncManager: SyncManagerProtocolSwift {
NotificationCenter.default.observe(once: .IncomingContactSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .IncomingGroupSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .OWSSyncManagerConfigurationSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .OWSBlockingManagerBlockedSyncDidComplete).asVoid()
NotificationCenter.default.observe(once: .OWSBlockingManagerBlockedSyncDidComplete).asVoid(),
NotificationCenter.default.observe(once: .OWSSyncManagerKeysSyncDidComplete).asVoid()
])
}
@ -74,6 +75,10 @@ extension OWSSyncManager: SyncManagerProtocolSwift {
}
KeyBackupService.storeSyncedKey(type: .storageService, data: syncMessage.storageService, transaction: transaction)
transaction.addAsyncCompletion {
NotificationCenter.default.postNotificationNameAsync(.OWSSyncManagerKeysSyncDidComplete, object: nil)
}
}
@objc

View File

@ -1050,6 +1050,13 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640;
return;
}
// We also keep track of our local profile key under a special hard coded address,
// update it accordingly. This should generally only happen if we're restoring
// our profile data from the storage service.
if (address.isLocalAddress) {
[self setLocalProfileKey:profileKey transaction:transaction];
}
OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForAddress:address transaction:transaction];
OWSAssertDebug(userProfile);
@ -1082,6 +1089,13 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640;
OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForAddress:address transaction:transaction];
[userProfile updateWithGivenName:givenName familyName:familyName transaction:transaction completion:nil];
if (address.isLocalAddress) {
[self.localUserProfile updateWithGivenName:givenName
familyName:familyName
transaction:transaction
completion:nil];
}
}
- (nullable NSData *)profileKeyDataForAddress:(SignalServiceAddress *)address

View File

@ -1,8 +1,9 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
@objc(OWSFakeStorageServiceManager)
class FakeStorageServiceManager: NSObject, StorageServiceManagerProtocol {
@ -13,5 +14,5 @@ class FakeStorageServiceManager: NSObject, StorageServiceManagerProtocol {
func recordPendingUpdates(updatedAddresses: [SignalServiceAddress]) {}
func backupPendingChanges() {}
func restoreOrCreateManifestIfNecessary() {}
func restoreOrCreateManifestIfNecessary() -> AnyPromise { AnyPromise(Promise.value(())) }
}

View File

@ -293,10 +293,7 @@ public class KeyBackupService: NSObject {
default:
// Most keys derive directly from the master key.
// Only a few exceptions derive from another derived key.
guard let masterKey = cacheQueue.sync(execute: { cachedMasterKey }) else {
owsFailDebug("missing master key")
return nil
}
guard let masterKey = cacheQueue.sync(execute: { cachedMasterKey }) else { return nil }
return masterKey
}
}
@ -543,15 +540,13 @@ public class KeyBackupService: NSObject {
}
// Only continue if we didn't previously have a master key or our master key has changed
guard masterKey != previousMasterKey else { return }
guard masterKey != previousMasterKey, tsAccountManager.isRegisteredAndReady else { return }
// Trigger a re-creation of the storage manifest, our keys have changed
storageServiceManager.restoreOrCreateManifestIfNecessary()
if tsAccountManager.isRegisteredAndReady {
// Sync our new keys with linked devices.
syncManager.sendKeysSyncMessage()
}
// Sync our new keys with linked devices.
syncManager.sendKeysSyncMessage()
}
public static func storeSyncedKey(type: DerivedKey, data: Data?, transaction: SDSAnyWriteTransaction) {

View File

@ -324,11 +324,15 @@ NSUInteger const kUserProfileSchemaVersion = 1;
return;
}
// Profile changes, record updates with storage service
[self.storageServiceManager recordPendingUpdatesWithUpdatedAddresses:@[ self.address ]];
BOOL isLocalUserProfile = [self.address.phoneNumber isEqualToString:kLocalProfileUniqueId];
// Profile changes, record updates with storage service
if (self.tsAccountManager.isRegisteredAndReady) {
[self.storageServiceManager
recordPendingUpdatesWithUpdatedAddresses:@[ isLocalUserProfile ? self.tsAccountManager.localAddress
: self.address ]];
}
[transaction
addAsyncCompletionWithQueue:dispatch_get_main_queue()
block:^{

View File

@ -14,7 +14,9 @@ public protocol StorageServiceManagerProtocol {
func recordPendingUpdates(updatedAddresses: [SignalServiceAddress])
func backupPendingChanges()
func restoreOrCreateManifestIfNecessary()
@discardableResult
func restoreOrCreateManifestIfNecessary() -> AnyPromise
}
public struct StorageService {
@ -306,7 +308,6 @@ public struct StorageService {
case 404:
status = .notFound
default:
owsFailDebug("invalid response \(response.statusCode)")
if response.statusCode >= 500 {
// This is a server error, retry
return resolver.reject(StorageError.retryableAssertion)