diff --git a/Postbox.xcodeproj/project.pbxproj b/Postbox.xcodeproj/project.pbxproj index 5ca0766ec5..40c2c41531 100644 --- a/Postbox.xcodeproj/project.pbxproj +++ b/Postbox.xcodeproj/project.pbxproj @@ -28,6 +28,8 @@ D01BAA561ED1D70C00295217 /* ManagedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01BAA541ED1D70C00295217 /* ManagedFile.swift */; }; D01C7EDE1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7EDD1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift */; }; D01C7EDF1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7EDD1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift */; }; + D01C7F071EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7F061EFC1ED3008305F1 /* UnorderedItemListTable.swift */; }; + D01C7F081EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7F061EFC1ED3008305F1 /* UnorderedItemListTable.swift */; }; D01F7D9B1CBEC390008765C9 /* MessageHistoryInvalidatedReadStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F7D9A1CBEC390008765C9 /* MessageHistoryInvalidatedReadStateTable.swift */; }; D01F7D9D1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F7D9C1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift */; }; D021E0D41DB4FAE100C6B04F /* ItemCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D31DB4FAE100C6B04F /* ItemCollection.swift */; }; @@ -304,6 +306,7 @@ D019B1CE1E2E770700F80DB3 /* MessageGloballyUniqueIdTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageGloballyUniqueIdTable.swift; sourceTree = ""; }; D01BAA541ED1D70C00295217 /* ManagedFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedFile.swift; sourceTree = ""; }; D01C7EDD1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryTextIndexTable.swift; sourceTree = ""; }; + D01C7F061EFC1ED3008305F1 /* UnorderedItemListTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnorderedItemListTable.swift; sourceTree = ""; }; D01F7D9A1CBEC390008765C9 /* MessageHistoryInvalidatedReadStateTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryInvalidatedReadStateTable.swift; sourceTree = ""; }; D01F7D9C1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizePeerReadStatesView.swift; sourceTree = ""; }; D021E0D31DB4FAE100C6B04F /* ItemCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollection.swift; sourceTree = ""; }; @@ -597,6 +600,7 @@ D08774FF1E3E3D9F00A97350 /* PreferencesTable.swift */, D0F82CFC1E4345D7007E499C /* OrderedItemListTable.swift */, D0F82D0E1E43A024007E499C /* OrderedItemListIndexTable.swift */, + D01C7F061EFC1ED3008305F1 /* UnorderedItemListTable.swift */, ); name = Tables; sourceTree = ""; @@ -932,6 +936,7 @@ files = ( D050F2661E4A5B5A00988324 /* MessageGloballyUniqueIdTable.swift in Sources */, D03229EF1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */, + D01C7F081EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */, D050F2671E4A5B5A00988324 /* TimestampBasedMessageAttributesTable.swift in Sources */, D050F2681E4A5B5A00988324 /* TimestampBasedMessageAttributesIndexTable.swift in Sources */, D050F2691E4A5B5A00988324 /* MultiplePeersView.swift in Sources */, @@ -1062,6 +1067,7 @@ files = ( D08775061E3E3F2100A97350 /* PreferencesView.swift in Sources */, D03229EE1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */, + D01C7F071EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */, D0F9E8631C579F0200037222 /* MediaCleanupTable.swift in Sources */, D0F3CC741DDE1EB9008148FA /* ItemCacheMetaTable.swift in Sources */, D08D451F1D5D2CA700A7428A /* RatingTable.swift in Sources */, diff --git a/Postbox/ChatListView.swift b/Postbox/ChatListView.swift index edf1b44bea..56aa4cb623 100644 --- a/Postbox/ChatListView.swift +++ b/Postbox/ChatListView.swift @@ -63,13 +63,13 @@ public func <(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool { } enum MutableChatListEntry: Equatable { - case IntermediateMessageEntry(ChatListIndex, IntermediateMessage?, CombinedPeerReadState?, PeerNotificationSettings?, PeerChatListEmbeddedInterfaceState?) + case IntermediateMessageEntry(ChatListIndex, IntermediateMessage?, CombinedPeerReadState?, PeerChatListEmbeddedInterfaceState?) case MessageEntry(ChatListIndex, Message?, CombinedPeerReadState?, PeerNotificationSettings?, PeerChatListEmbeddedInterfaceState?, RenderedPeer) case HoleEntry(ChatListHole) var index: ChatListIndex { switch self { - case let .IntermediateMessageEntry(index, _, _, _, _): + case let .IntermediateMessageEntry(index, _, _, _): return index case let .MessageEntry(index, _, _, _, _, _): return index @@ -119,6 +119,50 @@ final class MutableChatListViewReplayContext { } } +private func updateMessagePeers(_ message: Message, updatedPeers: [PeerId: Peer]) -> Message? { + var updated = false + for (peerId, currentPeer) in message.peers { + if let updatedPeer = updatedPeers[peerId], !arePeersEqual(currentPeer, updatedPeer) { + updated = true + break + } + } + if updated { + var peers = SimpleDictionary() + for (peerId, currentPeer) in message.peers { + if let updatedPeer = updatedPeers[peerId] { + peers[peerId] = updatedPeer + } else { + peers[peerId] = currentPeer + } + } + return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + } + return nil +} + +private func updatedRenderedPeer(_ renderedPeer: RenderedPeer, updatedPeers: [PeerId: Peer]) -> RenderedPeer? { + var updated = false + for (peerId, currentPeer) in renderedPeer.peers { + if let updatedPeer = updatedPeers[peerId], !arePeersEqual(currentPeer, updatedPeer) { + updated = true + break + } + } + if updated { + var peers = SimpleDictionary() + for (peerId, currentPeer) in renderedPeer.peers { + if let updatedPeer = updatedPeers[peerId] { + peers[peerId] = updatedPeer + } else { + peers[peerId] = currentPeer + } + } + return RenderedPeer(peerId: renderedPeer.peerId, peers: peers) + } + return nil +} + final class MutableChatListView { fileprivate var earlier: MutableChatListEntry? fileprivate var later: MutableChatListEntry? @@ -150,12 +194,12 @@ final class MutableChatListView { } } - func replay(_ operations: [ChatListOperation], updatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], context: MutableChatListViewReplayContext) -> Bool { + func replay(_ operations: [ChatListOperation], updatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], updatedPeers: [PeerId: Peer], context: MutableChatListViewReplayContext) -> Bool { var hasChanges = false for operation in operations { switch operation { case let .InsertEntry(index, message, combinedReadState, embeddedState): - if self.add(.IntermediateMessageEntry(index, message, combinedReadState, nil, embeddedState)) { + if self.add(.IntermediateMessageEntry(index, message, combinedReadState, embeddedState)) { hasChanges = true } case let .InsertHole(index): @@ -175,14 +219,31 @@ final class MutableChatListView { if !updatedPeerNotificationSettings.isEmpty { for i in 0 ..< self.entries.count { switch self.entries[i] { - case let .IntermediateMessageEntry(index, message, readState, _, embeddedState): - if let settings = updatedPeerNotificationSettings[index.messageIndex.id.peerId] { - self.entries[i] = .IntermediateMessageEntry(index, message, readState, settings, embeddedState) + case let .MessageEntry(index, message, readState, _, embeddedState, peer): + var notificationSettingsPeerId = peer.peerId + if let peer = peer.peers[peer.peerId], let associatedPeerId = peer.associatedPeerId { + notificationSettingsPeerId = associatedPeerId + } + if let settings = updatedPeerNotificationSettings[notificationSettingsPeerId] { + self.entries[i] = .MessageEntry(index, message, readState, settings, embeddedState, peer) hasChanges = true } - case let .MessageEntry(index, message, readState, _, embeddedState, peer): - if let settings = updatedPeerNotificationSettings[index.messageIndex.id.peerId] { - self.entries[i] = .MessageEntry(index, message, readState, settings, embeddedState, peer) + default: + continue + } + } + } + if !updatedPeers.isEmpty { + for i in 0 ..< self.entries.count { + switch self.entries[i] { + case let .MessageEntry(index, message, readState, settings, embeddedState, peer): + var updatedMessage: Message? + if let message = message { + updatedMessage = updateMessagePeers(message, updatedPeers: updatedPeers) + } + let updatedPeer = updatedRenderedPeer(peer, updatedPeers: updatedPeers) + if updatedMessage != nil || updatedPeer != nil { + self.entries[i] = .MessageEntry(index, updatedMessage ?? message, readState, settings, embeddedState, updatedPeer ?? peer) hasChanges = true } default: @@ -403,38 +464,10 @@ final class MutableChatListView { return nil } - func updatePeers(_ peers: [PeerId: Peer]) -> Bool { - let hasChanges = false - /*for i in 0 ..< self.entries.count { - switch self.entries[i] { - case let .MessageEntry(message): - var updatedAuthor: Peer? - if let author = message.author, let peer = peers[author.id] { - updatedAuthor = peer - } - - for peer in message.peers { - - } - - break - default: - break - } - }*/ - return hasChanges - } - func render(_ renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?) { for i in 0 ..< self.entries.count { switch self.entries[i] { - case let .IntermediateMessageEntry(index, message, combinedReadState, notificationSettings, embeddedState): - let updatedNotificationSettings: PeerNotificationSettings? - if let notificationSettings = notificationSettings { - updatedNotificationSettings = notificationSettings - } else { - updatedNotificationSettings = getPeerNotificationSettings(index.messageIndex.id.peerId) - } + case let .IntermediateMessageEntry(index, message, combinedReadState, embeddedState): let renderedMessage: Message? if let message = message { renderedMessage = renderMessage(message) @@ -442,15 +475,20 @@ final class MutableChatListView { renderedMessage = nil } var peers = SimpleDictionary() + var notificationSettings: PeerNotificationSettings? if let peer = getPeer(index.messageIndex.id.peerId) { peers[peer.id] = peer if let associatedPeerId = peer.associatedPeerId { if let associatedPeer = getPeer(associatedPeerId) { peers[associatedPeer.id] = associatedPeer } + notificationSettings = getPeerNotificationSettings(associatedPeerId) + } else { + notificationSettings = getPeerNotificationSettings(index.messageIndex.id.peerId) } } - self.entries[i] = .MessageEntry(index, renderedMessage, combinedReadState, updatedNotificationSettings, embeddedState, RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers)) + + self.entries[i] = .MessageEntry(index, renderedMessage, combinedReadState, notificationSettings, embeddedState, RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers)) default: break } diff --git a/Postbox/Peer.swift b/Postbox/Peer.swift index 3e8f22f758..7175ab49a8 100644 --- a/Postbox/Peer.swift +++ b/Postbox/Peer.swift @@ -108,3 +108,19 @@ public func arePeersEqual(_ lhs: Peer?, _ rhs: Peer?) -> Bool { return (lhs != nil) == (rhs != nil) } } + +public func arePeerDictionariesEqual(_ lhs: SimpleDictionary, _ rhs: SimpleDictionary) -> Bool { + if lhs.count != rhs.count { + return false + } + for (id, lhsPeer) in lhs { + if let rhsPeer = rhs[id] { + if !lhsPeer.isEqual(rhsPeer) { + return false + } + } else { + return false + } + } + return true +} diff --git a/Postbox/Postbox.swift b/Postbox/Postbox.swift index 906c79903f..d3ce4dca90 100644 --- a/Postbox/Postbox.swift +++ b/Postbox/Postbox.swift @@ -589,6 +589,24 @@ public final class Modifier { return [] } } + + public func unorderedItemListDifference(tag: UnorderedItemListEntryTag, updatedEntryInfos: [ValueBoxKey: UnorderedItemListEntryInfo]) -> (metaInfo: UnorderedItemListTagMetaInfo?, added: [ValueBoxKey], removed: [UnorderedItemListEntry], updated: [UnorderedItemListEntry]) { + assert(!self.disposed) + if let postbox = self.postbox { + return postbox.unorderedItemListTable.difference(tag: tag, updatedEntryInfos: updatedEntryInfos) + } else { + return (nil, [], [], []) + } + } + + public func unorderedItemListApplyDifference(tag: UnorderedItemListEntryTag, previousInfo: UnorderedItemListTagMetaInfo?, updatedInfo: UnorderedItemListTagMetaInfo, setItems: [UnorderedItemListEntry], removeItemIds: [ValueBoxKey]) -> Bool { + assert(!self.disposed) + if let postbox = self.postbox { + return postbox.unorderedItemListTable.applyDifference(tag: tag, previousInfo: previousInfo, updatedInfo: updatedInfo, setItems: setItems, removeItemIds: removeItemIds) + } else { + return false + } + } } fileprivate class PipeNotifier: NSObject { @@ -778,6 +796,7 @@ public final class Postbox { let orderedItemListTable: OrderedItemListTable let orderedItemListIndexTable: OrderedItemListIndexTable let textIndexTable: MessageHistoryTextIndexTable + let unorderedItemListTable: UnorderedItemListTable //temporary let peerRatingTable: RatingTable @@ -851,6 +870,7 @@ public final class Postbox { self.preferencesTable = PreferencesTable(valueBox: self.valueBox, table: PreferencesTable.tableSpec(35)) self.orderedItemListIndexTable = OrderedItemListIndexTable(valueBox: self.valueBox, table: OrderedItemListIndexTable.tableSpec(37)) self.orderedItemListTable = OrderedItemListTable(valueBox: self.valueBox, table: OrderedItemListTable.tableSpec(38), indexTable: self.orderedItemListIndexTable) + self.unorderedItemListTable = UnorderedItemListTable(valueBox: self.valueBox, table: UnorderedItemListTable.tableSpec(42)) var tables: [Table] = [] tables.append(self.metadataTable) @@ -893,6 +913,7 @@ public final class Postbox { tables.append(self.preferencesTable) tables.append(self.orderedItemListTable) tables.append(self.orderedItemListIndexTable) + tables.append(self.unorderedItemListTable) self.tables = tables @@ -1221,7 +1242,7 @@ public final class Postbox { for entry in intermediateEntries { switch entry { case let .Message(index, message, embeddedState): - entries.append(.IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), self.peerNotificationSettingsTable.get(index.messageIndex.id.peerId), embeddedState)) + entries.append(.IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), embeddedState)) case let .Hole(hole): entries.append(.HoleEntry(hole)) } @@ -1230,7 +1251,7 @@ public final class Postbox { if let intermediateLower = intermediateLower { switch intermediateLower { case let .Message(index, message, embeddedState): - lower = .IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), self.peerNotificationSettingsTable.get(index.messageIndex.id.peerId), embeddedState) + lower = .IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), embeddedState) case let .Hole(hole): lower = .HoleEntry(hole) } @@ -1239,7 +1260,7 @@ public final class Postbox { if let intermediateUpper = intermediateUpper { switch intermediateUpper { case let .Message(index, message, embeddedState): - upper = .IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), self.peerNotificationSettingsTable.get(index.messageIndex.id.peerId), embeddedState) + upper = .IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), embeddedState) case let .Hole(hole): upper = .HoleEntry(hole) } @@ -1254,7 +1275,7 @@ public final class Postbox { for entry in intermediateEntries { switch entry { case let .Message(index, message, embeddedState): - entries.append(.IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), self.peerNotificationSettingsTable.get(index.messageIndex.id.peerId), embeddedState)) + entries.append(.IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), embeddedState)) case let .Hole(hole): entries.append(.HoleEntry(hole)) } @@ -1268,7 +1289,7 @@ public final class Postbox { for entry in intermediateEntries { switch entry { case let .Message(index, message, embeddedState): - entries.append(.IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), self.peerNotificationSettingsTable.get(index.messageIndex.id.peerId), embeddedState)) + entries.append(.IntermediateMessageEntry(index, message, self.readStateTable.getCombinedState(index.messageIndex.id.peerId), embeddedState)) case let .Hole(index): entries.append(.HoleEntry(index)) } diff --git a/Postbox/SimpleDictionary.swift b/Postbox/SimpleDictionary.swift index 704f5b7ee9..18faca5d34 100644 --- a/Postbox/SimpleDictionary.swift +++ b/Postbox/SimpleDictionary.swift @@ -95,4 +95,3 @@ public struct SimpleDictionary: Sequence { return true } } - diff --git a/Postbox/UnorderedItemListTable.swift b/Postbox/UnorderedItemListTable.swift new file mode 100644 index 0000000000..6a35e9d3e2 --- /dev/null +++ b/Postbox/UnorderedItemListTable.swift @@ -0,0 +1,219 @@ +import Foundation + +public struct UnorderedItemListEntryInfo { + public let hashValue: Int64 + + public init(hashValue: Int64) { + self.hashValue = hashValue + } +} + +public struct UnorderedItemListEntry { + public let id: ValueBoxKey + public let info: UnorderedItemListEntryInfo + public let contents: Coding + + public init(id: ValueBoxKey, info: UnorderedItemListEntryInfo, contents: Coding) { + self.id = id + self.info = info + self.contents = contents + } +} + +public struct UnorderedItemListEntryTag { + public let value: ValueBoxKey + + public init(value: ValueBoxKey) { + self.value = value + } +} + +public protocol UnorderedItemListTagMetaInfo: Coding { + func isEqual(to: UnorderedItemListTagMetaInfo) -> Bool +} + +private enum UnorderedItemListTableKeyspace: UInt8 { + case metaInfo = 0 + case entries = 1 +} + +private func extractEntryKey(tagLength: Int, key: ValueBoxKey) -> ValueBoxKey { + let result = ValueBoxKey(length: key.length - tagLength - 1) + memcpy(result.memory, key.memory.advanced(by: 1 + tagLength), result.length) + return result +} + +private func extractEntryInfo(_ value: ReadBuffer) -> UnorderedItemListEntryInfo { + var hashValue: Int64 = 0 + value.read(&hashValue, offset: 0, length: 8) + return UnorderedItemListEntryInfo(hashValue: hashValue) +} + +final class UnorderedItemListTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary) + } + + private func metaInfoKey(tag: UnorderedItemListEntryTag) -> ValueBoxKey { + let tagValue = tag.value + let key = ValueBoxKey(length: 1 + tagValue.length) + key.setUInt8(0, value: UnorderedItemListTableKeyspace.metaInfo.rawValue) + memcpy(key.memory.advanced(by: 1), tagValue.memory, tagValue.length) + return key + } + + private func entryKey(tag: UnorderedItemListEntryTag, id: ValueBoxKey) -> ValueBoxKey { + let tagValue = tag.value + let key = ValueBoxKey(length: 1 + tagValue.length + id.length) + key.setUInt8(0, value: UnorderedItemListTableKeyspace.entries.rawValue) + memcpy(key.memory.advanced(by: 1), tagValue.memory, tagValue.length) + memcpy(key.memory.advanced(by: 1 + tagValue.length), id.memory, id.length) + return key + } + + private func entryLowerBoundKey(tag: UnorderedItemListEntryTag) -> ValueBoxKey { + let tagValue = tag.value + let key = ValueBoxKey(length: 1 + tagValue.length) + key.setUInt8(0, value: UnorderedItemListTableKeyspace.entries.rawValue) + memcpy(key.memory.advanced(by: 1), tagValue.memory, tagValue.length) + return key + } + + private func entryUpperBoundKey(tag: UnorderedItemListEntryTag) -> ValueBoxKey { + let tagValue = tag.value.successor + let key = ValueBoxKey(length: 1 + tagValue.length) + key.setUInt8(0, value: UnorderedItemListTableKeyspace.entries.rawValue) + memcpy(key.memory.advanced(by: 1), tagValue.memory, tagValue.length) + return key + } + + private func getMetaInfo(tag: UnorderedItemListEntryTag) -> UnorderedItemListTagMetaInfo? { + if let value = self.valueBox.get(self.table, key: self.metaInfoKey(tag: tag)), let info = Decoder(buffer: value).decodeRootObject() as? UnorderedItemListTagMetaInfo { + return info + } else { + return nil + } + } + + private func setMetaInfo(tag: UnorderedItemListEntryTag, info: UnorderedItemListTagMetaInfo) { + let encoder = Encoder() + encoder.encodeRootObject(info) + self.valueBox.set(self.table, key: self.metaInfoKey(tag: tag), value: encoder.readBufferNoCopy()) + } + + private func getEntryInfos(tag: UnorderedItemListEntryTag) -> [ValueBoxKey: UnorderedItemListEntryInfo] { + var result: [ValueBoxKey: UnorderedItemListEntryInfo] = [:] + let tagLength = tag.value.length + self.valueBox.range(self.table, start: self.entryLowerBoundKey(tag: tag), end: self.entryUpperBoundKey(tag: tag), values: { key, value in + result[extractEntryKey(tagLength: tagLength, key: key)] = extractEntryInfo(value) + return true + }, limit: 0) + return result + } + + private func getEntry(tag: UnorderedItemListEntryTag, id: ValueBoxKey) -> UnorderedItemListEntry? { + if let value = self.valueBox.get(self.table, key: self.entryKey(tag: tag, id: id)) { + var hashValue: Int64 = 0 + value.read(&hashValue, offset: 0, length: 8) + let tempBuffer = MemoryBuffer(memory: value.memory.advanced(by: 8), capacity: value.length - 8, length: value.length - 8, freeWhenDone: false) + let contents = withExtendedLifetime(tempBuffer, { + return Decoder(buffer: tempBuffer).decodeRootObject() + }) + if let contents = contents { + let entry = UnorderedItemListEntry(id: id, info: UnorderedItemListEntryInfo(hashValue: hashValue), contents: contents) + return entry + } else { + assertionFailure() + return nil + } + } else { + return nil + } + } + + private func setEntry(tag: UnorderedItemListEntryTag, entry: UnorderedItemListEntry, sharedBuffer: WriteBuffer, sharedEncoder: Encoder) { + sharedBuffer.reset() + sharedEncoder.reset() + + var hashValue: Int64 = entry.info.hashValue + sharedBuffer.write(&hashValue, offset: 0, length: 8) + + sharedEncoder.encodeRootObject(entry.contents) + let tempBuffer = sharedEncoder.readBufferNoCopy() + withExtendedLifetime(tempBuffer, { + sharedBuffer.write(tempBuffer.memory, offset: 0, length: tempBuffer.length) + }) + + self.valueBox.set(self.table, key: self.entryKey(tag: tag, id: entry.id), value: sharedBuffer) + } + + func difference(tag: UnorderedItemListEntryTag, updatedEntryInfos: [ValueBoxKey: UnorderedItemListEntryInfo]) -> (metaInfo: UnorderedItemListTagMetaInfo?, added: [ValueBoxKey], removed: [UnorderedItemListEntry], updated: [UnorderedItemListEntry]) { + let currentEntryInfos = self.getEntryInfos(tag: tag) + var currentInfoIds = Set() + for key in currentEntryInfos.keys { + currentInfoIds.insert(key) + } + + var updatedInfoIds = Set() + for key in updatedEntryInfos.keys { + updatedInfoIds.insert(key) + } + + let addedKeys = updatedInfoIds.subtracting(currentInfoIds) + let added: [ValueBoxKey] = Array(addedKeys) + + let removedKeys = currentInfoIds.subtracting(updatedInfoIds) + var removed: [UnorderedItemListEntry] = [] + for key in removedKeys { + if let entry = self.getEntry(tag: tag, id: key) { + removed.append(entry) + } else { + assertionFailure() + } + } + + var updated: [UnorderedItemListEntry] = [] + for (key, info) in updatedEntryInfos { + if !addedKeys.contains(key) { + if let currentInfo = currentEntryInfos[key] { + if info.hashValue != currentInfo.hashValue { + if let entry = self.getEntry(tag: tag, id: key) { + updated.append(entry) + } else { + assertionFailure() + } + } + } else { + assertionFailure() + } + } + } + + return (self.getMetaInfo(tag: tag), added, removed, updated) + } + + func applyDifference(tag: UnorderedItemListEntryTag, previousInfo: UnorderedItemListTagMetaInfo?, updatedInfo: UnorderedItemListTagMetaInfo, setItems: [UnorderedItemListEntry], removeItemIds: [ValueBoxKey]) -> Bool { + let currentInfo = self.getMetaInfo(tag: tag) + if let currentInfo = currentInfo, let previousInfo = previousInfo { + if !currentInfo.isEqual(to: previousInfo) { + return false + } + } else if (currentInfo != nil) != (previousInfo != nil) { + return false + } + + self.setMetaInfo(tag: tag, info: updatedInfo) + + let sharedBuffer = WriteBuffer() + let sharedEncoder = Encoder() + for entry in setItems { + self.setEntry(tag: tag, entry: entry, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder) + } + + for id in removeItemIds { + self.valueBox.remove(self.table, key: self.entryKey(tag: tag, id: id)) + } + + return true + } +} diff --git a/Postbox/ValueBoxKey.swift b/Postbox/ValueBoxKey.swift index 2e0553ae90..cd3ccea2f6 100644 --- a/Postbox/ValueBoxKey.swift +++ b/Postbox/ValueBoxKey.swift @@ -190,6 +190,14 @@ public struct ValueBoxKey: Hashable, CustomStringConvertible, Comparable { return string as String } + public var stringValue: String { + if let string = String(data: Data(bytes: self.memory, count: self.length), encoding: .utf8) { + return string + } else { + return "" + } + } + public var hashValue: Int { var hash = 37 let bytes = self.memory.assumingMemoryBound(to: Int8.self) diff --git a/Postbox/ViewTracker.swift b/Postbox/ViewTracker.swift index 7edb0680f9..b4474e5106 100644 --- a/Postbox/ViewTracker.swift +++ b/Postbox/ViewTracker.swift @@ -400,10 +400,10 @@ final class ViewTracker { } } - if !transaction.chatListOperations.isEmpty || !transaction.currentUpdatedPeerNotificationSettings.isEmpty { + if !transaction.chatListOperations.isEmpty || !transaction.currentUpdatedPeerNotificationSettings.isEmpty || !transaction.currentUpdatedPeers.isEmpty { for (mutableView, pipe) in self.chatListViews.copyItems() { let context = MutableChatListViewReplayContext() - if mutableView.replay(transaction.chatListOperations, updatedPeerNotificationSettings: transaction.currentUpdatedPeerNotificationSettings, context: context) { + if mutableView.replay(transaction.chatListOperations, updatedPeerNotificationSettings: transaction.currentUpdatedPeerNotificationSettings, updatedPeers: transaction.currentUpdatedPeers, context: context) { mutableView.complete(context: context, fetchEarlier: self.fetchEarlierChatEntries, fetchLater: self.fetchLaterChatEntries) mutableView.render(self.renderMessage, getPeer: { id in return self.getPeer(id)