diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index d83175045e..0970006cbb 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -3527,6 +3527,128 @@ }; name = "Release AppStore"; }; + D0CE6EF5213DC30700BCD44B /* Release AppStore LLC */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release AppStore LLC"; + }; + D0CE6EF6213DC30700BCD44B /* Release AppStore LLC */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + COPY_PHASE_STRIP = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = TelegramCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/third-party/FFmpeg-iOS/lib", + "$(PROJECT_DIR)/third-party/libwebp/lib", + ); + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/TelegramCore/module.private.modulemap"; + OTHER_LDFLAGS = "-Wl,-dead_strip"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 4.0; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + }; + name = "Release AppStore LLC"; + }; + D0CE6EF7213DC30700BCD44B /* Release AppStore LLC */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + INFOPLIST_FILE = TelegramCoreTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCoreTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = "Release AppStore LLC"; + }; + D0CE6EF8213DC30700BCD44B /* Release AppStore LLC */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = TelegramCoreMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/TelegramCore/module.private-mac.modulemap"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCoreMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + }; + name = "Release AppStore LLC"; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -3539,6 +3661,7 @@ C22069BE1E8EB4A200E82730 /* Release Hockeyapp */, D0924FE81FE52C12003F693F /* Release Hockeyapp Internal */, D06706551D51162400DED3E3 /* Release AppStore */, + D0CE6EF5213DC30700BCD44B /* Release AppStore LLC */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Debug AppStore"; @@ -3552,6 +3675,7 @@ C22069BF1E8EB4A200E82730 /* Release Hockeyapp */, D0924FE91FE52C12003F693F /* Release Hockeyapp Internal */, D06706561D51162400DED3E3 /* Release AppStore */, + D0CE6EF6213DC30700BCD44B /* Release AppStore LLC */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Debug AppStore"; @@ -3565,6 +3689,7 @@ C22069C01E8EB4A200E82730 /* Release Hockeyapp */, D0924FEA1FE52C12003F693F /* Release Hockeyapp Internal */, D06706571D51162400DED3E3 /* Release AppStore */, + D0CE6EF7213DC30700BCD44B /* Release AppStore LLC */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Debug AppStore"; @@ -3578,6 +3703,7 @@ C22069C11E8EB4A200E82730 /* Release Hockeyapp */, D0924FEB1FE52C12003F693F /* Release Hockeyapp Internal */, D0B4186F1D7E03D5004562A4 /* Release AppStore */, + D0CE6EF8213DC30700BCD44B /* Release AppStore LLC */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Debug AppStore"; diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index e931c627c5..5d93848314 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -850,7 +850,7 @@ public class Account { private let serviceQueue = Queue() public private(set) var stateManager: AccountStateManager! - private var contactSyncManager: ContactSyncManager! + private(set) var contactSyncManager: ContactSyncManager! public private(set) var callSessionManager: CallSessionManager! public private(set) var viewTracker: AccountViewTracker! public private(set) var pendingMessageManager: PendingMessageManager! @@ -1183,6 +1183,7 @@ public class Account { public func resetStateManagement() { self.stateManager.reset() self.contactSyncManager.beginSync(importableContacts: self.importableContacts.get()) + self.managedStickerPacksDisposable.set(manageStickerPacks(network: self.network, postbox: self.postbox).start()) } public func peerInputActivities(peerId: PeerId) -> Signal<[(PeerId, PeerInputActivity)], NoError> { @@ -1246,5 +1247,4 @@ public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: account.transformOutgoingMessageMedia = transformOutgoingMessageMedia account.pendingMessageManager.transformOutgoingMessageMedia = transformOutgoingMessageMedia - account.managedStickerPacksDisposable.set(manageStickerPacks(network: account.network, postbox: account.postbox).start()) } diff --git a/TelegramCore/AccountIntermediateState.swift b/TelegramCore/AccountIntermediateState.swift index aa65909961..3aa19330c4 100644 --- a/TelegramCore/AccountIntermediateState.swift +++ b/TelegramCore/AccountIntermediateState.swift @@ -73,6 +73,7 @@ enum AccountStateMutationOperation { case UpdateGlobalNotificationSettings(AccountStateGlobalNotificationSettingsSubject, MessageNotificationSettings) case MergeApiChats([Api.Chat]) case UpdatePeer(PeerId, (Peer?) -> Peer?) + case UpdateIsContact(PeerId, Bool) case UpdateCachedPeerData(PeerId, (CachedPeerData?) -> CachedPeerData?) case MergeApiUsers([Api.User]) case MergePeerPresences([PeerId: PeerPresence]) @@ -246,6 +247,10 @@ struct AccountMutableState { self.addOperation(.UpdatePeer(id, f)) } + mutating func updatePeerIsContact(_ id: PeerId, isContact: Bool) { + self.addOperation(.UpdateIsContact(id, isContact)) + } + mutating func updateCachedPeerData(_ id: PeerId, _ f: @escaping (CachedPeerData?) -> CachedPeerData?) { self.addOperation(.UpdateCachedPeerData(id, f)) } @@ -328,7 +333,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact: break case let .AddMessages(messages, _): for message in messages { @@ -399,6 +404,7 @@ struct AccountReplayedFinalState { let updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]] let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] + let isContactUpdates: [(PeerId, Bool)] let delayNotificatonsUntil: Int32? } @@ -407,11 +413,12 @@ struct AccountFinalStateEvents { let updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]] let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] + let isContactUpdates: [(PeerId, Bool)] let displayAlerts: [String] let delayNotificatonsUntil: Int32? var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.displayAlerts.isEmpty && delayNotificatonsUntil == nil + return self.addedIncomingMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && delayNotificatonsUntil == nil } init() { @@ -419,15 +426,17 @@ struct AccountFinalStateEvents { self.updatedTypingActivities = [:] self.updatedWebpages = [:] self.updatedCalls = [] + self.isContactUpdates = [] self.displayAlerts = [] self.delayNotificatonsUntil = nil } - init(addedIncomingMessageIds: [MessageId], updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]], updatedWebpages: [MediaId: TelegramMediaWebpage], updatedCalls: [Api.PhoneCall], displayAlerts: [String], delayNotificatonsUntil: Int32?) { + init(addedIncomingMessageIds: [MessageId], updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]], updatedWebpages: [MediaId: TelegramMediaWebpage], updatedCalls: [Api.PhoneCall], isContactUpdates: [(PeerId, Bool)], displayAlerts: [String], delayNotificatonsUntil: Int32?) { self.addedIncomingMessageIds = addedIncomingMessageIds self.updatedTypingActivities = updatedTypingActivities self.updatedWebpages = updatedWebpages self.updatedCalls = updatedCalls + self.isContactUpdates = isContactUpdates self.displayAlerts = displayAlerts self.delayNotificatonsUntil = delayNotificatonsUntil } @@ -437,6 +446,7 @@ struct AccountFinalStateEvents { self.updatedTypingActivities = state.updatedTypingActivities self.updatedWebpages = state.updatedWebpages self.updatedCalls = state.updatedCalls + self.isContactUpdates = state.isContactUpdates self.displayAlerts = state.state.state.displayAlerts self.delayNotificatonsUntil = state.delayNotificatonsUntil } @@ -448,6 +458,6 @@ struct AccountFinalStateEvents { delayNotificatonsUntil = other } } - return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil) + return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil) } } diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index e6c0fdfd2e..126383b73d 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -1081,6 +1081,15 @@ private func finalStateWithUpdatesAndServerTime(account: Account, state: Account return peer } }) + case let .updateContactLink(userId, myLink, _): + let isContact: Bool + switch myLink { + case .contactLinkContact: + isContact = true + default: + isContact = false + } + updatedState.updatePeerIsContact(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), isContact: isContact) case let .updateEncryption(chat, date): updatedState.updateSecretChat(chat: chat, timestamp: date) case let .updateNewEncryptedMessage(message, _): @@ -1813,7 +1822,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -1864,6 +1873,7 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, transaction: Tr var updatedSecretChatTypingActivities = Set() var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedCalls: [Api.PhoneCall] = [] + var isContactUpdates: [(PeerId, Bool)] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] var recentlyUsedStickers: [MediaId: (MessageIndex, TelegramMediaFile)] = [:] var recentlyUsedGifs: [MediaId: (MessageIndex, TelegramMediaFile)] = [:] @@ -2221,6 +2231,8 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, transaction: Tr } else { pollLangPack = true } + case let .UpdateIsContact(peerId, value): + isContactUpdates.append((peerId, value)) } } @@ -2445,5 +2457,5 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, transaction: Tr addedIncomingMessageIds.append(contentsOf: addedSecretMessageIds) - return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, addedSecretMessageIds: addedSecretMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, delayNotificatonsUntil: delayNotificatonsUntil) + return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, addedSecretMessageIds: addedSecretMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil) } diff --git a/TelegramCore/AccountStateManager.swift b/TelegramCore/AccountStateManager.swift index 188fae0fbc..ffa64b63c6 100644 --- a/TelegramCore/AccountStateManager.swift +++ b/TelegramCore/AccountStateManager.swift @@ -9,7 +9,7 @@ import Foundation import MtProtoKitDynamic #endif -private enum AccountStateManagerOperation { +private enum AccountStateManagerOperationContent { case pollDifference(AccountFinalStateEvents) case collectUpdateGroups([UpdateGroup], Double) case processUpdateGroups([UpdateGroup]) @@ -19,6 +19,20 @@ private enum AccountStateManagerOperation { case replayAsynchronouslyBuiltFinalState(AccountFinalState, () -> Void) } +private final class AccountStateManagerOperation { + var isRunning: Bool = false + let content: AccountStateManagerOperationContent + + init(content: AccountStateManagerOperationContent) { + self.content = content + } +} + +private enum AccountStateManagerAddOperationPosition { + case first + case last +} + #if os(macOS) private typealias SignalKitTimer = SwiftSignalKitMac.Timer #else @@ -137,22 +151,17 @@ public final class AccountStateManager { func addUpdateGroups(_ groups: [UpdateGroup]) { self.queue.async { if let last = self.operations.last { - switch last { + switch last.content { case .pollDifference, .processUpdateGroups, .custom, .pollCompletion, .processEvents, .replayAsynchronouslyBuiltFinalState: - self.operations.append(.collectUpdateGroups(groups, 0.0)) + self.addOperation(.collectUpdateGroups(groups, 0.0), position: .last) case let .collectUpdateGroups(currentGroups, timeout): - if timeout.isEqual(to: 0.0) { - self.operations[self.operations.count - 1] = .collectUpdateGroups(currentGroups + groups, timeout) - } else { - self.operations[self.operations.count - 1] = .processUpdateGroups(currentGroups + groups) - if self.operations.count == 1 { - self.startFirstOperation() - } - } + let operation = AccountStateManagerOperation(content: .collectUpdateGroups(currentGroups + groups, timeout)) + operation.isRunning = last.isRunning + self.operations[self.operations.count - 1] = operation + self.startFirstOperation() } } else { - self.operations.append(.collectUpdateGroups(groups, 0.0)) - self.startFirstOperation() + self.addOperation(.collectUpdateGroups(groups, 0.0), position: .last) } } } @@ -160,14 +169,10 @@ public final class AccountStateManager { func addReplayAsynchronouslyBuiltFinalState(_ finalState: AccountFinalState) -> Signal { return Signal { subscriber in self.queue.async { - let begin = self.operations.isEmpty - self.operations.append(.replayAsynchronouslyBuiltFinalState(finalState, { + self.addOperation(.replayAsynchronouslyBuiltFinalState(finalState, { subscriber.putNext(true) subscriber.putCompletion() - })) - if begin { - self.startFirstOperation() - } + }), position: .last) } return EmptyDisposable } @@ -199,60 +204,78 @@ public final class AccountStateManager { }) } - self.addOperation(.custom(self.getNextId(), signal)) + self.addOperation(.custom(self.getNextId(), signal), position: .last) return disposable } |> runOn(self.queue) } - private func replaceOperations(with operation: AccountStateManagerOperation) { - var collectedProcessUpdateGroups: [(AccountStateManagerOperation, Bool)] = [] + private func replaceOperations(with content: AccountStateManagerOperationContent) { + var collectedProcessUpdateGroups: [AccountStateManagerOperationContent] = [] var collectedMessageIds: [MessageId] = [] var collectedPollCompletionSubscribers: [(Int32, ([MessageId]) -> Void)] = [] var collectedReplayAsynchronouslyBuiltFinalState: [(AccountFinalState, () -> Void)] = [] var processEvents: [(Int32, AccountFinalStateEvents)] = [] + var replacedOperations: [AccountStateManagerOperation] = [] + for i in 0 ..< self.operations.count { - switch self.operations[i] { - case .processUpdateGroups: - collectedProcessUpdateGroups.append((self.operations[i], i == 0)) - case let .pollCompletion(_, messageIds, subscribers): - collectedMessageIds.append(contentsOf: messageIds) - collectedPollCompletionSubscribers.append(contentsOf: subscribers) - case let .replayAsynchronouslyBuiltFinalState(finalState, completion): - collectedReplayAsynchronouslyBuiltFinalState.append((finalState, completion)) - case let .processEvents(operationId, events): - processEvents.append((operationId, events)) - default: - break + if self.operations[i].isRunning { + replacedOperations.append(self.operations[i]) + } else { + switch self.operations[i].content { + case .processUpdateGroups: + collectedProcessUpdateGroups.append(self.operations[i].content) + case let .pollCompletion(_, messageIds, subscribers): + collectedMessageIds.append(contentsOf: messageIds) + collectedPollCompletionSubscribers.append(contentsOf: subscribers) + case let .replayAsynchronouslyBuiltFinalState(finalState, completion): + collectedReplayAsynchronouslyBuiltFinalState.append((finalState, completion)) + case let .processEvents(operationId, events): + processEvents.append((operationId, events)) + default: + break + } } } - self.operations.removeAll() + replacedOperations.append(contentsOf: collectedProcessUpdateGroups.map { AccountStateManagerOperation(content: $0) }) - self.operations.append(contentsOf: collectedProcessUpdateGroups.map { $0.0 }) - - self.operations.append(operation) + replacedOperations.append(AccountStateManagerOperation(content: content)) if !collectedPollCompletionSubscribers.isEmpty || !collectedMessageIds.isEmpty { - self.operations.append(.pollCompletion(self.getNextId(), collectedMessageIds, collectedPollCompletionSubscribers)) + replacedOperations.append(AccountStateManagerOperation(content: .pollCompletion(self.getNextId(), collectedMessageIds, collectedPollCompletionSubscribers))) } for (finalState, completion) in collectedReplayAsynchronouslyBuiltFinalState { - self.operations.append(.replayAsynchronouslyBuiltFinalState(finalState, completion)) + replacedOperations.append(AccountStateManagerOperation(content: .replayAsynchronouslyBuiltFinalState(finalState, completion))) } for (operationId, events) in processEvents { - self.operations.append(.processEvents(operationId, events)) + replacedOperations.append(AccountStateManagerOperation(content: .processEvents(operationId, events))) } + + self.operations.removeAll() + self.operations.append(contentsOf: replacedOperations) } - private func addOperation(_ operation: AccountStateManagerOperation) { + private func addOperation(_ content: AccountStateManagerOperationContent, position: AccountStateManagerAddOperationPosition) { self.queue.async { - let begin = self.operations.isEmpty - self.operations.append(operation) - if begin { - self.startFirstOperation() + let operation = AccountStateManagerOperation(content: content) + switch position { + case .first: + if self.operations.isEmpty || !self.operations[0].isRunning { + self.operations.insert(operation, at: 0) + self.startFirstOperation() + } else { + self.operations.insert(operation, at: 1) + } + case .last: + let begin = self.operations.isEmpty + self.operations.append(operation) + if begin { + self.startFirstOperation() + } } } } @@ -261,7 +284,11 @@ public final class AccountStateManager { guard let operation = self.operations.first else { return } - switch operation { + guard !operation.isRunning else { + return + } + operation.isRunning = true + switch operation.content { case let .pollDifference(currentEvents): self.operationTimer?.invalidate() self.currentIsUpdatingValue = true @@ -270,71 +297,73 @@ public final class AccountStateManager { let accountPeerId = account.peerId let auxiliaryMethods = self.auxiliaryMethods let signal = account.postbox.stateView() - |> mapToSignal { view -> Signal in - if let state = view.state as? AuthorizedAccountState { - return .single(state) - } else { - return .complete() - } + |> mapToSignal { view -> Signal in + if let state = view.state as? AuthorizedAccountState { + return .single(state) + } else { + return .complete() } - |> take(1) - |> mapToSignal { state -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in - if let authorizedState = state.state { - let request = account.network.request(Api.functions.updates.getDifference(flags: 1 << 0, pts: authorizedState.pts, ptsTotalLimit: 1000, date: authorizedState.date, qts: authorizedState.qts)) - |> retryRequest - return request |> mapToSignal { difference -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in - switch difference { - case .differenceTooLong: - return accountStateReset(postbox: account.postbox, network: account.network) |> mapToSignal { _ -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in - return .complete() - } |> then(.single((nil, nil))) - default: - return initialStateWithDifference(account, difference: difference) - |> mapToSignal { state -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in - if state.initialState.state != authorizedState { - Logger.shared.log("State", "pollDifference initial state \(authorizedState) != current state \(state.initialState.state)") - return .single((nil, nil)) - } else { - return finalStateWithDifference(account: account, state: state, difference: difference) - |> mapToSignal { finalState -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in - if !finalState.state.preCachedResources.isEmpty { - for (resource, data) in finalState.state.preCachedResources { - account.postbox.mediaBox.storeResourceData(resource.id, data: data) - } - } - return account.postbox.transaction { transaction -> (Api.updates.Difference?, AccountReplayedFinalState?) in - if let replayedState = replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState) { - return (difference, replayedState) - } else { - return (nil, nil) - } - } + } + |> take(1) + |> mapToSignal { state -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in + if let authorizedState = state.state { + let request = account.network.request(Api.functions.updates.getDifference(flags: 1 << 0, pts: authorizedState.pts, ptsTotalLimit: 1000, date: authorizedState.date, qts: authorizedState.qts)) + |> retryRequest + + return request + |> mapToSignal { difference -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in + switch difference { + case .differenceTooLong: + return accountStateReset(postbox: account.postbox, network: account.network) |> mapToSignal { _ -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in + return .complete() + } |> then(.single((nil, nil))) + default: + return initialStateWithDifference(account, difference: difference) + |> mapToSignal { state -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in + if state.initialState.state != authorizedState { + Logger.shared.log("State", "pollDifference initial state \(authorizedState) != current state \(state.initialState.state)") + return .single((nil, nil)) + } else { + return finalStateWithDifference(account: account, state: state, difference: difference) + |> mapToSignal { finalState -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in + if !finalState.state.preCachedResources.isEmpty { + for (resource, data) in finalState.state.preCachedResources { + account.postbox.mediaBox.storeResourceData(resource.id, data: data) + } + } + return account.postbox.transaction { transaction -> (Api.updates.Difference?, AccountReplayedFinalState?) in + if let replayedState = replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState) { + return (difference, replayedState) + } else { + return (nil, nil) + } } - } - } - } - } - } else { - let appliedState = account.network.request(Api.functions.updates.getState()) - |> retryRequest - |> mapToSignal { state in - return account.postbox.transaction { transaction -> (Api.updates.Difference?, AccountReplayedFinalState?) in - if let currentState = transaction.getState() as? AuthorizedAccountState { - switch state { - case let .state(pts, qts, date, seq, _): - transaction.setState(currentState.changedState(AuthorizedAccountState.State(pts: pts, qts: qts, date: date, seq: seq))) } } - return (nil, nil) } } - return appliedState } + } else { + let appliedState = account.network.request(Api.functions.updates.getState()) + |> retryRequest + |> mapToSignal { state in + return account.postbox.transaction { transaction -> (Api.updates.Difference?, AccountReplayedFinalState?) in + if let currentState = transaction.getState() as? AuthorizedAccountState { + switch state { + case let .state(pts, qts, date, seq, _): + transaction.setState(currentState.changedState(AuthorizedAccountState.State(pts: pts, qts: qts, date: date, seq: seq))) + } + } + return (nil, nil) + } + } + return appliedState } - |> deliverOn(self.queue) + } + |> deliverOn(self.queue) let _ = signal.start(next: { [weak self] difference, finalState in if let strongSelf = self { - if case .pollDifference = strongSelf.operations.removeFirst() { + if case .pollDifference = strongSelf.operations.removeFirst().content { let events: AccountFinalStateEvents if let finalState = finalState { events = currentEvents.union(with: AccountFinalStateEvents(state: finalState)) @@ -344,7 +373,7 @@ public final class AccountStateManager { if let difference = difference { switch difference { case .differenceSlice: - strongSelf.operations.insert(.pollDifference(events), at: 0) + strongSelf.addOperation(.pollDifference(events), position: .first) default: if !events.isEmpty { strongSelf.insertProcessEvents(events) @@ -370,9 +399,10 @@ public final class AccountStateManager { self.operationTimer?.invalidate() let operationTimer = SignalKitTimer(timeout: timeout, repeat: false, completion: { [weak self] in if let strongSelf = self { - if let firstOperation = strongSelf.operations.first, case let .collectUpdateGroups(groups, _) = firstOperation { + let firstOperation = strongSelf.operations.removeFirst() + if case let .collectUpdateGroups(groups, _) = firstOperation.content { if timeout.isEqual(to: 0.0) { - strongSelf.operations[0] = .processUpdateGroups(groups) + strongSelf.addOperation(.processUpdateGroups(groups), position: .first) } else { Logger.shared.log("AccountStateManager", "timeout while waiting for updates") strongSelf.replaceOperations(with: .pollDifference(AccountFinalStateEvents())) @@ -393,32 +423,32 @@ public final class AccountStateManager { let mediaBox = account.postbox.mediaBox let queue = self.queue let signal = initialStateWithUpdateGroups(account, groups: groups) - |> mapToSignal { state -> Signal<(AccountReplayedFinalState?, AccountFinalState), NoError> in - return finalStateWithUpdateGroups(account, state: state, groups: groups) - |> mapToSignal { finalState in - if !finalState.state.preCachedResources.isEmpty { - for (resource, data) in finalState.state.preCachedResources { - account.postbox.mediaBox.storeResourceData(resource.id, data: data) - } - } - - return account.postbox.transaction { transaction -> AccountReplayedFinalState? in - return replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState) - } - |> map({ ($0, finalState) }) - |> deliverOn(queue) + |> mapToSignal { state -> Signal<(AccountReplayedFinalState?, AccountFinalState), NoError> in + return finalStateWithUpdateGroups(account, state: state, groups: groups) + |> mapToSignal { finalState in + if !finalState.state.preCachedResources.isEmpty { + for (resource, data) in finalState.state.preCachedResources { + account.postbox.mediaBox.storeResourceData(resource.id, data: data) } + } + + return account.postbox.transaction { transaction -> AccountReplayedFinalState? in + return replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState) + } + |> map({ ($0, finalState) }) + |> deliverOn(queue) } + } let _ = signal.start(next: { [weak self] replayedState, finalState in if let strongSelf = self { - if case let .processUpdateGroups(groups) = strongSelf.operations.removeFirst() { + if case let .processUpdateGroups(groups) = strongSelf.operations.removeFirst().content { if let replayedState = replayedState, !finalState.shouldPoll { let events = AccountFinalStateEvents(state: replayedState) if !events.isEmpty { strongSelf.insertProcessEvents(events) } if finalState.incomplete { - strongSelf.operations.append(.collectUpdateGroups(groups, 2.0)) + strongSelf.addOperation(.collectUpdateGroups(groups, 2.0), position: .last) } } else { strongSelf.replaceOperations(with: .pollDifference(AccountFinalStateEvents())) @@ -436,8 +466,8 @@ public final class AccountStateManager { self.operationTimer?.invalidate() let completed: () -> Void = { [weak self] in if let strongSelf = self { - if let topOperation = strongSelf.operations.first, case .custom(operationId, _) = topOperation { - strongSelf.operations.removeFirst() + let topOperation = strongSelf.operations.removeFirst() + if case .custom(operationId, _) = topOperation.content { strongSelf.startFirstOperation() } else { assertionFailure() @@ -453,7 +483,8 @@ public final class AccountStateManager { self.operationTimer?.invalidate() let completed: () -> Void = { [weak self] in if let strongSelf = self { - if let topOperation = strongSelf.operations.first, case .processEvents(operationId, _) = topOperation { + let topOperation = strongSelf.operations.removeFirst() + if case .processEvents(operationId, _) = topOperation.content { if !events.updatedTypingActivities.isEmpty { strongSelf.peerInputActivityManager.transaction { manager in for (chatPeerId, peerActivities) in events.updatedTypingActivities { @@ -475,14 +506,18 @@ public final class AccountStateManager { strongSelf.account.callSessionManager.updateSession(call) } } - strongSelf.operations.removeFirst() + if !events.isContactUpdates.isEmpty { + strongSelf.account.contactSyncManager.addIsContactUpdates(events.isContactUpdates) + } var pollCount = 0 for i in 0 ..< strongSelf.operations.count { - if case let .pollCompletion(pollId, messageIds, subscribers) = strongSelf.operations[i] { + if case let .pollCompletion(pollId, messageIds, subscribers) = strongSelf.operations[i].content { pollCount += 1 var updatedMessageIds = messageIds updatedMessageIds.append(contentsOf: events.addedIncomingMessageIds) - strongSelf.operations[i] = .pollCompletion(pollId, updatedMessageIds, subscribers) + let operation = AccountStateManagerOperation(content: .pollCompletion(pollId, updatedMessageIds, subscribers)) + operation.isRunning = strongSelf.operations[i].isRunning + strongSelf.operations[i] = operation } } assert(pollCount <= 1) @@ -508,7 +543,8 @@ public final class AccountStateManager { return messages } - let _ = (signal |> deliverOn(self.queue)).start(next: { [weak self] messages in + let _ = (signal + |> deliverOn(self.queue)).start(next: { [weak self] messages in if let strongSelf = self { strongSelf.notificationMessagesPipe.putNext(messages) } @@ -529,13 +565,13 @@ public final class AccountStateManager { } else { self.operationTimer?.invalidate() let signal = self.account.network.request(Api.functions.help.test()) - |> deliverOn(self.queue) + |> deliverOn(self.queue) let completed: () -> Void = { [weak self] in if let strongSelf = self { - if let topOperation = strongSelf.operations.first, case let .pollCompletion(topPollId, messageIds, subscribers) = topOperation { + let topOperation = strongSelf.operations.removeFirst() + if case let .pollCompletion(topPollId, messageIds, subscribers) = topOperation.content { assert(topPollId == pollId) - strongSelf.operations.removeFirst() if strongSelf.operations.isEmpty { for (_, f) in subscribers { f(messageIds) @@ -573,7 +609,7 @@ public final class AccountStateManager { let _ = signal.start(next: { [weak self] replayedState, finalState in if let strongSelf = self { - if case .replayAsynchronouslyBuiltFinalState = strongSelf.operations.removeFirst() { + if case .replayAsynchronouslyBuiltFinalState = strongSelf.operations.removeFirst().content { if let replayedState = replayedState { let events = AccountFinalStateEvents(state: replayedState) if !events.isEmpty { @@ -596,21 +632,30 @@ public final class AccountStateManager { private func insertProcessEvents(_ events: AccountFinalStateEvents) { if !events.isEmpty { - var index = 0 - if !self.operations.isEmpty { - while case .processEvents = self.operations[index] { - index += 1 + let operation = AccountStateManagerOperation(content: .processEvents(self.getNextId(), events)) + var inserted = false + for i in 0 ..< self.operations.count { + if self.operations[i].isRunning { + continue } + if case .processEvents = self.operations[i].content { + continue + } + self.operations.insert(operation, at: i) + inserted = true + break + } + if !inserted { + self.operations.append(operation) } - self.operations.insert(.processEvents(self.getNextId(), events), at: 0) } } private func postponePollCompletionOperation(messageIds: [MessageId], subscribers: [(Int32, ([MessageId]) -> Void)]) { - self.operations.append(.pollCompletion(self.getNextId(), messageIds, subscribers)) + self.addOperation(.pollCompletion(self.getNextId(), messageIds, subscribers), position: .last) for i in 0 ..< self.operations.count { - if case .pollCompletion = self.operations[i] { + if case .pollCompletion = self.operations[i].content { if i != self.operations.count - 1 { assertionFailure() } @@ -624,31 +669,32 @@ public final class AccountStateManager { let updatedId: Int32 = self.getNextId() for i in 0 ..< self.operations.count { - if case let .pollCompletion(pollId, messageIds, subscribers) = self.operations[i] { + if case let .pollCompletion(pollId, messageIds, subscribers) = self.operations[i].content { var subscribers = subscribers subscribers.append((updatedId, f)) - self.operations[i] = .pollCompletion(pollId, messageIds, subscribers) + let operation = AccountStateManagerOperation(content: .pollCompletion(pollId, messageIds, subscribers)) + operation.isRunning = self.operations[i].isRunning + self.operations[i] = operation return updatedId } } let beginFirst = self.operations.isEmpty - self.operations.append(.pollCompletion(self.getNextId(), [], [(updatedId, f)])) - if beginFirst { - self.startFirstOperation() - } + self.addOperation(.pollCompletion(self.getNextId(), [], [(updatedId, f)]), position: .last) return updatedId } private func removePollCompletion(_ id: Int32) { for i in 0 ..< self.operations.count { - if case let .pollCompletion(pollId, messages, subscribers) = self.operations[i] { + if case let .pollCompletion(pollId, messages, subscribers) = self.operations[i].content { for j in 0 ..< subscribers.count { if subscribers[j].0 == id { var subscribers = subscribers subscribers.remove(at: j) - self.operations[i] = .pollCompletion(pollId, messages, subscribers) + let operation = AccountStateManagerOperation(content: .pollCompletion(pollId, messages, subscribers)) + operation.isRunning = self.operations[i].isRunning + self.operations[i] = operation break } } diff --git a/TelegramCore/ContactManagement.swift b/TelegramCore/ContactManagement.swift index d1d277ae36..6143220849 100644 --- a/TelegramCore/ContactManagement.swift +++ b/TelegramCore/ContactManagement.swift @@ -37,15 +37,15 @@ private func updatedRemoteContactPeers(network: Network, hash: Int32) -> Signal< } private func hashForCountAndIds(count: Int32, ids: [Int32]) -> Int32 { - var acc: UInt32 = 0 + var acc: Int64 = 0 - acc = (acc &* 20261) &+ UInt32(bitPattern: count) + acc = (acc &* 20261) &+ Int64(count) for id in ids { - let low = UInt32(bitPattern: id) - acc = (acc &* 20261) &+ low + acc = (acc &* 20261) &+ Int64(id) + acc = acc & Int64(0x7FFFFFFF) } - return Int32(bitPattern: acc & UInt32(0x7FFFFFFF)) + return Int32(acc & Int64(0x7FFFFFFF)) } func syncContactsOnce(network: Network, postbox: Postbox) -> Signal { diff --git a/TelegramCore/ContactSyncManager.swift b/TelegramCore/ContactSyncManager.swift index 0ef684c2cd..a8675f5748 100644 --- a/TelegramCore/ContactSyncManager.swift +++ b/TelegramCore/ContactSyncManager.swift @@ -20,8 +20,9 @@ private final class ContactSyncOperation { } private enum ContactSyncOperationContent { + case waitForUpdatedState case sync(importableContacts: [DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]?) - case updateIsContact(peerId: PeerId, isContact: Bool) + case updateIsContact([(PeerId, Bool)]) } private final class ContactSyncManagerImpl { @@ -53,15 +54,22 @@ private final class ContactSyncManagerImpl { guard let strongSelf = self else { return } + strongSelf.addOperation(.waitForUpdatedState) strongSelf.addOperation(.sync(importableContacts: importableContacts)) })) } + func addIsContactUpdates(_ updates: [(PeerId, Bool)]) { + self.addOperation(.updateIsContact(updates)) + } + func addOperation(_ content: ContactSyncOperationContent) { let id = self.nextId self.nextId += 1 let operation = ContactSyncOperation(id: id, content: content) switch content { + case .waitForUpdatedState: + self.operations.append(operation) case .sync: for i in (0 ..< self.operations.count).reversed() { if case .sync = self.operations[i].content { @@ -70,10 +78,28 @@ private final class ContactSyncManagerImpl { } } } - default: - break + self.operations.append(operation) + case let .updateIsContact(updates): + var mergedUpdates: [(PeerId, Bool)] = [] + var removeIndices: [Int] = [] + for i in 0 ..< self.operations.count { + if case let .updateIsContact(operationUpdates) = self.operations[i].content { + if !self.operations[i].isRunning { + mergedUpdates.append(contentsOf: operationUpdates) + removeIndices.append(i) + } + } + } + mergedUpdates.append(contentsOf: updates) + for index in removeIndices.reversed() { + self.operations.remove(at: index) + } + if self.operations.isEmpty || !self.operations[0].isRunning { + self.operations.insert(operation, at: 0) + } else { + self.operations.insert(operation, at: 1) + } } - self.operations.append(operation) self.updateOperations() } @@ -100,6 +126,11 @@ private final class ContactSyncManagerImpl { func startOperation(_ operation: ContactSyncOperationContent, disposable: DisposableSet, completion: @escaping () -> Void) { switch operation { + case .waitForUpdatedState: + disposable.add((self.stateManager.pollStateUpdateCompletion() + |> deliverOn(self.queue)).start(next: { _ in + completion() + })) case let .sync(importableContacts): let importSignal: Signal if let importableContacts = importableContacts { @@ -107,18 +138,14 @@ private final class ContactSyncManagerImpl { } else { importSignal = .single(PushDeviceContactsResult(addedReimportAttempts: [:])) } - disposable.add((self.stateManager.pollStateUpdateCompletion() - |> mapToSignal { _ -> Signal in - return .complete() - } - |> then( - syncContactsOnce(network: self.network, postbox: self.postbox) + disposable.add( + (syncContactsOnce(network: self.network, postbox: self.postbox) |> mapToSignal { _ -> Signal in return .complete() } |> then(importSignal) - ) - |> deliverOn(self.queue)).start(next: { [weak self] result in + |> deliverOn(self.queue) + ).start(next: { [weak self] result in guard let strongSelf = self else { return } @@ -128,17 +155,17 @@ private final class ContactSyncManagerImpl { completion() })) - case let .updateIsContact(peerId, isContact): + case let .updateIsContact(updates): disposable.add((self.postbox.transaction { transaction -> Void in - if transaction.isPeerContact(peerId: peerId) != isContact { - var contactPeerIds = transaction.getContactPeerIds() + var contactPeerIds = transaction.getContactPeerIds() + for (peerId, isContact) in updates { if isContact { contactPeerIds.insert(peerId) } else { contactPeerIds.remove(peerId) } - transaction.replaceContactPeerIds(contactPeerIds) } + transaction.replaceContactPeerIds(contactPeerIds) } |> deliverOnMainQueue).start(completed: { completion() @@ -320,4 +347,10 @@ final class ContactSyncManager { impl.beginSync(importableContacts: importableContacts) } } + + func addIsContactUpdates(_ updates: [(PeerId, Bool)]) { + self.impl.with { impl in + impl.addIsContactUpdates(updates) + } + } } diff --git a/TelegramCore/Localization.swift b/TelegramCore/Localization.swift index 5437f0ed72..2cc395e0e7 100644 --- a/TelegramCore/Localization.swift +++ b/TelegramCore/Localization.swift @@ -91,7 +91,7 @@ public final class Localization: PostboxCoding, Equatable { public let version: Int32 public let entries: [LocalizationEntry] - init(version: Int32, entries: [LocalizationEntry]) { + public init(version: Int32, entries: [LocalizationEntry]) { self.version = version self.entries = entries } diff --git a/TelegramCore/LocalizationSettings.swift b/TelegramCore/LocalizationSettings.swift index 1ef70a9123..70f198f46c 100644 --- a/TelegramCore/LocalizationSettings.swift +++ b/TelegramCore/LocalizationSettings.swift @@ -9,7 +9,7 @@ public final class LocalizationSettings: PreferencesEntry, Equatable { public let languageCode: String public let localization: Localization - init(languageCode: String, localization: Localization) { + public init(languageCode: String, localization: Localization) { self.languageCode = languageCode self.localization = localization } diff --git a/TelegramCore/ManagedSynchronizeInstalledStickerPacksOperations.swift b/TelegramCore/ManagedSynchronizeInstalledStickerPacksOperations.swift index 0e7262e02c..1e04174c8c 100644 --- a/TelegramCore/ManagedSynchronizeInstalledStickerPacksOperations.swift +++ b/TelegramCore/ManagedSynchronizeInstalledStickerPacksOperations.swift @@ -94,7 +94,13 @@ func managedSynchronizeInstalledStickerPacksOperations(postbox: Postbox, network let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal in if let entry = entry { if let operation = entry.contents as? SynchronizeInstalledStickerPacksOperation { - return synchronizeInstalledStickerPacks(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, namespace: namespace, operation: operation) + return stateManager.pollStateUpdateCompletion() + |> mapToSignal { _ -> Signal in + return postbox.transaction { transaction -> Signal in + return synchronizeInstalledStickerPacks(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, namespace: namespace, operation: operation) + } + |> switchToLatest + } } else { assertionFailure() } diff --git a/TelegramCore/PendingMessageManager.swift b/TelegramCore/PendingMessageManager.swift index 38dffd83fd..dfed93b238 100644 --- a/TelegramCore/PendingMessageManager.swift +++ b/TelegramCore/PendingMessageManager.swift @@ -866,6 +866,7 @@ public final class PendingMessageManager { } return sendMessageRequest + |> deliverOn(queue) |> mapToSignal { result -> Signal in if let strongSelf = self { return strongSelf.applySentMessage(postbox: postbox, stateManager: stateManager, message: message, result: result)