import Foundation import Postbox import SwiftSignalKit import TelegramApi import MtProtoKit import SyncCore struct PeerChatInfo { var notificationSettings: PeerNotificationSettings } struct AccountStateChannelState: Equatable { var pts: Int32 } final class AccountInitialState { let state: AuthorizedAccountState.State let peerIds: Set let channelStates: [PeerId: AccountStateChannelState] let peerChatInfos: [PeerId: PeerChatInfo] let peerIdsRequiringLocalChatState: Set let locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]] let cloudReadStates: [PeerId: PeerReadState] let channelsToPollExplicitely: Set init(state: AuthorizedAccountState.State, peerIds: Set, peerIdsRequiringLocalChatState: Set, channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]], cloudReadStates: [PeerId: PeerReadState], channelsToPollExplicitely: Set) { self.state = state self.peerIds = peerIds self.channelStates = channelStates self.peerIdsRequiringLocalChatState = peerIdsRequiringLocalChatState self.peerChatInfos = peerChatInfos self.locallyGeneratedMessageTimestamps = locallyGeneratedMessageTimestamps self.cloudReadStates = cloudReadStates self.channelsToPollExplicitely = channelsToPollExplicitely } } enum AccountStateUpdatePinnedItemIdsOperation { case pin(PinnedItemId) case unpin(PinnedItemId) case reorder([PinnedItemId]) case sync } enum AccountStateUpdateStickerPacksOperation { case add(Api.messages.StickerSet) case reorder(SynchronizeInstalledStickerPacksOperationNamespace, [Int64]) case sync } enum AccountStateNotificationSettingsSubject { case peer(PeerId) } enum AccountStateGlobalNotificationSettingsSubject { case privateChats case groups case channels } enum AccountStateMutationOperation { case AddMessages([StoreMessage], AddMessagesLocation) case AddScheduledMessages([StoreMessage]) case DeleteMessagesWithGlobalIds([Int32]) case DeleteMessages([MessageId]) case EditMessage(MessageId, StoreMessage) case UpdateMessagePoll(MediaId, Api.Poll?, Api.PollResults) //case UpdateMessageReactions(MessageId, Api.MessageReactions) case UpdateMedia(MediaId, Media?) case ReadInbox(MessageId) case ReadOutbox(MessageId, Int32?) case ResetReadState(peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32, markedUnread: Bool?) case ResetIncomingReadState(groupId: PeerGroupId, peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, count: Int32, pts: Int32) case UpdatePeerChatUnreadMark(PeerId, MessageId.Namespace, Bool) case ResetMessageTagSummary(PeerId, MessageId.Namespace, Int32, MessageHistoryTagNamespaceCountValidityRange) case ReadGroupFeedInbox(PeerGroupId, MessageIndex) case UpdateState(AuthorizedAccountState.State) case UpdateChannelState(PeerId, Int32) case UpdateChannelInvalidationPts(PeerId, Int32) case UpdateChannelSynchronizedUntilMessage(PeerId, MessageId.Id) case UpdateNotificationSettings(AccountStateNotificationSettingsSubject, PeerNotificationSettings) 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: Api.UserStatus], Bool) case UpdateSecretChat(chat: Api.EncryptedChat, timestamp: Int32) case AddSecretMessages([Api.EncryptedMessage]) case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32) case AddPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?) case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation) case ReadMessageContents((PeerId?, [Int32])) case UpdateMessageImpressionCount(MessageId, Int32) case UpdateMessageForwardsCount(MessageId, Int32) case UpdateInstalledStickerPacks(AccountStateUpdateStickerPacksOperation) case UpdateRecentGifs case UpdateChatInputState(PeerId, SynchronizeableChatInputState?) case UpdateCall(Api.PhoneCall) case AddCallSignalingData(Int64, Data) case UpdateLangPack(String, Api.LangPackDifference?) case UpdateMinAvailableMessage(MessageId) case UpdatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool) case UpdatePeersNearby([PeerNearby]) case UpdateTheme(TelegramTheme) case SyncChatListFilters case UpdateChatListFilterOrder(order: [Int32]) case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?) case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) } struct HoleFromPreviousState { var validateChannelPts: Int32? func mergedWith(_ other: HoleFromPreviousState) -> HoleFromPreviousState { var result = self if let pts = self.validateChannelPts, let otherPts = other.validateChannelPts { result.validateChannelPts = max(pts, otherPts) } else if let pts = self.validateChannelPts { result.validateChannelPts = pts } else if let otherPts = other.validateChannelPts { result.validateChannelPts = otherPts } return result } } struct AccountMutableState { let initialState: AccountInitialState let branchOperationIndex: Int var operations: [AccountStateMutationOperation] = [] var state: AuthorizedAccountState.State var peers: [PeerId: Peer] var channelStates: [PeerId: AccountStateChannelState] var peerChatInfos: [PeerId: PeerChatInfo] var referencedMessageIds: Set var storedMessages: Set var readInboxMaxIds: [PeerId: MessageId] var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]] var updatedOutgoingUniqueMessageIds: [Int64: Int32] var storedMessagesByPeerIdAndTimestamp: [PeerId: Set] var displayAlerts: [(text: String, isDropAuth: Bool)] = [] var insertedPeers: [PeerId: Peer] = [:] var preCachedResources: [(MediaResource, Data)] = [] var updatedMaxMessageId: Int32? var updatedQts: Int32? var externallyUpdatedPeerId = Set() var authorizationListUpdated: Bool = false init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedMessageIds: Set, initialStoredMessages: Set, initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set]) { self.initialState = initialState self.state = initialState.state self.peers = initialPeers self.referencedMessageIds = initialReferencedMessageIds self.storedMessages = initialStoredMessages self.readInboxMaxIds = initialReadInboxMaxIds self.channelStates = initialState.channelStates self.peerChatInfos = initialState.peerChatInfos self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp self.branchOperationIndex = 0 self.namespacesWithHolesFromPreviousState = [:] self.updatedOutgoingUniqueMessageIds = [:] } init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedMessageIds: Set, storedMessages: Set, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], branchOperationIndex: Int) { self.initialState = initialState self.operations = operations self.state = state self.peers = peers self.channelStates = channelStates self.referencedMessageIds = referencedMessageIds self.storedMessages = storedMessages self.peerChatInfos = peerChatInfos self.readInboxMaxIds = readInboxMaxIds self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp self.namespacesWithHolesFromPreviousState = namespacesWithHolesFromPreviousState self.updatedOutgoingUniqueMessageIds = updatedOutgoingUniqueMessageIds self.displayAlerts = displayAlerts self.branchOperationIndex = branchOperationIndex } func branch() -> AccountMutableState { return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedMessageIds: self.referencedMessageIds, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, branchOperationIndex: self.operations.count) } mutating func merge(_ other: AccountMutableState) { self.referencedMessageIds.formUnion(other.referencedMessageIds) for i in other.branchOperationIndex ..< other.operations.count { self.addOperation(other.operations[i]) } for (_, peer) in other.insertedPeers { self.peers[peer.id] = peer } self.preCachedResources.append(contentsOf: other.preCachedResources) self.externallyUpdatedPeerId.formUnion(other.externallyUpdatedPeerId) for (peerId, namespaces) in other.namespacesWithHolesFromPreviousState { if self.namespacesWithHolesFromPreviousState[peerId] == nil { self.namespacesWithHolesFromPreviousState[peerId] = [:] } for (namespace, namespaceState) in namespaces { if self.namespacesWithHolesFromPreviousState[peerId]![namespace] == nil { self.namespacesWithHolesFromPreviousState[peerId]![namespace] = namespaceState } else { self.namespacesWithHolesFromPreviousState[peerId]![namespace] = self.namespacesWithHolesFromPreviousState[peerId]![namespace]!.mergedWith(namespaceState) } } } self.updatedOutgoingUniqueMessageIds.merge(other.updatedOutgoingUniqueMessageIds, uniquingKeysWith: { lhs, _ in lhs }) self.displayAlerts.append(contentsOf: other.displayAlerts) } mutating func addPreCachedResource(_ resource: MediaResource, data: Data) { self.preCachedResources.append((resource, data)) } mutating func addExternallyUpdatedPeerId(_ peerId: PeerId) { self.externallyUpdatedPeerId.insert(peerId) } mutating func addMessages(_ messages: [StoreMessage], location: AddMessagesLocation) { self.addOperation(.AddMessages(messages, location)) } mutating func addScheduledMessages(_ messages: [StoreMessage]) { self.addOperation(.AddScheduledMessages(messages)) } mutating func addDisplayAlert(_ text: String, isDropAuth: Bool) { self.displayAlerts.append((text: text, isDropAuth: isDropAuth)) } mutating func deleteMessagesWithGlobalIds(_ globalIds: [Int32]) { self.addOperation(.DeleteMessagesWithGlobalIds(globalIds)) } mutating func deleteMessages(_ messageIds: [MessageId]) { self.addOperation(.DeleteMessages(messageIds)) } mutating func editMessage(_ id: MessageId, message: StoreMessage) { self.addOperation(.EditMessage(id, message)) } mutating func updateMessagePoll(_ id: MediaId, poll: Api.Poll?, results: Api.PollResults) { self.addOperation(.UpdateMessagePoll(id, poll, results)) } /*mutating func updateMessageReactions(_ messageId: MessageId, reactions: Api.MessageReactions) { self.addOperation(.UpdateMessageReactions(messageId, reactions)) }*/ mutating func updateMedia(_ id: MediaId, media: Media?) { self.addOperation(.UpdateMedia(id, media)) } mutating func readInbox(_ messageId: MessageId) { self.addOperation(.ReadInbox(messageId)) } mutating func readOutbox(_ messageId: MessageId, timestamp: Int32?) { self.addOperation(.ReadOutbox(messageId, timestamp)) } mutating func readThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) { self.addOperation(.UpdateReadThread(threadMessageId: threadMessageId, readMaxId: readMaxId, isIncoming: isIncoming, mainChannelMessage: mainChannelMessage)) } mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) { self.addOperation(.ReadGroupFeedInbox(groupId, index)) } mutating func resetReadState(_ peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32, markedUnread: Bool?) { self.addOperation(.ResetReadState(peerId: peerId, namespace: namespace, maxIncomingReadId: maxIncomingReadId, maxOutgoingReadId: maxOutgoingReadId, maxKnownId: maxKnownId, count: count, markedUnread: markedUnread)) } mutating func resetIncomingReadState(groupId: PeerGroupId, peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, count: Int32, pts: Int32) { self.addOperation(.ResetIncomingReadState(groupId: groupId, peerId: peerId, namespace: namespace, maxIncomingReadId: maxIncomingReadId, count: count, pts: pts)) } mutating func updatePeerChatUnreadMark(_ peerId: PeerId, namespace: MessageId.Namespace, value: Bool) { self.addOperation(.UpdatePeerChatUnreadMark(peerId, namespace, value)) } mutating func resetMessageTagSummary(_ peerId: PeerId, namespace: MessageId.Namespace, count: Int32, range: MessageHistoryTagNamespaceCountValidityRange) { self.addOperation(.ResetMessageTagSummary(peerId, namespace, count, range)) } mutating func updateState(_ state: AuthorizedAccountState.State) { if self.initialState.state.seq != state.qts { self.updatedQts = state.qts } self.addOperation(.UpdateState(state)) } mutating func updateChannelState(_ peerId: PeerId, pts: Int32) { self.addOperation(.UpdateChannelState(peerId, pts)) } mutating func updateChannelInvalidationPts(_ peerId: PeerId, invalidationPts: Int32) { self.addOperation(.UpdateChannelInvalidationPts(peerId, invalidationPts)) } mutating func updateChannelSynchronizedUntilMessage(_ peerId: PeerId, id: MessageId.Id) { self.addOperation(.UpdateChannelSynchronizedUntilMessage(peerId, id)) } mutating func updateNotificationSettings(_ subject: AccountStateNotificationSettingsSubject, notificationSettings: PeerNotificationSettings) { self.addOperation(.UpdateNotificationSettings(subject, notificationSettings)) } mutating func updateGlobalNotificationSettings(_ subject: AccountStateGlobalNotificationSettingsSubject, notificationSettings: MessageNotificationSettings) { self.addOperation(.UpdateGlobalNotificationSettings(subject, notificationSettings)) } mutating func setNeedsHoleFromPreviousState(peerId: PeerId, namespace: MessageId.Namespace, validateChannelPts: Int32?) { if self.namespacesWithHolesFromPreviousState[peerId] == nil { self.namespacesWithHolesFromPreviousState[peerId] = [:] } let namespaceState = HoleFromPreviousState(validateChannelPts: validateChannelPts) if self.namespacesWithHolesFromPreviousState[peerId]![namespace] == nil { self.namespacesWithHolesFromPreviousState[peerId]![namespace] = namespaceState } else { self.namespacesWithHolesFromPreviousState[peerId]![namespace] = self.namespacesWithHolesFromPreviousState[peerId]![namespace]!.mergedWith(namespaceState) } } mutating func mergeChats(_ chats: [Api.Chat]) { self.addOperation(.MergeApiChats(chats)) for chat in chats { switch chat { case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount): if let participantsCount = participantsCount { self.addOperation(.UpdateCachedPeerData(chat.peerId, { current in var previous: CachedChannelData if let current = current as? CachedChannelData { previous = current } else { previous = CachedChannelData() } return previous.withUpdatedParticipantsSummary(previous.participantsSummary.withUpdatedMemberCount(participantsCount)) })) } default: break } } } mutating func updatePeer(_ id: PeerId, _ f: @escaping (Peer?) -> Peer?) { 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)) } mutating func updateLangPack(langCode: String, difference: Api.LangPackDifference?) { self.addOperation(.UpdateLangPack(langCode, difference)) } mutating func updateMinAvailableMessage(_ id: MessageId) { self.addOperation(.UpdateMinAvailableMessage(id)) } mutating func updatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool) { self.addOperation(.UpdatePeerChatInclusion(peerId: peerId, groupId: groupId, changedGroup: changedGroup)) } mutating func updatePeersNearby(_ peersNearby: [PeerNearby]) { self.addOperation(.UpdatePeersNearby(peersNearby)) } mutating func updateTheme(_ theme: TelegramTheme) { self.addOperation(.UpdateTheme(theme)) } mutating func mergeUsers(_ users: [Api.User]) { self.addOperation(.MergeApiUsers(users)) var presences: [PeerId: Api.UserStatus] = [:] for user in users { switch user { case let .user(_, id, _, _, _, _, _, _, status, _, _, _, _): if let status = status { presences[PeerId(namespace: Namespaces.Peer.CloudUser, id: id)] = status } break case .userEmpty: break } } if !presences.isEmpty { self.addOperation(.MergePeerPresences(presences, false)) } } mutating func mergePeerPresences(_ presences: [PeerId: Api.UserStatus], explicit: Bool) { self.addOperation(.MergePeerPresences(presences, explicit)) } mutating func updateSecretChat(chat: Api.EncryptedChat, timestamp: Int32) { self.addOperation(.UpdateSecretChat(chat: chat, timestamp: timestamp)) } mutating func addSecretMessages(_ messages: [Api.EncryptedMessage]) { self.addOperation(.AddSecretMessages(messages)) } mutating func readSecretOutbox(peerId: PeerId, timestamp: Int32, actionTimestamp: Int32) { self.addOperation(.ReadSecretOutbox(peerId: peerId, maxTimestamp: timestamp, actionTimestamp: actionTimestamp)) } mutating func addPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?) { self.addOperation(.AddPeerInputActivity(chatPeerId: chatPeerId, peerId: peerId, activity: activity)) } mutating func addUpdatePinnedItemIds(groupId: PeerGroupId, operation: AccountStateUpdatePinnedItemIdsOperation) { self.addOperation(.UpdatePinnedItemIds(groupId, operation)) } mutating func addReadMessagesContents(_ peerIdsAndMessageIds: (PeerId?, [Int32])) { self.addOperation(.ReadMessageContents(peerIdsAndMessageIds)) } mutating func addUpdateMessageImpressionCount(id: MessageId, count: Int32) { self.addOperation(.UpdateMessageImpressionCount(id, count)) } mutating func addUpdateMessageForwardsCount(id: MessageId, count: Int32) { self.addOperation(.UpdateMessageForwardsCount(id, count)) } mutating func addUpdateInstalledStickerPacks(_ operation: AccountStateUpdateStickerPacksOperation) { self.addOperation(.UpdateInstalledStickerPacks(operation)) } mutating func addUpdateRecentGifs() { self.addOperation(.UpdateRecentGifs) } mutating func addUpdateChatInputState(peerId: PeerId, state: SynchronizeableChatInputState?) { self.addOperation(.UpdateChatInputState(peerId, state)) } mutating func addUpdateCall(_ call: Api.PhoneCall) { self.addOperation(.UpdateCall(call)) } mutating func addCallSignalingData(callId: Int64, data: Data) { self.addOperation(.AddCallSignalingData(callId, data)) } mutating func addSyncChatListFilters() { self.addOperation(.SyncChatListFilters) } mutating func addUpdateChatListFilterOrder(order: [Int32]) { self.addOperation(.UpdateChatListFilterOrder(order: order)) } mutating func addUpdateChatListFilter(id: Int32, filter: Api.DialogFilter?) { self.addOperation(.UpdateChatListFilter(id: id, filter: filter)) } mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread: break case let .AddMessages(messages, location): for message in messages { if case let .Id(id) = message.id { self.storedMessages.insert(id) if case .UpperHistoryBlock = location { if (id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup) && id.namespace == Namespaces.Message.Cloud { if let updatedMaxMessageId = self.updatedMaxMessageId { if updatedMaxMessageId < id.id { self.updatedMaxMessageId = id.id } } else { self.updatedMaxMessageId = id.id } } } } inner: for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { self.referencedMessageIds.insert(attribute.messageId) break inner } } } case let .AddScheduledMessages(messages): for message in messages { if case let .Id(id) = message.id { self.storedMessages.insert(id) } inner: for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { self.referencedMessageIds.insert(attribute.messageId) break inner } } } case let .UpdateState(state): self.state = state case let .UpdateChannelState(peerId, pts): self.channelStates[peerId] = AccountStateChannelState(pts: pts) case .UpdateChannelInvalidationPts: break case .UpdateChannelSynchronizedUntilMessage: break case let .UpdateNotificationSettings(subject, notificationSettings): if case let .peer(peerId) = subject { if var currentInfo = self.peerChatInfos[peerId] { currentInfo.notificationSettings = notificationSettings self.peerChatInfos[peerId] = currentInfo } } case .UpdateGlobalNotificationSettings: break case let .MergeApiChats(chats): for chat in chats { if let groupOrChannel = mergeGroupOrChannel(lhs: peers[chat.peerId], rhs: chat) { peers[groupOrChannel.id] = groupOrChannel insertedPeers[groupOrChannel.id] = groupOrChannel } } case let .MergeApiUsers(users): for apiUser in users { if let user = TelegramUser.merge(peers[apiUser.peerId] as? TelegramUser, rhs: apiUser) { peers[user.id] = user insertedPeers[user.id] = user } } case let .UpdatePeer(id, f): let peer = self.peers[id] if let updatedPeer = f(peer) { peers[id] = updatedPeer insertedPeers[id] = updatedPeer } case let .ReadInbox(messageId): let current = self.readInboxMaxIds[messageId.peerId] if current == nil || current! < messageId { self.readInboxMaxIds[messageId.peerId] = messageId } case let .ResetReadState(peerId, namespace, maxIncomingReadId, _, _, _, _): let current = self.readInboxMaxIds[peerId] if namespace == Namespaces.Message.Cloud { if current == nil || current!.id < maxIncomingReadId { self.readInboxMaxIds[peerId] = MessageId(peerId: peerId, namespace: namespace, id: maxIncomingReadId) } } case let .ResetIncomingReadState(_, peerId, namespace, maxIncomingReadId, _, _): let current = self.readInboxMaxIds[peerId] if namespace == Namespaces.Message.Cloud { if current == nil || current!.id < maxIncomingReadId { self.readInboxMaxIds[peerId] = MessageId(peerId: peerId, namespace: namespace, id: maxIncomingReadId) } } case let .ResetMessageTagSummary(peerId, namespace, count, range): break } self.operations.append(operation) } } struct AccountFinalState { var state: AccountMutableState var shouldPoll: Bool var incomplete: Bool var discard: Bool } struct AccountReplayedFinalState { let state: AccountFinalState let addedIncomingMessageIds: [MessageId] let wasScheduledMessageIds: [MessageId] let addedSecretMessageIds: [MessageId] let updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let delayNotificatonsUntil: Int32? let updatedIncomingThreadReadStates: [MessageId: MessageId.Id] let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] } struct AccountFinalStateEvents { let addedIncomingMessageIds: [MessageId] let wasScheduledMessageIds:[MessageId] let updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let displayAlerts: [(text: String, isDropAuth: Bool)] let delayNotificatonsUntil: Int32? let updatedMaxMessageId: Int32? let updatedQts: Int32? let externallyUpdatedPeerId: Set let authorizationListUpdated: Bool let updatedIncomingThreadReadStates: [MessageId: MessageId.Id] let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] var isEmpty: Bool { return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty } init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) { self.addedIncomingMessageIds = addedIncomingMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds self.updatedTypingActivities = updatedTypingActivities self.updatedWebpages = updatedWebpages self.updatedCalls = updatedCalls self.addedCallSignalingData = addedCallSignalingData self.updatedPeersNearby = updatedPeersNearby self.isContactUpdates = isContactUpdates self.displayAlerts = displayAlerts self.delayNotificatonsUntil = delayNotificatonsUntil self.updatedMaxMessageId = updatedMaxMessageId self.updatedQts = updatedQts self.externallyUpdatedPeerId = externallyUpdatedPeerId self.authorizationListUpdated = authorizationListUpdated self.updatedIncomingThreadReadStates = updatedIncomingThreadReadStates self.updatedOutgoingThreadReadStates = updatedOutgoingThreadReadStates } init(state: AccountReplayedFinalState) { self.addedIncomingMessageIds = state.addedIncomingMessageIds self.wasScheduledMessageIds = state.wasScheduledMessageIds self.updatedTypingActivities = state.updatedTypingActivities self.updatedWebpages = state.updatedWebpages self.updatedCalls = state.updatedCalls self.addedCallSignalingData = state.addedCallSignalingData self.updatedPeersNearby = state.updatedPeersNearby self.isContactUpdates = state.isContactUpdates self.displayAlerts = state.state.state.displayAlerts self.delayNotificatonsUntil = state.delayNotificatonsUntil self.updatedMaxMessageId = state.state.state.updatedMaxMessageId self.updatedQts = state.state.state.updatedQts self.externallyUpdatedPeerId = state.state.state.externallyUpdatedPeerId self.authorizationListUpdated = state.state.state.authorizationListUpdated self.updatedIncomingThreadReadStates = state.updatedIncomingThreadReadStates self.updatedOutgoingThreadReadStates = state.updatedOutgoingThreadReadStates } func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { var delayNotificatonsUntil = self.delayNotificatonsUntil if let other = self.delayNotificatonsUntil { if delayNotificatonsUntil == nil || other > delayNotificatonsUntil! { delayNotificatonsUntil = other } } var updatedMaxMessageId: Int32? var updatedQts: Int32? if let lhsMaxMessageId = self.updatedMaxMessageId, let rhsMaxMessageId = other.updatedMaxMessageId { updatedMaxMessageId = max(lhsMaxMessageId, rhsMaxMessageId) } else { updatedMaxMessageId = self.updatedMaxMessageId ?? other.updatedMaxMessageId } if let lhsQts = self.updatedQts, let rhsQts = other.updatedQts { updatedQts = max(lhsQts, rhsQts) } else { updatedQts = self.updatedQts ?? other.updatedQts } let externallyUpdatedPeerId = self.externallyUpdatedPeerId.union(other.externallyUpdatedPeerId) let authorizationListUpdated = self.authorizationListUpdated || other.authorizationListUpdated return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs })) } }