This commit is contained in:
overtake 2019-05-18 23:10:00 +02:00
commit 3f2f5a82f3
9 changed files with 627 additions and 288 deletions

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D0E3A7491B28A7E300A402D9"
BuildableName = "Postbox.framework"
BlueprintName = "Postbox"
ReferencedContainer = "container:Postbox.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "DebugHockeyapp"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D0E3A7541B28A7E300A402D9"
BuildableName = "PostboxTests.xctest"
BlueprintName = "PostboxTests"
ReferencedContainer = "container:Postbox.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D0E3A7491B28A7E300A402D9"
BuildableName = "Postbox.framework"
BlueprintName = "Postbox"
ReferencedContainer = "container:Postbox.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "DebugHockeyapp"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D0E3A7491B28A7E300A402D9"
BuildableName = "Postbox.framework"
BlueprintName = "Postbox"
ReferencedContainer = "container:Postbox.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "ReleaseHockeyapp"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D0E3A7491B28A7E300A402D9"
BuildableName = "Postbox.framework"
BlueprintName = "Postbox"
ReferencedContainer = "container:Postbox.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "DebugHockeyapp">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "ReleaseHockeyapp"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "DebugHockeyapp"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D0E3A7541B28A7E300A402D9"
BuildableName = "PostboxTests.xctest"
BlueprintName = "PostboxTests"
ReferencedContainer = "container:Postbox.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "DebugHockeyapp"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "ReleaseHockeyapp"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "DebugHockeyapp">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "ReleaseHockeyapp"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -28,9 +28,11 @@ public final class Database {
internal var handle: OpaquePointer? = nil internal var handle: OpaquePointer? = nil
public init?(_ location: String) { public init?(_ location: String) {
let _ = open(location + "-guard", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR) if location != ":memory:" {
let _ = open(location + "-guard", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)
}
let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
let res = sqlite3_open_v2(location.description, &self.handle, flags, nil) let res = sqlite3_open_v2(location, &self.handle, flags, nil)
if res != SQLITE_OK { if res != SQLITE_OK {
postboxLog("sqlite3_open_v2: \(res)") postboxLog("sqlite3_open_v2: \(res)")
return nil return nil

View File

@ -253,12 +253,12 @@ final class MutableMessageHistoryView {
self.topTaggedMessages = topTaggedMessages self.topTaggedMessages = topTaggedMessages
self.additionalDatas = additionalDatas self.additionalDatas = additionalDatas
self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, statistics: self.orderStatistics, limit: count + 2, locations: peerIds) self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds)
if case let .loading(loadingState) = self.state { if case let .loading(loadingState) = self.state {
let sampledState = loadingState.checkAndSample(postbox: postbox) let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState { switch sampledState {
case let .ready(anchor, holes): case let .ready(anchor, holes):
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, statistics: self.orderStatistics, limit: count + 2, locations: peerIds, postbox: postbox, holes: holes)) self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds, postbox: postbox, holes: holes))
self.sampledState = self.state.sample(postbox: postbox) self.sampledState = self.state.sample(postbox: postbox)
case .loadHole: case .loadHole:
break break
@ -270,12 +270,12 @@ final class MutableMessageHistoryView {
} }
private func reset(postbox: Postbox) { private func reset(postbox: Postbox) {
self.state = HistoryViewState(postbox: postbox, inputAnchor: self.anchor, tag: self.tag, statistics: self.orderStatistics, limit: self.fillCount + 2, locations: self.peerIds) self.state = HistoryViewState(postbox: postbox, inputAnchor: self.anchor, tag: self.tag, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds)
if case let .loading(loadingState) = self.state { if case let .loading(loadingState) = self.state {
let sampledState = loadingState.checkAndSample(postbox: postbox) let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState { switch sampledState {
case let .ready(anchor, holes): case let .ready(anchor, holes):
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, statistics: self.orderStatistics, limit: self.fillCount + 2, locations: self.peerIds, postbox: postbox, holes: holes)) self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
case .loadHole: case .loadHole:
break break
} }
@ -284,7 +284,7 @@ final class MutableMessageHistoryView {
let sampledState = loadingState.checkAndSample(postbox: postbox) let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState { switch sampledState {
case let .ready(anchor, holes): case let .ready(anchor, holes):
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, statistics: self.orderStatistics, limit: self.fillCount + 2, locations: self.peerIds, postbox: postbox, holes: holes)) self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
case .loadHole: case .loadHole:
break break
} }
@ -381,18 +381,18 @@ final class MutableMessageHistoryView {
switch operation { switch operation {
case let .InsertMessage(message): case let .InsertMessage(message):
if unwrappedTag.isEmpty || message.tags.contains(unwrappedTag) { if unwrappedTag.isEmpty || message.tags.contains(unwrappedTag) {
if loadedState.add(postbox: postbox, entry: .IntermediateMessageEntry(message, nil, nil)) { if loadedState.add(entry: .IntermediateMessageEntry(message, nil, nil)) {
hasChanges = true hasChanges = true
} }
} }
case let .Remove(indicesAndTags): case let .Remove(indicesAndTags):
for (index, _) in indicesAndTags { for (index, _) in indicesAndTags {
if loadedState.remove(postbox: postbox, index: index) { if loadedState.remove(index: index) {
hasChanges = true hasChanges = true
} }
} }
case let .UpdateEmbeddedMedia(index, buffer): case let .UpdateEmbeddedMedia(index, buffer):
if loadedState.updateEmbeddedMedia(postbox: postbox, index: index, buffer: buffer) { if loadedState.updateEmbeddedMedia(index: index, buffer: buffer) {
hasChanges = true hasChanges = true
} }
case let .UpdateGroupInfos(groupInfos): case let .UpdateGroupInfos(groupInfos):
@ -455,7 +455,7 @@ final class MutableMessageHistoryView {
let sampledState = loadingState.checkAndSample(postbox: postbox) let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState { switch sampledState {
case let .ready(anchor, holes): case let .ready(anchor, holes):
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, statistics: self.orderStatistics, limit: self.fillCount + 2, locations: self.peerIds, postbox: postbox, holes: holes)) self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
case .loadHole: case .loadHole:
break break
} }
@ -745,15 +745,17 @@ public final class MessageHistoryView {
assert(Set(entries.map({ $0.message.stableId })).count == entries.count) assert(Set(entries.map({ $0.message.stableId })).count == entries.count)
if !entries.isEmpty { if !entries.isEmpty {
let anchorIndex = binaryIndexOrLower(entries, state.anchor) let anchorIndex = binaryIndexOrLower(entries, state.anchor)
let lowerCount = mutableView.fillCount / 2 + 1 let lowerOrEqualThanAnchorCount = anchorIndex + 1
let upperCount = mutableView.fillCount / 2 let higherThanAnchorCount = entries.count - anchorIndex - 1
if anchorIndex >= 0 && entries.count - anchorIndex >= upperCount {
if higherThanAnchorCount > mutableView.fillCount {
self.laterId = entries[entries.count - 1].index self.laterId = entries[entries.count - 1].index
entries.removeLast() entries.removeLast()
} else { } else {
self.laterId = nil self.laterId = nil
} }
if anchorIndex >= lowerCount {
if lowerOrEqualThanAnchorCount > mutableView.fillCount {
self.earlierId = entries[0].index self.earlierId = entries[0].index
entries.removeFirst() entries.removeFirst()
} else { } else {

View File

@ -165,13 +165,12 @@ private func binaryIndexOrLower(_ inputArr: [MutableMessageHistoryEntry], _ sear
return hi return hi
} }
private func sampleEntries(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], anchor: HistoryViewAnchor, limit: Int) -> [(PeerIdAndNamespace, Int)] { private func sampleEntries(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], anchor: HistoryViewAnchor, halfLimit: Int) -> (lowerOrAtAnchor:[(PeerIdAndNamespace, Int)], higherThanAnchor: [(PeerIdAndNamespace, Int)]) {
var previousAnchorIndices: [PeerIdAndNamespace: Int] = [:] var previousAnchorIndices: [PeerIdAndNamespace: Int] = [:]
var nextAnchorIndices: [PeerIdAndNamespace: Int] = [:] var nextAnchorIndices: [PeerIdAndNamespace: Int] = [:]
for (space, items) in orderedEntriesBySpace { for (space, items) in orderedEntriesBySpace {
let index = binaryIndexOrLower(items.entries, anchor) previousAnchorIndices[space] = items.lowerOrAtAnchor.count - 1
previousAnchorIndices[space] = index nextAnchorIndices[space] = 0
nextAnchorIndices[space] = index + 1
} }
var backwardsResult: [(PeerIdAndNamespace, Int)] = [] var backwardsResult: [(PeerIdAndNamespace, Int)] = []
@ -182,7 +181,7 @@ private func sampleEntries(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHi
for (space, value) in previousAnchorIndices { for (space, value) in previousAnchorIndices {
if value != -1 { if value != -1 {
if let minSpaceValue = minSpace { if let minSpaceValue = minSpace {
if orderedEntriesBySpace[space]!.entries[value].index > orderedEntriesBySpace[minSpaceValue]!.entries[previousAnchorIndices[minSpaceValue]!].index { if orderedEntriesBySpace[space]!.lowerOrAtAnchor[value].index > orderedEntriesBySpace[minSpaceValue]!.lowerOrAtAnchor[previousAnchorIndices[minSpaceValue]!].index {
minSpace = space minSpace = space
} }
} else { } else {
@ -193,16 +192,22 @@ private func sampleEntries(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHi
if let minSpace = minSpace { if let minSpace = minSpace {
backwardsResult.append((minSpace, previousAnchorIndices[minSpace]!)) backwardsResult.append((minSpace, previousAnchorIndices[minSpace]!))
previousAnchorIndices[minSpace]! -= 1 previousAnchorIndices[minSpace]! -= 1
if (result.count + backwardsResult.count) == limit { if backwardsResult.count == halfLimit {
break break
} }
} }
if minSpace == nil {
break
}
}
while true {
var maxSpace: PeerIdAndNamespace? var maxSpace: PeerIdAndNamespace?
for (space, value) in nextAnchorIndices { for (space, value) in nextAnchorIndices {
if value != orderedEntriesBySpace[space]!.entries.count { if value != orderedEntriesBySpace[space]!.higherThanAnchor.count {
if let maxSpaceValue = maxSpace { if let maxSpaceValue = maxSpace {
if orderedEntriesBySpace[space]!.entries[value].index < orderedEntriesBySpace[maxSpaceValue]!.entries[nextAnchorIndices[maxSpaceValue]!].index { if orderedEntriesBySpace[space]!.higherThanAnchor[value].index < orderedEntriesBySpace[maxSpaceValue]!.higherThanAnchor[nextAnchorIndices[maxSpaceValue]!].index {
maxSpace = space maxSpace = space
} }
} else { } else {
@ -213,16 +218,16 @@ private func sampleEntries(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHi
if let maxSpace = maxSpace { if let maxSpace = maxSpace {
result.append((maxSpace, nextAnchorIndices[maxSpace]!)) result.append((maxSpace, nextAnchorIndices[maxSpace]!))
nextAnchorIndices[maxSpace]! += 1 nextAnchorIndices[maxSpace]! += 1
if (result.count + backwardsResult.count) == limit { if result.count == halfLimit {
break break
} }
} }
if minSpace == nil && maxSpace == nil { if maxSpace == nil {
break break
} }
} }
return backwardsResult.reversed() + result return (backwardsResult.reversed(), result)
} }
struct SampledHistoryViewHole: Equatable { struct SampledHistoryViewHole: Equatable {
@ -274,9 +279,9 @@ private func isIndex(index: MessageIndex, closerTo anchor: HistoryViewAnchor, th
} }
} }
private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, tag: MessageTags?) -> (clipRanges: [ClosedRange<MessageIndex>], sampledHole: SampledHistoryViewHole?) { private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, tag: MessageTags?, halfLimit: Int) -> (clipRanges: [ClosedRange<MessageIndex>], sampledHole: SampledHistoryViewHole?) {
var clipRanges: [ClosedRange<MessageIndex>] = [] var clipRanges: [ClosedRange<MessageIndex>] = []
var sampledHole: (itemIndex: Int?, hole: SampledHistoryViewHole)? var sampledHole: (distanceFromAnchor: Int?, hole: SampledHistoryViewHole)?
for (space, indices) in holes.holesBySpace { for (space, indices) in holes.holesBySpace {
if indices.isEmpty { if indices.isEmpty {
@ -292,7 +297,7 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere
} }
} }
} }
guard let items = orderedEntriesBySpace[space], !items.entries.isEmpty else { guard let items = orderedEntriesBySpace[space], (!items.lowerOrAtAnchor.isEmpty || !items.higherThanAnchor.isEmpty) else {
let holeBounds: (startId: MessageId.Id, endId: MessageId.Id) let holeBounds: (startId: MessageId.Id, endId: MessageId.Id)
switch anchor { switch anchor {
case .lowerBound: case .lowerBound:
@ -307,11 +312,176 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere
continue continue
} }
} }
guard let bounds = items.bounds else {
assertionFailure("A non-empty entry list should have non-nil bounds") for item in items.lowerOrAtAnchor {
continue if item.index.id.id == 76891 {
assert(true)
}
} }
let anchorIndex = binaryIndexOrLower(items.entries, anchor) for item in items.higherThanAnchor {
if item.index.id.id == 76891 {
assert(true)
}
}
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> = Int(currentMessageId) ... Int(startingMessageId)
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, 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.predecessor()
clipRanges.append(MessageIndex.absoluteLowerBound() ... clipIndex)
}
} else {
let clipIndex = items.lowerOrAtAnchor[0].index.predecessor()
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.predecessor()
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.predecessor()
}
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> = Int(startingMessageId) ... Int(currentMessageId)
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, 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.successor()
clipRanges.append(clipIndex ... MessageIndex.absoluteUpperBound())
}
} else {
let clipIndex = items.higherThanAnchor[items.higherThanAnchor.count - 1].index.successor()
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.successor()
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.successor()
}
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)
}
}
/*let anchorIndex = binaryIndexOrLower(items.entries, anchor)
let anchorStartingMessageId: MessageId.Id let anchorStartingMessageId: MessageId.Id
if anchorIndex == -1 { if anchorIndex == -1 {
anchorStartingMessageId = 1 anchorStartingMessageId = 1
@ -406,7 +576,7 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere
} }
} }
higherDirectionIndex += 1 higherDirectionIndex += 1
} }*/
} }
return (clipRanges, sampledHole?.hole) return (clipRanges, sampledHole?.hole)
} }
@ -443,8 +613,61 @@ struct HistoryViewHoles {
} }
struct OrderedHistoryViewEntries { struct OrderedHistoryViewEntries {
var entries: [MutableMessageHistoryEntry] var lowerOrAtAnchor: [MutableMessageHistoryEntry]
var bounds: (lower: MessageIndex, upper: MessageIndex)? var higherThanAnchor: [MutableMessageHistoryEntry]
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
}
}
mutating func mutableScan(_ f: (MutableMessageHistoryEntry) -> MutableMessageHistoryEntry?) -> Bool {
for i in 0 ..< self.lowerOrAtAnchor.count {
if let updated = f(self.lowerOrAtAnchor[i]) {
self.lowerOrAtAnchor[i] = updated
return true
}
}
for i in 0 ..< self.higherThanAnchor.count {
if let updated = f(self.higherThanAnchor[i]) {
self.higherThanAnchor[i] = updated
return true
}
}
return false
}
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.lowerOrAtAnchor[entryIndex] = updated
return true
}
} else if let entryIndex = binarySearch(self.higherThanAnchor, extract: { $0.index }, searchItem: index) {
if let updated = f(self.higherThanAnchor[entryIndex]) {
self.higherThanAnchor[entryIndex] = updated
return true
}
}
return false
}
mutating func remove(index: MessageIndex) -> Bool {
if let entryIndex = binarySearch(self.lowerOrAtAnchor, extract: { $0.index }, searchItem: index) {
self.lowerOrAtAnchor.remove(at: entryIndex)
return true
} else if let entryIndex = binarySearch(self.higherThanAnchor, extract: { $0.index }, searchItem: index) {
self.higherThanAnchor.remove(at: entryIndex)
return true
} else {
return false
}
}
} }
struct HistoryViewLoadedSample { struct HistoryViewLoadedSample {
@ -459,17 +682,17 @@ final class HistoryViewLoadedState {
let anchor: HistoryViewAnchor let anchor: HistoryViewAnchor
let tag: MessageTags? let tag: MessageTags?
let statistics: MessageHistoryViewOrderStatistics let statistics: MessageHistoryViewOrderStatistics
let limit: Int let halfLimit: Int
var orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries] var orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries]
var holes: HistoryViewHoles var holes: HistoryViewHoles
var spacesWithRemovals = Set<PeerIdAndNamespace>() var spacesWithRemovals = Set<PeerIdAndNamespace>()
init(anchor: HistoryViewAnchor, tag: MessageTags?, statistics: MessageHistoryViewOrderStatistics, limit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) { init(anchor: HistoryViewAnchor, tag: MessageTags?, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) {
precondition(limit >= 3) precondition(halfLimit >= 3)
self.anchor = anchor self.anchor = anchor
self.tag = tag self.tag = tag
self.statistics = statistics self.statistics = statistics
self.limit = limit self.halfLimit = halfLimit
self.orderedEntriesBySpace = [:] self.orderedEntriesBySpace = [:]
self.holes = holes self.holes = holes
@ -509,56 +732,43 @@ final class HistoryViewLoadedState {
anchorIndex = upperBound anchorIndex = upperBound
} }
var lowerMessages: [MutableMessageHistoryEntry] = [] var lowerOrAtAnchorMessages: [MutableMessageHistoryEntry] = []
var higherMessages: [MutableMessageHistoryEntry] = [] var higherThanAnchorMessages: [MutableMessageHistoryEntry] = []
if let currentEntries = self.orderedEntriesBySpace[space], !currentEntries.entries.isEmpty { if let currentEntries = self.orderedEntriesBySpace[space] {
let index = binaryIndexOrLower(currentEntries.entries, self.anchor) lowerOrAtAnchorMessages = currentEntries.lowerOrAtAnchor.reversed()
if index >= 0 { higherThanAnchorMessages = currentEntries.higherThanAnchor
lowerMessages = Array(currentEntries.entries[0 ... index].reversed())
}
if index < currentEntries.entries.count {
higherMessages = Array(currentEntries.entries[index + 1 ..< currentEntries.entries.count])
}
} }
func mapEntry(_ message: IntermediateMessage) -> MutableMessageHistoryEntry { func mapEntry(_ message: IntermediateMessage) -> MutableMessageHistoryEntry {
return .IntermediateMessageEntry(message, nil, nil) return .IntermediateMessageEntry(message, nil, nil)
} }
if lowerMessages.count < self.limit / 2 + 1 { if lowerOrAtAnchorMessages.count < self.halfLimit {
let nextLowerIndex: (index: MessageIndex, includeFrom: Bool) let nextLowerIndex: (index: MessageIndex, includeFrom: Bool)
if let lastMessage = lowerMessages.last { if let lastMessage = lowerOrAtAnchorMessages.last {
nextLowerIndex = (lastMessage.index, false) nextLowerIndex = (lastMessage.index, false)
} else { } else {
nextLowerIndex = (anchorIndex, true) nextLowerIndex = (anchorIndex, true)
} }
lowerMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.limit / 2 + 1 - lowerMessages.count).map(mapEntry)) lowerOrAtAnchorMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.halfLimit - lowerOrAtAnchorMessages.count).map(mapEntry))
} }
if higherMessages.count < self.limit - lowerMessages.count { if higherThanAnchorMessages.count < self.halfLimit {
let nextHigherIndex: MessageIndex let nextHigherIndex: MessageIndex
if let lastMessage = higherMessages.last { if let lastMessage = higherThanAnchorMessages.last {
nextHigherIndex = lastMessage.index nextHigherIndex = lastMessage.index
} else { } else {
nextHigherIndex = anchorIndex nextHigherIndex = anchorIndex
} }
higherMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.limit - lowerMessages.count - higherMessages.count).map(mapEntry)) higherThanAnchorMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.halfLimit - higherThanAnchorMessages.count).map(mapEntry))
} }
if !lowerMessages.isEmpty && lowerMessages.count + higherMessages.count < self.limit { lowerOrAtAnchorMessages.reverse()
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).map(mapEntry)
lowerMessages.append(contentsOf: additionalLowerMessages)
}
var messages: [MutableMessageHistoryEntry] = [] assert(lowerOrAtAnchorMessages.count <= self.halfLimit)
messages.append(contentsOf: lowerMessages.reversed()) assert(higherThanAnchorMessages.count <= self.halfLimit)
messages.append(contentsOf: higherMessages)
assert(messages.count <= self.limit) /*if let tag = self.tag, self.statistics.contains(.combinedLocation) {
let bounds = postbox.messageHistoryTable.fetchBoundaries(peerId: space.peerId, namespace: space.namespace, tag: self.tag)
if let tag = self.tag, self.statistics.contains(.combinedLocation) {
if let first = messages.first { if let first = messages.first {
let messageIndex = first.index let messageIndex = first.index
let previousCount = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex) let previousCount = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex)
@ -600,9 +810,9 @@ final class HistoryViewLoadedState {
nextLocation.1 = max(0, nextLocation.1 - 1) nextLocation.1 = max(0, nextLocation.1 - 1)
} }
} }
} }*/
self.orderedEntriesBySpace[space] = OrderedHistoryViewEntries(entries: messages, bounds: bounds) self.orderedEntriesBySpace[space] = OrderedHistoryViewEntries(lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages)
} }
func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool { func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
@ -618,15 +828,14 @@ final class HistoryViewLoadedState {
if self.orderedEntriesBySpace[space] == nil { if self.orderedEntriesBySpace[space] == nil {
return false return false
} }
guard let entryIndex = binarySearch(self.orderedEntriesBySpace[space]!.entries, extract: { $0.index }, searchItem: index) else { guard let entry = self.orderedEntriesBySpace[space]!.find(index: index) else {
return false return false
} }
let entry = self.orderedEntriesBySpace[space]!.entries[entryIndex]
var updated = false var updated = false
if self.remove(postbox: postbox, index: index) { if self.remove(index: index) {
updated = true updated = true
} }
if self.add(postbox: postbox, entry: entry.updatedTimestamp(timestamp)) { if self.add(entry: entry.updatedTimestamp(timestamp)) {
updated = true updated = true
} }
return updated return updated
@ -646,43 +855,46 @@ final class HistoryViewLoadedState {
if self.orderedEntriesBySpace[space] == nil { if self.orderedEntriesBySpace[space] == nil {
continue continue
} }
for i in 0 ..< self.orderedEntriesBySpace[space]!.entries.count { let spaceUpdated = self.orderedEntriesBySpace[space]!.mutableScan({ entry in
if let groupInfo = spaceMapping[self.orderedEntriesBySpace[space]!.entries[i].index.id.id] { if let groupInfo = spaceMapping[entry.index.id.id] {
updated = true updated = true
switch self.orderedEntriesBySpace[space]!.entries[i] { switch entry {
case let .IntermediateMessageEntry(message, location, monthLocation): case let .IntermediateMessageEntry(message, location, monthLocation):
self.orderedEntriesBySpace[space]!.entries[i] = .IntermediateMessageEntry(message.withUpdatedGroupInfo(groupInfo), location, monthLocation) return .IntermediateMessageEntry(message.withUpdatedGroupInfo(groupInfo), location, monthLocation)
case let .MessageEntry(messageEntry): case let .MessageEntry(messageEntry):
self.orderedEntriesBySpace[space]!.entries[i] = .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedGroupInfo(groupInfo), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes)) return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message.withUpdatedGroupInfo(groupInfo), location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes))
} }
} }
return nil
})
if spaceUpdated {
updated = true
} }
} }
return updated return updated
} }
func updateEmbeddedMedia(postbox: Postbox, index: MessageIndex, buffer: ReadBuffer) -> Bool { func updateEmbeddedMedia(index: MessageIndex, buffer: ReadBuffer) -> Bool {
let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace) let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace)
if self.orderedEntriesBySpace[space] == nil { if self.orderedEntriesBySpace[space] == nil {
return false return false
} }
guard let itemIndex = binarySearch(self.orderedEntriesBySpace[space]!.entries, extract: { $0.index }, searchItem: index) else {
return false return self.orderedEntriesBySpace[space]!.update(index: index, { entry in
} switch entry {
switch self.orderedEntriesBySpace[space]!.entries[itemIndex] { case let .IntermediateMessageEntry(message, location, monthLocation):
case let .IntermediateMessageEntry(message, location, monthLocation): return .IntermediateMessageEntry(message.withUpdatedEmbeddedMedia(buffer), location, monthLocation)
self.orderedEntriesBySpace[space]!.entries[itemIndex] = .IntermediateMessageEntry(message.withUpdatedEmbeddedMedia(buffer), location, monthLocation) case let .MessageEntry(messageEntry):
case let .MessageEntry(messageEntry): return .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message, location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes))
self.orderedEntriesBySpace[space]!.entries[itemIndex] = .MessageEntry(MessageHistoryMessageEntry(message: messageEntry.message, location: messageEntry.location, monthLocation: messageEntry.monthLocation, attributes: messageEntry.attributes)) }
} })
return true
} }
func updateMedia(updatedMedia: [MediaId: Media?]) -> Bool { func updateMedia(updatedMedia: [MediaId: Media?]) -> Bool {
var hasChanges = false var updated = false
for space in self.orderedEntriesBySpace.keys { for space in self.orderedEntriesBySpace.keys {
for i in 0 ..< self.orderedEntriesBySpace[space]!.entries.count { let spaceUpdated = self.orderedEntriesBySpace[space]!.mutableScan({ entry in
switch self.orderedEntriesBySpace[space]!.entries[i] { switch entry {
case let .MessageEntry(value): case let .MessageEntry(value):
let message = value.message let message = value.message
@ -706,41 +918,25 @@ final class HistoryViewLoadedState {
} }
} }
let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, 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) let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, 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)
self.orderedEntriesBySpace[space]!.entries[i] = .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes)) return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes))
hasChanges = true
}
case let .IntermediateMessageEntry(message, _, _):
var rebuild = false
for mediaId in message.referencedMedia {
if let media = updatedMedia[mediaId] , media?.id != mediaId {
rebuild = true
break
}
}
if rebuild {
var referencedMedia: [MediaId] = []
for mediaId in message.referencedMedia {
if let media = updatedMedia[mediaId] , media?.id != mediaId {
if let id = media?.id {
referencedMedia.append(id)
}
} else {
referencedMedia.append(mediaId)
}
}
hasChanges = true
} }
case .IntermediateMessageEntry:
break
} }
return nil
})
if spaceUpdated {
updated = true
} }
} }
return hasChanges return updated
} }
func add(postbox: Postbox, entry: MutableMessageHistoryEntry) -> Bool { func add(entry: MutableMessageHistoryEntry) -> Bool {
let space = PeerIdAndNamespace(peerId: entry.index.id.peerId, namespace: entry.index.id.namespace) let space = PeerIdAndNamespace(peerId: entry.index.id.peerId, namespace: entry.index.id.namespace)
if self.orderedEntriesBySpace[space] == nil { if self.orderedEntriesBySpace[space] == nil {
self.orderedEntriesBySpace[space] = OrderedHistoryViewEntries(entries: [], bounds: nil) self.orderedEntriesBySpace[space] = OrderedHistoryViewEntries(lowerOrAtAnchor: [], higherThanAnchor: [])
} }
var updated = false var updated = false
@ -763,66 +959,48 @@ final class HistoryViewLoadedState {
} }
}*/ }*/
let insertionIndex = binaryInsertionIndex(self.orderedEntriesBySpace[space]!.entries, extract: { $0.index }, searchItem: entry.index) 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]!.entries.count {
if self.orderedEntriesBySpace[space]!.entries[insertionIndex].index == entry.index { if insertionIndex < self.orderedEntriesBySpace[space]!.lowerOrAtAnchor.count {
assertionFailure("Inserting an existing index is not allowed") if self.orderedEntriesBySpace[space]!.lowerOrAtAnchor[insertionIndex].index == entry.index {
self.orderedEntriesBySpace[space]!.entries[insertionIndex] = entry assertionFailure("Inserting an existing index is not allowed")
return true self.orderedEntriesBySpace[space]!.lowerOrAtAnchor[insertionIndex] = entry
} return true
}
var shouldBeAdded = false
if insertionIndex == 0 {
if let bounds = self.orderedEntriesBySpace[space]!.bounds {
if entry.index <= bounds.lower {
shouldBeAdded = true
} }
} else {
//assert(self.orderedEntriesBySpace[space]!.entries.isEmpty, "A non-empty entry list should have non-nil bounds")
shouldBeAdded = true
}
} else if insertionIndex == self.orderedEntriesBySpace[space]!.entries.count {
if let bounds = self.orderedEntriesBySpace[space]!.bounds {
if entry.index >= bounds.upper {
shouldBeAdded = true
}
} else {
//assert(self.orderedEntriesBySpace[space]!.entries.isEmpty, "A non-empty entry list should have non-nil bounds")
shouldBeAdded = true
}
} else {
shouldBeAdded = true
}
if shouldBeAdded {
self.orderedEntriesBySpace[space]!.entries.insert(entry, at: insertionIndex)
if let currentBounds = self.orderedEntriesBySpace[space]!.bounds {
if entry.index < currentBounds.lower {
self.orderedEntriesBySpace[space]!.bounds = (lower: entry.index, upper: currentBounds.upper)
} else if entry.index > currentBounds.upper {
self.orderedEntriesBySpace[space]!.bounds = (lower: currentBounds.lower, upper: entry.index)
}
} else {
self.orderedEntriesBySpace[space]!.bounds = (lower: entry.index, upper: entry.index)
} }
if self.orderedEntriesBySpace[space]!.entries.count > self.limit { if insertionIndex == 0 && self.orderedEntriesBySpace[space]!.lowerOrAtAnchor.count >= self.halfLimit {
let anchorIndex = binaryIndexOrLower(self.orderedEntriesBySpace[space]!.entries, self.anchor) return updated
if anchorIndex > self.limit / 2 { }
self.orderedEntriesBySpace[space]!.entries.removeFirst() self.orderedEntriesBySpace[space]!.lowerOrAtAnchor.insert(entry, at: insertionIndex)
} else { if self.orderedEntriesBySpace[space]!.lowerOrAtAnchor.count > self.halfLimit {
self.orderedEntriesBySpace[space]!.entries.removeLast() self.orderedEntriesBySpace[space]!.lowerOrAtAnchor.removeFirst()
}
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]!.higherThanAnchor[insertionIndex] = entry
return true
} }
} }
updated = true
if insertionIndex == self.orderedEntriesBySpace[space]!.higherThanAnchor.count && self.orderedEntriesBySpace[space]!.higherThanAnchor.count >= self.halfLimit {
return updated
}
self.orderedEntriesBySpace[space]!.higherThanAnchor.insert(entry, at: insertionIndex)
if self.orderedEntriesBySpace[space]!.higherThanAnchor.count > self.halfLimit {
self.orderedEntriesBySpace[space]!.higherThanAnchor.removeLast()
}
return true
} }
return updated
} }
func remove(postbox: Postbox, index: MessageIndex) -> Bool { func remove(index: MessageIndex) -> Bool {
let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace) let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace)
if self.orderedEntriesBySpace[space] == nil { if self.orderedEntriesBySpace[space] == nil {
return false return false
@ -842,16 +1020,7 @@ final class HistoryViewLoadedState {
} }
}*/ }*/
if let itemIndex = binarySearch(self.orderedEntriesBySpace[space]!.entries, extract: { $0.index }, searchItem: index) { if self.orderedEntriesBySpace[space]!.remove(index: index) {
if let currentBounds = self.orderedEntriesBySpace[space]!.bounds {
if currentBounds.lower == index || currentBounds.upper
== index {
self.orderedEntriesBySpace[space]!.bounds = postbox.messageHistoryTable.fetchBoundaries(peerId: space.peerId, namespace: space.namespace, tag: self.tag)
}
} else {
//assertionFailure("A non-empty entry list should have non-nil bounds")
}
self.orderedEntriesBySpace[space]!.entries.remove(at: itemIndex)
self.spacesWithRemovals.insert(space) self.spacesWithRemovals.insert(space)
updated = true updated = true
} }
@ -866,48 +1035,62 @@ final class HistoryViewLoadedState {
} }
self.spacesWithRemovals.removeAll() self.spacesWithRemovals.removeAll()
} }
let combinedSpacesAndIndices = sampleEntries(orderedEntriesBySpace: self.orderedEntriesBySpace, anchor: self.anchor, limit: self.limit) let combinedSpacesAndIndicesByDirection = sampleEntries(orderedEntriesBySpace: self.orderedEntriesBySpace, anchor: self.anchor, halfLimit: self.halfLimit)
let (clipRanges, sampledHole) = sampleHoleRanges(orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, tag: self.tag) let (clipRanges, sampledHole) = sampleHoleRanges(orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, tag: self.tag, halfLimit: self.halfLimit)
var holesToLower = false var holesToLower = false
var holesToHigher = false var holesToHigher = false
var result: [MessageHistoryMessageEntry] = [] var result: [MessageHistoryMessageEntry] = []
if combinedSpacesAndIndices.isEmpty { if combinedSpacesAndIndicesByDirection.lowerOrAtAnchor.isEmpty && combinedSpacesAndIndicesByDirection.higherThanAnchor.isEmpty {
if !clipRanges.isEmpty { if !clipRanges.isEmpty {
holesToLower = true holesToLower = true
holesToHigher = true holesToHigher = true
} }
} else { } else {
outer: for i in 0 ..< combinedSpacesAndIndices.count { let directions = [combinedSpacesAndIndicesByDirection.lowerOrAtAnchor, combinedSpacesAndIndicesByDirection.higherThanAnchor]
let (space, index) = combinedSpacesAndIndices[i] for directionIndex in 0 ..< directions.count {
outer: for i in 0 ..< directions[directionIndex].count {
if !clipRanges.isEmpty { let (space, index) = directions[directionIndex][i]
let entryIndex = self.orderedEntriesBySpace[space]!.entries[index].index
for range in clipRanges { let entry: MutableMessageHistoryEntry
if range.contains(entryIndex) { if directionIndex == 0 {
if i == 0 { entry = self.orderedEntriesBySpace[space]!.lowerOrAtAnchor[index]
holesToLower = true } else {
entry = self.orderedEntriesBySpace[space]!.higherThanAnchor[index]
}
if !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
} }
if i == combinedSpacesAndIndices.count - 1 {
holesToHigher = true
}
continue outer
} }
} }
}
switch entry {
switch self.orderedEntriesBySpace[space]!.entries[index] { case let .MessageEntry(value):
case let .MessageEntry(value): result.append(value)
result.append(value) case let .IntermediateMessageEntry(message, location, monthLocation):
case let .IntermediateMessageEntry(message, location, monthLocation): let renderedMessage = postbox.messageHistoryTable.renderMessage(message, peerTable: postbox.peerTable)
let renderedMessage = postbox.messageHistoryTable.renderMessage(message, peerTable: postbox.peerTable) var authorIsContact = false
var authorIsContact = false if let author = renderedMessage.author {
if let author = renderedMessage.author { authorIsContact = postbox.contactsTable.isContact(peerId: author.id)
authorIsContact = postbox.contactsTable.isContact(peerId: author.id) }
} let entry = MessageHistoryMessageEntry(message: renderedMessage, location: location, monthLocation: monthLocation, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: authorIsContact))
let entry = MessageHistoryMessageEntry(message: renderedMessage, location: location, monthLocation: monthLocation, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: authorIsContact)) if directionIndex == 0 {
self.orderedEntriesBySpace[space]!.entries[index] = .MessageEntry(entry) self.orderedEntriesBySpace[space]!.lowerOrAtAnchor[index] = .MessageEntry(entry)
result.append(entry) } else {
self.orderedEntriesBySpace[space]!.higherThanAnchor[index] = .MessageEntry(entry)
}
result.append(entry)
}
} }
} }
} }
@ -948,13 +1131,13 @@ enum HistoryViewLoadingSample {
final class HistoryViewLoadingState { final class HistoryViewLoadingState {
var messageId: MessageId var messageId: MessageId
let tag: MessageTags? let tag: MessageTags?
let limit: Int let halfLimit: Int
var holes: HistoryViewHoles var holes: HistoryViewHoles
init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, messageId: MessageId, limit: Int) { init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, messageId: MessageId, halfLimit: Int) {
self.messageId = messageId self.messageId = messageId
self.tag = tag self.tag = tag
self.limit = limit self.halfLimit = halfLimit
self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag)) self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag))
} }
@ -995,14 +1178,14 @@ enum HistoryViewState {
case loaded(HistoryViewLoadedState) case loaded(HistoryViewLoadedState)
case loading(HistoryViewLoadingState) case loading(HistoryViewLoadingState)
init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, statistics: MessageHistoryViewOrderStatistics, limit: Int, locations: MessageHistoryViewPeerIds) { init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds) {
switch inputAnchor { switch inputAnchor {
case let .index(index): case let .index(index):
self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, statistics: statistics, limit: limit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag)))) self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag))))
case .lowerBound: case .lowerBound:
self = .loaded(HistoryViewLoadedState(anchor: .lowerBound, tag: tag, statistics: statistics, limit: limit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag)))) self = .loaded(HistoryViewLoadedState(anchor: .lowerBound, tag: tag, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag))))
case .upperBound: case .upperBound:
self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, statistics: statistics, limit: limit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag)))) self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag))))
case .unread: case .unread:
let anchorPeerId: PeerId let anchorPeerId: PeerId
switch locations { switch locations {
@ -1035,26 +1218,26 @@ enum HistoryViewState {
} }
} }
if let messageId = messageId { if let messageId = messageId {
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, messageId: messageId, limit: limit) let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, messageId: messageId, halfLimit: halfLimit)
let sampledState = loadingState.checkAndSample(postbox: postbox) let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState { switch sampledState {
case let .ready(anchor, holes): case let .ready(anchor, holes):
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, statistics: statistics, limit: limit, locations: locations, postbox: postbox, holes: holes)) self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
case .loadHole: case .loadHole:
self = .loading(loadingState) self = .loading(loadingState)
} }
} else { } else {
self = .loaded(HistoryViewLoadedState(anchor: anchor ?? .upperBound, tag: tag, statistics: statistics, limit: limit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag)))) self = .loaded(HistoryViewLoadedState(anchor: anchor ?? .upperBound, tag: tag, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag))))
} }
} else { } else {
preconditionFailure() preconditionFailure()
} }
case let .message(messageId): case let .message(messageId):
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, messageId: messageId, limit: limit) let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, messageId: messageId, halfLimit: halfLimit)
let sampledState = loadingState.checkAndSample(postbox: postbox) let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState { switch sampledState {
case let .ready(anchor, holes): case let .ready(anchor, holes):
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, statistics: statistics, limit: limit, locations: locations, postbox: postbox, holes: holes)) self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
case .loadHole: case .loadHole:
self = .loading(loadingState) self = .loading(loadingState)
} }

