mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-18 19:40:19 +00:00
643 lines
27 KiB
Swift
643 lines
27 KiB
Swift
import Foundation
|
|
|
|
private enum MessageOfInterestLocation: Equatable {
|
|
case id(MessageId)
|
|
case index(MessageIndex)
|
|
|
|
static func ==(lhs: MessageOfInterestLocation, rhs: MessageOfInterestLocation) -> Bool {
|
|
switch lhs {
|
|
case let .id(value):
|
|
if case .id(value) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .index(value):
|
|
if case .index(value) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func getAnchorId(postbox: Postbox, location: MessageOfInterestViewLocation, namespace: MessageId.Namespace) -> MessageOfInterestLocation? {
|
|
switch location {
|
|
case let .peer(peerId):
|
|
if let readState = postbox.readStateTable.getCombinedState(peerId) {
|
|
loop: for (stateNamespace, state) in readState.states {
|
|
if stateNamespace == namespace {
|
|
if case let .idBased(maxIncomingReadId, _, _, _) = state {
|
|
return .id(MessageId(peerId: peerId, namespace: namespace, id: maxIncomingReadId))
|
|
}
|
|
break loop
|
|
}
|
|
}
|
|
}
|
|
case let .group(groupId):
|
|
if let state = postbox.groupFeedReadStateTable.get(groupId) {
|
|
return .index(state.maxReadIndex)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private struct HolesViewEntryHole {
|
|
let hole: MessageHistoryHole
|
|
let lowerIndex: MessageIndex?
|
|
}
|
|
|
|
private struct HolesViewEntry {
|
|
let index: MessageIndex
|
|
let hole: HolesViewEntryHole?
|
|
|
|
init(index: MessageIndex, hole: HolesViewEntryHole?) {
|
|
self.index = index
|
|
self.hole = hole
|
|
}
|
|
|
|
init(_ entry: HistoryIndexEntry) {
|
|
switch entry {
|
|
case let .Message(index):
|
|
self.index = index
|
|
self.hole = nil
|
|
case let .Hole(hole):
|
|
self.index = hole.maxIndex
|
|
self.hole = HolesViewEntryHole(hole: hole, lowerIndex: nil)
|
|
}
|
|
}
|
|
|
|
init(_ entry: IntermediateMessageHistoryEntry) {
|
|
switch entry {
|
|
case let .Message(message):
|
|
self.index = MessageIndex(message)
|
|
self.hole = nil
|
|
case let .Hole(hole, lowerIndex):
|
|
self.index = hole.maxIndex
|
|
self.hole = HolesViewEntryHole(hole: hole, lowerIndex: lowerIndex)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func fetchEntries(postbox: Postbox, location: MessageOfInterestViewLocation, anchor: MessageOfInterestLocation, count: Int) -> (entries: [HolesViewEntry], earlier: MessageIndex?, later: MessageIndex?) {
|
|
switch location {
|
|
case let .peer(peerId):
|
|
switch anchor {
|
|
case let .id(id):
|
|
assert(peerId == id.peerId)
|
|
let (entries, earlier, later) = postbox.messageHistoryIndexTable.entriesAround(id: id, count: count)
|
|
return (entries.map(HolesViewEntry.init), earlier?.index, later?.index)
|
|
case let .index(index):
|
|
assert(peerId == index.id.peerId)
|
|
let (entries, earlier, later) = postbox.messageHistoryIndexTable.entriesAround(id: index.id, count: count)
|
|
return (entries.map(HolesViewEntry.init), earlier?.index, later?.index)
|
|
}
|
|
case let .group(groupId):
|
|
switch anchor {
|
|
case let .index(index):
|
|
let (entries, earlier, later) = postbox.groupFeedIndexTable.entriesAround(groupId: groupId, index: index, count: count, messageHistoryTable: postbox.messageHistoryTable)
|
|
return (entries.map(HolesViewEntry.init), earlier?.index, later?.index)
|
|
default:
|
|
assertionFailure()
|
|
return ([], nil, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func fetchLater(postbox: Postbox, location: MessageOfInterestViewLocation, anchor: MessageOfInterestLocation, count: Int) -> [HolesViewEntry] {
|
|
switch location {
|
|
case let .peer(peerId):
|
|
switch anchor {
|
|
case let .id(id):
|
|
assert(id.peerId == peerId)
|
|
return postbox.messageHistoryIndexTable.laterEntries(id: id, count: count).map(HolesViewEntry.init)
|
|
case let .index(index):
|
|
assert(index.id.peerId == peerId)
|
|
return postbox.messageHistoryIndexTable.laterEntries(id: index.id, count: count).map(HolesViewEntry.init)
|
|
}
|
|
case let .group(groupId):
|
|
switch anchor {
|
|
case let .index(index):
|
|
return postbox.groupFeedIndexTable.laterEntries(groupId: groupId, index: index, count: count, messageHistoryTable: postbox.messageHistoryTable).map(HolesViewEntry.init)
|
|
default:
|
|
assertionFailure()
|
|
return []
|
|
}
|
|
}
|
|
}
|
|
|
|
private func fetchEarlier(postbox: Postbox, location: MessageOfInterestViewLocation, anchor: MessageOfInterestLocation, count: Int) -> [HolesViewEntry] {
|
|
switch location {
|
|
case let .peer(peerId):
|
|
switch anchor {
|
|
case let .id(id):
|
|
assert(id.peerId == peerId)
|
|
return postbox.messageHistoryIndexTable.earlierEntries(id: id, count: count).map(HolesViewEntry.init)
|
|
case let .index(index):
|
|
assert(index.id.peerId == peerId)
|
|
return postbox.messageHistoryIndexTable.earlierEntries(id: index.id, count: count).map(HolesViewEntry.init)
|
|
}
|
|
case let .group(groupId):
|
|
switch anchor {
|
|
case let .index(index):
|
|
return postbox.groupFeedIndexTable.earlierEntries(groupId: groupId, index: index, count: count, messageHistoryTable: postbox.messageHistoryTable).map(HolesViewEntry.init)
|
|
default:
|
|
assertionFailure()
|
|
return []
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct MessageOfInterestHole: Hashable, Equatable {
|
|
public let hole: MessageHistoryViewHole
|
|
public let direction: MessageHistoryViewRelativeHoleDirection
|
|
|
|
public static func ==(lhs: MessageOfInterestHole, rhs: MessageOfInterestHole) -> Bool {
|
|
return lhs.hole == rhs.hole && lhs.direction == rhs.direction
|
|
}
|
|
|
|
public var hashValue: Int {
|
|
return self.hole.hashValue
|
|
}
|
|
}
|
|
|
|
public enum MessageOfInterestViewLocation: Hashable {
|
|
case peer(PeerId)
|
|
case group(PeerGroupId)
|
|
|
|
public static func ==(lhs: MessageOfInterestViewLocation, rhs: MessageOfInterestViewLocation) -> Bool {
|
|
switch lhs {
|
|
case let .peer(value):
|
|
if case .peer(value) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .group(value):
|
|
if case .group(value) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
public var hashValue: Int {
|
|
switch self {
|
|
case let .peer(id):
|
|
return id.hashValue
|
|
case let .group(id):
|
|
return id.hashValue
|
|
}
|
|
}
|
|
}
|
|
|
|
private func isGreaterOrEqual(index: MessageIndex, than location: MessageOfInterestLocation) -> Bool {
|
|
switch location {
|
|
case let .id(id):
|
|
return index.id >= id
|
|
case let .index(locationIndex):
|
|
return index >= locationIndex
|
|
}
|
|
}
|
|
|
|
final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
|
private let location: MessageOfInterestViewLocation
|
|
private let namespace: MessageId.Namespace
|
|
private let count: Int
|
|
|
|
private var anchorLocation: MessageOfInterestLocation?
|
|
|
|
private var earlier: MessageIndex?
|
|
private var later: MessageIndex?
|
|
private var entries: [HolesViewEntry] = []
|
|
|
|
fileprivate var closestHole: MessageOfInterestHole?
|
|
|
|
init(postbox: Postbox, location: MessageOfInterestViewLocation, namespace: MessageId.Namespace, count: Int) {
|
|
self.location = location
|
|
self.namespace = namespace
|
|
self.count = count
|
|
self.anchorLocation = getAnchorId(postbox: postbox, location: self.location, namespace: self.namespace)
|
|
if let anchorLocation = self.anchorLocation {
|
|
let (entries, earlier, later) = fetchEntries(postbox: postbox, location: self.location, anchor: anchorLocation, count: self.count)
|
|
self.entries = entries
|
|
self.earlier = earlier
|
|
self.later = later
|
|
|
|
self.closestHole = self.firstHole()
|
|
}
|
|
}
|
|
|
|
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
|
|
var updated = false
|
|
|
|
var anchorUpdated = false
|
|
switch self.location {
|
|
case let .peer(peerId):
|
|
if transaction.alteredInitialPeerCombinedReadStates[peerId] != nil {
|
|
let anchorLocation = getAnchorId(postbox: postbox, location: self.location, namespace: self.namespace)
|
|
if self.anchorLocation != anchorLocation {
|
|
self.anchorLocation = anchorLocation
|
|
anchorUpdated = true
|
|
}
|
|
}
|
|
case let .group(groupId):
|
|
if transaction.currentGroupFeedReadStateContext.updatedStates[groupId] != nil {
|
|
let anchorLocation = getAnchorId(postbox: postbox, location: self.location, namespace: self.namespace)
|
|
if self.anchorLocation != anchorLocation {
|
|
self.anchorLocation = anchorLocation
|
|
anchorUpdated = true
|
|
}
|
|
}
|
|
}
|
|
if anchorUpdated {
|
|
if let anchorLocation = self.anchorLocation {
|
|
let (entries, earlier, later) = fetchEntries(postbox: postbox, location: self.location, anchor: anchorLocation, count: self.count)
|
|
self.entries = entries
|
|
self.earlier = earlier
|
|
self.later = later
|
|
} else {
|
|
self.entries = []
|
|
self.earlier = nil
|
|
self.later = nil
|
|
}
|
|
updated = true
|
|
} else {
|
|
var invalidEarlier = false
|
|
var invalidLater = false
|
|
var removedEntries = false
|
|
var hasChanges = false
|
|
|
|
switch self.location {
|
|
case let .peer(peerId):
|
|
if let operations = transaction.currentOperationsByPeerId[peerId] {
|
|
for operation in operations {
|
|
switch operation {
|
|
case let .InsertHole(hole):
|
|
if hole.id.namespace == self.namespace {
|
|
if self.add(HolesViewEntry(index: hole.maxIndex, hole: HolesViewEntryHole(hole: hole, lowerIndex: nil))) {
|
|
hasChanges = true
|
|
}
|
|
}
|
|
case let .InsertMessage(intermediateMessage):
|
|
if intermediateMessage.id.namespace == self.namespace {
|
|
if self.add(HolesViewEntry(index: MessageIndex(intermediateMessage), hole: nil)) {
|
|
hasChanges = true
|
|
}
|
|
}
|
|
case let .Remove(indices):
|
|
if self.remove(indices, invalidEarlier: &invalidEarlier, invalidLater: &invalidLater, removedEntries: &removedEntries) {
|
|
hasChanges = true
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
case let .group(groupId):
|
|
if let operations = transaction.currentGroupFeedOperations[groupId] {
|
|
for operation in operations {
|
|
switch operation {
|
|
case let .insertMessage(message):
|
|
if self.add(HolesViewEntry(index: MessageIndex(message), hole: nil)) {
|
|
hasChanges = true
|
|
}
|
|
case let .insertHole(hole, lowerIndex):
|
|
if self.add(HolesViewEntry(index: hole.maxIndex, hole: HolesViewEntryHole(hole: hole, lowerIndex: lowerIndex))) {
|
|
hasChanges = true
|
|
}
|
|
case let .removeMessage(index):
|
|
if self.remove([(index, false, [])], invalidEarlier: &invalidEarlier, invalidLater: &invalidLater, removedEntries: &removedEntries) {
|
|
hasChanges = true
|
|
}
|
|
case let .removeHole(index):
|
|
if self.remove([(index, false, [])], invalidEarlier: &invalidEarlier, invalidLater: &invalidLater, removedEntries: &removedEntries) {
|
|
hasChanges = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if hasChanges {
|
|
updated = true
|
|
|
|
if let anchorLocation = self.anchorLocation {
|
|
if removedEntries && self.entries.count < self.count {
|
|
if self.entries.count == 0 {
|
|
let (entries, earlier, later) = fetchEntries(postbox: postbox, location: self.location, anchor: anchorLocation, count: self.count)
|
|
self.entries = entries
|
|
self.earlier = earlier
|
|
self.later = later
|
|
} else {
|
|
let fetchedLaterEntries = fetchLater(postbox: postbox, location: self.location, anchor: .index(self.entries.last!.index), count: self.count + 1)
|
|
self.entries.append(contentsOf: fetchedLaterEntries)
|
|
|
|
let fetchedEarlierEntries = fetchEarlier(postbox: postbox, location: self.location, anchor: .index(self.entries[0].index), count: self.count + 1)
|
|
for entry in fetchedEarlierEntries {
|
|
self.entries.insert(entry, at: 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
var centerIndex: Int?
|
|
|
|
for i in 0 ..< self.entries.count {
|
|
if isGreaterOrEqual(index: self.entries[i].index, than: anchorLocation) {
|
|
centerIndex = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if let centerIndex = centerIndex {
|
|
var minIndex = centerIndex
|
|
var maxIndex = centerIndex
|
|
let upperBound = self.entries.count - 1
|
|
var count = 1
|
|
while true {
|
|
if minIndex != 0 {
|
|
minIndex -= 1
|
|
count += 1
|
|
}
|
|
if count >= self.count {
|
|
break
|
|
}
|
|
if maxIndex != upperBound {
|
|
maxIndex += 1
|
|
count += 1
|
|
}
|
|
if count >= self.count {
|
|
break
|
|
}
|
|
if minIndex == 0 && maxIndex == upperBound {
|
|
break
|
|
}
|
|
}
|
|
if maxIndex != upperBound {
|
|
self.later = self.entries[maxIndex + 1].index
|
|
invalidLater = false
|
|
self.entries.removeLast(upperBound - maxIndex)
|
|
} else {
|
|
invalidLater = true
|
|
}
|
|
if minIndex != 0 {
|
|
self.earlier = self.entries[minIndex - 1].index
|
|
invalidEarlier = false
|
|
self.entries.removeFirst(minIndex)
|
|
} else {
|
|
invalidEarlier = true
|
|
}
|
|
}
|
|
|
|
if invalidEarlier {
|
|
if !self.entries.isEmpty {
|
|
let earlyIndex = self.entries[0].index
|
|
self.earlier = fetchEarlier(postbox: postbox, location: self.location, anchor: .index(earlyIndex), count: 1).first?.index
|
|
} else {
|
|
self.earlier = nil
|
|
}
|
|
}
|
|
|
|
if invalidLater {
|
|
if !self.entries.isEmpty {
|
|
let lateIndex = self.entries.last!.index
|
|
self.later = fetchLater(postbox: postbox, location: self.location, anchor: .index(lateIndex), count: 1).first?.index
|
|
} else {
|
|
self.later = nil
|
|
}
|
|
}
|
|
} else {
|
|
self.entries = []
|
|
self.earlier = nil
|
|
self.later = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if updated {
|
|
let closestHole = self.firstHole()
|
|
if closestHole != self.closestHole {
|
|
self.closestHole = closestHole
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
private func add(_ entry: HolesViewEntry) -> Bool {
|
|
let updated: Bool
|
|
|
|
if self.entries.count == 0 {
|
|
self.entries.append(entry)
|
|
updated = true
|
|
} else {
|
|
let latestIndex = self.entries[self.entries.count - 1].index
|
|
let earliestIndex = self.entries[0].index
|
|
|
|
let index = entry.index
|
|
|
|
if index < earliestIndex {
|
|
if self.earlier == nil || self.earlier! < index {
|
|
self.entries.insert(entry, at: 0)
|
|
updated = true
|
|
} else {
|
|
updated = false
|
|
}
|
|
} else if index > latestIndex {
|
|
if let later = self.later {
|
|
if index < later {
|
|
self.entries.append(entry)
|
|
updated = true
|
|
} else {
|
|
updated = false
|
|
}
|
|
} else {
|
|
self.entries.append(entry)
|
|
updated = true
|
|
}
|
|
} else if index != earliestIndex && index != latestIndex {
|
|
var i = self.entries.count
|
|
while i >= 1 {
|
|
if self.entries[i - 1].index < index {
|
|
break
|
|
}
|
|
i -= 1
|
|
}
|
|
self.entries.insert(entry, at: i)
|
|
updated = true
|
|
} else {
|
|
updated = false
|
|
}
|
|
}
|
|
|
|
return updated
|
|
}
|
|
|
|
private func remove(_ indicesAndFlags: [(MessageIndex, Bool, MessageTags)], invalidEarlier: inout Bool, invalidLater: inout Bool, removedEntries: inout Bool) -> Bool {
|
|
let indices = Set(indicesAndFlags.map { $0.0 })
|
|
var hasChanges = false
|
|
if let earlier = self.earlier, indices.contains(earlier) {
|
|
invalidEarlier = true
|
|
hasChanges = true
|
|
}
|
|
|
|
if let later = self.later, indices.contains(later) {
|
|
invalidLater = true
|
|
hasChanges = true
|
|
}
|
|
|
|
if self.entries.count != 0 {
|
|
var i = self.entries.count - 1
|
|
while i >= 0 {
|
|
let entry = self.entries[i]
|
|
if indices.contains(entry.index) {
|
|
self.entries.remove(at: i)
|
|
removedEntries = true
|
|
hasChanges = true
|
|
}
|
|
i -= 1
|
|
}
|
|
}
|
|
|
|
return hasChanges
|
|
}
|
|
|
|
private func firstHole() -> MessageOfInterestHole? {
|
|
if self.entries.isEmpty {
|
|
return nil
|
|
}
|
|
guard let anchorLocation = self.anchorLocation else {
|
|
return nil
|
|
}
|
|
|
|
var referenceIndex = self.entries.count - 1
|
|
for i in 0 ..< self.entries.count {
|
|
if isGreaterOrEqual(index: self.entries[i].index, than: anchorLocation) {
|
|
referenceIndex = i
|
|
break
|
|
}
|
|
}
|
|
|
|
var i = referenceIndex
|
|
var j = referenceIndex + 1
|
|
|
|
switch self.location {
|
|
case let .group(groupId):
|
|
while i >= 0 || j < self.entries.count {
|
|
if j < self.entries.count {
|
|
if let hole = self.entries[j].hole {
|
|
switch anchorLocation {
|
|
case let .index(index):
|
|
if let lowerIndex = hole.lowerIndex {
|
|
if index >= lowerIndex && index <= hole.hole.maxIndex {
|
|
return MessageOfInterestHole(hole: .groupFeed(groupId, lowerIndex: lowerIndex, upperIndex: hole.hole.maxIndex), direction: .AroundIndex(index))
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
|
|
if let lowerIndex = hole.lowerIndex {
|
|
return MessageOfInterestHole(hole: .groupFeed(groupId, lowerIndex: lowerIndex, upperIndex: hole.hole.maxIndex), direction: isGreaterOrEqual(index: hole.hole.maxIndex, than: anchorLocation) ? .LowerToUpper : .UpperToLower)
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
}
|
|
}
|
|
|
|
if i >= 0 {
|
|
if let hole = self.entries[i].hole {
|
|
switch anchorLocation {
|
|
case let .index(index):
|
|
if let lowerIndex = hole.lowerIndex {
|
|
if index >= lowerIndex && index <= hole.hole.maxIndex {
|
|
return MessageOfInterestHole(hole: .groupFeed(groupId, lowerIndex: lowerIndex, upperIndex: hole.hole.maxIndex), direction: .AroundIndex(index))
|
|
}
|
|
|
|
if hole.hole.maxIndex.timestamp >= Int32.max - 1 && index.timestamp >= Int32.max - 1 {
|
|
return MessageOfInterestHole(hole: .groupFeed(groupId, lowerIndex: lowerIndex, upperIndex: hole.hole.maxIndex), direction: .UpperToLower)
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
|
|
/*if case .upperBound = self.anchorIndex, hole.maxIndex.timestamp >= Int32.max - 1 {
|
|
if case let .group(groupId) = self.peerIds {
|
|
if let lowerIndex = lowerIndex {
|
|
return (.groupFeed(groupId, lowerIndex: lowerIndex, upperIndex: hole.maxIndex), .UpperToLower)
|
|
}
|
|
} else {
|
|
return (.peer(hole), .UpperToLower)
|
|
}
|
|
} else {*/
|
|
if let lowerIndex = hole.lowerIndex {
|
|
return MessageOfInterestHole(hole: .groupFeed(groupId, lowerIndex: lowerIndex, upperIndex: hole.hole.maxIndex), direction: isGreaterOrEqual(index: hole.hole.maxIndex, than: anchorLocation) ? .LowerToUpper : .UpperToLower)
|
|
}
|
|
//}
|
|
}
|
|
}
|
|
|
|
i -= 1
|
|
j += 1
|
|
}
|
|
case .peer:
|
|
let anchorId: MessageId
|
|
switch anchorLocation {
|
|
case let .id(id):
|
|
anchorId = id
|
|
case let .index(index):
|
|
anchorId = index.id
|
|
}
|
|
while i >= 0 || j < self.entries.count {
|
|
if j < self.entries.count {
|
|
if let hole = self.entries[j].hole {
|
|
if anchorId.id >= hole.hole.min && anchorId.id <= hole.hole.maxIndex.id.id {
|
|
return MessageOfInterestHole(hole: .peer(hole.hole), direction: .AroundId(anchorId))
|
|
}
|
|
|
|
return MessageOfInterestHole(hole: .peer(hole.hole), direction: hole.hole.maxIndex.id <= anchorId ? .UpperToLower : .LowerToUpper)
|
|
}
|
|
}
|
|
|
|
if i >= 0 {
|
|
if let hole = self.entries[i].hole {
|
|
if anchorId.id >= hole.hole.min && anchorId.id <= hole.hole.maxIndex.id.id {
|
|
return MessageOfInterestHole(hole: .peer(hole.hole), direction: .AroundId(anchorId))
|
|
}
|
|
|
|
return MessageOfInterestHole(hole: .peer(hole.hole), direction: hole.hole.maxIndex.id <= anchorId ? .UpperToLower : .LowerToUpper)
|
|
}
|
|
}
|
|
|
|
i -= 1
|
|
j += 1
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func immutableView() -> PostboxView {
|
|
return MessageOfInterestHolesView(self)
|
|
}
|
|
}
|
|
|
|
public final class MessageOfInterestHolesView: PostboxView {
|
|
public let closestHole: MessageOfInterestHole?
|
|
|
|
init(_ view: MutableMessageOfInterestHolesView) {
|
|
self.closestHole = view.closestHole
|
|
}
|
|
}
|
|
|