diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 20f5e29d3c..3258627d0f 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -596,7 +596,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { return (state, filterId) }) - if self.controlsHistoryPreload { + if self.controlsHistoryPreload, case .chatList(groupId: .root) = self.location { self.context.account.viewTracker.chatListPreloadItems.set(combineLatest(queue: .mainQueue(), context.sharedContext.hasOngoingCall.get(), itemNode.listNode.preloadItems.get() diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 3888ca3751..f085c1710c 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -2955,7 +2955,7 @@ final class PostboxImpl { return ActionDisposable { [weak self] in disposable.dispose() if let strongSelf = self { - strongSelf.queue.async { + strongSelf.queue.justDispatch { strongSelf.viewTracker.removeMessageHistoryView(index: index) } } diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 950622bcb8..137d236ee8 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -143,6 +143,38 @@ enum StateResetForumTopics { case error(PeerId) } +struct ReferencedReplyMessageIds { + var targetIdsBySourceId: [MessageId: MessageId] = [:] + + var isEmpty: Bool { + return self.targetIdsBySourceId.isEmpty + } + + mutating func add(sourceId: MessageId, targetId: MessageId) { + if self.targetIdsBySourceId[targetId] == nil { + self.targetIdsBySourceId[targetId] = sourceId + } + } + + mutating func formUnion(_ other: ReferencedReplyMessageIds) { + for (targetId, sourceId) in other.targetIdsBySourceId { + if self.targetIdsBySourceId[targetId] == nil { + self.targetIdsBySourceId[targetId] = sourceId + } + } + } + + func subtractingStoredIds(_ ids: Set) -> ReferencedReplyMessageIds { + var result = ReferencedReplyMessageIds() + for (targetId, sourceId) in self.targetIdsBySourceId { + if !ids.contains(targetId) { + result.add(sourceId: sourceId, targetId: targetId) + } + } + return result + } +} + struct AccountMutableState { let initialState: AccountInitialState let branchOperationIndex: Int @@ -153,7 +185,8 @@ struct AccountMutableState { var peers: [PeerId: Peer] var channelStates: [PeerId: AccountStateChannelState] var peerChatInfos: [PeerId: PeerChatInfo] - var referencedMessageIds: Set + var referencedReplyMessageIds: ReferencedReplyMessageIds + var referencedGeneralMessageIds: Set var storedMessages: Set var readInboxMaxIds: [PeerId: MessageId] var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]] @@ -176,11 +209,12 @@ struct AccountMutableState { var authorizationListUpdated: Bool = false - init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedMessageIds: Set, initialStoredMessages: Set, initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set]) { + init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedReplyMessageIds: ReferencedReplyMessageIds, initialReferencedGeneralMessageIds: Set, initialStoredMessages: Set, initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set]) { self.initialState = initialState self.state = initialState.state self.peers = initialPeers - self.referencedMessageIds = initialReferencedMessageIds + self.referencedReplyMessageIds = initialReferencedReplyMessageIds + self.referencedGeneralMessageIds = initialReferencedGeneralMessageIds self.storedMessages = initialStoredMessages self.readInboxMaxIds = initialReadInboxMaxIds self.channelStates = initialState.channelStates @@ -191,13 +225,14 @@ struct AccountMutableState { 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)], dismissBotWebViews: [Int64], branchOperationIndex: Int) { + init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedReplyMessageIds: ReferencedReplyMessageIds, referencedGeneralMessageIds: Set, storedMessages: Set, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], dismissBotWebViews: [Int64], branchOperationIndex: Int) { self.initialState = initialState self.operations = operations self.state = state self.peers = peers self.channelStates = channelStates - self.referencedMessageIds = referencedMessageIds + self.referencedReplyMessageIds = referencedReplyMessageIds + self.referencedGeneralMessageIds = referencedGeneralMessageIds self.storedMessages = storedMessages self.peerChatInfos = peerChatInfos self.readInboxMaxIds = readInboxMaxIds @@ -210,11 +245,13 @@ struct AccountMutableState { } 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, dismissBotWebViews: self.dismissBotWebViews, branchOperationIndex: self.operations.count) + return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedReplyMessageIds: self.referencedReplyMessageIds, referencedGeneralMessageIds: self.referencedGeneralMessageIds, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, dismissBotWebViews: self.dismissBotWebViews, branchOperationIndex: self.operations.count) } mutating func merge(_ other: AccountMutableState) { - self.referencedMessageIds.formUnion(other.referencedMessageIds) + self.referencedReplyMessageIds.formUnion(other.referencedReplyMessageIds) + self.referencedGeneralMessageIds.formUnion(other.referencedGeneralMessageIds) + for i in other.branchOperationIndex ..< other.operations.count { self.addOperation(other.operations[i]) } @@ -567,11 +604,11 @@ struct AccountMutableState { } } } - } - inner: for attribute in message.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - self.referencedMessageIds.insert(attribute.messageId) - break inner + inner: for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + self.referencedReplyMessageIds.add(sourceId: id, targetId: attribute.messageId) + break inner + } } } } @@ -579,11 +616,11 @@ struct AccountMutableState { 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 + inner: for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + self.referencedReplyMessageIds.add(sourceId: id, targetId: attribute.messageId) + break inner + } } } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 4385ed3812..2d5f32b142 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -236,24 +236,30 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } } -func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? { +func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, _, _, chatPeerId, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, id, _, chatPeerId, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId switch replyTo { case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, _): - return [MessageId(peerId: replyToPeerId?.peerId ?? peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] + let targetId = MessageId(peerId: replyToPeerId?.peerId ?? peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId) + var replyIds = ReferencedReplyMessageIds() + replyIds.add(sourceId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), targetId: targetId) + return (replyIds, []) } } case .messageEmpty: break - case let .messageService(_, _, _, chatPeerId, replyHeader, _, _, _): + case let .messageService(_, id, _, chatPeerId, replyHeader, _, _, _): if let replyHeader = replyHeader { switch replyHeader { case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, _): - return [MessageId(peerId: replyToPeerId?.peerId ?? chatPeerId.peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] + let targetId = MessageId(peerId: replyToPeerId?.peerId ?? chatPeerId.peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId) + var replyIds = ReferencedReplyMessageIds() + replyIds.add(sourceId: MessageId(peerId: chatPeerId.peerId, namespace: Namespaces.Message.Cloud, id: id), targetId: targetId) + return (replyIds, []) } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index b83d450fd5..41ed72fd10 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -88,20 +88,20 @@ private func activeChannelsFromUpdateGroups(_ groups: [UpdateGroup]) -> Set Set { - var messageIds = Set() +private func associatedMessageIdsFromUpdateGroups(_ groups: [UpdateGroup]) -> (replyIds: ReferencedReplyMessageIds, generalIds: Set) { + var replyIds = ReferencedReplyMessageIds() + var generalIds = Set() for group in groups { for update in group.updates { if let associatedMessageIds = update.associatedMessageIds { - for messageId in associatedMessageIds { - messageIds.insert(messageId) - } + replyIds.formUnion(associatedMessageIds.replyIds) + generalIds.formUnion(associatedMessageIds.generalIds) } } } - return messageIds + return (replyIds, generalIds) } private func peerIdsRequiringLocalChatStateFromUpdates(_ updates: [Api.Update]) -> Set { @@ -282,23 +282,22 @@ private func activeChannelsFromDifference(_ difference: Api.updates.Difference) return peerIds } -private func associatedMessageIdsFromDifference(_ difference: Api.updates.Difference) -> Set { - var messageIds = Set() +private func associatedMessageIdsFromDifference(_ difference: Api.updates.Difference) -> (replyIds: ReferencedReplyMessageIds, generalIds: Set) { + var replyIds = ReferencedReplyMessageIds() + var generalIds = Set() switch difference { case let .difference(newMessages, _, otherUpdates, _, _, _): for message in newMessages { if let associatedMessageIds = apiMessageAssociatedMessageIds(message) { - for messageId in associatedMessageIds { - messageIds.insert(messageId) - } + replyIds.formUnion(associatedMessageIds.replyIds) + generalIds.formUnion(associatedMessageIds.generalIds) } } for update in otherUpdates { if let associatedMessageIds = update.associatedMessageIds { - for messageId in associatedMessageIds { - messageIds.insert(messageId) - } + replyIds.formUnion(associatedMessageIds.replyIds) + generalIds.formUnion(associatedMessageIds.generalIds) } } case .differenceEmpty: @@ -306,24 +305,22 @@ private func associatedMessageIdsFromDifference(_ difference: Api.updates.Differ case let .differenceSlice(newMessages, _, otherUpdates, _, _, _): for message in newMessages { if let associatedMessageIds = apiMessageAssociatedMessageIds(message) { - for messageId in associatedMessageIds { - messageIds.insert(messageId) - } + replyIds.formUnion(associatedMessageIds.replyIds) + generalIds.formUnion(associatedMessageIds.generalIds) } } for update in otherUpdates { if let associatedMessageIds = update.associatedMessageIds { - for messageId in associatedMessageIds { - messageIds.insert(messageId) - } + replyIds.formUnion(associatedMessageIds.replyIds) + generalIds.formUnion(associatedMessageIds.generalIds) } } case .differenceTooLong: break } - return messageIds + return (replyIds, generalIds) } private func peerIdsRequiringLocalChatStateFromDifference(_ difference: Api.updates.Difference) -> Set { @@ -424,7 +421,7 @@ private func locallyGeneratedMessageTimestampsFromDifference(_ difference: Api.u return messageTimestamps } -private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set, activeChannelIds: Set, associatedMessageIds: Set, peerIdsRequiringLocalChatState: Set, locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) -> AccountMutableState { +private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set, activeChannelIds: Set, referencedReplyMessageIds: ReferencedReplyMessageIds, referencedGeneralMessageIds: Set, peerIdsRequiringLocalChatState: Set, locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) -> AccountMutableState { var peers: [PeerId: Peer] = [:] var channelStates: [PeerId: AccountStateChannelState] = [:] @@ -454,7 +451,8 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set] = [:] if !locallyGeneratedMessageTimestamps.isEmpty { for (peerId, namespacesAndTimestamps) in locallyGeneratedMessageTimestamps { @@ -517,7 +515,7 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set Si let associatedMessageIds = associatedMessageIdsFromUpdateGroups(groups) let peerIdsRequiringLocalChatState = peerIdsRequiringLocalChatStateFromUpdateGroups(groups) - return initialStateWithPeerIds(transaction, peerIds: peerIds, activeChannelIds: activeChannelIds, associatedMessageIds: associatedMessageIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestampsFromUpdateGroups(groups)) + return initialStateWithPeerIds(transaction, peerIds: peerIds, activeChannelIds: activeChannelIds, referencedReplyMessageIds: associatedMessageIds.replyIds, referencedGeneralMessageIds: associatedMessageIds.generalIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestampsFromUpdateGroups(groups)) } } @@ -538,7 +536,7 @@ func initialStateWithDifference(postbox: Postbox, difference: Api.updates.Differ let activeChannelIds = activeChannelsFromDifference(difference) let associatedMessageIds = associatedMessageIdsFromDifference(difference) let peerIdsRequiringLocalChatState = peerIdsRequiringLocalChatStateFromDifference(difference) - return initialStateWithPeerIds(transaction, peerIds: peerIds, activeChannelIds: activeChannelIds, associatedMessageIds: associatedMessageIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestampsFromDifference(difference)) + return initialStateWithPeerIds(transaction, peerIds: peerIds, activeChannelIds: activeChannelIds, referencedReplyMessageIds: associatedMessageIds.replyIds, referencedGeneralMessageIds: associatedMessageIds.generalIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestampsFromDifference(difference)) } } @@ -2058,8 +2056,10 @@ private func reactionsFromState(_ state: AccountMutableState) -> [MessageReactio } private func resolveAssociatedMessages(postbox: Postbox, network: Network, state: AccountMutableState) -> Signal { - let missingMessageIds = state.referencedMessageIds.subtracting(state.storedMessages) - if missingMessageIds.isEmpty { + let missingReplyMessageIds = state.referencedReplyMessageIds.subtractingStoredIds(state.storedMessages) + let missingGeneralMessageIds = state.referencedGeneralMessageIds.subtracting(state.storedMessages) + + if missingReplyMessageIds.isEmpty && missingGeneralMessageIds.isEmpty { return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), reactions: reactionsFromState(state), result: state) |> mapToSignal { state in return resolveForumThreads(postbox: postbox, network: network, state: state) @@ -2069,7 +2069,38 @@ private func resolveAssociatedMessages(postbox: Postbox, network: Network, state let _ = missingPeers var signals: [Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>] = [] - for (peerId, messageIds) in messagesIdsGroupedByPeerId(missingMessageIds) { + for (peerId, messageIds) in messagesIdsGroupedByPeerId(missingReplyMessageIds) { + if let peer = state.peers[peerId] { + var signal: Signal? + if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup { + signal = network.request(Api.functions.messages.getMessages(id: messageIds.targetIdsBySourceId.values.map({ Api.InputMessage.inputMessageReplyTo(id: $0.id) }))) + } else if peerId.namespace == Namespaces.Peer.CloudChannel { + if let inputChannel = apiInputChannel(peer) { + signal = network.request(Api.functions.channels.getMessages(channel: inputChannel, id: messageIds.targetIdsBySourceId.values.map({ Api.InputMessage.inputMessageReplyTo(id: $0.id) }))) + } + } + if let signal = signal { + signals.append(signal |> map { result in + switch result { + case let .messages(messages, chats, users): + return (messages, chats, users) + case let .messagesSlice(_, _, _, _, messages, chats, users): + return (messages, chats, users) + case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users): + let _ = apiTopics + return (messages, chats, users) + case .messagesNotModified: + return ([], [], []) + } + } |> `catch` { _ in + return Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>.single(([], [], [])) + }) + } + } else { + missingPeers = true + } + } + for (peerId, messageIds) in messagesIdsGroupedByPeerId(missingGeneralMessageIds) { if let peer = state.peers[peerId] { var signal: Signal? if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup { @@ -2290,7 +2321,7 @@ func pollChannelOnce(accountPeerId: PeerId, postbox: Postbox, network: Network, peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings) } } - let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:]) + let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:]) return pollChannel(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, state: initialState) |> mapToSignal { (finalState, _, timeout) -> Signal in return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState) @@ -2344,7 +2375,7 @@ public func standalonePollChannelOnce(accountPeerId: PeerId, postbox: Postbox, n peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings) } } - let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:]) + let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:]) return pollChannel(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, state: initialState) |> mapToSignal { (finalState, _, timeout) -> Signal in return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 156d74ae59..dfad00a94b 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1562,7 +1562,7 @@ public final class AccountViewTracker { let (pollMessageIds, pollMessageDict) = pollMessages(entries: next.0.entries) strongSelf.updatePolls(viewId: viewId, messageIds: pollMessageIds, messages: pollMessageDict) if case let .peer(peerId, _) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { - strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0) + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation) } else if case let .thread(peerId, _, _) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation) } @@ -1576,7 +1576,7 @@ public final class AccountViewTracker { switch chatLocation { case let .peer(peerId, _): if peerId.namespace == Namespaces.Peer.CloudChannel { - strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil, location: chatLocation) } case let .thread(peerId, _, _): if peerId.namespace == Namespaces.Peer.CloudChannel { @@ -1694,7 +1694,7 @@ public final class AccountViewTracker { if let strongSelf = self { strongSelf.queue.async { strongSelf.updatePendingWebpages(viewId: viewId, messageIds: [], localWebpages: [:]) - strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil, location: nil) } } }) diff --git a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift index e2cde230dc..ce3b14a45e 100644 --- a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift @@ -137,7 +137,13 @@ final class HistoryViewStateValidationContexts { self.accountPeerId = accountPeerId } - func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocationInput? = nil) { + func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocationInput?) { + #if DEBUG + if "".isEmpty { + return + } + #endif + assert(self.queue.isCurrent()) guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage || view.tagMask == MessageTags.unseenReaction || view.tagMask == MessageTags.music || view.tagMask == MessageTags.pinned else { if self.contexts[id] != nil { diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index dcbab5ae5b..04c6db137d 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -103,20 +103,29 @@ func resolveUnknownEmojiFiles(postbox: Postbox, source: FetchMessageHistoryHo private func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal { return postbox.transaction { transaction -> Signal in var storedIds = Set() - var referencedIds = Set() + var referencedReplyIds = ReferencedReplyMessageIds() + var referencedGeneralIds = Set() for message in storeMessages { guard case let .Id(id) = message.id else { continue } storedIds.insert(id) for attribute in message.attributes { - referencedIds.formUnion(attribute.associatedMessageIds) + if let attribute = attribute as? ReplyMessageAttribute { + referencedReplyIds.add(sourceId: id, targetId: attribute.messageId) + } else { + referencedGeneralIds.formUnion(attribute.associatedMessageIds) + } } } - referencedIds.subtract(storedIds) - referencedIds.subtract(transaction.filterStoredMessageIds(referencedIds)) - if referencedIds.isEmpty { + let allPossiblyStoredReferencedIds = storedIds.union(referencedGeneralIds).union(referencedReplyIds.targetIdsBySourceId.keys) + let allStoredReferencedIds = transaction.filterStoredMessageIds(allPossiblyStoredReferencedIds) + + referencedReplyIds = referencedReplyIds.subtractingStoredIds(allStoredReferencedIds) + referencedGeneralIds.subtract(allStoredReferencedIds) + + if referencedReplyIds.isEmpty && referencedGeneralIds.isEmpty { return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, reactions: [], result: Void()) |> mapToSignal { _ -> Signal in return postbox.transaction { transaction -> T in @@ -125,7 +134,38 @@ private func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMe } } else { var signals: [Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>] = [] - for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedIds) { + for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedReplyIds) { + if let peer = transaction.getPeer(peerId) ?? peers[peerId] { + var signal: Signal? + if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup { + signal = source.request(Api.functions.messages.getMessages(id: messageIds.targetIdsBySourceId.values.map({ Api.InputMessage.inputMessageReplyTo(id: $0.id) }))) + } else if peerId.namespace == Namespaces.Peer.CloudChannel { + if let inputChannel = apiInputChannel(peer) { + signal = source.request(Api.functions.channels.getMessages(channel: inputChannel, id: messageIds.targetIdsBySourceId.values.map({ Api.InputMessage.inputMessageReplyTo(id: $0.id) }))) + } + } + if let signal = signal { + signals.append(signal + |> map { result in + switch result { + case let .messages(messages, chats, users): + return (messages, chats, users) + case let .messagesSlice(_, _, _, _, messages, chats, users): + return (messages, chats, users) + case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users): + let _ = apiTopics + return (messages, chats, users) + case .messagesNotModified: + return ([], [], []) + } + } + |> `catch` { _ in + return Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>.single(([], [], [])) + }) + } + } + } + for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedGeneralIds) { if let peer = transaction.getPeer(peerId) ?? peers[peerId] { var signal: Signal? if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup { @@ -231,6 +271,10 @@ struct FetchMessageHistoryHoleResult: Equatable { func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerInput: FetchMessageHistoryHoleThreadInput, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal { let count = min(100, rawCount) + if peerInput.requestThreadId != nil, case .everywhere = space, case .aroundId = direction { + assert(true) + } + return postbox.stateView() |> mapToSignal { view -> Signal in if let state = view.state as? AuthorizedAccountState { @@ -654,6 +698,12 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH return nil } } + + print("fetchMessageHistoryHole for \(peerInput) space \(space) done") + if peerInput.requestThreadId != nil, case .everywhere = space, case .aroundId = direction { + assert(true) + } + if ids.count == 0 || implicitelyFillHole { filledRange = minMaxRange strictFilledIndices = IndexSet() @@ -702,8 +752,6 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH }) updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) - print("fetchMessageHistoryHole for \(peerInput) space \(space) done") - let result = FetchMessageHistoryHoleResult( removedIndices: IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)), strictRemovedIndices: strictFilledIndices, diff --git a/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift b/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift index 893190a18f..f78c8b9ff4 100644 --- a/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift +++ b/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift @@ -3,46 +3,148 @@ import Postbox import SwiftSignalKit private final class ManagedMessageHistoryHolesState { - private var holeDisposables: [MessageHistoryHolesViewEntry: Disposable] = [:] + private struct LocationKey: Equatable { + var peerId: PeerId + var threadId: Int64? + var space: MessageHistoryHoleSpace + + init(peerId: PeerId, threadId: Int64?, space: MessageHistoryHoleSpace) { + self.peerId = peerId + self.threadId = threadId + self.space = space + } + } + + private struct PendingEntry { + var key: LocationKey + var entry: MessageHistoryHolesViewEntry + var disposable: Disposable + + init(key: LocationKey, entry: MessageHistoryHolesViewEntry, disposable: Disposable) { + self.key = key + self.entry = entry + self.disposable = disposable + } + } + + private struct DiscardedEntry { + var entry: PendingEntry + var timestamp: Double + + init(entry: PendingEntry, timestamp: Double) { + self.entry = entry + self.timestamp = timestamp + } + } + + private var pendingEntries: [PendingEntry] = [] + private var discardedEntries: [DiscardedEntry] = [] + + private let performWork: (@escaping (ManagedMessageHistoryHolesState) -> Void) -> Void + private var oldEntriesTimer: SwiftSignalKit.Timer? + + init(performWork: @escaping (@escaping (ManagedMessageHistoryHolesState) -> Void) -> Void) { + self.performWork = performWork + } + + deinit { + self.oldEntriesTimer?.invalidate() + } func clearDisposables() -> [Disposable] { - let disposables = Array(self.holeDisposables.values) - self.holeDisposables.removeAll() + var disposables = Array(self.pendingEntries.map(\.disposable)) + disposables.append(contentsOf: self.discardedEntries.map(\.entry.disposable)) + self.pendingEntries.removeAll() + self.discardedEntries.removeAll() return disposables } - func update(entries: Set) -> (removed: [Disposable], added: [MessageHistoryHolesViewEntry: MetaDisposable]) { - var removed: [Disposable] = [] + private func updateNeedsTimer() { + let needsTimer = !self.discardedEntries.isEmpty + if needsTimer { + if self.oldEntriesTimer == nil { + let performWork = self.performWork + self.oldEntriesTimer = SwiftSignalKit.Timer(timeout: 0.2, repeat: true, completion: { + performWork { impl in + let disposables = impl.discardOldEntries() + for disposable in disposables { + disposable.dispose() + } + } + }, queue: .mainQueue()) + self.oldEntriesTimer?.start() + } + } else if let oldEntriesTimer = self.oldEntriesTimer { + self.oldEntriesTimer = nil + oldEntriesTimer.invalidate() + } + } + + private func discardOldEntries() -> [Disposable] { + let timestamp = CFAbsoluteTimeGetCurrent() + + var result: [Disposable] = [] + for i in (0 ..< self.discardedEntries.count).reversed() { + if self.discardedEntries[i].timestamp < timestamp - 0.5 { + result.append(self.discardedEntries[i].entry.disposable) + self.discardedEntries.remove(at: i) + } + } + + return result + } + + func update(entries: Set) -> (removed: [Disposable], added: [MessageHistoryHolesViewEntry: MetaDisposable], hasOldEntries: Bool) { + let removed: [Disposable] = [] var added: [MessageHistoryHolesViewEntry: MetaDisposable] = [:] - for (entry, disposable) in self.holeDisposables { - if !entries.contains(entry) { - removed.append(disposable) - self.holeDisposables.removeValue(forKey: entry) + let timestamp = CFAbsoluteTimeGetCurrent() + + for i in (0 ..< self.pendingEntries.count).reversed() { + if !entries.contains(self.pendingEntries[i].entry) { + self.discardedEntries.append(DiscardedEntry(entry: self.pendingEntries[i], timestamp: timestamp)) + self.pendingEntries.remove(at: i) + //removed.append(self.pendingEntries[i].disposable) } } for entry in entries { switch entry.hole { - case .peer: - if self.holeDisposables[entry] == nil { - let disposable = MetaDisposable() - self.holeDisposables[entry] = disposable - added[entry] = disposable + case let .peer(peerHole): + let key = LocationKey(peerId: peerHole.peerId, threadId: peerHole.threadId, space: entry.space) + if !self.pendingEntries.contains(where: { $0.key == key }) { + if let discardedIndex = self.discardedEntries.firstIndex(where: { $0.entry.entry == entry }) { + let discardedEntry = self.discardedEntries.remove(at: discardedIndex) + self.pendingEntries.append(discardedEntry.entry) + } else { + let disposable = MetaDisposable() + self.pendingEntries.append(PendingEntry(key: key, entry: entry, disposable: disposable)) + added[entry] = disposable + } } } } - return (removed, added) + self.updateNeedsTimer() + + return (removed, added, !self.discardedEntries.isEmpty) } } func managedMessageHistoryHoles(accountPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { return Signal { _ in - let state = Atomic(value: ManagedMessageHistoryHolesState()) + var performWorkImpl: ((@escaping (ManagedMessageHistoryHolesState) -> Void) -> Void)? + let state = Atomic(value: ManagedMessageHistoryHolesState(performWork: { f in + performWorkImpl?(f) + })) + performWorkImpl = { [weak state] f in + state?.with { state in + f(state) + } + } let disposable = postbox.messageHistoryHolesView().start(next: { view in - let (removed, added) = state.with { state -> (removed: [Disposable], added: [MessageHistoryHolesViewEntry: MetaDisposable]) in + let (removed, added, _) = state.with { state in return state.update(entries: view.entries) } diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 2c00487e9a..f5f4064547 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -305,7 +305,7 @@ extension Api.Update { } } - var associatedMessageIds: [MessageId]? { + var associatedMessageIds: (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch self { case let .updateNewMessage(message, _, _): return apiMessageAssociatedMessageIds(message) diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index c64078714a..e18032c41f 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -156,6 +156,17 @@ func messagesIdsGroupedByPeerId(_ ids: [MessageId]) -> [PeerId: [MessageId]] { return dict } +func messagesIdsGroupedByPeerId(_ ids: ReferencedReplyMessageIds) -> [PeerId: ReferencedReplyMessageIds] { + var dict: [PeerId: ReferencedReplyMessageIds] = [:] + + for (targetId, sourceId) in ids.targetIdsBySourceId { + let peerId = sourceId.peerId + dict[peerId, default: ReferencedReplyMessageIds()].add(sourceId: sourceId, targetId: targetId) + } + + return dict +} + func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Message? { guard case let .Id(id) = message.id else { return nil diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 78819a748f..063e6278c4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5792,7 +5792,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var index: Int } - let topMessage = pinnedHistorySignal(anchorMessageId: nil, count: 3) + let topMessage = pinnedHistorySignal(anchorMessageId: nil, count: 10) |> map { update -> TopMessage? in switch update { case .Loading: @@ -5816,7 +5816,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - let loadCount = 100 + let loadCount = 10 struct PinnedHistory { struct PinnedMessage {