View File

@ -935,7 +935,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
#if DEBUG #if DEBUG
//debugSaveState(basePath: basePath, name: "previous1") //debugSaveState(basePath: basePath, name: "previous1")
//debugRestoreState(basePath: basePath, name: "previous1") debugRestoreState(basePath: basePath, name: "previous1")
#endif #endif
let startTime = CFAbsoluteTimeGetCurrent() let startTime = CFAbsoluteTimeGetCurrent()

View File

@ -159,6 +159,7 @@ public final class SqliteValueBox: ValueBox {
private let lock = NSRecursiveLock() private let lock = NSRecursiveLock()
fileprivate let basePath: String fileprivate let basePath: String
private let inMemory: Bool
private let encryptionParameters: ValueBoxEncryptionParameters? private let encryptionParameters: ValueBoxEncryptionParameters?
private let databasePath: String private let databasePath: String
private var database: Database! private var database: Database!
@ -196,8 +197,9 @@ public final class SqliteValueBox: ValueBox {
private let queue: Queue private let queue: Queue
public init(basePath: String, queue: Queue, encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void) { public init(basePath: String, queue: Queue, encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void, inMemory: Bool = false) {
self.basePath = basePath self.basePath = basePath
self.inMemory = inMemory
self.encryptionParameters = encryptionParameters self.encryptionParameters = encryptionParameters
self.databasePath = basePath + "/db_sqlite" self.databasePath = basePath + "/db_sqlite"
self.queue = queue self.queue = queue
@ -247,7 +249,7 @@ public final class SqliteValueBox: ValueBox {
#endif #endif
var database: Database var database: Database
if let result = Database(path) { if let result = Database(self.inMemory ? ":memory:" : path) {
database = result database = result
} else { } else {
postboxLog("Couldn't open DB") postboxLog("Couldn't open DB")

View File

@ -22,7 +22,7 @@ private extension MessageTags {
} }
class MessageHistoryIndexTableTests: XCTestCase { class MessageHistoryIndexTableTests: XCTestCase {
var valueBox: ValueBox? var valueBox: SqliteValueBox?
var path: String? var path: String?
var postbox: Postbox? var postbox: Postbox?
@ -45,7 +45,7 @@ class MessageHistoryIndexTableTests: XCTestCase {
arc4random_buf(bytes, 16) arc4random_buf(bytes, 16)
}) })
self.valueBox = SqliteValueBox(basePath: path!, queue: Queue.mainQueue(), encryptionParameters: ValueBoxEncryptionParameters(key: ValueBoxEncryptionParameters.Key(data: randomKey)!, salt: ValueBoxEncryptionParameters.Salt(data: randomSalt)!)) self.valueBox = SqliteValueBox(basePath: path!, queue: Queue.mainQueue(), encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: true, key: ValueBoxEncryptionParameters.Key(data: randomKey)!, salt: ValueBoxEncryptionParameters.Salt(data: randomSalt)!), upgradeProgress: { _ in })
let messageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]] = [ let messageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]] = [
peerId.namespace: [ peerId.namespace: [
@ -53,7 +53,7 @@ class MessageHistoryIndexTableTests: XCTestCase {
] ]
] ]
let seedConfiguration = SeedConfiguration(globalMessageIdsPeerIdNamespaces: Set(), initializeChatListWithHole: (topLevel: nil, groups: nil), messageHoles: messageHoles, existingMessageTags: [.media], messageTagsWithSummary: [], existingGlobalMessageTags: [], peerNamespacesRequiringMessageTextIndex: [], peerSummaryCounterTags: { _ in PeerSummaryCounterTags(rawValue: 0) }, additionalChatListIndexNamespace: nil) let seedConfiguration = SeedConfiguration(globalMessageIdsPeerIdNamespaces: Set(), initializeChatListWithHole: (topLevel: nil, groups: nil), messageHoles: messageHoles, existingMessageTags: [.media], messageTagsWithSummary: [], existingGlobalMessageTags: [], peerNamespacesRequiringMessageTextIndex: [], peerSummaryCounterTags: { _ in PeerSummaryCounterTags(rawValue: 0) }, additionalChatListIndexNamespace: nil, messageNamespacesRequiringGroupStatsValidation: Set())
self.postbox = Postbox(queue: Queue.mainQueue(), basePath: path!, seedConfiguration: seedConfiguration, valueBox: self.valueBox!) self.postbox = Postbox(queue: Queue.mainQueue(), basePath: path!, seedConfiguration: seedConfiguration, valueBox: self.valueBox!)
} }

