diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index defb850e8e..f7f1caf57f 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -212,6 +212,7 @@ struct AccountMutableState { var insertedPeers: [PeerId: Peer] = [:] var preCachedResources: [(MediaResource, Data)] = [] + var preCachedStories: [StoryId: Api.StoryItem] = [:] var updatedMaxMessageId: Int32? var updatedQts: Int32? @@ -281,6 +282,11 @@ struct AccountMutableState { self.apiChats[chat.peerId] = chat } self.preCachedResources.append(contentsOf: other.preCachedResources) + + for (id, story) in other.preCachedStories { + self.preCachedStories[id] = story + } + self.externallyUpdatedPeerId.formUnion(other.externallyUpdatedPeerId) for (peerId, namespaces) in other.namespacesWithHolesFromPreviousState { if self.namespacesWithHolesFromPreviousState[peerId] == nil { @@ -305,6 +311,10 @@ struct AccountMutableState { self.preCachedResources.append((resource, data)) } + mutating func addPreCachedStory(id: StoryId, story: Api.StoryItem) { + self.preCachedStories[id] = story + } + mutating func addExternallyUpdatedPeerId(_ peerId: PeerId) { self.externallyUpdatedPeerId.insert(peerId) } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index fe503dcd3d..9c8da8be8b 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -727,6 +727,11 @@ func finalStateWithDifference(accountPeerId: PeerId, postbox: Postbox, network: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = message.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } var peerIsForum = false if let peerId = message.peerId { peerIsForum = updatedState.isPeerForum(peerId: peerId) @@ -954,6 +959,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } var attributes = message.attributes attributes.append(ChannelMessageStateVersionAttribute(pts: pts)) updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes)) @@ -1020,6 +1030,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } updatedState.editMessage(messageId, message: message) for media in message.media { if let media = media as? TelegramMediaAction { @@ -1050,6 +1065,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } var attributes = message.attributes attributes.append(ChannelMessageStateVersionAttribute(pts: pts)) updatedState.addMessages([message.withUpdatedAttributes(attributes)], location: .UpperHistoryBlock) @@ -1082,6 +1102,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } updatedState.addMessages([message], location: .UpperHistoryBlock) } case let .updateServiceNotification(flags, date, type, text, media, entities): @@ -1715,7 +1740,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: |> mapToSignal { finalState in return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState) |> mapToSignal { resultingState -> Signal in - return resolveAssociatedStories(postbox: postbox, network: network, state: finalState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: finalState) |> mapToSignal { resultingState -> Signal in return resolveMissingPeerChatInfos(network: network, state: resultingState) |> map { resultingState, resolveError -> AccountFinalState in @@ -2073,16 +2098,102 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe } } -func resolveAssociatedStories(postbox: Postbox, network: Network, state: AccountMutableState) -> Signal { +func resolveStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, storyIds: Set, result: T) -> Signal { + var storyBuckets: [PeerId: [Int32]] = [:] + for id in storyIds { + if storyBuckets[id.peerId] == nil { + storyBuckets[id.peerId] = [] + } + storyBuckets[id.peerId]?.append(id.id) + } + + var signals: [Signal] = [] + for (peerId, allIds) in storyBuckets { + var idOffset = 0 + while idOffset < allIds.count { + let bucketLength = min(100, allIds.count - idOffset) + let ids = Array(allIds[idOffset ..< (idOffset + bucketLength)]) + signals.append(_internal_getStoriesById(accountPeerId: accountPeerId, postbox: postbox, source: source, peerId: peerId, ids: ids) + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in + for id in ids { + let current = transaction.getStory(id: StoryId(peerId: peerId, id: id)) + var updated: CodableEntry? + if let updatedItem = result.first(where: { $0.id == id }) { + if let entry = CodableEntry(updatedItem) { + updated = entry + } + } + if current != updated { + transaction.setStory(id: StoryId(peerId: peerId, id: id), value: updated ?? CodableEntry(data: Data())) + } + } + } + |> ignoreValues + }) + idOffset += bucketLength + } + } + + return combineLatest(signals) + |> ignoreValues + |> map { _ -> T in + } + |> then(.single(result)) +} + +func resolveAssociatedStories(postbox: Postbox, network: Network, accountPeerId: PeerId, state: AccountMutableState) -> Signal { return postbox.transaction { transaction -> Signal in - return .single(state) + var missingStoryIds = Set() + + for operation in state.operations { + switch operation { + case let .AddMessages(messages, _): + for message in messages { + for media in message.media { + for id in media.storyIds { + if let existing = transaction.getStory(id: id), case .item = existing.get(Stories.StoredItem.self) { + } else if state.preCachedStories[id] != nil { + } else { + missingStoryIds.insert(id) + } + } + } + } + default: + break + } + } + + if !missingStoryIds.isEmpty { + return resolveStories(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, storyIds: missingStoryIds, result: state) + } else { + return .single(state) + } } |> switchToLatest } -func resolveAssociatedStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, messages: [StoreMessage], result: T) -> Signal { +func resolveAssociatedStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, messages: [StoreMessage], result: T) -> Signal { return postbox.transaction { transaction -> Signal in - return .single(result) + var missingStoryIds = Set() + + for message in messages { + for media in message.media { + for id in media.storyIds { + if let existing = transaction.getStory(id: id), case .item = existing.get(Stories.StoredItem.self) { + } else { + missingStoryIds.insert(id) + } + } + } + } + + if !missingStoryIds.isEmpty { + return resolveStories(postbox: postbox, source: source, accountPeerId: accountPeerId, storyIds: missingStoryIds, result: result) + } else { + return .single(result) + } } |> switchToLatest } @@ -2420,7 +2531,7 @@ func pollChannelOnce(accountPeerId: PeerId, postbox: Postbox, network: Network, |> mapToSignal { (finalState, _, timeout) -> Signal in return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState) |> mapToSignal { resultingState -> Signal in - return resolveAssociatedStories(postbox: postbox, network: network, state: finalState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: finalState) } |> mapToSignal { resultingState -> Signal in return resolveMissingPeerChatInfos(network: network, state: resultingState) @@ -2477,7 +2588,7 @@ public func standalonePollChannelOnce(accountPeerId: PeerId, postbox: Postbox, n |> mapToSignal { (finalState, _, timeout) -> Signal in return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState) |> mapToSignal { resultingState -> Signal in - return resolveAssociatedStories(postbox: postbox, network: network, state: finalState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: finalState) } |> mapToSignal { resultingState -> Signal in return resolveMissingPeerChatInfos(network: network, state: resultingState) @@ -2687,7 +2798,7 @@ func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Network, pe // TODO: delete messages later than top return resolveAssociatedMessages(postbox: postbox, network: network, state: updatedState) |> mapToSignal { resultingState -> Signal in - return resolveAssociatedStories(postbox: postbox, network: network, state: updatedState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: updatedState) } } } @@ -2758,6 +2869,11 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } updatedState.addMessages([message], location: .UpperHistoryBlock) if case let .Id(id) = message.id { updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id) @@ -2787,6 +2903,11 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } var attributes = message.attributes attributes.append(ChannelMessageStateVersionAttribute(pts: pts)) updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes)) @@ -2837,7 +2958,7 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo return resolveForumThreads(postbox: postbox, network: network, state: updatedState) |> mapToSignal { updatedState in - return resolveAssociatedStories(postbox: postbox, network: network, state: updatedState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: updatedState) |> map { updatedState -> (AccountMutableState, Bool, Int32?) in return (updatedState, true, apiTimeout) } @@ -2904,6 +3025,11 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } let location: AddMessagesLocation if case let .Id(id) = message.id, id.id == topMessage { @@ -3167,6 +3293,14 @@ func replayFinalState( var holesFromPreviousStateMessageIds: [MessageId] = [] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] + for (id, story) in finalState.state.preCachedStories { + if let storyItem = Stories.StoredItem(apiStoryItem: story, peerId: id.peerId, transaction: transaction) { + if let entry = CodableEntry(storyItem) { + transaction.setStory(id: id, value: entry) + } + } + } + for (peerId, namespaces) in finalState.state.namespacesWithHolesFromPreviousState { for (namespace, namespaceState) in namespaces { if let pts = namespaceState.validateChannelPts { diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 169a101bc5..3c82fac281 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -100,7 +100,7 @@ func resolveUnknownEmojiFiles(postbox: Postbox, source: FetchMessageHistoryHo } } -func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal { +func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal { return postbox.transaction { transaction -> Signal in var storedIds = Set() var referencedReplyIds = ReferencedReplyMessageIds() @@ -129,7 +129,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis if referencedReplyIds.isEmpty && referencedGeneralIds.isEmpty { return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, reactions: [], result: Void()) |> mapToSignal { _ -> Signal in - return resolveAssociatedStories(postbox: postbox, source: source, messages: storeMessages, result: Void()) + return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages, result: Void()) |> mapToSignal { _ -> Signal in return postbox.transaction { transaction -> T in return f(transaction, [], []) @@ -227,7 +227,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, reactions: [], result: Void()) |> mapToSignal { _ -> Signal in - return resolveAssociatedStories(postbox: postbox, source: source, messages: storeMessages + additionalMessages, result: Void()) + return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages + additionalMessages, result: Void()) |> mapToSignal { _ -> Signal in return postbox.transaction { transaction -> T in return f(transaction, additionalPeers, additionalMessages) @@ -669,7 +669,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } - return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> FetchMessageHistoryHoleResult? in + return withResolvedAssociatedMessages(postbox: postbox, source: source, accountPeerId: accountPeerId, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> FetchMessageHistoryHoleResult? in let _ = transaction.addMessages(storeMessages, location: .Random) let _ = transaction.addMessages(additionalMessages, location: .Random) var filledRange: ClosedRange @@ -805,7 +805,7 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId } |> ignoreValues } - return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in + return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in updatePeers(transaction: transaction, peers: fetchedChats.peers + additionalPeers, update: { _, updated -> Peer in return updated }) diff --git a/submodules/TelegramCore/Sources/State/ResetState.swift b/submodules/TelegramCore/Sources/State/ResetState.swift index f247619f02..521acded1a 100644 --- a/submodules/TelegramCore/Sources/State/ResetState.swift +++ b/submodules/TelegramCore/Sources/State/ResetState.swift @@ -14,7 +14,7 @@ func _internal_resetAccountState(postbox: Postbox, network: Network, accountPeer guard let fetchedChats = fetchedChats else { return .never() } - return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in + return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in for peerId in transaction.chatListGetAllPeerIds() { if peerId.namespace != Namespaces.Peer.SecretChat { transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded) diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index bf5e2b990a..7222906a27 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -85,6 +85,19 @@ extension Api.MessageMedia { return nil } } + + var preCachedStories: [StoryId: Api.StoryItem]? { + switch self { + case let .messageMediaStory(_, userId, id, story): + if let story = story { + return [StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: id): story] + } else { + return nil + } + default: + return nil + } + } } extension Api.Message { @@ -142,10 +155,19 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): - return media?.preCachedResources - default: - return nil + case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): + return media?.preCachedResources + default: + return nil + } + } + + var preCachedStories: [StoryId: Api.StoryItem]? { + switch self { + case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): + return media?.preCachedStories + default: + return nil } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index f19c83a8ac..f6954e3be3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1263,7 +1263,7 @@ func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: } } -func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: Network, peerId: PeerId, ids: [Int32]) -> Signal<[Stories.StoredItem], NoError> { +func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, peerId: PeerId, ids: [Int32]) -> Signal<[Stories.StoredItem], NoError> { return postbox.transaction { transaction -> Api.InputUser? in return transaction.getPeer(peerId).flatMap(apiInputUser) } @@ -1272,7 +1272,7 @@ func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: return .single([]) } - return network.request(Api.functions.stories.getStoriesByID(userId: inputUser, id: ids)) + return source.request(Api.functions.stories.getStoriesByID(userId: inputUser, id: ids)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -1685,7 +1685,7 @@ func _internal_exportStoryLink(account: Account, peerId: EnginePeer.Id, id: Int3 } func _internal_refreshStories(account: Account, peerId: PeerId, ids: [Int32]) -> Signal { - return _internal_getStoriesById(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, peerId: peerId, ids: ids) + return _internal_getStoriesById(accountPeerId: account.peerId, postbox: account.postbox, source: .network(account.network), peerId: peerId, ids: ids) |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> Void in var currentItems = transaction.getStoryItems(peerId: peerId) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index f7f6894b5d..246ffedbd7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -891,6 +891,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private var presentationDataDisposable: Disposable? private weak var pendingOpenListContext: PeerStoryListContentContextImpl? + + private var preloadArchiveListContext: PeerStoryListContext? public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) { self.context = context @@ -1448,6 +1450,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr }) self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false) + + if peerId == context.account.peerId { + self.preloadArchiveListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: true) + } } deinit {