Swiftgram/submodules/Postbox/Sources/MessageHistoryViewState.swift
2021-11-09 21:55:54 +04:00

1535 lines
71 KiB
Swift

import Foundation
public enum MessageHistoryInput: Equatable, Hashable {
public struct Automatic: Equatable, Hashable {
public var tag: MessageTags
public var appendMessagesFromTheSameGroup: Bool
public init(tag: MessageTags, appendMessagesFromTheSameGroup: Bool) {
self.tag = tag
self.appendMessagesFromTheSameGroup = appendMessagesFromTheSameGroup
}
}
case automatic(Automatic?)
case external(MessageHistoryViewExternalInput, MessageTags?)
public func hash(into hasher: inout Hasher) {
switch self {
case .automatic:
hasher.combine(1)
case .external:
hasher.combine(2)
}
}
}
private extension MessageHistoryInput {
func fetch(postbox: PostboxImpl, peerId: PeerId, namespace: MessageId.Namespace, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] {
switch self {
case let .automatic(automatic):
var items = postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: automatic?.tag, threadId: nil, from: fromIndex, includeFrom: includeFrom, to: toIndex, limit: limit)
if let automatic = automatic, automatic.appendMessagesFromTheSameGroup {
enum Direction {
case lowToHigh
case highToLow
}
func processItem(index: Int, direction: Direction) {
guard let _ = items[index].groupInfo else {
return
}
if var group = postbox.messageHistoryTable.getMessageGroup(at: items[index].index, limit: 20), group.count > 1 {
switch direction {
case .lowToHigh:
group.sort(by: { lhs, rhs in
return lhs.index < rhs.index
})
case .highToLow:
group.sort(by: { lhs, rhs in
return lhs.index > rhs.index
})
}
items.replaceSubrange(index ..< index + 1, with: group)
switch direction {
case .lowToHigh:
items.removeFirst(group.count - 1)
case .highToLow:
items.removeLast(group.count - 1)
}
}
}
if fromIndex < toIndex {
for i in 0 ..< items.count {
processItem(index: i, direction: .lowToHigh)
}
} else {
for i in (0 ..< items.count).reversed() {
processItem(index: i, direction: .highToLow)
}
}
}
return items
case let .external(input, tag):
return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: tag, threadId: input.threadId, from: fromIndex, includeFrom: includeFrom, to: toIndex, limit: limit)
}
}
func getMessageCountInRange(postbox: PostboxImpl, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int {
switch self {
case let .automatic(automatic):
if let automatic = automatic {
return postbox.messageHistoryTagsTable.getMessageCountInRange(tag: automatic.tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound)
} else {
return postbox.messageHistoryTable.getMessageCountInRange(peerId: peerId, namespace: namespace, tag: nil, lowerBound: lowerBound, upperBound: upperBound)
}
case .external:
return 0
}
}
}
public struct PeerIdAndNamespace: Hashable {
public let peerId: PeerId
public let namespace: MessageId.Namespace
public init(peerId: PeerId, namespace: MessageId.Namespace) {
self.peerId = peerId
self.namespace = namespace
}
}
private func canContainHoles(_ peerIdAndNamespace: PeerIdAndNamespace, input: MessageHistoryInput, seedConfiguration: SeedConfiguration) -> Bool {
switch input {
case .automatic:
guard let messageNamespaces = seedConfiguration.messageHoles[peerIdAndNamespace.peerId.namespace] else {
return false
}
return messageNamespaces[peerIdAndNamespace.namespace] != nil
case .external:
return true
}
}
private struct MessageMonthIndex: Equatable {
let year: Int32
let month: Int32
var timestamp: Int32 {
var timeinfo = tm()
timeinfo.tm_year = self.year
timeinfo.tm_mon = self.month
return Int32(timegm(&timeinfo))
}
init(year: Int32, month: Int32) {
self.year = year
self.month = month
}
init(timestamp: Int32) {
var t = Int(timestamp)
var timeinfo = tm()
gmtime_r(&t, &timeinfo)
self.year = timeinfo.tm_year
self.month = timeinfo.tm_mon
}
var successor: MessageMonthIndex {
if self.month == 11 {
return MessageMonthIndex(year: self.year + 1, month: 0)
} else {
return MessageMonthIndex(year: self.year, month: self.month + 1)
}
}
var predecessor: MessageMonthIndex {
if self.month == 0 {
return MessageMonthIndex(year: self.year - 1, month: 11)
} else {
return MessageMonthIndex(year: self.year, month: self.month - 1)
}
}
}
private func monthUpperBoundIndex(peerId: PeerId, namespace: MessageId.Namespace, index: MessageMonthIndex) -> MessageIndex {
return MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: 0), timestamp: index.successor.timestamp)
}
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
}
func binaryIndexOrLower(_ inputArr: [MessageHistoryEntry], _ 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
}
func binaryIndexOrLower(_ inputArr: [MessageHistoryMessageEntry], _ 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].message.index) {
lo = mid + 1
} else if searchItem.isLower(than: inputArr[mid].message.index) {
hi = mid - 1
} else {
return mid
}
}
return hi
}
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(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], anchor: HistoryViewAnchor, halfLimit: Int) -> (lowerOrAtAnchor: [(PeerIdAndNamespace, Int)], higherThanAnchor: [(PeerIdAndNamespace, Int)]) {
var previousAnchorIndices: [PeerIdAndNamespace: Int] = [:]
var nextAnchorIndices: [PeerIdAndNamespace: Int] = [:]
for (space, items) in orderedEntriesBySpace {
previousAnchorIndices[space] = items.lowerOrAtAnchor.count - 1
nextAnchorIndices[space] = 0
}
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 orderedEntriesBySpace[space]!.lowerOrAtAnchor[value].index > orderedEntriesBySpace[minSpaceValue]!.lowerOrAtAnchor[previousAnchorIndices[minSpaceValue]!].index {
minSpace = space
}
} else {
minSpace = space
}
}
}
if let minSpace = minSpace {
backwardsResult.append((minSpace, previousAnchorIndices[minSpace]!))
previousAnchorIndices[minSpace]! -= 1
if backwardsResult.count == halfLimit {
break
}
}
if minSpace == nil {
break
}
}
while true {
var maxSpace: PeerIdAndNamespace?
for (space, value) in nextAnchorIndices {
if value != orderedEntriesBySpace[space]!.higherThanAnchor.count {
if let maxSpaceValue = maxSpace {
if orderedEntriesBySpace[space]!.higherThanAnchor[value].index < orderedEntriesBySpace[maxSpaceValue]!.higherThanAnchor[nextAnchorIndices[maxSpaceValue]!].index {
maxSpace = space
}
} else {
maxSpace = space
}
}
}
if let maxSpace = maxSpace {
result.append((maxSpace, nextAnchorIndices[maxSpace]!))
nextAnchorIndices[maxSpace]! += 1
if result.count == halfLimit {
break
}
}
if maxSpace == nil {
break
}
}
return (backwardsResult.reversed(), result)
}
struct SampledHistoryViewHole: Equatable {
let peerId: PeerId
let namespace: MessageId.Namespace
let tag: MessageTags?
let threadId: Int64?
let indices: IndexSet
let startId: MessageId.Id
let endId: MessageId.Id?
}
private func isIndex(index: MessageIndex, closerTo anchor: HistoryViewAnchor, than other: MessageIndex) -> Bool {
if index.timestamp != other.timestamp {
let anchorTimestamp: Int32
switch anchor {
case .lowerBound:
anchorTimestamp = 0
case .upperBound:
anchorTimestamp = Int32.max
case let .index(index):
anchorTimestamp = index.timestamp
}
if abs(anchorTimestamp - index.timestamp) < abs(anchorTimestamp - other.timestamp) {
return true
} else {
return false
}
} else if index.id.peerId == other.id.peerId {
if index.id.namespace == other.id.namespace {
let anchorId: Int32
switch anchor {
case .lowerBound:
anchorId = 0
case .upperBound:
anchorId = Int32.max
case let .index(index):
anchorId = index.id.id
}
if abs(anchorId - index.id.id) < abs(anchorId - other.id.id) {
return true
} else {
return false
}
} else {
return index.id.namespace < other.id.namespace
}
} else {
return index.id.peerId.toInt64() < other.id.peerId.toInt64()
}
}
private func sampleHoleRanges(input: MessageHistoryInput, orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, halfLimit: Int, seedConfiguration: SeedConfiguration) -> (clipRanges: [ClosedRange<MessageIndex>], sampledHole: SampledHistoryViewHole?) {
var clipRanges: [ClosedRange<MessageIndex>] = []
var sampledHole: (distanceFromAnchor: Int?, hole: SampledHistoryViewHole)?
var tag: MessageTags?
var threadId: Int64?
switch input {
case let .automatic(automatic):
tag = automatic?.tag
case let .external(value, _):
threadId = value.threadId
}
for (space, indices) in holes.holesBySpace {
if indices.isEmpty {
continue
}
assert(canContainHoles(space, input: input, seedConfiguration: seedConfiguration))
switch anchor {
case .lowerBound, .upperBound:
break
case let .index(index):
if index.id.peerId == space.peerId && index.id.namespace == space.namespace {
if indices.contains(Int(index.id.id)) {
return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: index.id.id, endId: nil))
}
}
}
guard let items = orderedEntriesBySpace[space], (!items.lowerOrAtAnchor.isEmpty || !items.higherThanAnchor.isEmpty) else {
let holeBounds: (startId: MessageId.Id, endId: MessageId.Id)
switch anchor {
case .lowerBound:
holeBounds = (1, Int32.max - 1)
case .upperBound, .index:
holeBounds = (Int32.max - 1, 1)
}
if case let .index(index) = anchor, index.id.peerId == space.peerId {
return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId))
} else {
sampledHole = (nil, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId))
continue
}
}
var lowerOrAtAnchorHole: (distanceFromAnchor: Int, hole: SampledHistoryViewHole)?
for i in (-1 ..< items.lowerOrAtAnchor.count).reversed() {
let startingMessageId: MessageId.Id
if items.higherThanAnchor.isEmpty {
startingMessageId = Int32.max - 1
} else {
startingMessageId = items.higherThanAnchor[0].index.id.id
}
let currentMessageId: MessageId.Id
if i == -1 {
if items.lowerOrAtAnchor.count >= halfLimit {
break
}
currentMessageId = 1
} else {
currentMessageId = items.lowerOrAtAnchor[i].index.id.id
}
let range: ClosedRange<Int>
if currentMessageId <= startingMessageId {
range = Int(currentMessageId) ... Int(startingMessageId)
} else {
assertionFailure()
range = Int(startingMessageId) ... Int(currentMessageId)
}
if indices.intersects(integersIn: range) {
let holeStartIndex: Int
if let value = indices.integerLessThanOrEqualTo(Int(startingMessageId)) {
holeStartIndex = value
} else {
holeStartIndex = indices[indices.endIndex]
}
lowerOrAtAnchorHole = (items.lowerOrAtAnchor.count - i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: Int32(holeStartIndex), endId: 1))
if i == -1 {
if items.lowerOrAtAnchor.count == 0 {
if items.higherThanAnchor.count == 0 {
clipRanges.append(MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound())
} else {
let clipIndex = items.higherThanAnchor[0].index.peerLocalPredecessor()
clipRanges.append(MessageIndex.absoluteLowerBound() ... clipIndex)
}
} else {
let clipIndex = items.lowerOrAtAnchor[0].index.peerLocalPredecessor()
clipRanges.append(MessageIndex.absoluteLowerBound() ... clipIndex)
}
} else {
if i == items.lowerOrAtAnchor.count - 1 {
if items.higherThanAnchor.count == 0 {
clipRanges.append(MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound())
} else {
let clipIndex = items.higherThanAnchor[0].index.peerLocalPredecessor()
clipRanges.append(MessageIndex.absoluteLowerBound() ... clipIndex)
}
} else {
let clipIndex: MessageIndex
if indices.contains(Int(items.lowerOrAtAnchor[i + 1].index.id.id)) {
clipIndex = items.lowerOrAtAnchor[i + 1].index
} else {
clipIndex = items.lowerOrAtAnchor[i + 1].index.peerLocalPredecessor()
}
clipRanges.append(MessageIndex.absoluteLowerBound() ... clipIndex)
}
}
break
}
}
var higherThanAnchorHole: (distanceFromAnchor: Int, hole: SampledHistoryViewHole)?
for i in (0 ..< items.higherThanAnchor.count + 1) {
let startingMessageId: MessageId.Id
if items.lowerOrAtAnchor.isEmpty {
startingMessageId = 1
} else {
startingMessageId = items.lowerOrAtAnchor[items.lowerOrAtAnchor.count - 1].index.id.id
}
let currentMessageId: MessageId.Id
if i == items.higherThanAnchor.count {
if items.higherThanAnchor.count >= halfLimit {
break
}
currentMessageId = Int32.max - 1
} else {
currentMessageId = items.higherThanAnchor[i].index.id.id
}
let range: ClosedRange<Int>
if startingMessageId <= currentMessageId {
range = Int(startingMessageId) ... Int(currentMessageId)
} else {
assertionFailure()
range = Int(currentMessageId) ... Int(startingMessageId)
}
if indices.intersects(integersIn: range) {
let holeStartIndex: Int
if let value = indices.integerGreaterThanOrEqualTo(Int(startingMessageId)) {
holeStartIndex = value
} else {
holeStartIndex = indices[indices.startIndex]
}
higherThanAnchorHole = (i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: Int32(holeStartIndex), endId: Int32.max - 1))
if i == items.higherThanAnchor.count {
if items.higherThanAnchor.count == 0 {
if items.lowerOrAtAnchor.count == 0 {
clipRanges.append(MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound())
} else {
let clipIndex = items.lowerOrAtAnchor[items.lowerOrAtAnchor.count - 1].index.peerLocalSuccessor()
clipRanges.append(clipIndex ... MessageIndex.absoluteUpperBound())
}
} else {
let clipIndex = items.higherThanAnchor[items.higherThanAnchor.count - 1].index.peerLocalSuccessor()
clipRanges.append(clipIndex ... MessageIndex.absoluteUpperBound())
}
} else {
if i == 0 {
if items.lowerOrAtAnchor.count == 0 {
clipRanges.append(MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound())
} else {
let clipIndex = items.lowerOrAtAnchor[items.lowerOrAtAnchor.count - 1].index.peerLocalSuccessor()
clipRanges.append(clipIndex ... MessageIndex.absoluteUpperBound())
}
} else {
let clipIndex: MessageIndex
if indices.contains(Int(items.higherThanAnchor[i - 1].index.id.id)) {
clipIndex = items.higherThanAnchor[i - 1].index
} else {
clipIndex = items.higherThanAnchor[i - 1].index.peerLocalSuccessor()
}
clipRanges.append(clipIndex ... MessageIndex.absoluteUpperBound())
}
}
break
}
}
var chosenHole: (distanceFromAnchor: Int, hole: SampledHistoryViewHole)?
if let lowerOrAtAnchorHole = lowerOrAtAnchorHole, let higherThanAnchorHole = higherThanAnchorHole {
if items.lowerOrAtAnchor.isEmpty != items.higherThanAnchor.isEmpty {
if !items.lowerOrAtAnchor.isEmpty {
chosenHole = lowerOrAtAnchorHole
} else {
chosenHole = higherThanAnchorHole
}
} else {
if lowerOrAtAnchorHole.distanceFromAnchor < higherThanAnchorHole.distanceFromAnchor {
chosenHole = lowerOrAtAnchorHole
} else {
chosenHole = higherThanAnchorHole
}
}
} else if let lowerOrAtAnchorHole = lowerOrAtAnchorHole {
chosenHole = lowerOrAtAnchorHole
} else if let higherThanAnchorHole = higherThanAnchorHole {
chosenHole = higherThanAnchorHole
}
if let chosenHole = chosenHole {
if let current = sampledHole {
if let distance = current.distanceFromAnchor {
if chosenHole.distanceFromAnchor < distance {
sampledHole = (chosenHole.distanceFromAnchor, chosenHole.hole)
}
}
} else {
sampledHole = (chosenHole.distanceFromAnchor, chosenHole.hole)
}
}
}
return (clipRanges, sampledHole?.hole)
}
struct HistoryViewHoles {
var holesBySpace: [PeerIdAndNamespace: IndexSet]
mutating func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
if self.holesBySpace[space] == nil {
self.holesBySpace[space] = IndexSet()
}
let intRange = Int(range.lowerBound) ... Int(range.upperBound)
if self.holesBySpace[space]!.contains(integersIn: intRange) {
self.holesBySpace[space]!.insert(integersIn: intRange)
return true
} else {
return false
}
}
mutating func removeHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
if self.holesBySpace[space] != nil {
let intRange = Int(range.lowerBound) ... Int(range.upperBound)
if self.holesBySpace[space]!.intersects(integersIn: intRange) {
self.holesBySpace[space]!.remove(integersIn: intRange)
return true
} else {
return false
}
} else {
return false
}
}
}
struct OrderedHistoryViewEntries {
private(set) var lowerOrAtAnchor: [MutableMessageHistoryEntry]
private(set) var higherThanAnchor: [MutableMessageHistoryEntry]
private(set) var reverseAssociatedIndices: [MessageId: [MessageIndex]] = [:]
fileprivate init(lowerOrAtAnchor: [MutableMessageHistoryEntry], higherThanAnchor: [MutableMessageHistoryEntry]) {
self.lowerOrAtAnchor = lowerOrAtAnchor
self.higherThanAnchor = higherThanAnchor
for entry in lowerOrAtAnchor {
for id in entry.getAssociatedMessageIds() {
if self.reverseAssociatedIndices[id] == nil {
self.reverseAssociatedIndices[id] = [entry.index]
} else {
self.reverseAssociatedIndices[id]!.append(entry.index)
}
}
}
for entry in higherThanAnchor {
for id in entry.getAssociatedMessageIds() {
if self.reverseAssociatedIndices[id] == nil {
self.reverseAssociatedIndices[id] = [entry.index]
} else {
self.reverseAssociatedIndices[id]!.append(entry.index)
}
}
}
}
mutating func setLowerOrAtAnchorAtArrayIndex(_ index: Int, to value: MutableMessageHistoryEntry) {
let previousIndex = self.lowerOrAtAnchor[index].index
let updatedIndex = value.index
let previousAssociatedIds = self.lowerOrAtAnchor[index].getAssociatedMessageIds()
let updatedAssociatedIds = value.getAssociatedMessageIds()
self.lowerOrAtAnchor[index] = value
if previousAssociatedIds != updatedAssociatedIds {
for id in previousAssociatedIds {
self.reverseAssociatedIndices[id]?.removeAll(where: { $0 == previousIndex })
if let isEmpty = self.reverseAssociatedIndices[id]?.isEmpty, isEmpty {
self.reverseAssociatedIndices.removeValue(forKey: id)
}
}
for id in updatedAssociatedIds {
if self.reverseAssociatedIndices[id] == nil {
self.reverseAssociatedIndices[id] = [updatedIndex]
} else {
self.reverseAssociatedIndices[id]!.append(updatedIndex)
}
}
}
}
mutating func setHigherThanAnchorAtArrayIndex(_ index: Int, to value: MutableMessageHistoryEntry) {
let previousIndex = self.higherThanAnchor[index].index
let updatedIndex = value.index
let previousAssociatedIds = self.higherThanAnchor[index].getAssociatedMessageIds()
let updatedAssociatedIds = value.getAssociatedMessageIds()
self.higherThanAnchor[index] = value
if previousAssociatedIds != updatedAssociatedIds {
for id in previousAssociatedIds {
self.reverseAssociatedIndices[id]?.removeAll(where: { $0 == previousIndex })
if let isEmpty = self.reverseAssociatedIndices[id]?.isEmpty, isEmpty {
self.reverseAssociatedIndices.removeValue(forKey: id)
}
}
for id in updatedAssociatedIds {
if self.reverseAssociatedIndices[id] == nil {
self.reverseAssociatedIndices[id] = [updatedIndex]
} else {
self.reverseAssociatedIndices[id]!.append(updatedIndex)
}
}
}
}
mutating func insertLowerOrAtAnchorAtArrayIndex(_ index: Int, value: MutableMessageHistoryEntry) {
self.lowerOrAtAnchor.insert(value, at: index)
for id in value.getAssociatedMessageIds() {
if self.reverseAssociatedIndices[id] == nil {
self.reverseAssociatedIndices[id] = [value.index]
} else {
self.reverseAssociatedIndices[id]!.append(value.index)
}
}
}
mutating func insertHigherThanAnchorAtArrayIndex(_ index: Int, value: MutableMessageHistoryEntry) {
self.higherThanAnchor.insert(value, at: index)
for id in value.getAssociatedMessageIds() {
if self.reverseAssociatedIndices[id] == nil {
self.reverseAssociatedIndices[id] = [value.index]
} else {
self.reverseAssociatedIndices[id]!.append(value.index)
}
}
}
mutating func removeLowerOrAtAnchorAtArrayIndex(_ index: Int) {
let previousIndex = self.lowerOrAtAnchor[index].index
for id in self.lowerOrAtAnchor[index].getAssociatedMessageIds() {
self.reverseAssociatedIndices[id]?.removeAll(where: { $0 == previousIndex })
if let isEmpty = self.reverseAssociatedIndices[id]?.isEmpty, isEmpty {
self.reverseAssociatedIndices.removeValue(forKey: id)
}
}
self.lowerOrAtAnchor.remove(at: index)
}
mutating func removeHigherThanAnchorAtArrayIndex(_ index: Int) {
let previousIndex = self.higherThanAnchor[index].index
for id in self.higherThanAnchor[index].getAssociatedMessageIds() {
self.reverseAssociatedIndices[id]?.removeAll(where: { $0 == previousIndex })
if let isEmpty = self.reverseAssociatedIndices[id]?.isEmpty, isEmpty {
self.reverseAssociatedIndices.removeValue(forKey: id)
}
}
self.higherThanAnchor.remove(at: index)
}
mutating func fixMonotony() {
if self.lowerOrAtAnchor.count > 1 {
for i in 1 ..< self.lowerOrAtAnchor.count {
if self.lowerOrAtAnchor[i].index < self.lowerOrAtAnchor[i - 1].index {
//assertionFailure()
break
}
}
}
if self.higherThanAnchor.count > 1 {
for i in 1 ..< self.higherThanAnchor.count {
if self.higherThanAnchor[i].index < self.higherThanAnchor[i - 1].index {
//assertionFailure()
break
}
}
}
var fix = false
if self.lowerOrAtAnchor.count > 1 {
for i in 1 ..< self.lowerOrAtAnchor.count {
if self.lowerOrAtAnchor[i].index.id.id < self.lowerOrAtAnchor[i - 1].index.id.id {
fix = true
break
}
}
}
if !fix && self.higherThanAnchor.count > 1 {
for i in 1 ..< self.higherThanAnchor.count {
if self.higherThanAnchor[i].index.id.id < self.higherThanAnchor[i - 1].index.id.id {
fix = true
break
}
}
}
if fix {
//assertionFailure()
self.lowerOrAtAnchor.sort(by: { $0.index.id.id < $1.index.id.id })
self.higherThanAnchor.sort(by: { $0.index.id.id < $1.index.id.id })
}
}
func find(index: MessageIndex) -> MutableMessageHistoryEntry? {
if let entryIndex = binarySearch(self.lowerOrAtAnchor, extract: { $0.index }, searchItem: index) {
return self.lowerOrAtAnchor[entryIndex]
} else if let entryIndex = binarySearch(self.higherThanAnchor, extract: { $0.index }, searchItem: index) {
return self.higherThanAnchor[entryIndex]
} else {
return nil
}
}
func indicesForAssociatedMessageId(_ id: MessageId) -> [MessageIndex]? {
return self.reverseAssociatedIndices[id]
}
var first: MutableMessageHistoryEntry? {
return self.lowerOrAtAnchor.first ?? self.higherThanAnchor.first
}
mutating func mutableScan(_ f: (MutableMessageHistoryEntry) -> MutableMessageHistoryEntry?) -> 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: MessageIndex, _ f: (MutableMessageHistoryEntry) -> MutableMessageHistoryEntry?) -> Bool {
if let entryIndex = binarySearch(self.lowerOrAtAnchor, extract: { $0.index }, searchItem: index) {
if let updated = f(self.lowerOrAtAnchor[entryIndex]) {
self.setLowerOrAtAnchorAtArrayIndex(entryIndex, to: updated)
return true
}
} else if let entryIndex = binarySearch(self.higherThanAnchor, extract: { $0.index }, searchItem: index) {
if let updated = f(self.higherThanAnchor[entryIndex]) {
self.setHigherThanAnchorAtArrayIndex(entryIndex, to: updated)
return true
}
}
return false
}
mutating func remove(index: MessageIndex) -> Bool {
if let entryIndex = binarySearch(self.lowerOrAtAnchor, extract: { $0.index }, searchItem: index) {
self.removeLowerOrAtAnchorAtArrayIndex(entryIndex)
return true
} else if let entryIndex = binarySearch(self.higherThanAnchor, extract: { $0.index }, searchItem: index) {
self.removeHigherThanAnchorAtArrayIndex(entryIndex)
return true
} else {
return false
}
}
}
struct HistoryViewLoadedSample {
let anchor: HistoryViewAnchor
let entries: [MessageHistoryMessageEntry]
let holesToLower: Bool
let holesToHigher: Bool
let hole: SampledHistoryViewHole?
}
final class HistoryViewLoadedState {
let anchor: HistoryViewAnchor
let namespaces: MessageIdNamespaces
let input: MessageHistoryInput
let statistics: MessageHistoryViewOrderStatistics
let halfLimit: Int
let seedConfiguration: SeedConfiguration
var orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries]
var holes: HistoryViewHoles
var spacesWithRemovals = Set<PeerIdAndNamespace>()
init(anchor: HistoryViewAnchor, tag: MessageTags?, appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewInput, postbox: PostboxImpl, holes: HistoryViewHoles) {
precondition(halfLimit >= 3)
self.anchor = anchor
self.namespaces = namespaces
self.statistics = statistics
self.halfLimit = halfLimit
self.seedConfiguration = postbox.seedConfiguration
self.orderedEntriesBySpace = [:]
self.holes = holes
var peerIds: [PeerId] = []
let input: MessageHistoryInput
switch locations {
case let .single(peerId):
peerIds.append(peerId)
input = .automatic(tag.flatMap { tag in
MessageHistoryInput.Automatic(tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup)
})
case let .associated(peerId, associatedId):
peerIds.append(peerId)
if let associatedId = associatedId {
peerIds.append(associatedId.peerId)
}
input = .automatic(tag.flatMap { tag in
MessageHistoryInput.Automatic(tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup)
})
case let .external(external):
peerIds.append(external.peerId)
input = .external(external, tag)
}
self.input = input
var spaces: [PeerIdAndNamespace] = []
for peerId in peerIds {
for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: peerId) {
if namespaces.contains(namespace) {
spaces.append(PeerIdAndNamespace(peerId: peerId, namespace: namespace))
}
}
}
for space in spaces {
self.fillSpace(space: space, postbox: postbox)
}
}
private func fillSpace(space: PeerIdAndNamespace, postbox: PostboxImpl) {
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.withPeerId(space.peerId).withNamespace(space.namespace)
case .lowerBound:
anchorIndex = lowerBound
case .upperBound:
anchorIndex = upperBound
}
var lowerOrAtAnchorMessages: [MutableMessageHistoryEntry] = []
var higherThanAnchorMessages: [MutableMessageHistoryEntry] = []
if let currentEntries = self.orderedEntriesBySpace[space] {
lowerOrAtAnchorMessages = currentEntries.lowerOrAtAnchor.reversed()
higherThanAnchorMessages = currentEntries.higherThanAnchor
}
func mapEntry(_ message: IntermediateMessage) -> MutableMessageHistoryEntry {
return .IntermediateMessageEntry(message, nil, nil)
}
if lowerOrAtAnchorMessages.count < self.halfLimit {
let nextLowerIndex: (index: MessageIndex, includeFrom: Bool)
if let lastMessage = lowerOrAtAnchorMessages.min(by: { $0.index < $1.index }) {
nextLowerIndex = (lastMessage.index, false)
} else {
nextLowerIndex = (anchorIndex, true)
}
lowerOrAtAnchorMessages.append(contentsOf: self.input.fetch(postbox: postbox, peerId: space.peerId, namespace: space.namespace, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.halfLimit - lowerOrAtAnchorMessages.count).map(mapEntry))
}
if higherThanAnchorMessages.count < self.halfLimit {
let nextHigherIndex: MessageIndex
if let lastMessage = higherThanAnchorMessages.max(by: { $0.index < $1.index }) {
nextHigherIndex = lastMessage.index
} else {
nextHigherIndex = anchorIndex
}
higherThanAnchorMessages.append(contentsOf: self.input.fetch(postbox: postbox, peerId: space.peerId, namespace: space.namespace, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.halfLimit - higherThanAnchorMessages.count).map(mapEntry))
}
lowerOrAtAnchorMessages.reverse()
assert(lowerOrAtAnchorMessages.count <= self.halfLimit)
assert(higherThanAnchorMessages.count <= self.halfLimit)
var entries = OrderedHistoryViewEntries(lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages)
if case .automatic = self.input, self.statistics.contains(.combinedLocation), let first = entries.first {
let messageIndex = first.index
let previousCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex)
let nextCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace))
let initialLocation = MessageHistoryEntryLocation(index: previousCount - 1, count: previousCount + nextCount - 1)
var nextLocation = initialLocation
let _ = entries.mutableScan { entry in
let currentLocation = nextLocation
nextLocation = nextLocation.successor
switch entry {
case let .IntermediateMessageEntry(message, _, monthLocation):
return .IntermediateMessageEntry(message, currentLocation, monthLocation)
case let .MessageEntry(entry, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: currentLocation, monthLocation: entry.monthLocation, attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
}
}
if canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration) {
entries.fixMonotony()
}
self.orderedEntriesBySpace[space] = entries
}
func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
assert(canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration))
return self.holes.insertHole(space: space, range: range)
}
func removeHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
assert(canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration))
return self.holes.removeHole(space: space, range: range)
}
func updateTimestamp(postbox: PostboxImpl, index: MessageIndex, timestamp: Int32) -> Bool {
let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace)
if self.orderedEntriesBySpace[space] == nil {
return false
}
guard let entry = self.orderedEntriesBySpace[space]!.find(index: index) else {
return false
}
var updated = false
if self.remove(index: index) {
updated = true
}
if self.add(entry: entry.updatedTimestamp(timestamp)) {
updated = true
}
return updated
}
func updateGroupInfo(mapping: [MessageId: MessageGroupInfo]) -> Bool {
var mappingsBySpace: [PeerIdAndNamespace: [MessageId.Id: MessageGroupInfo]] = [:]
for (id, info) in mapping {
let space = PeerIdAndNamespace(peerId: id.peerId, namespace: id.namespace)
if mappingsBySpace[space] == nil {
mappingsBySpace[space] = [:]
}
mappingsBySpace[space]![id.id] = info
}
var updated = false
for (space, spaceMapping) in mappingsBySpace {
if self.orderedEntriesBySpace[space] == nil {
continue
}
let spaceUpdated = self.orderedEntriesBySpace[space]!.mutableScan({ entry in
if let groupInfo = spaceMapping[entry.index.id.id] {
updated = true
switch entry {
case let .IntermediateMessageEntry(message, location, monthLocation):
return .IntermediateMessageEntry(message.withUpdatedGroupInfo(groupInfo), location, monthLocation)
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedGroupInfo(groupInfo), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
}
return nil
})
if spaceUpdated {
updated = true
}
}
return updated
}
func updateEmbeddedMedia(index: MessageIndex, buffer: ReadBuffer) -> Bool {
let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace)
if self.orderedEntriesBySpace[space] == nil {
return false
}
return self.orderedEntriesBySpace[space]!.update(index: index, { entry in
switch entry {
case let .IntermediateMessageEntry(message, location, monthLocation):
return .IntermediateMessageEntry(message.withUpdatedEmbeddedMedia(buffer), location, monthLocation)
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message, location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
})
}
func updateMedia(updatedMedia: [MediaId: Media?]) -> Bool {
var updated = false
for space in self.orderedEntriesBySpace.keys {
let spaceUpdated = self.orderedEntriesBySpace[space]!.mutableScan({ entry in
switch entry {
case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
let message = value.message
var reloadPeers = reloadPeers
var rebuild = false
for media in message.media {
if let mediaId = media.id, let _ = updatedMedia[mediaId] {
rebuild = true
break
}
}
if rebuild {
var messageMedia: [Media] = []
for media in message.media {
if let mediaId = media.id, let updated = updatedMedia[mediaId] {
if let updated = updated {
if media.peerIds != updated.peerIds {
reloadPeers = true
}
messageMedia.append(updated)
}
} else {
messageMedia.append(media)
}
}
let updatedMessage = 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: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds)
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
}
case .IntermediateMessageEntry:
break
}
return nil
})
if spaceUpdated {
updated = true
}
}
return updated
}
func add(entry: MutableMessageHistoryEntry) -> Bool {
let space = PeerIdAndNamespace(peerId: entry.index.id.peerId, namespace: entry.index.id.namespace)
if self.orderedEntriesBySpace[space] == nil {
self.orderedEntriesBySpace[space] = OrderedHistoryViewEntries(lowerOrAtAnchor: [], higherThanAnchor: [])
}
var updated = false
if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(entry.index.id) {
for associatedIndex in associatedIndices {
let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in
switch current {
case .IntermediateMessageEntry:
return current
case let .MessageEntry(messageEntry, _, reloadPeers):
updated = true
return .MessageEntry(messageEntry, reloadAssociatedMessages: true, reloadPeers: reloadPeers)
}
})
}
}
if self.anchor.isEqualOrGreater(than: entry.index) {
let insertionIndex = binaryInsertionIndex(self.orderedEntriesBySpace[space]!.lowerOrAtAnchor, extract: { $0.index }, searchItem: entry.index)
if insertionIndex < self.orderedEntriesBySpace[space]!.lowerOrAtAnchor.count {
if self.orderedEntriesBySpace[space]!.lowerOrAtAnchor[insertionIndex].index == entry.index {
assertionFailure("Inserting an existing index is not allowed")
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(insertionIndex, to: entry)
return true
}
}
if insertionIndex == 0 && self.orderedEntriesBySpace[space]!.lowerOrAtAnchor.count >= self.halfLimit {
return updated
}
self.orderedEntriesBySpace[space]!.insertLowerOrAtAnchorAtArrayIndex(insertionIndex, value: entry)
if self.orderedEntriesBySpace[space]!.lowerOrAtAnchor.count > self.halfLimit {
self.orderedEntriesBySpace[space]!.removeLowerOrAtAnchorAtArrayIndex(0)
}
return true
} else {
let insertionIndex = binaryInsertionIndex(self.orderedEntriesBySpace[space]!.higherThanAnchor, extract: { $0.index }, searchItem: entry.index)
if insertionIndex < self.orderedEntriesBySpace[space]!.higherThanAnchor.count {
if self.orderedEntriesBySpace[space]!.higherThanAnchor[insertionIndex].index == entry.index {
assertionFailure("Inserting an existing index is not allowed")
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(insertionIndex, to: entry)
return true
}
}
if insertionIndex == self.orderedEntriesBySpace[space]!.higherThanAnchor.count && self.orderedEntriesBySpace[space]!.higherThanAnchor.count >= self.halfLimit {
return updated
}
self.orderedEntriesBySpace[space]!.insertHigherThanAnchorAtArrayIndex(insertionIndex, value: entry)
if self.orderedEntriesBySpace[space]!.higherThanAnchor.count > self.halfLimit {
self.orderedEntriesBySpace[space]!.removeHigherThanAnchorAtArrayIndex(self.orderedEntriesBySpace[space]!.higherThanAnchor.count - 1)
}
return true
}
}
func addAssociated(entry: MutableMessageHistoryEntry) -> Bool {
var updated = false
for (space, _) in self.orderedEntriesBySpace {
if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(entry.index.id) {
for associatedIndex in associatedIndices {
let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in
switch current {
case .IntermediateMessageEntry:
return current
case let .MessageEntry(messageEntry, _, reloadPeers):
updated = true
return .MessageEntry(messageEntry, reloadAssociatedMessages: true, reloadPeers: reloadPeers)
}
})
}
}
}
return updated
}
func remove(index: MessageIndex) -> Bool {
let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace)
if self.orderedEntriesBySpace[space] == nil {
return false
}
var updated = false
if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(index.id) {
for associatedIndex in associatedIndices {
let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in
switch current {
case .IntermediateMessageEntry:
return current
case let .MessageEntry(messageEntry, reloadAssociatedMessages, reloadPeers):
updated = true
if let associatedMessages = messageEntry.message.associatedMessages.filteredOut(keysIn: [index.id]) {
return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedAssociatedMessages(associatedMessages), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers)
} else {
return current
}
}
})
}
}
if self.orderedEntriesBySpace[space]!.remove(index: index) {
self.spacesWithRemovals.insert(space)
updated = true
}
return updated
}
func completeAndSample(postbox: PostboxImpl, clipHoles: Bool) -> HistoryViewLoadedSample {
if !self.spacesWithRemovals.isEmpty {
for space in self.spacesWithRemovals {
self.fillSpace(space: space, postbox: postbox)
}
self.spacesWithRemovals.removeAll()
}
let combinedSpacesAndIndicesByDirection = sampleEntries(orderedEntriesBySpace: self.orderedEntriesBySpace, anchor: self.anchor, halfLimit: self.halfLimit)
let (clipRanges, sampledHole) = sampleHoleRanges(input: self.input, orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, halfLimit: self.halfLimit, seedConfiguration: self.seedConfiguration)
var holesToLower = false
var holesToHigher = false
var result: [MessageHistoryMessageEntry] = []
if combinedSpacesAndIndicesByDirection.lowerOrAtAnchor.isEmpty && combinedSpacesAndIndicesByDirection.higherThanAnchor.isEmpty {
if !clipRanges.isEmpty {
holesToLower = true
holesToHigher = true
}
} else {
let directions = [combinedSpacesAndIndicesByDirection.lowerOrAtAnchor, combinedSpacesAndIndicesByDirection.higherThanAnchor]
for directionIndex in 0 ..< directions.count {
outer: for i in 0 ..< directions[directionIndex].count {
let (space, index) = directions[directionIndex][i]
let entry: MutableMessageHistoryEntry
if directionIndex == 0 {
entry = self.orderedEntriesBySpace[space]!.lowerOrAtAnchor[index]
} else {
entry = self.orderedEntriesBySpace[space]!.higherThanAnchor[index]
}
if clipHoles && !clipRanges.isEmpty {
let entryIndex = entry.index
for range in clipRanges {
if range.contains(entryIndex) {
if directionIndex == 0 && i == 0 {
holesToLower = true
}
if directionIndex == 1 && i == directions[directionIndex].count - 1 {
holesToHigher = true
}
continue outer
}
}
}
switch entry {
case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers):
var updatedMessage = value.message
if reloadAssociatedMessages {
let associatedMessages = postbox.messageHistoryTable.renderAssociatedMessages(associatedMessageIds: value.message.associatedMessageIds, peerTable: postbox.peerTable)
updatedMessage = value.message.withUpdatedAssociatedMessages(associatedMessages)
}
if reloadPeers {
updatedMessage = postbox.messageHistoryTable.renderMessagePeers(updatedMessage, peerTable: postbox.peerTable)
}
if value.message !== updatedMessage {
let updatedValue = MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes)
if directionIndex == 0 {
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false, reloadPeers: false))
} else {
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(updatedValue, reloadAssociatedMessages: false, reloadPeers: false))
}
result.append(updatedValue)
} else {
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))
if directionIndex == 0 {
self.orderedEntriesBySpace[space]!.setLowerOrAtAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false, reloadPeers: false))
} else {
self.orderedEntriesBySpace[space]!.setHigherThanAnchorAtArrayIndex(index, to: .MessageEntry(entry, reloadAssociatedMessages: false, reloadPeers: false))
}
result.append(entry)
}
}
}
}
//assert(Set(result.map({ $0.message.stableId })).count == result.count)
return HistoryViewLoadedSample(anchor: self.anchor, entries: result, holesToLower: holesToLower, holesToHigher: holesToHigher, hole: sampledHole)
}
}
private func fetchHoles(postbox: PostboxImpl, locations: MessageHistoryViewInput, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] {
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)
}
case let .external(input):
peerIds.append(input.peerId)
}
switch locations {
case .single, .associated:
var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:]
let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere
for peerId in peerIds {
for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) {
if namespaces.contains(namespace) {
let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1))
if !indices.isEmpty {
let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace)
assert(canContainHoles(peerIdAndNamespace, input: .automatic(tag.flatMap { tag in
MessageHistoryInput.Automatic(tag: tag, appendMessagesFromTheSameGroup: false)
}), seedConfiguration: postbox.seedConfiguration))
holesBySpace[peerIdAndNamespace] = indices
}
}
}
}
return holesBySpace
case let .external(input):
var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:]
for peerId in peerIds {
for (namespace, indices) in input.holes {
if namespaces.contains(namespace) {
if !indices.isEmpty {
let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace)
assert(canContainHoles(peerIdAndNamespace, input: .external(input, tag), seedConfiguration: postbox.seedConfiguration))
holesBySpace[peerIdAndNamespace] = indices
}
}
}
}
return holesBySpace
}
}
enum HistoryViewLoadingSample {
case ready(HistoryViewAnchor, HistoryViewHoles)
case loadHole(PeerId, MessageId.Namespace, MessageTags?, Int64?, MessageId.Id)
}
final class HistoryViewLoadingState {
var messageId: MessageId
let tag: MessageTags?
let threadId: Int64?
let halfLimit: Int
var holes: HistoryViewHoles
init(postbox: PostboxImpl, locations: MessageHistoryViewInput, tag: MessageTags?, threadId: Int64?, namespaces: MessageIdNamespaces, messageId: MessageId, halfLimit: Int) {
self.messageId = messageId
self.tag = tag
self.threadId = threadId
self.halfLimit = halfLimit
self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))
}
func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
return self.holes.insertHole(space: space, range: range)
}
func removeHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
return self.holes.removeHole(space: space, range: range)
}
func checkAndSample(postbox: PostboxImpl) -> HistoryViewLoadingSample {
while true {
if let indices = self.holes.holesBySpace[PeerIdAndNamespace(peerId: self.messageId.peerId, namespace: self.messageId.namespace)] {
if indices.contains(Int(messageId.id)) {
return .loadHole(messageId.peerId, messageId.namespace, self.tag, self.threadId, messageId.id)
}
}
if let index = postbox.messageHistoryIndexTable.getIndex(self.messageId) {
return .ready(.index(index), self.holes)
}
if let nextHigherIndex = postbox.messageHistoryIndexTable.indexForId(higherThan: self.messageId) {
self.messageId = nextHigherIndex.id
} else {
return .ready(.upperBound, self.holes)
}
}
}
}
enum HistoryViewSample {
case loaded(HistoryViewLoadedSample)
case loading(HistoryViewLoadingSample)
}
enum HistoryViewState {
case loaded(HistoryViewLoadedState)
case loading(HistoryViewLoadingState)
init(postbox: PostboxImpl, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewInput) {
switch inputAnchor {
case let .index(index):
self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
case .lowerBound:
self = .loaded(HistoryViewLoadedState(anchor: .lowerBound, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
case .upperBound:
self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
case .unread:
let anchorPeerId: PeerId
switch locations {
case let .single(peerId):
anchorPeerId = peerId
case let .associated(peerId, _):
anchorPeerId = peerId
case .external:
self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
return
}
if postbox.chatListIndexTable.get(peerId: anchorPeerId).includedIndex(peerId: anchorPeerId) != nil, let combinedState = postbox.readStateTable.getCombinedState(anchorPeerId) {
var messageId: MessageId?
var anchor: HistoryViewAnchor?
loop: for (namespace, state) in combinedState.states {
switch state {
case let .idBased(maxIncomingReadId, _, _, count, _):
if count == 0 {
anchor = .upperBound
break loop
} else {
messageId = MessageId(peerId: anchorPeerId, namespace: namespace, id: maxIncomingReadId)
break loop
}
case let .indexBased(maxIncomingReadIndex, _, count, _):
if count == 0 {
anchor = .upperBound
break loop
} else {
anchor = .index(maxIncomingReadIndex)
break loop
}
}
}
if let messageId = messageId {
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, threadId: nil, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit)
let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState {
case let .ready(anchor, holes):
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
case .loadHole:
self = .loading(loadingState)
}
} else {
self = .loaded(HistoryViewLoadedState(anchor: anchor ?? .upperBound, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
}
} else {
preconditionFailure()
}
case let .message(messageId):
var threadId: Int64?
switch locations {
case let .external(input):
threadId = input.threadId
default:
break
}
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, threadId: threadId, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit)
let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState {
case let .ready(anchor, holes):
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
case .loadHole:
self = .loading(loadingState)
}
}
}
func sample(postbox: PostboxImpl, clipHoles: Bool) -> HistoryViewSample {
switch self {
case let .loading(loadingState):
return .loading(loadingState.checkAndSample(postbox: postbox))
case let .loaded(loadedState):
return .loaded(loadedState.completeAndSample(postbox: postbox, clipHoles: clipHoles))
}
}
}