View File

@ -10,42 +10,33 @@ import SwiftSignalKit
private let peerId = PeerId(namespace: 1, id: 1) private let peerId = PeerId(namespace: 1, id: 1)
private let namespace: Int32 = 1 private let namespace: Int32 = 1
private func extract(from array: [Int32], aroundIndex: Int, limit: Int) -> [Int32] { private func extract(from array: [Int32], aroundIndex: Int, halfLimit: Int) -> [Int32] {
var lower: [Int32] = [] var lower: [Int32] = []
var higher: [Int32] = [] var higher: [Int32] = []
var i = aroundIndex var i = aroundIndex
while i >= 0 && lower.count < limit / 2 + 1 { while i >= 0 && lower.count < halfLimit {
lower.append(array[i]) lower.append(array[i])
i -= 1 i -= 1
} }
var j = aroundIndex + 1 var j = aroundIndex + 1
while j < array.count && higher.count < limit - lower.count { while j < array.count && higher.count < halfLimit {
higher.append(array[j]) higher.append(array[j])
j += 1 j += 1
} }
if !lower.isEmpty && lower.count + higher.count < limit {
var additionalLower: [Int32] = []
while i >= 0 && additionalLower.count < limit - lower.count - higher.count {
additionalLower.append(array[i])
i -= 1
}
lower.append(contentsOf: additionalLower)
}
var result: [Int32] = [] var result: [Int32] = []
result.append(contentsOf: lower.reversed()) result.append(contentsOf: lower.reversed())
result.append(contentsOf: higher) result.append(contentsOf: higher)
assert(result.count <= limit) assert(result.count <= halfLimit * 2)
return result return result
} }
class MessageHistoryViewTests: XCTestCase { class MessageHistoryViewTests: XCTestCase {
var valueBox: ValueBox? var valueBox: SqliteValueBox?
var path: String? var path: String?
var postbox: Postbox? var postbox: Postbox?
@ -68,7 +59,7 @@ class MessageHistoryViewTests: XCTestCase {
arc4random_buf(bytes, 16) arc4random_buf(bytes, 16)
}) })
self.valueBox = SqliteValueBox(basePath: path!, queue: Queue.mainQueue(), encryptionParameters: ValueBoxEncryptionParameters(key: ValueBoxEncryptionParameters.Key(data: randomKey)!, salt: ValueBoxEncryptionParameters.Salt(data: randomSalt)!)) self.valueBox = SqliteValueBox(basePath: path!, queue: Queue.mainQueue(), encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: randomKey)!, salt: ValueBoxEncryptionParameters.Salt(data: randomSalt)!), upgradeProgress: { _ in }, inMemory: true)
let messageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]] = [ let messageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]] = [
peerId.namespace: [: peerId.namespace: [:
@ -76,7 +67,7 @@ class MessageHistoryViewTests: XCTestCase {
] ]
] ]
let seedConfiguration = SeedConfiguration(globalMessageIdsPeerIdNamespaces: Set(), initializeChatListWithHole: (topLevel: nil, groups: nil), messageHoles: messageHoles, existingMessageTags: [], messageTagsWithSummary: [], existingGlobalMessageTags: [], peerNamespacesRequiringMessageTextIndex: [], peerSummaryCounterTags: { _ in PeerSummaryCounterTags(rawValue: 0) }, additionalChatListIndexNamespace: nil) let seedConfiguration = SeedConfiguration(globalMessageIdsPeerIdNamespaces: Set(), initializeChatListWithHole: (topLevel: nil, groups: nil), messageHoles: messageHoles, existingMessageTags: [], messageTagsWithSummary: [], existingGlobalMessageTags: [], peerNamespacesRequiringMessageTextIndex: [], peerSummaryCounterTags: { _ in PeerSummaryCounterTags(rawValue: 0) }, additionalChatListIndexNamespace: nil, messageNamespacesRequiringGroupStatsValidation: Set())
self.postbox = Postbox(queue: Queue.mainQueue(), basePath: path!, seedConfiguration: seedConfiguration, valueBox: self.valueBox!) self.postbox = Postbox(queue: Queue.mainQueue(), basePath: path!, seedConfiguration: seedConfiguration, valueBox: self.valueBox!)
} }
@ -101,10 +92,14 @@ class MessageHistoryViewTests: XCTestCase {
}).start() }).start()
} }
private func addMessage(_ id: Int32, _ timestamp: Int32, _ groupingKey: Int64? = nil) { private func addMessage(_ id: Int32, _ timestamp: Int32, _ groupingKey: Int64? = nil) -> UInt32 {
var stableId: UInt32?
let _ = self.postbox!.transaction({ transaction -> Void in let _ = self.postbox!.transaction({ transaction -> Void in
let _ = transaction.addMessages([StoreMessage(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, timestamp: timestamp, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [])], location: .Random) let messageId = MessageId(peerId: peerId, namespace: namespace, id: id)
let _ = transaction.addMessages([StoreMessage(id: messageId, globallyUniqueId: nil, groupingKey: nil, timestamp: timestamp, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [])], location: .Random)
stableId = transaction.getMessage(messageId)!.stableId
}).start() }).start()
return stableId!
} }
private func removeMessage(_ id: Int32) { private func removeMessage(_ id: Int32) {
@ -120,7 +115,7 @@ class MessageHistoryViewTests: XCTestCase {
} }
func testEmpty() { func testEmpty() {
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, limit: 10, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, statistics: [], halfLimit: 10, locations: .single(peerId))
switch state { switch state {
case let .loaded(loadedState): case let .loaded(loadedState):
let entries = loadedState.completeAndSample(postbox: self.postbox!).entries let entries = loadedState.completeAndSample(postbox: self.postbox!).entries
@ -134,10 +129,10 @@ class MessageHistoryViewTests: XCTestCase {
var testIds: [MessageId.Id] = [] var testIds: [MessageId.Id] = []
for i in 1 ..< 11 { for i in 1 ..< 11 {
testIds.append(Int32(i * 10)) testIds.append(Int32(i * 10))
addMessage(Int32(i * 10), Int32(i * 10)) let _ = addMessage(Int32(i * 10), Int32(i * 10))
} }
for i in 3 ... testIds.count + 10 { for i in 3 ... testIds.count + 10 {
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, limit: i, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, statistics: [], halfLimit: i, locations: .single(peerId))
switch state { switch state {
case let .loaded(loadedState): case let .loaded(loadedState):
let entries = loadedState.completeAndSample(postbox: self.postbox!).entries let entries = loadedState.completeAndSample(postbox: self.postbox!).entries
@ -154,7 +149,7 @@ class MessageHistoryViewTests: XCTestCase {
} }
} }
for i in 3 ... testIds.count + 10 { for i in 3 ... testIds.count + 10 {
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .lowerBound, tag: nil, limit: i, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .lowerBound, tag: nil, statistics: [], halfLimit: i, locations: .single(peerId))
switch state { switch state {
case let .loaded(loadedState): case let .loaded(loadedState):
let entries = loadedState.completeAndSample(postbox: self.postbox!).entries let entries = loadedState.completeAndSample(postbox: self.postbox!).entries
@ -172,11 +167,11 @@ class MessageHistoryViewTests: XCTestCase {
} }
for i in 3 ... testIds.count + 10 { for i in 3 ... testIds.count + 10 {
for j in testIds[0] - 10 ... testIds.last! + 10 { for j in testIds[0] - 10 ... testIds.last! + 10 {
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: Int32(j)), timestamp: Int32(j))), tag: nil, limit: i, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: Int32(j)), timestamp: Int32(j))), tag: nil, statistics: [], halfLimit: i, locations: .single(peerId))
let clippedTestIds: [Int32] let clippedTestIds: [Int32]
if let index = testIds.firstIndex(where: { $0 > Int32(j) }), index >= 0 { if let index = testIds.firstIndex(where: { $0 > Int32(j) }), index >= 0 {
clippedTestIds = extract(from: testIds, aroundIndex: index - 1, limit: i) clippedTestIds = extract(from: testIds, aroundIndex: index - 1, halfLimit: i)
} else { } else {
if i >= testIds.count { if i >= testIds.count {
clippedTestIds = testIds clippedTestIds = testIds
@ -233,12 +228,12 @@ class MessageHistoryViewTests: XCTestCase {
for operationSetIndex in 0 ..< operationSets.count { for operationSetIndex in 0 ..< operationSets.count {
let operations = operationSets[operationSetIndex] let operations = operationSets[operationSetIndex]
for limit in [3, 4, 5, 6, 7, 200] { for halfLimit in [3, 4, 5, 6, 7, 200] {
for position in 10 ... 110 { for position in 10 ... 110 {
removeAllMessages() removeAllMessages()
var testIds: [MessageId.Id] = [] var testIds: [MessageId.Id] = []
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: Int32(position)), timestamp: Int32(position))), tag: nil, limit: limit, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: Int32(position)), timestamp: Int32(position))), tag: nil, statistics: [], halfLimit: halfLimit, locations: .single(peerId))
switch state { switch state {
case let .loaded(loadedState): case let .loaded(loadedState):
for operationIndex in 0 ..< operations.count { for operationIndex in 0 ..< operations.count {
@ -252,20 +247,20 @@ class MessageHistoryViewTests: XCTestCase {
let attributesData = ReadBuffer(data: Data()) let attributesData = ReadBuffer(data: Data())
addMessage(Int32(insertId), Int32(insertId)) let stableId = addMessage(Int32(insertId), Int32(insertId))
let _ = loadedState.add(entry: .IntermediateMessageEntry(IntermediateMessage(stableId: UInt32(insertId), stableVersion: 0, id: MessageId(peerId: peerId, namespace: namespace, id: insertId), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: insertId, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributesData: attributesData, embeddedMediaData: attributesData, referencedMedia: []), nil, nil)) let _ = loadedState.add(entry: .IntermediateMessageEntry(IntermediateMessage(stableId: stableId, stableVersion: 0, id: MessageId(peerId: peerId, namespace: namespace, id: insertId), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: insertId, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributesData: attributesData, embeddedMediaData: attributesData, referencedMedia: []), nil, nil))
let entries = loadedState.completeAndSample(postbox: self.postbox!).entries let entries = loadedState.completeAndSample(postbox: self.postbox!).entries
let ids = entries.map({ $0.message.id.id }) let ids = entries.map({ $0.message.id.id })
let clippedTestIds: [Int32] let clippedTestIds: [Int32]
if let index = testIds.firstIndex(where: { $0 > Int32(position) }), index >= 0 { if let index = testIds.firstIndex(where: { $0 > Int32(position) }), index >= 0 {
clippedTestIds = extract(from: testIds, aroundIndex: index - 1, limit: limit) clippedTestIds = extract(from: testIds, aroundIndex: index - 1, halfLimit: halfLimit)
} else { } else {
if limit >= testIds.count { if halfLimit >= testIds.count {
clippedTestIds = testIds clippedTestIds = testIds
} else { } else {
clippedTestIds = Array(testIds.dropFirst(testIds.count - limit)) clippedTestIds = Array(testIds.dropFirst(testIds.count - halfLimit))
} }
} }
@ -310,12 +305,12 @@ class MessageHistoryViewTests: XCTestCase {
for operationSetIndex in 0 ..< operationSets.count { for operationSetIndex in 0 ..< operationSets.count {
let operations = operationSets[operationSetIndex] let operations = operationSets[operationSetIndex]
for limit in [3, 4, 5, 6, 7, 200] { for halfLimit in [3, 4, 5, 6, 7, 200] {
for position in 10 ... 110 { for position in 10 ... 110 {
removeAllMessages() removeAllMessages()
var testIds: [MessageId.Id] = [] var testIds: [MessageId.Id] = []
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: Int32(position)), timestamp: Int32(position))), tag: nil, limit: limit, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: Int32(position)), timestamp: Int32(position))), tag: nil, statistics: [], halfLimit: halfLimit, locations: .single(peerId))
switch state { switch state {
case let .loaded(loadedState): case let .loaded(loadedState):
for operationIndex in 0 ..< operations.count { for operationIndex in 0 ..< operations.count {
@ -337,12 +332,12 @@ class MessageHistoryViewTests: XCTestCase {
let messageId = MessageId(peerId: peerId, namespace: namespace, id: itemId) let messageId = MessageId(peerId: peerId, namespace: namespace, id: itemId)
if isAdd { if isAdd {
addMessage(Int32(itemId), Int32(itemId)) let stableId = addMessage(Int32(itemId), Int32(itemId))
let attributesData = ReadBuffer(data: Data()) let attributesData = ReadBuffer(data: Data())
let _ = loadedState.add(entry: .IntermediateMessageEntry(IntermediateMessage(stableId: UInt32(messageId.id), stableVersion: 0, id: messageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: itemId, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributesData: attributesData, embeddedMediaData: attributesData, referencedMedia: []), nil, nil)) let _ = loadedState.add(entry: .IntermediateMessageEntry(IntermediateMessage(stableId: stableId, stableVersion: 0, id: messageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: itemId, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributesData: attributesData, embeddedMediaData: attributesData, referencedMedia: []), nil, nil))
} else { } else {
removeMessage(itemId) removeMessage(itemId)
let _ = loadedState.remove(postbox: self.postbox!, index: MessageIndex(id: messageId, timestamp: itemId)) let _ = loadedState.remove(index: MessageIndex(id: messageId, timestamp: itemId))
} }
let entries = loadedState.completeAndSample(postbox: self.postbox!).entries let entries = loadedState.completeAndSample(postbox: self.postbox!).entries
@ -350,12 +345,12 @@ class MessageHistoryViewTests: XCTestCase {
let clippedTestIds: [Int32] let clippedTestIds: [Int32]
if let index = testIds.firstIndex(where: { $0 > Int32(position) }), index >= 0 { if let index = testIds.firstIndex(where: { $0 > Int32(position) }), index >= 0 {
clippedTestIds = extract(from: testIds, aroundIndex: index - 1, limit: limit) clippedTestIds = extract(from: testIds, aroundIndex: index - 1, halfLimit: halfLimit)
} else { } else {
if limit >= testIds.count { if halfLimit >= testIds.count {
clippedTestIds = testIds clippedTestIds = testIds
} else { } else {
clippedTestIds = Array(testIds.dropFirst(testIds.count - limit)) clippedTestIds = Array(testIds.dropFirst(testIds.count - halfLimit))
} }
} }
@ -371,7 +366,7 @@ class MessageHistoryViewTests: XCTestCase {
func testLoadInitialHole() { func testLoadInitialHole() {
addHole(1 ... 1000, space: .everywhere) addHole(1 ... 1000, space: .everywhere)
var state = HistoryViewState(postbox: self.postbox!, inputAnchor: .message(MessageId(peerId: peerId, namespace: namespace, id: Int32(100))), tag: nil, limit: 10, locations: .single(peerId)) var state = HistoryViewState(postbox: self.postbox!, inputAnchor: .message(MessageId(peerId: peerId, namespace: namespace, id: Int32(100))), tag: nil, statistics: [], halfLimit: 10, locations: .single(peerId))
switch state { switch state {
case .loaded: case .loaded:
XCTAssert(false) XCTAssert(false)
@ -405,7 +400,7 @@ class MessageHistoryViewTests: XCTestCase {
default: default:
XCTAssert(false) XCTAssert(false)
} }
state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: nil, limit: 10, locations: .single(peerId), postbox: self.postbox!, holes: holes)) state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: nil, statistics: [], halfLimit: 10, locations: .single(peerId), postbox: self.postbox!, holes: holes))
case .loadHole: case .loadHole:
XCTAssert(false) XCTAssert(false)
} }
@ -421,13 +416,13 @@ class MessageHistoryViewTests: XCTestCase {
} }
func testEdgeHoles1() { func testEdgeHoles1() {
addMessage(100, 100) let _ = addMessage(100, 100)
addMessage(200, 200) let _ = addMessage(200, 200)
addMessage(300, 300) let _ = addMessage(300, 300)
addHole(1 ... 100, space: .everywhere) addHole(1 ... 100, space: .everywhere)
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, limit: 10, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, statistics: [], halfLimit: 10, locations: .single(peerId))
guard case let .loaded(loadedState) = state else { guard case let .loaded(loadedState) = state else {
XCTAssert(false) XCTAssert(false)
return return
@ -441,13 +436,13 @@ class MessageHistoryViewTests: XCTestCase {
} }
func testEdgeHoles2() { func testEdgeHoles2() {
addMessage(100, 100) let _ = addMessage(100, 100)
addMessage(200, 200) let _ = addMessage(200, 200)
addMessage(300, 300) let _ = addMessage(300, 300)
addHole(1 ... 99, space: .everywhere) addHole(1 ... 99, space: .everywhere)
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, limit: 10, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, statistics: [], halfLimit: 10, locations: .single(peerId))
guard case let .loaded(loadedState) = state else { guard case let .loaded(loadedState) = state else {
XCTAssert(false) XCTAssert(false)
return return
@ -461,13 +456,13 @@ class MessageHistoryViewTests: XCTestCase {
} }
func testEdgeHoles3() { func testEdgeHoles3() {
addMessage(100, 100) let _ = addMessage(100, 100)
addMessage(200, 200) let _ = addMessage(200, 200)
addMessage(300, 300) let _ = addMessage(300, 300)
addHole(300 ... 400, space: .everywhere) addHole(300 ... 400, space: .everywhere)
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, limit: 10, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .upperBound, tag: nil, statistics: [], halfLimit: 10, locations: .single(peerId))
guard case let .loaded(loadedState) = state else { guard case let .loaded(loadedState) = state else {
XCTAssert(false) XCTAssert(false)
return return
@ -475,19 +470,19 @@ class MessageHistoryViewTests: XCTestCase {
let sampledState = loadedState.completeAndSample(postbox: self.postbox!) let sampledState = loadedState.completeAndSample(postbox: self.postbox!)
let ids = sampledState.entries.map({ $0.message.id.id }) let ids = sampledState.entries.map({ $0.message.id.id })
XCTAssert(ids == []) XCTAssert(ids == [])
XCTAssert(sampledState.hole == SampledHistoryViewHole(peerId: peerId, namespace: namespace, tag: nil, indices: IndexSet(integersIn: 300 ... 400), startId: 300, endId: 1)) XCTAssert(sampledState.hole == SampledHistoryViewHole(peerId: peerId, namespace: namespace, tag: nil, indices: IndexSet(integersIn: 300 ... 400), startId: 400, endId: 1))
XCTAssert(sampledState.holesToHigher == true) XCTAssert(sampledState.holesToHigher == false)
XCTAssert(sampledState.holesToLower == true) XCTAssert(sampledState.holesToLower == true)
} }
func testEdgeHoles4() { func testEdgeHoles4() {
addMessage(100, 100) let _ = addMessage(100, 100)
addMessage(200, 200) let _ = addMessage(200, 200)
addMessage(300, 300) let _ = addMessage(300, 300)
addHole(300 ... 400, space: .everywhere) addHole(300 ... 400, space: .everywhere)
let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .message(MessageId(peerId: peerId, namespace: namespace, id: 200)), tag: nil, limit: 10, locations: .single(peerId)) let state = HistoryViewState(postbox: self.postbox!, inputAnchor: .message(MessageId(peerId: peerId, namespace: namespace, id: 200)), tag: nil, statistics: [], halfLimit: 10, locations: .single(peerId))
guard case let .loaded(loadedState) = state else { guard case let .loaded(loadedState) = state else {
XCTAssert(false) XCTAssert(false)
return return