Swiftgram/Postbox/MessageHistoryViewState.swift

316 lines
12 KiB
Swift

import Foundation
struct PeerIdAndNamespace: Hashable {
let peerId: PeerId
let namespace: MessageId.Namespace
}
enum HistoryViewAnchor {
case upperBound
case lowerBound
case index(MessageIndex)
func isLower(than otherIndex: MessageIndex) -> Bool {
switch self {
case .upperBound:
return false
case .lowerBound:
return true
case let .index(index):
return index < otherIndex
}
}
func isEqualOrLower(than otherIndex: MessageIndex) -> Bool {
switch self {
case .upperBound:
return false
case .lowerBound:
return true
case let .index(index):
return index <= otherIndex
}
}
func isGreater(than otherIndex: MessageIndex) -> Bool {
switch self {
case .upperBound:
return true
case .lowerBound:
return false
case let .index(index):
return index > otherIndex
}
}
func isEqualOrGreater(than otherIndex: MessageIndex) -> Bool {
switch self {
case .upperBound:
return true
case .lowerBound:
return false
case let .index(index):
return index >= otherIndex
}
}
}
private func binaryInsertionIndex(_ inputArr: [MutableMessageHistoryEntry], searchItem: HistoryViewAnchor) -> Int {
var lo = 0
var hi = inputArr.count - 1
while lo <= hi {
let mid = (lo + hi) / 2
let value = inputArr[mid]
if searchItem.isGreater(than: value.index) {
lo = mid + 1
} else if searchItem.isLower(than: value.index) {
hi = mid - 1
} else {
return mid
}
}
return lo
}
private func binaryIndexOrLower(_ inputArr: [MutableMessageHistoryEntry], _ searchItem: HistoryViewAnchor) -> Int {
var lo = 0
var hi = inputArr.count - 1
while lo <= hi {
let mid = (lo + hi) / 2
if searchItem.isGreater(than: inputArr[mid].index) {
lo = mid + 1
} else if searchItem.isLower(than: inputArr[mid].index) {
hi = mid - 1
} else {
return mid
}
}
return hi
}
private func sampleEntries(sortedEntriesBySpace: [PeerIdAndNamespace: [MutableMessageHistoryEntry]], anchor: HistoryViewAnchor, limit: Int) -> [(PeerIdAndNamespace, Int)] {
var previousAnchorIndices: [PeerIdAndNamespace: Int] = [:]
var nextAnchorIndices: [PeerIdAndNamespace: Int] = [:]
for (space, items) in sortedEntriesBySpace {
let index = binaryIndexOrLower(items, anchor)
previousAnchorIndices[space] = index
nextAnchorIndices[space] = index + 1
}
var backwardsResult: [(PeerIdAndNamespace, Int)] = []
var result: [(PeerIdAndNamespace, Int)] = []
while true {
var minSpace: PeerIdAndNamespace?
for (space, value) in previousAnchorIndices {
if value != -1 {
if let minSpaceValue = minSpace {
if sortedEntriesBySpace[space]![value].index > sortedEntriesBySpace[minSpaceValue]![previousAnchorIndices[minSpaceValue]!].index {
minSpace = space
}
} else {
minSpace = space
}
}
}
if let minSpace = minSpace {
backwardsResult.append((minSpace, previousAnchorIndices[minSpace]!))
//result.insert(sortedEntriesBySpace[minSpace]![previousAnchorIndices[minSpace]!], at: 0)
previousAnchorIndices[minSpace]! -= 1
if result.count == limit {
break
}
}
var maxSpace: PeerIdAndNamespace?
for (space, value) in nextAnchorIndices {
if value != sortedEntriesBySpace[space]!.count {
if let maxSpaceValue = maxSpace {
if sortedEntriesBySpace[space]![value].index < sortedEntriesBySpace[maxSpaceValue]![nextAnchorIndices[maxSpaceValue]!].index {
maxSpace = space
}
} else {
maxSpace = space
}
}
}
if let maxSpace = maxSpace {
result.append((maxSpace, nextAnchorIndices[maxSpace]!))
//result.append(sortedEntriesBySpace[maxSpace]![nextAnchorIndices[maxSpace]!])
nextAnchorIndices[maxSpace]! += 1
if result.count == limit {
break
}
}
if minSpace == nil && maxSpace == nil {
break
}
}
return backwardsResult.reversed() + result
}
struct HistoryViewLoadedState {
let anchor: HistoryViewAnchor
let tag: MessageTags?
let limit: Int
var sortedEntriesBySpace: [PeerIdAndNamespace: [MutableMessageHistoryEntry]]
var holesBySpace: [PeerIdAndNamespace: IndexSet]
var spacesWithRemovals = Set<PeerIdAndNamespace>()
init(anchor: HistoryViewAnchor, tag: MessageTags?, limit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox) {
precondition(limit > 0)
self.anchor = anchor
self.tag = tag
self.limit = limit
self.sortedEntriesBySpace = [:]
self.holesBySpace = [:]
var peerIds: [PeerId] = []
switch locations {
case let .single(peerId):
peerIds.append(peerId)
case let .associated(peerId, associatedId):
peerIds.append(peerId)
if let associatedId = associatedId {
peerIds.append(associatedId.peerId)
}
}
var spaces: [PeerIdAndNamespace] = []
for peerId in peerIds {
for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: peerId) {
spaces.append(PeerIdAndNamespace(peerId: peerId, namespace: namespace))
}
}
for space in spaces {
self.fillSpace(space: space, postbox: postbox)
}
}
private mutating func fillSpace(space: PeerIdAndNamespace, postbox: Postbox) {
let anchorIndex: MessageIndex
let lowerBound = MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace)
let upperBound = MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace)
switch self.anchor {
case let .index(index):
anchorIndex = index
case .lowerBound:
anchorIndex = lowerBound
case .upperBound:
anchorIndex = upperBound
}
var lowerMessages: [IntermediateMessage]
var higherMessages: [IntermediateMessage]
lowerMessages = postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: anchorIndex, includeFrom: true, to: lowerBound, limit: self.limit / 2)
higherMessages = postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: anchorIndex, includeFrom: false, to: upperBound, limit: self.limit - lowerMessages.count)
if !lowerMessages.isEmpty && lowerMessages.count + higherMessages.count < self.limit {
let additionalLowerMessages = postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: lowerMessages[lowerMessages.count - 1].index, includeFrom: false, to: lowerBound, limit: self.limit - lowerMessages.count - higherMessages.count + 1)
lowerMessages.append(contentsOf: additionalLowerMessages)
}
var messages: [IntermediateMessage] = []
messages.append(contentsOf: lowerMessages.reversed())
messages.append(contentsOf: higherMessages)
self.sortedEntriesBySpace[space] = messages.map({ message -> MutableMessageHistoryEntry in
return .IntermediateMessageEntry(message, nil, nil)
})
}
mutating func add(entry: MutableMessageHistoryEntry) -> Bool {
let space = PeerIdAndNamespace(peerId: entry.index.id.peerId, namespace: entry.index.id.namespace)
if self.sortedEntriesBySpace[space] == nil {
self.sortedEntriesBySpace[space] = []
}
let insertionIndex = binaryInsertionIndex(self.sortedEntriesBySpace[space]!, extract: { $0.index }, searchItem: entry.index)
var shouldBeAdded = false
if insertionIndex == 0 {
if self.anchor.isEqualOrLower(than: entry.index) {
shouldBeAdded = true
}
} else if insertionIndex == self.sortedEntriesBySpace[space]!.count {
if self.anchor.isEqualOrGreater(than: entry.index) {
shouldBeAdded = true
}
} else {
shouldBeAdded = true
}
if shouldBeAdded {
self.sortedEntriesBySpace[space]!.insert(entry, at: insertionIndex)
if self.sortedEntriesBySpace[space]!.count > self.limit {
if self.anchor.isEqualOrLower(than: entry.index) {
self.sortedEntriesBySpace[space]!.removeLast()
} else {
self.sortedEntriesBySpace[space]!.removeFirst()
}
}
return true
} else {
return false
}
}
mutating func remove(index: MessageIndex) -> Bool {
let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace)
if self.sortedEntriesBySpace[space] == nil {
return false
}
if let itemIndex = binarySearch(self.sortedEntriesBySpace[space]!, extract: { $0.index }, searchItem: index) {
self.sortedEntriesBySpace[space]!.remove(at: itemIndex)
self.spacesWithRemovals.insert(space)
return true
} else {
return false
}
}
mutating func completeAndSample(postbox: Postbox) -> [MessageHistoryMessageEntry] {
if !self.spacesWithRemovals.isEmpty {
for space in self.spacesWithRemovals {
self.sortedEntriesBySpace[space]?.removeAll()
if self.sortedEntriesBySpace[space]!.isEmpty {
self.fillSpace(space: space, postbox: postbox)
} else {
assertionFailure()
}
}
self.spacesWithRemovals.removeAll()
}
let combinedSpacesAndIndices = sampleEntries(sortedEntriesBySpace: self.sortedEntriesBySpace, anchor: self.anchor, limit: self.limit)
var result: [MessageHistoryMessageEntry] = []
for (space, index) in combinedSpacesAndIndices {
switch self.sortedEntriesBySpace[space]![index] {
case let .MessageEntry(value):
result.append(value)
case let .IntermediateMessageEntry(message, location, monthLocation):
let renderedMessage = postbox.messageHistoryTable.renderMessage(message, peerTable: postbox.peerTable)
var authorIsContact = false
if let author = renderedMessage.author {
authorIsContact = postbox.contactsTable.isContact(peerId: author.id)
}
let entry = MessageHistoryMessageEntry(message: renderedMessage, location: location, monthLocation: monthLocation, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: authorIsContact))
self.sortedEntriesBySpace[space]![index] = .MessageEntry(entry)
result.append(entry)
}
}
return result
}
}
enum HistoryViewState {
case loaded(HistoryViewLoadedState)
case loading(MessageId)
}