diff --git a/SignalMessaging/groups/GroupV2UpdatesImpl.swift b/SignalMessaging/groups/GroupV2UpdatesImpl.swift index 12887a9b28..39c34019ec 100644 --- a/SignalMessaging/groups/GroupV2UpdatesImpl.swift +++ b/SignalMessaging/groups/GroupV2UpdatesImpl.swift @@ -20,12 +20,8 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { return SSKEnvironment.shared.messageProcessing } - private var groupsV2: GroupsV2 { - return SSKEnvironment.shared.groupsV2 - } - - private var groupsV2Swift: GroupsV2Swift { - return self.groupsV2 as! GroupsV2Swift + private var groupsV2: GroupsV2Swift { + return SSKEnvironment.shared.groupsV2 as! GroupsV2Swift } private var tsAccountManager: TSAccountManager { @@ -305,15 +301,25 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { return self.fetchAndApplyChangeActionsFromService(groupSecretParamsData: groupSecretParamsData, groupUpdateMode: groupUpdateMode) .recover { (error) throws -> Promise in - switch error { - case GroupsV2Error.groupNotInDatabase: - // Unknown groups are handled by snapshot. - break - default: - owsFailDebug("Error: \(error)") - } + let shouldTrySnapshot = { () -> Bool in + // GroupsV2 TODO: This should not fail over in the case of networking problems. + switch error { + case GroupsV2Error.groupNotInDatabase: + // Unknown groups are handled by snapshot. + return true + case GroupsV2Error.unauthorized: + // We can recover from some auth edge cases + // using a snapshot. + return true + default: + owsFailDebug("Error: \(error)") + return false + } + }() - // GroupsV2 TODO: This should not fail over in the case of networking problems. + guard shouldTrySnapshot else { + throw error + } // Failover to applying latest snapshot. return self.fetchAndApplyCurrentGroupV2SnapshotFromService(groupSecretParamsData: groupSecretParamsData, @@ -362,7 +368,7 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { let updatedGroupThread = try GroupManager.updateExistingGroupThreadInDatabaseAndCreateInfoMessage(groupThread: groupThread, newGroupModel: changedGroupModel.newGroupModel, groupUpdateSourceAddress: groupUpdateSourceAddress, - transaction: transaction).groupThread + transaction: transaction).groupThread GroupManager.storeProfileKeysFromGroupProtos(changedGroupModel.profileKeys) @@ -377,9 +383,17 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { private func fetchAndApplyChangeActionsFromService(groupSecretParamsData: Data, groupUpdateMode: GroupUpdateMode) -> Promise { - return firstly { - groupsV2Swift.fetchGroupChangeActions(groupSecretParamsData: groupSecretParamsData) - }.then(on: DispatchQueue.global()) { (groupChanges) throws -> Promise in + return firstly { () -> Promise<[GroupV2Change]> in + var firstKnownRevision: UInt32? + switch groupUpdateMode { + case .upToSpecificRevisionImmediately(let upToRevision): + firstKnownRevision = upToRevision + default: + break + } + return self.groupsV2.fetchGroupChangeActions(groupSecretParamsData: groupSecretParamsData, + firstKnownRevision: firstKnownRevision) + }.then(on: DispatchQueue.global()) { (groupChanges: [GroupV2Change]) throws -> Promise in let groupId = try self.groupsV2.groupId(forGroupSecretParamsData: groupSecretParamsData) return self.tryToApplyGroupChangesFromService(groupId: groupId, groupChanges: groupChanges, @@ -462,7 +476,7 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { groupThread = try GroupManager.updateExistingGroupThreadInDatabaseAndCreateInfoMessage(groupThread: groupThread, newGroupModel: newGroupModel, groupUpdateSourceAddress: groupUpdateSourceAddress, - transaction: transaction).groupThread + transaction: transaction).groupThread } return groupThread @@ -474,7 +488,7 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { private func fetchAndApplyCurrentGroupV2SnapshotFromService(groupSecretParamsData: Data, groupUpdateMode: GroupUpdateMode) -> Promise { return firstly { - self.groupsV2Swift.fetchCurrentGroupV2Snapshot(groupSecretParamsData: groupSecretParamsData) + self.groupsV2.fetchCurrentGroupV2Snapshot(groupSecretParamsData: groupSecretParamsData) }.then(on: .global()) { groupV2Snapshot in return self.tryToApplyCurrentGroupV2SnapshotFromService(groupV2Snapshot: groupV2Snapshot, groupUpdateMode: groupUpdateMode) @@ -502,9 +516,9 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { // author(s) of changes reflected in the snapshot. let groupUpdateSourceAddress: SignalServiceAddress? = nil let result = try GroupManager.tryToUpsertExistingGroupThreadInDatabaseAndCreateInfoMessage(newGroupModel: newGroupModel, - groupUpdateSourceAddress: groupUpdateSourceAddress, - canInsert: true, - transaction: transaction) + groupUpdateSourceAddress: groupUpdateSourceAddress, + canInsert: true, + transaction: transaction) GroupManager.storeProfileKeysFromGroupProtos(groupV2Snapshot.profileKeys) diff --git a/SignalMessaging/groups/GroupsV2Impl.swift b/SignalMessaging/groups/GroupsV2Impl.swift index d4c8dabae8..9181aa9381 100644 --- a/SignalMessaging/groups/GroupsV2Impl.swift +++ b/SignalMessaging/groups/GroupsV2Impl.swift @@ -271,7 +271,8 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { // MARK: - Fetch Group Change Actions - public func fetchGroupChangeActions(groupSecretParamsData: Data) -> Promise<[GroupV2Change]> { + public func fetchGroupChangeActions(groupSecretParamsData: Data, + firstKnownRevision: UInt32?) -> Promise<[GroupV2Change]> { guard let localUuid = tsAccountManager.localUuid else { return Promise(error: OWSAssertionError("Missing localUuid.")) } @@ -282,19 +283,25 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { }.then(on: DispatchQueue.global()) { (groupId: Data, groupV2Params: GroupV2Params) -> Promise<[GroupV2Change]> in return self.fetchGroupChangeActions(groupId: groupId, groupV2Params: groupV2Params, - localUuid: localUuid) + localUuid: localUuid, + firstKnownRevision: firstKnownRevision) } } private func fetchGroupChangeActions(groupId: Data, groupV2Params: GroupV2Params, - localUuid: UUID) -> Promise<[GroupV2Change]> { + localUuid: UUID, + firstKnownRevision: UInt32?) -> Promise<[GroupV2Change]> { let requestBuilder: RequestBuilder = { (authCredential, sessionManager) in return DispatchQueue.global().async(.promise) { () -> NSURLRequest in let fromRevision = try self.databaseStorage.read { (transaction) throws -> UInt32 in guard let groupThread = TSGroupThread.fetch(groupId: groupId, transaction: transaction) else { // This probably isn't an error and will be handled upstream. + if let firstKnownRevision = firstKnownRevision { + Logger.info("Group not in database, using first known revision.") + return firstKnownRevision + } throw GroupsV2Error.groupNotInDatabase } guard groupThread.groupModel.groupsVersion == .V2 else { diff --git a/SignalServiceKit/src/Network/Receiving/GroupsV2MessageProcessor.swift b/SignalServiceKit/src/Network/Receiving/GroupsV2MessageProcessor.swift index 066658f31f..a5185ee952 100644 --- a/SignalServiceKit/src/Network/Receiving/GroupsV2MessageProcessor.swift +++ b/SignalServiceKit/src/Network/Receiving/GroupsV2MessageProcessor.swift @@ -589,8 +589,8 @@ class IncomingGroupsV2MessageQueue: NSObject { let groupUpdateMode = GroupUpdateMode.upToSpecificRevisionImmediately(upToRevision: groupContext.revision) return firstly { groupV2UpdatesSwift.tryToRefreshV2GroupThreadWithThrottling(groupId: groupContextInfo.groupId, - groupSecretParamsData: groupContextInfo.groupSecretParamsData, - groupUpdateMode: groupUpdateMode) + groupSecretParamsData: groupContextInfo.groupSecretParamsData, + groupUpdateMode: groupUpdateMode) }.map(on: .global()) { (_) in return UpdateOutcome.successShouldProcess }.recover(on: .global()) { error -> Guarantee in diff --git a/SignalServiceKit/src/groups/GroupsV2.swift b/SignalServiceKit/src/groups/GroupsV2.swift index b7ebf1c84d..17471eae90 100644 --- a/SignalServiceKit/src/groups/GroupsV2.swift +++ b/SignalServiceKit/src/groups/GroupsV2.swift @@ -55,7 +55,8 @@ public protocol GroupsV2Swift: GroupsV2 { func fetchCurrentGroupV2Snapshot(groupSecretParamsData: Data) -> Promise - func fetchGroupChangeActions(groupSecretParamsData: Data) -> Promise<[GroupV2Change]> + func fetchGroupChangeActions(groupSecretParamsData: Data, + firstKnownRevision: UInt32?) -> Promise<[GroupV2Change]> func buildChangeSet(oldGroupModel: TSGroupModel, newGroupModel: TSGroupModel, @@ -94,6 +95,8 @@ public enum GroupUpdateMode { // * Group update should halt at a specific revision. // * Group update _should not_ block on message processing. // * Group update _should not_ be throttled. + // + // upToRevision is inclusive. case upToSpecificRevisionImmediately(upToRevision: UInt32) // * Group update should continue until current revision. // * Group update _should_ block on message processing. @@ -222,7 +225,8 @@ public class MockGroupsV2: NSObject, GroupsV2, GroupsV2Swift { owsFail("Not implemented.") } - public func fetchGroupChangeActions(groupSecretParamsData: Data) -> Promise<[GroupV2Change]> { + public func fetchGroupChangeActions(groupSecretParamsData: Data, + firstKnownRevision: UInt32?) -> Promise<[GroupV2Change]> { owsFail("Not implemented.") }