diff --git a/Postbox.xcodeproj/project.pbxproj b/Postbox.xcodeproj/project.pbxproj index 5b633cafd8..d918e00b64 100644 --- a/Postbox.xcodeproj/project.pbxproj +++ b/Postbox.xcodeproj/project.pbxproj @@ -174,6 +174,10 @@ D0BEAF701E54BC1E00BD963D /* AccountRecordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF6F1E54BC1E00BD963D /* AccountRecordsView.swift */; }; D0BEAF711E54BC1E00BD963D /* AccountRecordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF6F1E54BC1E00BD963D /* AccountRecordsView.swift */; }; D0C07F6A1B67DB4800966E43 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C07F691B67DB4800966E43 /* SwiftSignalKit.framework */; }; + D0C0B5AB1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5AA1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift */; }; + D0C0B5AC1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5AA1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift */; }; + D0C0B5AE1EE1B3F4000F4D2C /* PostboxUpgrade_13to14.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5AD1EE1B3F4000F4D2C /* PostboxUpgrade_13to14.swift */; }; + D0C0B5AF1EE1B3F4000F4D2C /* PostboxUpgrade_13to14.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5AD1EE1B3F4000F4D2C /* PostboxUpgrade_13to14.swift */; }; D0C674C81CBB11C600183765 /* MessageHistoryReadStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C674C71CBB11C600183765 /* MessageHistoryReadStateTable.swift */; }; D0C674CC1CBB14A700183765 /* PeerReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C674CB1CBB14A700183765 /* PeerReadState.swift */; }; D0C735281C864DF300BB3149 /* PeerChatStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C735271C864DF300BB3149 /* PeerChatStateTable.swift */; }; @@ -376,6 +380,8 @@ D0BEAF6C1E54B77900BD963D /* AccountManagerRecordTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManagerRecordTable.swift; sourceTree = ""; }; D0BEAF6F1E54BC1E00BD963D /* AccountRecordsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountRecordsView.swift; sourceTree = ""; }; D0C07F691B67DB4800966E43 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0C0B5AA1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseAssociatedPeerTable.swift; sourceTree = ""; }; + D0C0B5AD1EE1B3F4000F4D2C /* PostboxUpgrade_13to14.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_13to14.swift; sourceTree = ""; }; D0C674C71CBB11C600183765 /* MessageHistoryReadStateTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryReadStateTable.swift; sourceTree = ""; }; D0C674CB1CBB14A700183765 /* PeerReadState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerReadState.swift; sourceTree = ""; }; D0C735271C864DF300BB3149 /* PeerChatStateTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatStateTable.swift; sourceTree = ""; }; @@ -536,6 +542,7 @@ children = ( D0F02CDE1E99223D0065DEE2 /* Upgrades.swift */, D0F02CE11E9922F50065DEE2 /* PostboxUpgrade_12to13.swift */, + D0C0B5AD1EE1B3F4000F4D2C /* PostboxUpgrade_13to14.swift */, ); name = Upgrade; sourceTree = ""; @@ -577,6 +584,7 @@ D0F3CC711DDE1CDC008148FA /* ItemCacheTable.swift */, D07827C21E008F7300071108 /* ReverseIndexReferenceTable.swift */, D07827C41E00B23F00071108 /* PeerNameIndexTable.swift */, + D0C0B5AA1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift */, D0AD23261E194D1C00A7089A /* PeerOperationLogTable.swift */, D010B6191E1E463900C3E282 /* PeerMergedOperationLogIndexTable.swift */, D0AD23281E196B6400A7089A /* PeerOperationLogMetadataTable.swift */, @@ -978,6 +986,7 @@ D0BEAF641E54B2FA00BD963D /* AccountManager.swift in Sources */, D073CE7F1DCBF3B4007511FD /* ItemCollection.swift in Sources */, D0F7B1D91E045C6A007EB8A5 /* OrderStatisticTable.swift in Sources */, + D0C0B5AF1EE1B3F4000F4D2C /* PostboxUpgrade_13to14.swift in Sources */, D073CEA01DCBF3C1007511FD /* ItemCollectionsView.swift in Sources */, D0BE383A1E7C1FD4000079AF /* ItemCollectionInfoView.swift in Sources */, D0B418491D7DFE20004562A4 /* ChatListView.swift in Sources */, @@ -1027,6 +1036,7 @@ D0B418311D7DFE16004562A4 /* ValueBox.swift in Sources */, D08775041E3E3E7400A97350 /* PreferencesEntry.swift in Sources */, D0B4184E1D7DFE20004562A4 /* MessageHistoryHolesView.swift in Sources */, + D0C0B5AC1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift in Sources */, D0B844051DAB91B5005F29E1 /* MediaBox.swift in Sources */, D0B418321D7DFE16004562A4 /* SqliteValueBox.swift in Sources */, D073CE741DCBF3B4007511FD /* Peer.swift in Sources */, @@ -1105,6 +1115,7 @@ D0BEAF631E54B2FA00BD963D /* AccountManager.swift in Sources */, D0AD23271E194D1C00A7089A /* PeerOperationLogTable.swift in Sources */, D021E0D41DB4FAE100C6B04F /* ItemCollection.swift in Sources */, + D0C0B5AE1EE1B3F4000F4D2C /* PostboxUpgrade_13to14.swift in Sources */, D0D510F91D63BCC200A97B8A /* IntermediateMessage.swift in Sources */, D0BE38391E7C1FD4000079AF /* ItemCollectionInfoView.swift in Sources */, D0F9E86B1C59719800037222 /* ChatListView.swift in Sources */, @@ -1154,6 +1165,7 @@ D03120FC1DA55427006A2A60 /* PeerNotificationSettings.swift in Sources */, D0F7AB321DCFAB18009AD9A1 /* PeerChatTopIndexableMessageIds.swift in Sources */, D03BCCF81C73561C0097A291 /* Table.swift in Sources */, + D0C0B5AB1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift in Sources */, D021E0DC1DB5237C00C6B04F /* ItemCollectionsView.swift in Sources */, D0C735281C864DF300BB3149 /* PeerChatStateTable.swift in Sources */, D0AAD1B31E3266B100D5B9DE /* TimestampBasedMessageAttributesTable.swift in Sources */, diff --git a/Postbox.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/PostboxMac.xcscheme b/Postbox.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/PostboxMac.xcscheme index f4a9378c19..c2494365aa 100644 --- a/Postbox.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/PostboxMac.xcscheme +++ b/Postbox.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/PostboxMac.xcscheme @@ -1,6 +1,6 @@ () if let peer = getPeer(index.messageIndex.id.peerId) { peers[peer.id] = peer - if let associatedPeerIds = peer.associatedPeerIds { - for associatedPeerId in associatedPeerIds { - if let associatedPeer = getPeer(associatedPeerId) { - peers[associatedPeer.id] = associatedPeer - } + if let associatedPeerId = peer.associatedPeerId { + if let associatedPeer = getPeer(associatedPeerId) { + peers[associatedPeer.id] = associatedPeer } } } diff --git a/Postbox/MessageHistoryTable.swift b/Postbox/MessageHistoryTable.swift index d502a97efe..549d49fca3 100644 --- a/Postbox/MessageHistoryTable.swift +++ b/Postbox/MessageHistoryTable.swift @@ -1625,11 +1625,9 @@ final class MessageHistoryTable: Table { if let chatPeer = peerTable.get(message.id.peerId) { peers[chatPeer.id] = chatPeer - if let associatedPeerIds = chatPeer.associatedPeerIds { - for peerId in associatedPeerIds { - if let peer = peerTable.get(peerId) { - peers[peer.id] = peer - } + if let associatedPeerId = chatPeer.associatedPeerId { + if let peer = peerTable.get(associatedPeerId) { + peers[peer.id] = peer } } } diff --git a/Postbox/Peer.swift b/Postbox/Peer.swift index b2100dfd5d..3e8f22f758 100644 --- a/Postbox/Peer.swift +++ b/Postbox/Peer.swift @@ -95,7 +95,7 @@ public func <(lhs: PeerId, rhs: PeerId) -> Bool { public protocol Peer: class, Coding { var id: PeerId { get } var indexName: PeerIndexNameRepresentation { get } - var associatedPeerIds: [PeerId]? { get } + var associatedPeerId: PeerId? { get } var notificationSettingsPeerId: PeerId? { get } func isEqual(_ other: Peer) -> Bool diff --git a/Postbox/PeerNameIndexTable.swift b/Postbox/PeerNameIndexTable.swift index 865f5ceac7..8baaceebfa 100644 --- a/Postbox/PeerNameIndexTable.swift +++ b/Postbox/PeerNameIndexTable.swift @@ -257,7 +257,7 @@ final class PeerNameIndexTable: Table { } } - func matchingPeerIds(tokens: (regular: [ValueBoxKey], transliterated: [ValueBoxKey]?), categories: PeerNameIndexCategories, chatListIndexTable: ChatListIndexTable, contactTable: ContactTable) -> (chats: [PeerId], contacts: [PeerId]) { + func matchingPeerIds(tokens: (regular: [ValueBoxKey], transliterated: [ValueBoxKey]?), categories: PeerNameIndexCategories, chatListIndexTable: ChatListIndexTable, contactTable: ContactTable, reverseAssociatedPeerTable: ReverseAssociatedPeerTable) -> (chats: [PeerId], contacts: [PeerId]) { if categories.isEmpty { return ([], []) } else { diff --git a/Postbox/PeerTable.swift b/Postbox/PeerTable.swift index 5439cd664a..fb628d307f 100644 --- a/Postbox/PeerTable.swift +++ b/Postbox/PeerTable.swift @@ -5,11 +5,19 @@ final class PeerTable: Table { return ValueBoxTable(id: id, keyType: .int64) } + private let reverseAssociatedTable: ReverseAssociatedPeerTable + private let sharedEncoder = Encoder() private let sharedKey = ValueBoxKey(length: 8) private var cachedPeers: [PeerId: Peer] = [:] - private var updatedPeerIds = Set() + private var updatedInitialPeers: [PeerId: Peer?] = [:] + + init(valueBox: ValueBox, table: ValueBoxTable, reverseAssociatedTable: ReverseAssociatedPeerTable) { + self.reverseAssociatedTable = reverseAssociatedTable + + super.init(valueBox: valueBox, table: table) + } private func key(_ id: PeerId) -> ValueBoxKey { self.sharedKey.setInt64(0, value: id.toInt64()) @@ -17,8 +25,11 @@ final class PeerTable: Table { } func set(_ peer: Peer) { + let previous = self.get(peer.id) self.cachedPeers[peer.id] = peer - self.updatedPeerIds.insert(peer.id) + if self.updatedInitialPeers[peer.id] == nil { + self.updatedInitialPeers[peer.id] = previous + } } func get(_ id: PeerId) -> Peer? { @@ -36,19 +47,31 @@ final class PeerTable: Table { override func clearMemoryCache() { self.cachedPeers.removeAll() - self.updatedPeerIds.removeAll() + assert(self.updatedInitialPeers.isEmpty) } override func beforeCommit() { - for peerId in self.updatedPeerIds { + for (peerId, previousPeer) in self.updatedInitialPeers { if let peer = self.cachedPeers[peerId] { self.sharedEncoder.reset() self.sharedEncoder.encodeRootObject(peer) self.valueBox.set(self.table, key: self.key(peerId), value: self.sharedEncoder.readBufferNoCopy()) + + let previousAssociation = previousPeer?.associatedPeerId + if previousAssociation != peer.associatedPeerId { + if let previousAssociation = previousAssociation { + self.reverseAssociatedTable.removeReverseAssociation(target: previousAssociation, from: peerId) + } + if let associatedPeerId = peer.associatedPeerId { + self.reverseAssociatedTable.addReverseAssociation(target: associatedPeerId, from: peerId) + } + } + } else { + assertionFailure() } } - self.updatedPeerIds.removeAll() + self.updatedInitialPeers.removeAll() } } diff --git a/Postbox/PeerView.swift b/Postbox/PeerView.swift index 494f39270c..25d5a185c1 100644 --- a/Postbox/PeerView.swift +++ b/Postbox/PeerView.swift @@ -1,6 +1,6 @@ import Foundation -final class MutablePeerView { +final class MutablePeerView: MutablePostboxView { let peerId: PeerId var notificationSettings: PeerNotificationSettings? var cachedData: CachedPeerData? @@ -8,17 +8,27 @@ final class MutablePeerView { var peerPresences: [PeerId: PeerPresence] = [:] var peerIsContact: Bool - init(peerId: PeerId, notificationSettings: PeerNotificationSettings?, cachedData: CachedPeerData?, peerIsContact: Bool, getPeer: (PeerId) -> Peer?, getPeerPresence: (PeerId) -> PeerPresence?) { + init(postbox: Postbox, peerId: PeerId) { + let notificationSettings = postbox.peerNotificationSettingsTable.get(peerId) + let cachedData = postbox.cachedPeerDataTable.get(peerId) + let peerIsContact = postbox.contactsTable.isContact(peerId: peerId) + + let getPeer: (PeerId) -> Peer? = { peerId in + return postbox.peerTable.get(peerId) + } + + let getPeerPresence: (PeerId) -> PeerPresence? = { peerId in + return postbox.peerPresenceTable.get(peerId) + } + self.peerId = peerId self.notificationSettings = notificationSettings self.cachedData = cachedData self.peerIsContact = peerIsContact var peerIds = Set() peerIds.insert(peerId) - if let peer = getPeer(peerId), let associatedPeerIds = peer.associatedPeerIds { - for peerId in associatedPeerIds { - peerIds.insert(peerId) - } + if let peer = getPeer(peerId), let associatedPeerId = peer.associatedPeerId { + peerIds.insert(associatedPeerId) } if let cachedData = cachedData { peerIds.formUnion(cachedData.peerIds) @@ -31,19 +41,35 @@ final class MutablePeerView { self.peerPresences[id] = presence } } - if let peer = self.peers[peerId], let associatedPeerIds = peer.associatedPeerIds { - for id in associatedPeerIds { - if let peer = getPeer(id) { - self.peers[id] = peer - } - if let presence = getPeerPresence(id) { - self.peerPresences[id] = presence - } + if let peer = self.peers[peerId], let associatedPeerId = peer.associatedPeerId { + if let peer = getPeer(associatedPeerId) { + self.peers[associatedPeerId] = peer + } + if let presence = getPeerPresence(associatedPeerId) { + self.peerPresences[associatedPeerId] = presence } } } - func replay(updatedPeers: [PeerId: Peer], updatedNotificationSettings: [PeerId: PeerNotificationSettings], updatedCachedPeerData: [PeerId: CachedPeerData], updatedPeerPresences: [PeerId: PeerPresence], replaceContactPeerIds: Set?, getPeer: (PeerId) -> Peer?, getPeerPresence: (PeerId) -> PeerPresence?) -> Bool { + func reset(postbox: Postbox) -> Bool { + return false + } + + func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool { + let updatedPeers = transaction.currentUpdatedPeers + let updatedNotificationSettings = transaction.currentUpdatedPeerNotificationSettings + let updatedCachedPeerData = transaction.currentUpdatedCachedPeerData + let updatedPeerPresences = transaction.currentUpdatedPeerPresences + let replaceContactPeerIds = transaction.replaceContactPeerIds + + let getPeer: (PeerId) -> Peer? = { peerId in + return postbox.peerTable.get(peerId) + } + + let getPeerPresence: (PeerId) -> PeerPresence? = { peerId in + return postbox.peerPresenceTable.get(peerId) + } + var updated = false if let cachedData = updatedCachedPeerData[self.peerId], self.cachedData == nil || !self.cachedData!.isEqual(to: cachedData) { @@ -52,10 +78,8 @@ final class MutablePeerView { var peerIds = Set() peerIds.insert(self.peerId) - if let peer = getPeer(self.peerId), let associatedPeerIds = peer.associatedPeerIds { - for peerId in associatedPeerIds { - peerIds.insert(peerId) - } + if let peer = getPeer(self.peerId), let associatedPeerId = peer.associatedPeerId { + peerIds.insert(associatedPeerId) } peerIds.formUnion(cachedData.peerIds) @@ -97,10 +121,8 @@ final class MutablePeerView { } else { var peerIds = Set() peerIds.insert(self.peerId) - if let peer = getPeer(self.peerId), let associatedPeerIds = peer.associatedPeerIds { - for peerId in associatedPeerIds { - peerIds.insert(peerId) - } + if let peer = getPeer(self.peerId), let associatedPeerId = peer.associatedPeerId { + peerIds.insert(associatedPeerId) } if let cachedData = self.cachedData { peerIds.formUnion(cachedData.peerIds) @@ -139,9 +161,13 @@ final class MutablePeerView { return updated } + + func immutableView() -> PostboxView { + return PeerView(self) + } } -public final class PeerView { +public final class PeerView: PostboxView { public let peerId: PeerId public let cachedData: CachedPeerData? public let notificationSettings: PeerNotificationSettings? diff --git a/Postbox/Postbox.swift b/Postbox/Postbox.swift index da9059817e..0b9f61ff78 100644 --- a/Postbox/Postbox.swift +++ b/Postbox/Postbox.swift @@ -739,6 +739,7 @@ public final class Postbox { let itemCacheTable: ItemCacheTable let peerNameTokenIndexTable: ReverseIndexReferenceTable let peerNameIndexTable: PeerNameIndexTable + let reverseAssociatedPeerTable: ReverseAssociatedPeerTable let peerChatTopTaggedMessageIdsTable: PeerChatTopTaggedMessageIdsTable let peerOperationLogMetadataTable: PeerOperationLogMetadataTable let peerMergedOperationLogIndexTable: PeerMergedOperationLogIndexTable @@ -782,7 +783,8 @@ public final class Postbox { self.metadataTable = MetadataTable(valueBox: self.valueBox, table: MetadataTable.tableSpec(0)) self.keychainTable = KeychainTable(valueBox: self.valueBox, table: KeychainTable.tableSpec(1)) - self.peerTable = PeerTable(valueBox: self.valueBox, table: PeerTable.tableSpec(2)) + self.reverseAssociatedPeerTable = ReverseAssociatedPeerTable(valueBox: self.valueBox, table:ReverseAssociatedPeerTable.tableSpec(40)) + self.peerTable = PeerTable(valueBox: self.valueBox, table: PeerTable.tableSpec(2), reverseAssociatedTable: self.reverseAssociatedPeerTable) self.globalMessageIdsTable = GlobalMessageIdsTable(valueBox: self.valueBox, table: GlobalMessageIdsTable.tableSpec(3), namespace: self.globalMessageIdsNamespace) self.globallyUniqueMessageIdsTable = MessageGloballyUniqueIdTable(valueBox: self.valueBox, table: MessageGloballyUniqueIdTable.tableSpec(32)) self.messageHistoryMetadataTable = MessageHistoryMetadataTable(valueBox: self.valueBox, table: MessageHistoryMetadataTable.tableSpec(10)) @@ -850,6 +852,7 @@ public final class Postbox { tables.append(self.itemCacheMetaTable) tables.append(self.itemCacheTable) tables.append(self.peerNameIndexTable) + tables.append(self.reverseAssociatedPeerTable) tables.append(self.peerNameTokenIndexTable) tables.append(self.peerChatTopTaggedMessageIdsTable) tables.append(self.peerOperationLogMetadataTable) @@ -1257,7 +1260,7 @@ public final class Postbox { for table in self.tables { table.clearMemoryCache() } - self.viewTracker.refreshViewsDueToExternalTransaction(fetchAroundChatEntries: self.fetchAroundChatEntries, fetchAroundHistoryEntries: self.fetchAroundHistoryEntries, fetchUnsentMessageIds: { + self.viewTracker.refreshViewsDueToExternalTransaction(postbox: self, fetchAroundChatEntries: self.fetchAroundChatEntries, fetchAroundHistoryEntries: self.fetchAroundHistoryEntries, fetchUnsentMessageIds: { return self.messageHistoryUnsentTable.get() }, fetchSynchronizePeerReadStateOperations: { return self.synchronizeReadStateTable.get(getCombinedPeerReadState: { peerId in @@ -1781,7 +1784,7 @@ public final class Postbox { public func searchContacts(query: String) -> Signal<[Peer], NoError> { return self.modify { modifier -> Signal<[Peer], NoError> in - let (_, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable) + let (_, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable, reverseAssociatedPeerTable: self.reverseAssociatedPeerTable) var contactPeers: [Peer] = [] for peerId in contactPeerIds { @@ -1800,7 +1803,7 @@ public final class Postbox { var peerIds = Set() var chatPeers: [Peer] = [] - let (chatPeerIds, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.chats, .contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable) + let (chatPeerIds, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.chats, .contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable, reverseAssociatedPeerTable: self.reverseAssociatedPeerTable) for peerId in chatPeerIds { if let peer = self.peerTable.get(peerId) { @@ -1825,7 +1828,7 @@ public final class Postbox { public func peerView(id: PeerId) -> Signal { return self.modify { modifier -> Signal in - let view = MutablePeerView(peerId: id, notificationSettings: self.peerNotificationSettingsTable.get(id), cachedData: self.cachedPeerDataTable.get(id), peerIsContact: self.contactsTable.isContact(peerId: id), getPeer: { self.peerTable.get($0) }, getPeerPresence: { self.peerPresenceTable.get($0) }) + let view = MutablePeerView(postbox: self, peerId: id) let (index, signal) = self.viewTracker.addPeerView(view) return (.single(PeerView(view)) diff --git a/Postbox/PostboxUpgrade_13to14.swift b/Postbox/PostboxUpgrade_13to14.swift new file mode 100644 index 0000000000..2517910a57 --- /dev/null +++ b/Postbox/PostboxUpgrade_13to14.swift @@ -0,0 +1,41 @@ +import Foundation + +private func writePeerIds(_ buffer: WriteBuffer, _ peerIds: Set) { + for id in peerIds { + var value: Int64 = id.toInt64() + buffer.write(&value, offset: 0, length: 8) + } +} + +func postboxUpgrade_13to14(metadataTable: MetadataTable, valueBox: ValueBox) { + var reverseAssociations: [PeerId: Set] = [:] + + let peerTable = ValueBoxTable(id: 2, keyType: .int64) + valueBox.scan(peerTable, values: { _, value in + if let peer = Decoder(buffer: value).decodeRootObject() as? Peer { + if let association = peer.associatedPeerId { + if reverseAssociations[association] == nil { + reverseAssociations[association] = Set() + } + reverseAssociations[association]!.insert(peer.id) + } + } else { + assertionFailure() + } + return true + }) + + let reverseAssociatedPeerTable = ValueBoxTable(id: 40, keyType: .int64) + + let sharedKey = ValueBoxKey(length: 8) + let sharedBuffer = WriteBuffer() + for (peerId, associations) in reverseAssociations { + sharedBuffer.reset() + writePeerIds(sharedBuffer, associations) + + sharedKey.setInt64(0, value: peerId.toInt64()) + valueBox.set(reverseAssociatedPeerTable, key: sharedKey, value: sharedBuffer) + } + + metadataTable.setUserVersion(14) +} diff --git a/Postbox/ReverseAssociatedPeerTable.swift b/Postbox/ReverseAssociatedPeerTable.swift new file mode 100644 index 0000000000..b2ca5870cd --- /dev/null +++ b/Postbox/ReverseAssociatedPeerTable.swift @@ -0,0 +1,99 @@ +import Foundation + +private func readPeerIds(_ buffer: ReadBuffer) -> Set { + assert(buffer.length % 8 == 0) + let count = buffer.length / 8 + var result = Set() + for _ in 0 ..< count { + var value: Int64 = 0 + buffer.read(&value, offset: 0, length: 8) + result.insert(PeerId(value)) + } + return result +} + +private func writePeerIds(_ buffer: WriteBuffer, _ peerIds: Set) { + for id in peerIds { + var value: Int64 = id.toInt64() + buffer.write(&value, offset: 0, length: 8) + } +} + +final class ReverseAssociatedPeerTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .int64) + } + + private let sharedKey = ValueBoxKey(length: 8) + + private var cachedAssociations: [PeerId: Set] = [:] + private var updatedAssociations = Set() + + private func key(_ peerId: PeerId) -> ValueBoxKey { + self.sharedKey.setInt64(0, value: peerId.toInt64()) + return self.sharedKey + } + + func get(peerId: PeerId) -> Set { + if let cached = self.cachedAssociations[peerId] { + return cached + } else { + if let value = self.valueBox.get(self.table, key: self.key(peerId)) { + let peerIds = readPeerIds(value) + self.cachedAssociations[peerId] = peerIds + return peerIds + } else { + self.cachedAssociations[peerId] = Set() + return Set() + } + } + } + + func addReverseAssociation(target targetPeerId: PeerId, from sourcePeerId: PeerId) { + var value = self.get(peerId: targetPeerId) + if value.contains(sourcePeerId) { + assertionFailure() + } else { + value.insert(sourcePeerId) + self.cachedAssociations[targetPeerId] = value + self.updatedAssociations.insert(targetPeerId) + } + } + + func removeReverseAssociation(target targetPeerId: PeerId, from sourcePeerId: PeerId) { + var value = self.get(peerId: targetPeerId) + if !value.contains(sourcePeerId) { + assertionFailure() + } else { + value.remove(sourcePeerId) + self.cachedAssociations[targetPeerId] = value + self.updatedAssociations.insert(targetPeerId) + } + } + + override func clearMemoryCache() { + self.cachedAssociations.removeAll() + assert(self.updatedAssociations.isEmpty) + } + + override func beforeCommit() { + if !self.updatedAssociations.isEmpty { + let buffer = WriteBuffer() + for peerId in self.updatedAssociations { + if let peerIds = self.cachedAssociations[peerId] { + if peerIds.isEmpty { + self.valueBox.remove(self.table, key: self.key(peerId)) + } else { + buffer.reset() + writePeerIds(buffer, peerIds) + self.valueBox.set(self.table, key: self.key(peerId), value: buffer) + } + } else { + assertionFailure() + } + } + + self.updatedAssociations.removeAll() + } + } +} diff --git a/Postbox/Upgrades.swift b/Postbox/Upgrades.swift index 6c70a9d015..21e053a2d6 100644 --- a/Postbox/Upgrades.swift +++ b/Postbox/Upgrades.swift @@ -7,5 +7,6 @@ enum PostboxUpgradeOperation { func registeredUpgrades() -> [Int32: PostboxUpgradeOperation] { var dict: [Int32: PostboxUpgradeOperation] = [:] dict[12] = .inplace(postboxUpgrade_12to13) + dict[13] = .inplace(postboxUpgrade_13to14) return dict } diff --git a/Postbox/ViewTracker.swift b/Postbox/ViewTracker.swift index b5f4ca9d38..0ff4d25f89 100644 --- a/Postbox/ViewTracker.swift +++ b/Postbox/ViewTracker.swift @@ -289,7 +289,7 @@ final class ViewTracker { self.combinedViews.remove(index) } - func refreshViewsDueToExternalTransaction(fetchAroundChatEntries: (_ index: ChatListIndex, _ count: Int) -> (entries: [MutableChatListEntry], earlier: MutableChatListEntry?, later: MutableChatListEntry?), fetchAroundHistoryEntries: (_ index: MessageIndex, _ count: Int, _ tagMask: MessageTags?) -> (entries: [MutableMessageHistoryEntry], lower: MutableMessageHistoryEntry?, upper: MutableMessageHistoryEntry?), fetchUnsentMessageIds: () -> [MessageId], fetchSynchronizePeerReadStateOperations: () -> [PeerId: PeerReadStateSynchronizationOperation]) { + func refreshViewsDueToExternalTransaction(postbox: Postbox, fetchAroundChatEntries: (_ index: ChatListIndex, _ count: Int) -> (entries: [MutableChatListEntry], earlier: MutableChatListEntry?, later: MutableChatListEntry?), fetchAroundHistoryEntries: (_ index: MessageIndex, _ count: Int, _ tagMask: MessageTags?) -> (entries: [MutableMessageHistoryEntry], lower: MutableMessageHistoryEntry?, upper: MutableMessageHistoryEntry?), fetchUnsentMessageIds: () -> [MessageId], fetchSynchronizePeerReadStateOperations: () -> [PeerId: PeerReadStateSynchronizationOperation]) { var updateTrackedHolesPeerIds: [PeerId] = [] for (peerId, bag) in self.messageHistoryViews { @@ -327,35 +327,7 @@ final class ViewTracker { } for (mutableView, pipe) in self.peerViews.copyItems() { - var updatedPeers: [PeerId: Peer] = [:] - var updatedPeerPresences: [PeerId: PeerPresence] = [:] - var updatedNotificationSettings: [PeerId: PeerNotificationSettings] = [:] - var updatedCachedPeerData: [PeerId: CachedPeerData] = [:] - - let peerId = mutableView.peerId - - if let peer = self.getPeer(peerId) { - updatedPeers[peerId] = peer - } - if let presence = self.getPeerPresence(peerId) { - updatedPeerPresences[peerId] = presence - } - if let notificationSettings = self.getPeerNotificationSettings(peerId) { - updatedNotificationSettings[peerId] = notificationSettings - } - if let cachedPeerData = self.getCachedPeerData(peerId) { - updatedCachedPeerData[peerId] = cachedPeerData - for cachedPeerId in cachedPeerData.peerIds { - if let peer = self.getPeer(cachedPeerId) { - updatedPeers[cachedPeerId] = peer - } - if let presence = self.getPeerPresence(cachedPeerId) { - updatedPeerPresences[cachedPeerId] = presence - } - } - } - - if mutableView.replay(updatedPeers: updatedPeers, updatedNotificationSettings: updatedNotificationSettings, updatedCachedPeerData: updatedCachedPeerData, updatedPeerPresences: updatedPeerPresences, replaceContactPeerIds: nil, getPeer: self.getPeer, getPeerPresence: self.getPeerPresence) { + if mutableView.reset(postbox: postbox) { pipe.putNext(PeerView(mutableView)) } } @@ -483,7 +455,7 @@ final class ViewTracker { } for (mutableView, pipe) in self.peerViews.copyItems() { - if mutableView.replay(updatedPeers: transaction.currentUpdatedPeers, updatedNotificationSettings: transaction.currentUpdatedPeerNotificationSettings, updatedCachedPeerData: transaction.currentUpdatedCachedPeerData, updatedPeerPresences: transaction.currentUpdatedPeerPresences, replaceContactPeerIds: transaction.replaceContactPeerIds, getPeer: self.getPeer, getPeerPresence: self.getPeerPresence) { + if mutableView.replay(postbox: postbox, transaction: transaction) { pipe.putNext(PeerView(mutableView)) } } diff --git a/Postbox/Views.swift b/Postbox/Views.swift index a8993553e9..109c129844 100644 --- a/Postbox/Views.swift +++ b/Postbox/Views.swift @@ -9,6 +9,7 @@ public enum PostboxViewKey: Hashable { case accessChallengeData case preferences(keys: Set) case globalMessageTags(globalTag: GlobalMessageTags, position: MessageIndex, count: Int, groupingPredicate: ((Message, Message) -> Bool)?) + case peer(peerId: PeerId) public var hashValue: Int { switch self { @@ -28,6 +29,8 @@ public enum PostboxViewKey: Hashable { return 3 case .globalMessageTags: return 4 + case let .peer(peerId): + return peerId.hashValue } } @@ -81,6 +84,12 @@ public enum PostboxViewKey: Hashable { } else { return false } + case let .peer(peerId): + if case .peer(peerId) = rhs { + return true + } else { + return false + } } } } @@ -103,5 +112,7 @@ func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxV return MutablePreferencesView(postbox: postbox, keys: keys) case let .globalMessageTags(globalTag, position, count, groupingPredicate): return MutableGlobalMessageTagsView(postbox: postbox, globalTag: globalTag, position: position, count: count, groupingPredicate: groupingPredicate) + case let .peer(peerId): + return MutablePeerView(postbox: postbox, peerId: peerId) } }