Swiftgram/submodules/Postbox/Sources/ChatListViewState.swift
2022-07-08 19:53:20 +02:00

1558 lines
81 KiB
Swift

enum ChatListViewSpacePinned {
case notPinned
case includePinned
case includePinnedAsUnpinned
var include: Bool {
switch self {
case .notPinned:
return false
case .includePinned, .includePinnedAsUnpinned:
return true
}
}
}
enum ChatListViewSpace: Hashable {
case group(groupId: PeerGroupId, pinned: ChatListViewSpacePinned, predicate: ChatListFilterPredicate?)
case peers(peerIds: [PeerId], asPinned: Bool)
static func ==(lhs: ChatListViewSpace, rhs: ChatListViewSpace) -> Bool {
switch lhs {
case let .group(groupId, pinned, _):
if case let .group(rhsGroupId, rhsPinned, _) = rhs {
if groupId != rhsGroupId {
return false
}
if pinned != rhsPinned {
return false
}
return true
} else {
return false
}
case let .peers(peerIds, asPinned):
if case .peers(peerIds, asPinned) = rhs {
return true
} else {
return false
}
}
}
func hash(into hasher: inout Hasher) {
switch self {
case let .group(groupId, pinned, _):
hasher.combine(groupId)
hasher.combine(pinned)
case let .peers(peerIds, asPinned):
hasher.combine(peerIds)
hasher.combine(asPinned)
}
}
}
private func mappedChatListFilterPredicate(postbox: PostboxImpl, currentTransaction: Transaction, groupId: PeerGroupId, predicate: ChatListFilterPredicate) -> (ChatListIntermediateEntry) -> Bool {
let globalNotificationSettings = postbox.getGlobalNotificationSettings(transaction: currentTransaction)
return { entry in
switch entry {
case let .message(index, _):
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
let isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
let isContact = postbox.contactsTable.isContact(peerId: notificationsPeerId)
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId))
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, calculation: predicate.messageTagSummary)
if predicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: messageTagSummaryResult) {
return true
} else {
return false
}
} else {
return false
}
case .hole:
return true
}
}
}
private func updateMessagePeers(_ message: Message, updatedPeers: [PeerId: Peer]) -> Message? {
var updated = false
for (peerId, currentPeer) in message.peers {
if let updatedPeer = updatedPeers[peerId], !arePeersEqual(currentPeer, updatedPeer) {
updated = true
break
}
}
if updated {
var peers = SimpleDictionary<PeerId, Peer>()
for (peerId, currentPeer) in message.peers {
if let updatedPeer = updatedPeers[peerId] {
peers[peerId] = updatedPeer
} else {
peers[peerId] = currentPeer
}
}
return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds, associatedMedia: message.associatedMedia)
}
return nil
}
private func updatedRenderedPeer(_ renderedPeer: RenderedPeer, updatedPeers: [PeerId: Peer]) -> RenderedPeer? {
var updated = false
for (peerId, currentPeer) in renderedPeer.peers {
if let updatedPeer = updatedPeers[peerId], !arePeersEqual(currentPeer, updatedPeer) {
updated = true
break
}
}
if updated {
var peers = SimpleDictionary<PeerId, Peer>()
for (peerId, currentPeer) in renderedPeer.peers {
if let updatedPeer = updatedPeers[peerId] {
peers[peerId] = updatedPeer
} else {
peers[peerId] = currentPeer
}
}
return RenderedPeer(peerId: renderedPeer.peerId, peers: peers)
}
return nil
}
private final class ChatListViewSpaceState {
private let space: ChatListViewSpace
private let anchorIndex: MutableChatListEntryIndex
private let summaryComponents: ChatListEntrySummaryComponents
private let halfLimit: Int
var orderedEntries: OrderedChatListViewEntries
init(postbox: PostboxImpl, currentTransaction: Transaction, space: ChatListViewSpace, anchorIndex: MutableChatListEntryIndex, summaryComponents: ChatListEntrySummaryComponents, halfLimit: Int) {
self.space = space
self.anchorIndex = anchorIndex
self.summaryComponents = summaryComponents
self.halfLimit = halfLimit
self.orderedEntries = OrderedChatListViewEntries(anchorIndex: anchorIndex.index, lowerOrAtAnchor: [], higherThanAnchor: [])
self.fillSpace(postbox: postbox, currentTransaction: currentTransaction)
self.checkEntries(postbox: postbox)
}
private func fillSpace(postbox: PostboxImpl, currentTransaction: Transaction) {
switch self.space {
case let .group(groupId, pinned, filterPredicate):
let lowerBound: MutableChatListEntryIndex
let upperBound: MutableChatListEntryIndex
if pinned.include {
upperBound = .absoluteUpperBound
lowerBound = MutableChatListEntryIndex(index: ChatListIndex.pinnedLowerBound, isMessage: true)
} else {
upperBound = MutableChatListEntryIndex(index: ChatListIndex.pinnedLowerBound.predecessor, isMessage: true)
lowerBound = .absoluteLowerBound
}
let resolvedAnchorIndex = min(upperBound, max(self.anchorIndex, lowerBound))
var lowerOrAtAnchorMessages: [MutableChatListEntry] = self.orderedEntries.lowerOrAtAnchor.reversed()
var higherThanAnchorMessages: [MutableChatListEntry] = self.orderedEntries.higherThanAnchor
func mapEntry(_ entry: ChatListIntermediateEntry) -> MutableChatListEntry {
switch entry {
case let .message(index, messageIndex):
var updatedIndex = index
if case .includePinnedAsUnpinned = pinned {
updatedIndex = ChatListIndex(pinningIndex: nil, messageIndex: index.messageIndex)
}
return .IntermediateMessageEntry(index: updatedIndex, messageIndex: messageIndex)
case let .hole(hole):
return .HoleEntry(hole)
}
}
if case .includePinnedAsUnpinned = pinned {
let unpinnedLowerBound: MutableChatListEntryIndex
let unpinnedUpperBound: MutableChatListEntryIndex
unpinnedUpperBound = .absoluteUpperBound
unpinnedLowerBound = MutableChatListEntryIndex(index: ChatListIndex.absoluteLowerBound, isMessage: true)
let resolvedUnpinnedAnchorIndex = min(unpinnedUpperBound, max(self.anchorIndex, unpinnedLowerBound))
if lowerOrAtAnchorMessages.count < self.halfLimit || higherThanAnchorMessages.count < self.halfLimit {
let loadedMessages = postbox.chatListTable.entries(groupId: groupId, from: (ChatListIndex.pinnedLowerBound, true), to: (ChatListIndex.absoluteUpperBound, true), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit * 2, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, currentTransaction: currentTransaction, groupId: groupId, predicate: $0) }).map(mapEntry).sorted(by: { $0.entryIndex < $1.entryIndex })
if lowerOrAtAnchorMessages.count < self.halfLimit {
var nextLowerIndex: MutableChatListEntryIndex
if let lastMessage = lowerOrAtAnchorMessages.min(by: { $0.entryIndex < $1.entryIndex }) {
nextLowerIndex = lastMessage.entryIndex.predecessor
} else {
nextLowerIndex = min(resolvedUnpinnedAnchorIndex, self.anchorIndex)
}
var loadedLowerMessages = Array(loadedMessages.filter({ $0.entryIndex <= nextLowerIndex }).reversed())
let lowerLimit = self.halfLimit - lowerOrAtAnchorMessages.count
if loadedLowerMessages.count > lowerLimit {
loadedLowerMessages.removeLast(loadedLowerMessages.count - lowerLimit)
}
lowerOrAtAnchorMessages.append(contentsOf: loadedLowerMessages)
}
if higherThanAnchorMessages.count < self.halfLimit {
var nextHigherIndex: MutableChatListEntryIndex
if let lastMessage = higherThanAnchorMessages.max(by: { $0.entryIndex < $1.entryIndex }) {
nextHigherIndex = lastMessage.entryIndex.successor
} else {
nextHigherIndex = max(resolvedUnpinnedAnchorIndex, self.anchorIndex.successor)
}
var loadedHigherMessages = loadedMessages.filter({ $0.entryIndex > nextHigherIndex })
let higherLimit = self.halfLimit - higherThanAnchorMessages.count
if loadedHigherMessages.count > higherLimit {
loadedHigherMessages.removeLast(loadedHigherMessages.count - higherLimit)
}
higherThanAnchorMessages.append(contentsOf: loadedHigherMessages)
}
}
} else {
if lowerOrAtAnchorMessages.count < self.halfLimit {
var nextLowerIndex: MutableChatListEntryIndex
if let lastMessage = lowerOrAtAnchorMessages.min(by: { $0.entryIndex < $1.entryIndex }) {
nextLowerIndex = lastMessage.entryIndex
} else {
nextLowerIndex = resolvedAnchorIndex.successor
}
let loadedLowerMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextLowerIndex.index, nextLowerIndex.isMessage), to: (lowerBound.index, lowerBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - lowerOrAtAnchorMessages.count, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, currentTransaction: currentTransaction, groupId: groupId, predicate: $0) }).map(mapEntry)
lowerOrAtAnchorMessages.append(contentsOf: loadedLowerMessages)
}
if higherThanAnchorMessages.count < self.halfLimit {
var nextHigherIndex: MutableChatListEntryIndex
if let lastMessage = higherThanAnchorMessages.max(by: { $0.entryIndex < $1.entryIndex }) {
nextHigherIndex = lastMessage.entryIndex
} else {
nextHigherIndex = resolvedAnchorIndex
}
let loadedHigherMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextHigherIndex.index, nextHigherIndex.isMessage), to: (upperBound.index, upperBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - higherThanAnchorMessages.count, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, currentTransaction: currentTransaction, groupId: groupId, predicate: $0) }).map(mapEntry)
higherThanAnchorMessages.append(contentsOf: loadedHigherMessages)
}
}
lowerOrAtAnchorMessages.reverse()
assert(lowerOrAtAnchorMessages.count <= self.halfLimit)
assert(higherThanAnchorMessages.count <= self.halfLimit)
let allIndices = (lowerOrAtAnchorMessages + higherThanAnchorMessages).map { $0.entryIndex }
let allEntityIds = (lowerOrAtAnchorMessages + higherThanAnchorMessages).map { $0.entityId }
if Set(allIndices).count != allIndices.count {
var debugRepeatedIndices = Set<MutableChatListEntryIndex>()
var existingIndices = Set<MutableChatListEntryIndex>()
for i in (0 ..< lowerOrAtAnchorMessages.count).reversed() {
if !existingIndices.contains(lowerOrAtAnchorMessages[i].entryIndex) {
existingIndices.insert(lowerOrAtAnchorMessages[i].entryIndex)
} else {
debugRepeatedIndices.insert(lowerOrAtAnchorMessages[i].entryIndex)
lowerOrAtAnchorMessages.remove(at: i)
}
}
for i in (0 ..< higherThanAnchorMessages.count).reversed() {
if !existingIndices.contains(higherThanAnchorMessages[i].entryIndex) {
existingIndices.insert(higherThanAnchorMessages[i].entryIndex)
} else {
debugRepeatedIndices.insert(higherThanAnchorMessages[i].entryIndex)
higherThanAnchorMessages.remove(at: i)
}
}
postboxLog("allIndices not unique, repeated: \(debugRepeatedIndices)")
assert(false)
//preconditionFailure()
}
if Set(allEntityIds).count != allEntityIds.count {
var existingEntityIds = Set<MutableChatListEntryEntityId>()
for i in (0 ..< lowerOrAtAnchorMessages.count).reversed() {
if !existingEntityIds.contains(lowerOrAtAnchorMessages[i].entityId) {
existingEntityIds.insert(lowerOrAtAnchorMessages[i].entityId)
} else {
lowerOrAtAnchorMessages.remove(at: i)
}
}
for i in (0 ..< higherThanAnchorMessages.count).reversed() {
if !existingEntityIds.contains(higherThanAnchorMessages[i].entityId) {
existingEntityIds.insert(higherThanAnchorMessages[i].entityId)
} else {
higherThanAnchorMessages.remove(at: i)
}
}
postboxLog("existingEntityIds not unique: \(allEntityIds)")
postboxLog("allIndices: \(allIndices)")
assert(false)
//preconditionFailure()
}
assert(allIndices.sorted() == allIndices)
let entries = OrderedChatListViewEntries(anchorIndex: self.anchorIndex.index, lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages)
self.orderedEntries = entries
case let .peers(peerIds, asPinned):
var lowerOrAtAnchorMessages: [MutableChatListEntry] = self.orderedEntries.lowerOrAtAnchor.reversed()
var higherThanAnchorMessages: [MutableChatListEntry] = self.orderedEntries.higherThanAnchor
let unpinnedLowerBound: MutableChatListEntryIndex
let unpinnedUpperBound: MutableChatListEntryIndex
unpinnedUpperBound = .absoluteUpperBound
unpinnedLowerBound = MutableChatListEntryIndex(index: ChatListIndex.absoluteLowerBound, isMessage: true)
let resolvedUnpinnedAnchorIndex = min(unpinnedUpperBound, max(self.anchorIndex, unpinnedLowerBound))
if lowerOrAtAnchorMessages.count < self.halfLimit || higherThanAnchorMessages.count < self.halfLimit {
func mapEntry(_ entry: ChatListIntermediateEntry, pinningIndex: UInt16?) -> MutableChatListEntry {
switch entry {
case let .message(index, messageIndex):
var updatedIndex = index
updatedIndex = ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex)
return .IntermediateMessageEntry(index: updatedIndex, messageIndex: messageIndex)
case let .hole(hole):
return .HoleEntry(hole)
}
}
var loadedMessages: [MutableChatListEntry] = []
for i in 0 ..< peerIds.count {
let peerId = peerIds[i]
if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
loadedMessages.append(mapEntry(entry, pinningIndex: asPinned ? UInt16(i) : nil))
}
}
loadedMessages.sort(by: { $0.entryIndex < $1.entryIndex })
if lowerOrAtAnchorMessages.count < self.halfLimit {
var nextLowerIndex: MutableChatListEntryIndex
if let lastMessage = lowerOrAtAnchorMessages.min(by: { $0.entryIndex < $1.entryIndex }) {
nextLowerIndex = lastMessage.entryIndex.predecessor
} else {
nextLowerIndex = min(resolvedUnpinnedAnchorIndex, self.anchorIndex)
}
var loadedLowerMessages = Array(loadedMessages.filter({ $0.entryIndex <= nextLowerIndex }).reversed())
let lowerLimit = self.halfLimit - lowerOrAtAnchorMessages.count
if loadedLowerMessages.count > lowerLimit {
loadedLowerMessages.removeLast(loadedLowerMessages.count - lowerLimit)
}
lowerOrAtAnchorMessages.append(contentsOf: loadedLowerMessages)
}
if higherThanAnchorMessages.count < self.halfLimit {
var nextHigherIndex: MutableChatListEntryIndex
if let lastMessage = higherThanAnchorMessages.max(by: { $0.entryIndex < $1.entryIndex }) {
nextHigherIndex = lastMessage.entryIndex.successor
} else {
nextHigherIndex = max(resolvedUnpinnedAnchorIndex, self.anchorIndex.successor)
}
var loadedHigherMessages = loadedMessages.filter({ $0.entryIndex > nextHigherIndex })
let higherLimit = self.halfLimit - higherThanAnchorMessages.count
if loadedHigherMessages.count > higherLimit {
loadedHigherMessages.removeLast(loadedHigherMessages.count - higherLimit)
}
higherThanAnchorMessages.append(contentsOf: loadedHigherMessages)
}
lowerOrAtAnchorMessages.reverse()
assert(lowerOrAtAnchorMessages.count <= self.halfLimit)
assert(higherThanAnchorMessages.count <= self.halfLimit)
let allIndices = (lowerOrAtAnchorMessages + higherThanAnchorMessages).map { $0.entryIndex }
assert(Set(allIndices).count == allIndices.count)
assert(allIndices.sorted() == allIndices)
let entries = OrderedChatListViewEntries(anchorIndex: self.anchorIndex.index, lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages)
self.orderedEntries = entries
}
}
assert(self.orderedEntries.lowerOrAtAnchor.count <= self.halfLimit)
assert(self.orderedEntries.higherThanAnchor.count <= self.halfLimit)
}
func replay(postbox: PostboxImpl, currentTransaction: Transaction, transaction: PostboxTransaction) -> Bool {
var hasUpdates = false
var hadRemovals = false
var globalNotificationSettings: PostboxGlobalNotificationSettings?
for (groupId, operations) in transaction.chatListOperations {
inner: for operation in operations {
switch operation {
case let .InsertEntry(index, messageIndex):
switch self.space {
case let .group(spaceGroupId, pinned, filterPredicate):
let matchesGroup = groupId == spaceGroupId && (index.pinningIndex != nil) == pinned.include
if !matchesGroup {
continue inner
}
var updatedIndex = index
if case .includePinnedAsUnpinned = pinned {
updatedIndex = ChatListIndex(pinningIndex: nil, messageIndex: index.messageIndex)
}
if let filterPredicate = filterPredicate {
if let peer = postbox.peerTable.get(updatedIndex.messageIndex.id.peerId) {
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
let globalNotificationSettingsValue: PostboxGlobalNotificationSettings
if let current = globalNotificationSettings {
globalNotificationSettingsValue = current
} else {
globalNotificationSettingsValue = postbox.getGlobalNotificationSettings(transaction: currentTransaction)
globalNotificationSettings = globalNotificationSettingsValue
}
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId))
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, calculation: filterPredicate.messageTagSummary)
if !filterPredicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: postbox.readStateTable.getCombinedState(peer.id)?.isUnread ?? false, isContact: postbox.contactsTable.isContact(peerId: notificationsPeerId), messageTagSummaryResult: messageTagSummaryResult) {
continue inner
}
} else {
continue inner
}
}
if self.add(entry: .IntermediateMessageEntry(index: updatedIndex, messageIndex: messageIndex)) {
hasUpdates = true
} else {
hasUpdates = true
hadRemovals = true
}
case let .peers(peerIds, asPinned):
if let peerIndex = peerIds.firstIndex(of: index.messageIndex.id.peerId) {
var updatedIndex = index
if asPinned {
updatedIndex = ChatListIndex(pinningIndex: UInt16(peerIndex), messageIndex: index.messageIndex)
}
if self.add(entry: .IntermediateMessageEntry(index: updatedIndex, messageIndex: messageIndex)) {
hasUpdates = true
} else {
hasUpdates = true
hadRemovals = true
}
} else {
continue inner
}
}
case let .InsertHole(hole):
switch self.space {
case let .group(spaceGroupId, pinned, _):
if spaceGroupId == groupId && !pinned.include {
if self.add(entry: .HoleEntry(hole)) {
hasUpdates = true
} else {
hasUpdates = true
hadRemovals = true
}
}
case .peers:
break
}
case let .RemoveEntry(indices):
switch self.space {
case let .group(spaceGroupId, pinned, _):
if spaceGroupId == groupId {
for index in indices {
var updatedIndex = index
if case .includePinnedAsUnpinned = pinned {
updatedIndex = ChatListIndex(pinningIndex: nil, messageIndex: index.messageIndex)
}
if self.orderedEntries.remove(index: MutableChatListEntryIndex(index: updatedIndex, isMessage: true)) {
hasUpdates = true
hadRemovals = true
}
}
}
case let .peers(peerIds, asPinned):
for index in indices {
if let peerIndex = peerIds.firstIndex(of: index.messageIndex.id.peerId) {
var updatedIndex = index
if asPinned {
updatedIndex = ChatListIndex(pinningIndex: UInt16(peerIndex), messageIndex: index.messageIndex)
}
if self.orderedEntries.remove(index: MutableChatListEntryIndex(index: updatedIndex, isMessage: true)) {
hasUpdates = true
hadRemovals = true
}
}
}
}
case let .RemoveHoles(indices):
switch self.space {
case let .group(spaceGroupId, pinned, _):
if spaceGroupId == groupId && !pinned.include {
for index in indices {
if self.orderedEntries.remove(index: MutableChatListEntryIndex(index: index, isMessage: false)) {
hasUpdates = true
hadRemovals = true
}
}
}
case .peers:
break
}
}
}
}
if !transaction.currentUpdatedPeerNotificationSettings.isEmpty, case let .group(groupId, pinned, maybeFilterPredicate) = self.space, let filterPredicate = maybeFilterPredicate {
var removeEntryIndices: [MutableChatListEntryIndex] = []
let _ = self.orderedEntries.mutableScan { entry in
let entryPeer: Peer
let entryNotificationsPeerId: PeerId
switch entry {
case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _):
if let peer = renderedPeer.peer {
entryPeer = peer
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
} else {
return nil
}
case let .IntermediateMessageEntry(index, _):
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
entryPeer = peer
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
} else {
return nil
}
case .HoleEntry:
return nil
}
if let settingsChange = transaction.currentUpdatedPeerNotificationSettings[entryNotificationsPeerId] {
let isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false
let globalNotificationSettingsValue: PostboxGlobalNotificationSettings
if let current = globalNotificationSettings {
globalNotificationSettingsValue = current
} else {
globalNotificationSettingsValue = postbox.getGlobalNotificationSettings(transaction: currentTransaction)
globalNotificationSettings = globalNotificationSettingsValue
}
let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: entryPeer, peerSettings: settingsChange.1)
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: entryPeer.id, calculation: filterPredicate.messageTagSummary)
let isIncluded = filterPredicate.includes(peer: entryPeer, groupId: groupId, isRemovedFromTotalUnreadCount: nowRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: entryNotificationsPeerId), messageTagSummaryResult: messageTagSummaryResult)
if !isIncluded {
removeEntryIndices.append(entry.entryIndex)
}
}
return nil
}
if !removeEntryIndices.isEmpty {
hasUpdates = true
hadRemovals = true
for index in removeEntryIndices {
let _ = self.orderedEntries.remove(index: index)
}
}
for (peerId, settingsChange) in transaction.currentUpdatedPeerNotificationSettings {
if let mainPeer = postbox.peerTable.get(peerId) {
var peers: [Peer] = [mainPeer]
for associatedId in postbox.reverseAssociatedPeerTable.get(peerId: mainPeer.id) {
if let associatedPeer = postbox.peerTable.get(associatedId) {
peers.append(associatedPeer)
}
}
assert(Set(peers.map { $0.id }).count == peers.count)
let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
let globalNotificationSettingsValue: PostboxGlobalNotificationSettings
if let current = globalNotificationSettings {
globalNotificationSettingsValue = current
} else {
globalNotificationSettingsValue = postbox.getGlobalNotificationSettings(transaction: currentTransaction)
globalNotificationSettings = globalNotificationSettingsValue
}
let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: mainPeer, peerSettings: settingsChange.1)
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peerId, calculation: filterPredicate.messageTagSummary)
let isIncluded = filterPredicate.includes(peer: mainPeer, groupId: groupId, isRemovedFromTotalUnreadCount: nowRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: peerId), messageTagSummaryResult: messageTagSummaryResult)
if isIncluded && self.orderedEntries.indicesForPeerId(mainPeer.id) == nil {
for peer in peers {
let tableEntry = postbox.chatListTable.getEntry(groupId: groupId, peerId: peer.id, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable)
if let entry = tableEntry {
if pinned.include == (entry.index.pinningIndex != nil) {
if self.orderedEntries.indicesForPeerId(peer.id) == nil {
switch entry {
case let .message(index, messageIndex):
if self.add(entry: .IntermediateMessageEntry(index: index, messageIndex: messageIndex)) {
hasUpdates = true
} else {
hasUpdates = true
hadRemovals = true
}
default:
break
}
}
}
}
}
}
}
}
}
if !transaction.currentUpdatedPeerNotificationSettings.isEmpty {
let globalNotificationSettings = postbox.getGlobalNotificationSettings(transaction: currentTransaction)
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, hasFailedMessages, isContact):
if let peer = renderedPeer.peer {
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
if let (_, updated) = transaction.currentUpdatedPeerNotificationSettings[notificationsPeerId] {
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: updated)
return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, hasFailedMessages: hasFailedMessages, isContact: isContact)
} else {
return nil
}
} else {
return nil
}
default:
return nil
}
}) {
hasUpdates = true
}
}
if !transaction.updatedFailedMessagePeerIds.isEmpty {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, _, isContact):
if transaction.updatedFailedMessagePeerIds.contains(index.messageIndex.id.peerId) {
return .MessageEntry(
index: index,
messages: messages,
readState: readState,
notificationSettings: notificationSettings,
isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount,
embeddedInterfaceState: embeddedInterfaceState,
renderedPeer: renderedPeer,
presence: presence,
tagSummaryInfo: tagSummaryInfo,
hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId),
isContact: isContact
)
} else {
return nil
}
default:
return nil
}
}) {
hasUpdates = true
}
}
if !transaction.currentUpdatedPeers.isEmpty {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, hasFailedMessages, isContact):
var updatedMessages: [Message] = messages
var hasUpdatedMessages = false
for i in 0 ..< updatedMessages.count {
if let updatedMessage = updateMessagePeers(updatedMessages[i], updatedPeers: transaction.currentUpdatedPeers) {
updatedMessages[i] = updatedMessage
hasUpdatedMessages = true
}
}
let renderedPeer = updatedRenderedPeer(entryRenderedPeer, updatedPeers: transaction.currentUpdatedPeers)
if hasUpdatedMessages || renderedPeer != nil {
return .MessageEntry(
index: index,
messages: updatedMessages,
readState: readState,
notificationSettings: notificationSettings,
isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount,
embeddedInterfaceState: embeddedInterfaceState,
renderedPeer: renderedPeer ?? entryRenderedPeer,
presence: presence,
tagSummaryInfo: tagSummaryInfo,
hasFailedMessages: hasFailedMessages,
isContact: isContact)
} else {
return nil
}
default:
return nil
}
}) {
hasUpdates = true
}
}
if !transaction.currentUpdatedPeerPresences.isEmpty {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, hasFailedMessages, isContact):
var presencePeerId = entryRenderedPeer.peerId
if let peer = entryRenderedPeer.peers[entryRenderedPeer.peerId], let associatedPeerId = peer.associatedPeerId {
presencePeerId = associatedPeerId
}
if let presence = transaction.currentUpdatedPeerPresences[presencePeerId] {
return .MessageEntry(
index: index,
messages: messages,
readState: readState,
notificationSettings: notificationSettings,
isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount,
embeddedInterfaceState: embeddedInterfaceState,
renderedPeer: entryRenderedPeer,
presence: presence,
tagSummaryInfo: tagSummaryInfo,
hasFailedMessages: hasFailedMessages,
isContact: isContact
)
} else {
return nil
}
default:
return nil
}
}) {
hasUpdates = true
}
}
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty, case let .group(groupId, pinned, maybeFilterPredicate) = self.space, let filterPredicate = maybeFilterPredicate, let filterMessageTagSummary = filterPredicate.messageTagSummary {
var removeEntryIndices: [MutableChatListEntryIndex] = []
let _ = self.orderedEntries.mutableScan { entry in
let entryPeer: Peer
let entryNotificationsPeerId: PeerId
switch entry {
case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _):
if let peer = entryRenderedPeer.peer {
entryPeer = peer
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
} else {
return nil
}
case let .IntermediateMessageEntry(index, _):
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
entryPeer = peer
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
} else {
return nil
}
case .HoleEntry:
return nil
}
let updatedMessageSummary = transaction.currentUpdatedMessageTagSummaries[MessageHistoryTagsSummaryKey(tag: filterMessageTagSummary.addCount.tag, peerId: entryPeer.id, namespace: filterMessageTagSummary.addCount.namespace)]
let updatedActionsSummary = transaction.currentUpdatedMessageActionsSummaries[PendingMessageActionsSummaryKey(type: filterMessageTagSummary.subtractCount.type, peerId: entryPeer.id, namespace: filterMessageTagSummary.subtractCount.namespace)]
if updatedMessageSummary != nil || updatedActionsSummary != nil {
let isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false
let globalNotificationSettingsValue: PostboxGlobalNotificationSettings
if let current = globalNotificationSettings {
globalNotificationSettingsValue = current
} else {
globalNotificationSettingsValue = postbox.getGlobalNotificationSettings(transaction: currentTransaction)
globalNotificationSettings = globalNotificationSettingsValue
}
let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: entryPeer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(entryPeer.id))
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: entryPeer.id, calculation: filterPredicate.messageTagSummary)
let isIncluded = filterPredicate.includes(peer: entryPeer, groupId: groupId, isRemovedFromTotalUnreadCount: nowRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: entryNotificationsPeerId), messageTagSummaryResult: messageTagSummaryResult)
if !isIncluded {
removeEntryIndices.append(entry.entryIndex)
}
}
return nil
}
if !removeEntryIndices.isEmpty {
hasUpdates = true
hadRemovals = true
for index in removeEntryIndices {
let _ = self.orderedEntries.remove(index: index)
}
}
var changedPeerIds = Set<PeerId>()
for key in transaction.currentUpdatedMessageTagSummaries.keys {
changedPeerIds.insert(key.peerId)
}
for key in transaction.currentUpdatedMessageTagSummaries.keys {
changedPeerIds.insert(key.peerId)
}
for peerId in changedPeerIds {
if let mainPeer = postbox.peerTable.get(peerId) {
var peers: [Peer] = [mainPeer]
for associatedId in postbox.reverseAssociatedPeerTable.get(peerId: mainPeer.id) {
if let associatedPeer = postbox.peerTable.get(associatedId) {
peers.append(associatedPeer)
}
}
assert(Set(peers.map { $0.id }).count == peers.count)
let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
let globalNotificationSettingsValue: PostboxGlobalNotificationSettings
if let current = globalNotificationSettings {
globalNotificationSettingsValue = current
} else {
globalNotificationSettingsValue = postbox.getGlobalNotificationSettings(transaction: currentTransaction)
globalNotificationSettings = globalNotificationSettingsValue
}
let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: mainPeer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(mainPeer.id))
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peerId, calculation: filterPredicate.messageTagSummary)
let isIncluded = filterPredicate.includes(peer: mainPeer, groupId: groupId, isRemovedFromTotalUnreadCount: nowRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: peerId), messageTagSummaryResult: messageTagSummaryResult)
if isIncluded && self.orderedEntries.indicesForPeerId(mainPeer.id) == nil {
for peer in peers {
let tableEntry = postbox.chatListTable.getEntry(groupId: groupId, peerId: peer.id, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable)
if let entry = tableEntry {
if pinned.include == (entry.index.pinningIndex != nil) {
if self.orderedEntries.indicesForPeerId(peer.id) == nil {
switch entry {
case let .message(index, messageIndex):
if self.add(entry: .IntermediateMessageEntry(index: index, messageIndex: messageIndex)) {
hasUpdates = true
} else {
hasUpdates = true
hadRemovals = true
}
default:
break
}
}
}
}
}
}
}
}
}
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, hasFailedMessages, isContact):
var updatedChatListMessageTagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = tagSummaryInfo
var didUpdateSummaryInfo = false
for (key, component) in self.summaryComponents.components {
var updatedTagSummaryCount: Int32?
if let tagSummary = component.tagSummary {
let key = MessageHistoryTagsSummaryKey(tag: key.tag, peerId: index.messageIndex.id.peerId, namespace: tagSummary.namespace)
if let summary = transaction.currentUpdatedMessageTagSummaries[key] {
updatedTagSummaryCount = summary.count
}
}
var updatedActionsSummaryCount: Int32?
if let actionsSummary = component.actionsSummary {
let key = PendingMessageActionsSummaryKey(type: key.actionType, peerId: index.messageIndex.id.peerId, namespace: actionsSummary.namespace)
if let count = transaction.currentUpdatedMessageActionsSummaries[key] {
updatedActionsSummaryCount = count
}
}
if updatedTagSummaryCount != nil || updatedActionsSummaryCount != nil {
updatedChatListMessageTagSummaryInfo[key] = ChatListMessageTagSummaryInfo(
tagSummaryCount: updatedTagSummaryCount ?? updatedChatListMessageTagSummaryInfo[key]?.tagSummaryCount,
actionsSummaryCount: updatedActionsSummaryCount ?? updatedChatListMessageTagSummaryInfo[key]?.actionsSummaryCount
)
didUpdateSummaryInfo = true
}
}
if didUpdateSummaryInfo {
return .MessageEntry(
index: index,
messages: messages,
readState: readState,
notificationSettings: notificationSettings,
isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount,
embeddedInterfaceState: embeddedInterfaceState,
renderedPeer: entryRenderedPeer,
presence: presence,
tagSummaryInfo: updatedChatListMessageTagSummaryInfo,
hasFailedMessages: hasFailedMessages,
isContact: isContact
)
} else {
return nil
}
default:
return nil
}
}) {
hasUpdates = true
}
}
if true || hadRemovals {
self.fillSpace(postbox: postbox, currentTransaction: currentTransaction)
}
self.checkEntries(postbox: postbox)
self.checkReplayEntries(postbox: postbox)
return hasUpdates
}
private func checkEntries(postbox: PostboxImpl) {
#if DEBUG
if case .group(.root, .notPinned, nil) = self.space {
let allEntries = self.orderedEntries.lowerOrAtAnchor + self.orderedEntries.higherThanAnchor
if !allEntries.isEmpty {
assert(allEntries.sorted(by: { $0.index < $1.index }) == allEntries)
func mapEntry(_ entry: ChatListIntermediateEntry) -> MutableChatListEntry {
switch entry {
case let .message(index, messageIndex):
return .IntermediateMessageEntry(index: index, messageIndex: messageIndex)
case let .hole(hole):
return .HoleEntry(hole)
}
}
//let loadedEntries = postbox.chatListTable.entries(groupId: .root, from: (allEntries[0].index.predecessor, true), to: (allEntries[allEntries.count - 1].index.successor, true), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: 1000, predicate: nil).map(mapEntry)
//assert(loadedEntries.map({ $0.index }) == allEntries.map({ $0.index }))
}
}
#endif
}
private func checkReplayEntries(postbox: PostboxImpl) {
#if DEBUG
//let cleanState = ChatListViewSpaceState(postbox: postbox, space: self.space, anchorIndex: self.anchorIndex, summaryComponents: self.summaryComponents, halfLimit: self.halfLimit)
//assert(self.orderedEntries.lowerOrAtAnchor.map { $0.index } == cleanState.orderedEntries.lowerOrAtAnchor.map { $0.index })
//assert(self.orderedEntries.higherThanAnchor.map { $0.index } == cleanState.orderedEntries.higherThanAnchor.map { $0.index })
#endif
}
private func add(entry: MutableChatListEntry) -> Bool {
if self.anchorIndex >= entry.entryIndex {
let insertionIndex = binaryInsertionIndex(self.orderedEntries.lowerOrAtAnchor, extract: { $0.entryIndex }, searchItem: entry.entryIndex)
if insertionIndex < self.orderedEntries.lowerOrAtAnchor.count {
if self.orderedEntries.lowerOrAtAnchor[insertionIndex].entryIndex == entry.entryIndex {
assertionFailure("Inserting an existing index is not allowed")
self.orderedEntries.setLowerOrAtAnchorAtArrayIndex(insertionIndex, to: entry)
return true
}
}
if insertionIndex == 0 {
return false
}
self.orderedEntries.insertLowerOrAtAnchorAtArrayIndex(insertionIndex, value: entry)
if self.orderedEntries.lowerOrAtAnchor.count > self.halfLimit {
self.orderedEntries.removeLowerOrAtAnchorAtArrayIndex(0)
}
return true
} else {
let insertionIndex = binaryInsertionIndex(orderedEntries.higherThanAnchor, extract: { $0.entryIndex }, searchItem: entry.entryIndex)
if insertionIndex < self.orderedEntries.higherThanAnchor.count {
if self.orderedEntries.higherThanAnchor[insertionIndex].entryIndex == entry.entryIndex {
assertionFailure("Inserting an existing index is not allowed")
self.orderedEntries.setHigherThanAnchorAtArrayIndex(insertionIndex, to: entry)
return true
}
}
if insertionIndex == self.orderedEntries.higherThanAnchor.count {
return false
}
self.orderedEntries.insertHigherThanAnchorAtArrayIndex(insertionIndex, value: entry)
if self.orderedEntries.higherThanAnchor.count > self.halfLimit {
self.orderedEntries.removeHigherThanAnchorAtArrayIndex(self.orderedEntries.higherThanAnchor.count - 1)
}
return true
}
}
}
private struct MutableChatListEntryIndex: Hashable, Comparable {
var index: ChatListIndex
var isMessage: Bool
var predecessor: MutableChatListEntryIndex {
return MutableChatListEntryIndex(index: self.index.predecessor, isMessage: self.isMessage)
}
var successor: MutableChatListEntryIndex {
return MutableChatListEntryIndex(index: self.index.successor, isMessage: self.isMessage)
}
static let absoluteLowerBound = MutableChatListEntryIndex(index: .absoluteLowerBound, isMessage: true)
static let absoluteUpperBound = MutableChatListEntryIndex(index: .absoluteUpperBound, isMessage: true)
static func <(lhs: MutableChatListEntryIndex, rhs: MutableChatListEntryIndex) -> Bool {
if lhs.index != rhs.index {
return lhs.index < rhs.index
} else if lhs.isMessage != rhs.isMessage {
return lhs.isMessage
} else {
return false
}
}
}
private enum MutableChatListEntryEntityId: Hashable {
case hole(MessageIndex)
case peer(PeerId)
}
private extension MutableChatListEntry {
var messagePeerId: PeerId? {
switch self {
case let .IntermediateMessageEntry(index, _):
return index.messageIndex.id.peerId
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
return index.messageIndex.id.peerId
case .HoleEntry:
return nil
}
}
var entryIndex: MutableChatListEntryIndex {
switch self {
case let .IntermediateMessageEntry(index, _):
return MutableChatListEntryIndex(index: index, isMessage: true)
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
return MutableChatListEntryIndex(index: index, isMessage: true)
case let .HoleEntry(hole):
return MutableChatListEntryIndex(index: ChatListIndex(pinningIndex: nil, messageIndex: hole.index), isMessage: false)
}
}
var entityId: MutableChatListEntryEntityId {
switch self {
case let .IntermediateMessageEntry(index, _):
return .peer(index.messageIndex.id.peerId)
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
return .peer(index.messageIndex.id.peerId)
case let .HoleEntry(hole):
return .hole(hole.index)
}
}
}
private struct OrderedChatListViewEntries {
private let anchorIndex: ChatListIndex
private(set) var lowerOrAtAnchor: [MutableChatListEntry]
private(set) var higherThanAnchor: [MutableChatListEntry]
private(set) var reverseIndices: [PeerId: [MutableChatListEntryIndex]] = [:]
fileprivate init(anchorIndex: ChatListIndex, lowerOrAtAnchor: [MutableChatListEntry], higherThanAnchor: [MutableChatListEntry]) {
self.anchorIndex = anchorIndex
assert(!lowerOrAtAnchor.contains(where: { $0.index > anchorIndex }))
assert(!higherThanAnchor.contains(where: { $0.index <= anchorIndex }))
self.lowerOrAtAnchor = lowerOrAtAnchor
self.higherThanAnchor = higherThanAnchor
for entry in lowerOrAtAnchor {
if let peerId = entry.messagePeerId {
if self.reverseIndices[peerId] == nil {
self.reverseIndices[peerId] = [entry.entryIndex]
} else {
self.reverseIndices[peerId]!.append(entry.entryIndex)
}
}
}
for entry in higherThanAnchor {
if let peerId = entry.messagePeerId {
if self.reverseIndices[peerId] == nil {
self.reverseIndices[peerId] = [entry.entryIndex]
} else {
self.reverseIndices[peerId]!.append(entry.entryIndex)
}
}
}
}
mutating func setLowerOrAtAnchorAtArrayIndex(_ index: Int, to value: MutableChatListEntry) {
assert(value.index <= self.anchorIndex)
let previousIndex = self.lowerOrAtAnchor[index].entryIndex
let updatedIndex = value.entryIndex
let previousPeerId = self.lowerOrAtAnchor[index].messagePeerId
let updatedPeerId = value.messagePeerId
self.lowerOrAtAnchor[index] = value
if previousPeerId != updatedPeerId {
if let previousPeerId = previousPeerId {
self.reverseIndices[previousPeerId]?.removeAll(where: { $0 == previousIndex })
if let isEmpty = self.reverseIndices[previousPeerId]?.isEmpty, isEmpty {
self.reverseIndices.removeValue(forKey: previousPeerId)
}
}
if let updatedPeerId = updatedPeerId {
if self.reverseIndices[updatedPeerId] == nil {
self.reverseIndices[updatedPeerId] = [updatedIndex]
} else {
self.reverseIndices[updatedPeerId]!.append(updatedIndex)
}
}
}
}
mutating func setHigherThanAnchorAtArrayIndex(_ index: Int, to value: MutableChatListEntry) {
assert(value.index > self.anchorIndex)
let previousIndex = self.higherThanAnchor[index].entryIndex
let updatedIndex = value.entryIndex
let previousPeerId = self.higherThanAnchor[index].messagePeerId
let updatedPeerId = value.messagePeerId
self.higherThanAnchor[index] = value
if previousPeerId != updatedPeerId {
if let previousPeerId = previousPeerId {
self.reverseIndices[previousPeerId]?.removeAll(where: { $0 == previousIndex })
if let isEmpty = self.reverseIndices[previousPeerId]?.isEmpty, isEmpty {
self.reverseIndices.removeValue(forKey: previousPeerId)
}
}
if let updatedPeerId = updatedPeerId {
if self.reverseIndices[updatedPeerId] == nil {
self.reverseIndices[updatedPeerId] = [updatedIndex]
} else {
self.reverseIndices[updatedPeerId]!.append(updatedIndex)
}
}
}
}
mutating func insertLowerOrAtAnchorAtArrayIndex(_ index: Int, value: MutableChatListEntry) {
assert(value.index <= self.anchorIndex)
self.lowerOrAtAnchor.insert(value, at: index)
if let peerId = value.messagePeerId {
if self.reverseIndices[peerId] == nil {
self.reverseIndices[peerId] = [value.entryIndex]
} else {
self.reverseIndices[peerId]!.append(value.entryIndex)
}
}
}
mutating func insertHigherThanAnchorAtArrayIndex(_ index: Int, value: MutableChatListEntry) {
assert(value.index > self.anchorIndex)
self.higherThanAnchor.insert(value, at: index)
if let peerId = value.messagePeerId {
if self.reverseIndices[peerId] == nil {
self.reverseIndices[peerId] = [value.entryIndex]
} else {
self.reverseIndices[peerId]!.append(value.entryIndex)
}
}
}
mutating func removeLowerOrAtAnchorAtArrayIndex(_ index: Int) {
let previousIndex = self.lowerOrAtAnchor[index].entryIndex
if let peerId = self.lowerOrAtAnchor[index].messagePeerId {
self.reverseIndices[peerId]?.removeAll(where: { $0 == previousIndex })
if let isEmpty = self.reverseIndices[peerId]?.isEmpty, isEmpty {
self.reverseIndices.removeValue(forKey: peerId)
}
}
self.lowerOrAtAnchor.remove(at: index)
}
mutating func removeHigherThanAnchorAtArrayIndex(_ index: Int) {
let previousIndex = self.higherThanAnchor[index].entryIndex
if let peerId = self.higherThanAnchor[index].messagePeerId {
self.reverseIndices[peerId]?.removeAll(where: { $0 == previousIndex })
if let isEmpty = self.reverseIndices[peerId]?.isEmpty, isEmpty {
self.reverseIndices.removeValue(forKey: peerId)
}
}
self.higherThanAnchor.remove(at: index)
}
func find(index: MutableChatListEntryIndex) -> MutableChatListEntry? {
if let entryIndex = binarySearch(self.lowerOrAtAnchor, extract: { $0.entryIndex }, searchItem: index) {
return self.lowerOrAtAnchor[entryIndex]
} else if let entryIndex = binarySearch(self.higherThanAnchor, extract: { $0.entryIndex }, searchItem: index) {
return self.higherThanAnchor[entryIndex]
} else {
return nil
}
}
func indicesForPeerId(_ peerId: PeerId) -> [MutableChatListEntryIndex]? {
return self.reverseIndices[peerId]
}
var first: MutableChatListEntry? {
return self.lowerOrAtAnchor.first ?? self.higherThanAnchor.first
}
mutating func mutableScan(_ f: (MutableChatListEntry) -> MutableChatListEntry?) -> Bool {
var anyUpdated = false
for i in 0 ..< self.lowerOrAtAnchor.count {
if let updated = f(self.lowerOrAtAnchor[i]) {
self.setLowerOrAtAnchorAtArrayIndex(i, to: updated)
anyUpdated = true
}
}
for i in 0 ..< self.higherThanAnchor.count {
if let updated = f(self.higherThanAnchor[i]) {
self.setHigherThanAnchorAtArrayIndex(i, to: updated)
anyUpdated = true
}
}
return anyUpdated
}
mutating func update(index: MutableChatListEntryIndex, _ f: (MutableChatListEntry) -> MutableChatListEntry?) -> Bool {
if let entryIndex = binarySearch(self.lowerOrAtAnchor, extract: { $0.entryIndex }, searchItem: index) {
if let updated = f(self.lowerOrAtAnchor[entryIndex]) {
assert(updated.index == self.lowerOrAtAnchor[entryIndex].index)
self.setLowerOrAtAnchorAtArrayIndex(entryIndex, to: updated)
return true
}
} else if let entryIndex = binarySearch(self.higherThanAnchor, extract: { $0.entryIndex }, searchItem: index) {
if let updated = f(self.higherThanAnchor[entryIndex]) {
assert(updated.index == self.lowerOrAtAnchor[entryIndex].index)
self.setHigherThanAnchorAtArrayIndex(entryIndex, to: updated)
return true
}
}
return false
}
mutating func remove(index: MutableChatListEntryIndex) -> Bool {
if let entryIndex = binarySearch(self.lowerOrAtAnchor, extract: { $0.entryIndex }, searchItem: index) {
self.removeLowerOrAtAnchorAtArrayIndex(entryIndex)
return true
} else if let entryIndex = binarySearch(self.higherThanAnchor, extract: { $0.entryIndex }, searchItem: index) {
self.removeHigherThanAnchorAtArrayIndex(entryIndex)
return true
} else {
return false
}
}
}
final class ChatListViewSample {
let entries: [MutableChatListEntry]
let lower: MutableChatListEntry?
let upper: MutableChatListEntry?
let anchorIndex: ChatListIndex
let hole: (PeerGroupId, ChatListHole)?
fileprivate init(entries: [MutableChatListEntry], lower: MutableChatListEntry?, upper: MutableChatListEntry?, anchorIndex: ChatListIndex, hole: (PeerGroupId, ChatListHole)?) {
self.entries = entries
self.lower = lower
self.upper = upper
self.anchorIndex = anchorIndex
self.hole = hole
}
}
struct ChatListViewState {
private let anchorIndex: MutableChatListEntryIndex
private let summaryComponents: ChatListEntrySummaryComponents
private let halfLimit: Int
private var stateBySpace: [ChatListViewSpace: ChatListViewSpaceState] = [:]
init(postbox: PostboxImpl, currentTransaction: Transaction, spaces: [ChatListViewSpace], anchorIndex: ChatListIndex, summaryComponents: ChatListEntrySummaryComponents, halfLimit: Int) {
self.anchorIndex = MutableChatListEntryIndex(index: anchorIndex, isMessage: true)
self.summaryComponents = summaryComponents
self.halfLimit = halfLimit
for space in spaces {
self.stateBySpace[space] = ChatListViewSpaceState(postbox: postbox, currentTransaction: currentTransaction, space: space, anchorIndex: self.anchorIndex, summaryComponents: summaryComponents, halfLimit: halfLimit)
}
}
func replay(postbox: PostboxImpl, currentTransaction: Transaction, transaction: PostboxTransaction) -> Bool {
var updated = false
for (_, state) in self.stateBySpace {
if state.replay(postbox: postbox, currentTransaction: currentTransaction, transaction: transaction) {
updated = true
}
}
return updated
}
private func sampleIndices() -> (lowerOrAtAnchor: [(ChatListViewSpace, Int)], higherThanAnchor: [(ChatListViewSpace, Int)]) {
var previousAnchorIndices: [ChatListViewSpace: Int] = [:]
var nextAnchorIndices: [ChatListViewSpace: Int] = [:]
for (space, state) in self.stateBySpace {
previousAnchorIndices[space] = state.orderedEntries.lowerOrAtAnchor.count - 1
nextAnchorIndices[space] = 0
}
var backwardsResult: [(ChatListViewSpace, Int)] = []
var backwardsResultIndices: [ChatListIndex] = []
var result: [(ChatListViewSpace, Int)] = []
var resultIndices: [ChatListIndex] = []
while true {
var minSpace: ChatListViewSpace?
for (space, value) in previousAnchorIndices {
if value != -1 {
if let minSpaceValue = minSpace {
if self.stateBySpace[space]!.orderedEntries.lowerOrAtAnchor[value].entryIndex > self.stateBySpace[minSpaceValue]!.orderedEntries.lowerOrAtAnchor[previousAnchorIndices[minSpaceValue]!].entryIndex {
minSpace = space
}
} else {
minSpace = space
}
}
}
if let minSpace = minSpace {
backwardsResult.append((minSpace, previousAnchorIndices[minSpace]!))
backwardsResultIndices.append(self.stateBySpace[minSpace]!.orderedEntries.lowerOrAtAnchor[previousAnchorIndices[minSpace]!].index)
previousAnchorIndices[minSpace]! -= 1
if backwardsResult.count == self.halfLimit {
break
}
}
if minSpace == nil {
break
}
}
while true {
var maxSpace: ChatListViewSpace?
for (space, value) in nextAnchorIndices {
if value != self.stateBySpace[space]!.orderedEntries.higherThanAnchor.count {
if let maxSpaceValue = maxSpace {
if self.stateBySpace[space]!.orderedEntries.higherThanAnchor[value].entryIndex < self.stateBySpace[maxSpaceValue]!.orderedEntries.higherThanAnchor[nextAnchorIndices[maxSpaceValue]!].entryIndex {
maxSpace = space
}
} else {
maxSpace = space
}
}
}
if let maxSpace = maxSpace {
result.append((maxSpace, nextAnchorIndices[maxSpace]!))
resultIndices.append(self.stateBySpace[maxSpace]!.orderedEntries.higherThanAnchor[nextAnchorIndices[maxSpace]!].index)
nextAnchorIndices[maxSpace]! += 1
if result.count == self.halfLimit {
break
}
}
if maxSpace == nil {
break
}
}
backwardsResultIndices.reverse()
assert(backwardsResultIndices.sorted() == backwardsResultIndices)
assert(resultIndices.sorted() == resultIndices)
let combinedIndices = (backwardsResultIndices + resultIndices)
assert(combinedIndices.sorted() == combinedIndices)
return (backwardsResult.reversed(), result)
}
func sample(postbox: PostboxImpl, currentTransaction: Transaction) -> ChatListViewSample {
let combinedSpacesAndIndicesByDirection = self.sampleIndices()
var result: [(ChatListViewSpace, MutableChatListEntry)] = []
var sampledHoleIndices: [Int] = []
var sampledAnchorBoundaryIndex: Int?
var sampledHoleChatListIndices = Set<ChatListIndex>()
let directions = [combinedSpacesAndIndicesByDirection.lowerOrAtAnchor, combinedSpacesAndIndicesByDirection.higherThanAnchor]
for directionIndex in 0 ..< directions.count {
outer: for i in 0 ..< directions[directionIndex].count {
let (space, listIndex) = directions[directionIndex][i]
let entry: MutableChatListEntry
if directionIndex == 0 {
entry = self.stateBySpace[space]!.orderedEntries.lowerOrAtAnchor[listIndex]
} else {
entry = self.stateBySpace[space]!.orderedEntries.higherThanAnchor[listIndex]
}
if entry.entryIndex >= self.anchorIndex {
sampledAnchorBoundaryIndex = result.count
}
switch entry {
case let .IntermediateMessageEntry(index, messageIndex):
var peers = SimpleDictionary<PeerId, Peer>()
var notificationsPeerId = index.messageIndex.id.peerId
var presence: PeerPresence?
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
peers[peer.id] = peer
if let notificationSettingsPeerId = peer.notificationSettingsPeerId {
notificationsPeerId = notificationSettingsPeerId
}
if let associatedPeerId = peer.associatedPeerId {
if let associatedPeer = postbox.peerTable.get(associatedPeerId) {
peers[associatedPeer.id] = associatedPeer
}
presence = postbox.peerPresenceTable.get(associatedPeerId)
} else {
presence = postbox.peerPresenceTable.get(index.messageIndex.id.peerId)
}
}
let renderedPeer = RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers)
var tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = [:]
for (key, component) in self.summaryComponents.components {
var tagSummaryCount: Int32?
var actionsSummaryCount: Int32?
if let tagSummary = component.tagSummary {
let key = MessageHistoryTagsSummaryKey(tag: key.tag, peerId: index.messageIndex.id.peerId, namespace: tagSummary.namespace)
if let summary = postbox.messageHistoryTagsSummaryTable.get(key) {
tagSummaryCount = summary.count
}
}
if let actionsSummary = component.actionsSummary {
let key = PendingMessageActionsSummaryKey(type: key.actionType, peerId: index.messageIndex.id.peerId, namespace: actionsSummary.namespace)
actionsSummaryCount = postbox.pendingMessageActionsMetadataTable.getCount(.peerNamespaceAction(key.peerId, key.namespace, key.type))
}
tagSummaryInfo[key] = ChatListMessageTagSummaryInfo(
tagSummaryCount: tagSummaryCount,
actionsSummaryCount: actionsSummaryCount
)
}
let notificationSettings = postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId)
let isRemovedFromTotalUnreadCount: Bool
if let peer = renderedPeer.peers[notificationsPeerId] {
isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: postbox.getGlobalNotificationSettings(transaction: currentTransaction), peer: peer, peerSettings: notificationSettings)
} else {
isRemovedFromTotalUnreadCount = false
}
var renderedMessages: [Message] = []
if let messageIndex = messageIndex {
if let messageGroup = postbox.messageHistoryTable.getMessageGroup(at: messageIndex, limit: 10) {
renderedMessages.append(contentsOf: messageGroup.compactMap(postbox.renderIntermediateMessage))
}
}
let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId))
if directionIndex == 0 {
self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry)
} else {
self.stateBySpace[space]!.orderedEntries.setHigherThanAnchorAtArrayIndex(listIndex, to: updatedEntry)
}
result.append((space, updatedEntry))
case .MessageEntry:
result.append((space, entry))
case .HoleEntry:
if !sampledHoleChatListIndices.contains(entry.index) {
sampledHoleChatListIndices.insert(entry.index)
sampledHoleIndices.append(result.count)
result.append((space, entry))
}
}
}
}
let allIndices = result.map { $0.1.entryIndex }
let allIndicesSorted = allIndices.sorted()
for i in 0 ..< allIndicesSorted.count {
assert(allIndicesSorted[i] == allIndices[i])
}
if Set(allIndices).count != allIndices.count {
var seenIndices = Set<MutableChatListEntryIndex>()
var updatedResult: [(ChatListViewSpace, MutableChatListEntry)] = []
for item in result {
if !seenIndices.contains(item.1.entryIndex) {
seenIndices.insert(item.1.entryIndex)
updatedResult.append(item)
}
}
result = updatedResult
let allIndices = result.map { $0.1.entryIndex }
let allIndicesSorted = allIndices.sorted()
for i in 0 ..< allIndicesSorted.count {
assert(allIndicesSorted[i] == allIndices[i])
}
assert(Set(allIndices).count == allIndices.count)
assert(false)
}
var sampledHoleIndex: Int?
if !sampledHoleIndices.isEmpty {
if let sampledAnchorBoundaryIndex = sampledAnchorBoundaryIndex {
var found = false
for i in 0 ..< sampledHoleIndices.count {
if i >= sampledAnchorBoundaryIndex {
sampledHoleIndex = sampledHoleIndices[i]
found = true
break
}
}
if !found {
sampledHoleIndex = sampledHoleIndices.first
}
} else if let index = sampledHoleIndices.first {
sampledHoleIndex = index
}
}
var sampledHole: (ChatListViewSpace, ChatListHole)?
if let index = sampledHoleIndex {
let (space, entry) = result[index]
if case let .HoleEntry(hole) = entry {
sampledHole = (space, hole)
} else {
assertionFailure()
}
}
var lower: MutableChatListEntry?
if combinedSpacesAndIndicesByDirection.lowerOrAtAnchor.count >= self.halfLimit {
lower = result[0].1
result.removeFirst()
}
var upper: MutableChatListEntry?
if combinedSpacesAndIndicesByDirection.higherThanAnchor.count >= self.halfLimit {
upper = result.last?.1
result.removeLast()
}
return ChatListViewSample(entries: result.map { $0.1 }, lower: lower, upper: upper, anchorIndex: self.anchorIndex.index, hole: sampledHole.flatMap { space, hole in
switch space {
case let .group(groupId, _, _):
return (groupId, hole)
case .peers:
return nil
}
})
}
}