diff --git a/Postbox.xcodeproj/project.pbxproj b/Postbox.xcodeproj/project.pbxproj index 9c9d7efdfd..519ce3fb02 100644 --- a/Postbox.xcodeproj/project.pbxproj +++ b/Postbox.xcodeproj/project.pbxproj @@ -54,6 +54,9 @@ D07516771B2EC90400AE42E0 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = D07516741B2EC90400AE42E0 /* fts3_tokenizer.h */; }; D07516781B2EC90400AE42E0 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = D07516751B2EC90400AE42E0 /* SQLite-Bridging.h */; }; D07516791B2EC90400AE42E0 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = D07516761B2EC90400AE42E0 /* SQLite-Bridging.m */; }; + D07CFF811DCA765D00761F81 /* PeerChatInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF801DCA765D00761F81 /* PeerChatInterfaceState.swift */; }; + D07CFF831DCA909100761F81 /* PeerChatInterfaceStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF821DCA909100761F81 /* PeerChatInterfaceStateTable.swift */; }; + D07CFF851DCA99C400761F81 /* InitialMessageHistoryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF841DCA99C400761F81 /* InitialMessageHistoryData.swift */; }; D08C713A1C501F0700779C0F /* MessageHistoryHole.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C71391C501F0700779C0F /* MessageHistoryHole.swift */; }; D08C713C1C51283C00779C0F /* MessageHistoryIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C713B1C51283C00779C0F /* MessageHistoryIndexTable.swift */; }; D08C713E1C512EA500779C0F /* MessageHistoryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C713D1C512EA500779C0F /* MessageHistoryTable.swift */; }; @@ -221,6 +224,9 @@ D07516741B2EC90400AE42E0 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; D07516751B2EC90400AE42E0 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; D07516761B2EC90400AE42E0 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; + D07CFF801DCA765D00761F81 /* PeerChatInterfaceState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatInterfaceState.swift; sourceTree = ""; }; + D07CFF821DCA909100761F81 /* PeerChatInterfaceStateTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatInterfaceStateTable.swift; sourceTree = ""; }; + D07CFF841DCA99C400761F81 /* InitialMessageHistoryData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialMessageHistoryData.swift; sourceTree = ""; }; D08C71391C501F0700779C0F /* MessageHistoryHole.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryHole.swift; sourceTree = ""; }; D08C713B1C51283C00779C0F /* MessageHistoryIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryIndexTable.swift; sourceTree = ""; }; D08C713D1C512EA500779C0F /* MessageHistoryTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryTable.swift; sourceTree = ""; }; @@ -397,6 +403,7 @@ D08D451E1D5D2CA700A7428A /* RatingTable.swift */, D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */, D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */, + D07CFF821DCA909100761F81 /* PeerChatInterfaceStateTable.swift */, ); name = Tables; sourceTree = ""; @@ -416,6 +423,7 @@ D0B844501DAC04FE005F29E1 /* PeerPresence.swift */, D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */, D021E0D31DB4FAE100C6B04F /* ItemCollection.swift */, + D07CFF801DCA765D00761F81 /* PeerChatInterfaceState.swift */, ); name = Objects; sourceTree = ""; @@ -447,6 +455,7 @@ isa = PBXGroup; children = ( D003E4E51B38DBDB00C22CBC /* MessageHistoryView.swift */, + D07CFF841DCA99C400761F81 /* InitialMessageHistoryData.swift */, D0F9E86A1C59719800037222 /* ChatListView.swift */, D033A6F81C73E440006A2EAB /* UnsentMessageHistoryView.swift */, D01F7D9C1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift */, @@ -778,6 +787,7 @@ files = ( D0F9E8631C579F0200037222 /* MediaCleanupTable.swift in Sources */, D08D451F1D5D2CA700A7428A /* RatingTable.swift in Sources */, + D07CFF831DCA909100761F81 /* PeerChatInterfaceStateTable.swift in Sources */, D0F9E86F1C5A0E7600037222 /* KeychainTable.swift in Sources */, D0E3A7821B28ADD000A402D9 /* Postbox.swift in Sources */, D0E3A79E1B28B50400A402D9 /* Message.swift in Sources */, @@ -793,6 +803,7 @@ D0079F6B1D5B3AAB00A27A2C /* PeerNameIndexRepresentation.swift in Sources */, D08C713A1C501F0700779C0F /* MessageHistoryHole.swift in Sources */, D044CA2A1C617D39002160FF /* SeedConfiguration.swift in Sources */, + D07CFF811DCA765D00761F81 /* PeerChatInterfaceState.swift in Sources */, D01F7D9D1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift in Sources */, D021E0D81DB4FD1300C6B04F /* ItemCollectionItemTable.swift in Sources */, D0F9E85B1C565EBB00037222 /* MessageMediaTable.swift in Sources */, @@ -823,6 +834,7 @@ D0977FA01B8244D7009994B2 /* SqliteValueBox.swift in Sources */, D0F9E8651C58CB7F00037222 /* ChatListTable.swift in Sources */, D0F9E8711C5A0E9B00037222 /* PeerTable.swift in Sources */, + D07CFF851DCA99C400761F81 /* InitialMessageHistoryData.swift in Sources */, D05F09A61C9E9F9300BB6F96 /* MediaResourceStatus.swift in Sources */, D033A6F71C73D512006A2EAB /* MessageHistoryUnsentTable.swift in Sources */, D0D511021D64D73D00A97B8A /* IpcNotifier.mm in Sources */, diff --git a/Postbox/Coding.swift b/Postbox/Coding.swift index aed292f76b..67eec67e5f 100644 --- a/Postbox/Coding.swift +++ b/Postbox/Coding.swift @@ -910,7 +910,7 @@ public final class Decoder { } } - public func decodeBytesForKeyNoCopy(_ key: StaticString) -> ReadBuffer! { + public func decodeBytesForKeyNoCopy(_ key: StaticString) -> ReadBuffer? { if Decoder.positionOnKey(self.buffer.memory, offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Bytes) { var length: Int32 = 0 memcpy(&length, self.buffer.memory + self.offset, 4) diff --git a/Postbox/InitialMessageHistoryData.swift b/Postbox/InitialMessageHistoryData.swift new file mode 100644 index 0000000000..178b92fda9 --- /dev/null +++ b/Postbox/InitialMessageHistoryData.swift @@ -0,0 +1,6 @@ +import Foundation + +public struct InitialMessageHistoryData { + public let peer: Peer? + public let chatInterfaceState: PeerChatInterfaceState? +} diff --git a/Postbox/PeerChatInterfaceState.swift b/Postbox/PeerChatInterfaceState.swift new file mode 100644 index 0000000000..da475f2186 --- /dev/null +++ b/Postbox/PeerChatInterfaceState.swift @@ -0,0 +1,12 @@ + +public protocol PeerChatListEmbeddedInterfaceState: Coding { + var timestamp: Int32 { get } + + func isEqual(to: PeerChatListEmbeddedInterfaceState) -> Bool +} + +public protocol PeerChatInterfaceState: Coding { + var chatListEmbeddedState: PeerChatListEmbeddedInterfaceState? { get } + + func isEqual(to: PeerChatInterfaceState) -> Bool +} diff --git a/Postbox/PeerChatInterfaceStateTable.swift b/Postbox/PeerChatInterfaceStateTable.swift new file mode 100644 index 0000000000..2c2dbe1937 --- /dev/null +++ b/Postbox/PeerChatInterfaceStateTable.swift @@ -0,0 +1,64 @@ +import Foundation + +final class PeerChatInterfaceStateTable: Table { + private var states: [PeerId: PeerChatInterfaceState?] = [:] + private var peerIdsWithUpdatedStates = Set() + + private let sharedKey = ValueBoxKey(length: 8) + + private func key(_ peerId: PeerId, sharedKey: ValueBoxKey = ValueBoxKey(length: 8)) -> ValueBoxKey { + sharedKey.setInt64(0, value: peerId.toInt64()) + return sharedKey + } + + func get(_ peerId: PeerId) -> PeerChatInterfaceState? { + if let cachedValue = self.states[peerId] { + return cachedValue + } else if let value = self.valueBox.get(self.tableId, key: self.key(peerId, sharedKey: self.sharedKey)), let state = Decoder(buffer: value).decodeRootObject() as? PeerChatInterfaceState { + self.states[peerId] = state + return state + } else { + self.states[peerId] = nil + return nil + } + } + + func set(_ peerId: PeerId, state: PeerChatInterfaceState?) { + let currentState = self.get(peerId) + var updated = false + if let currentState = currentState, let state = state { + if !currentState.isEqual(to: state) { + updated = true + } + } else if (currentState != nil) != (state != nil) { + updated = true + } + if updated { + self.states[peerId] = state + self.peerIdsWithUpdatedStates.insert(peerId) + } + } + + override func clearMemoryCache() { + self.states.removeAll() + self.peerIdsWithUpdatedStates.removeAll() + } + + override func beforeCommit() { + if !self.peerIdsWithUpdatedStates.isEmpty { + let sharedEncoder = Encoder() + for peerId in self.peerIdsWithUpdatedStates { + if let state = self.states[peerId] { + if let state = state { + sharedEncoder.reset() + sharedEncoder.encodeRootObject(state) + self.valueBox.set(self.tableId, key: self.key(peerId, sharedKey: self.sharedKey), value: sharedEncoder.readBufferNoCopy()) + } else { + self.valueBox.remove(self.tableId, key: self.key(peerId, sharedKey: self.sharedKey)) + } + } + } + self.peerIdsWithUpdatedStates.removeAll() + } + } +} diff --git a/Postbox/Postbox.swift b/Postbox/Postbox.swift index a344e41ec0..d0598c9e9d 100644 --- a/Postbox/Postbox.swift +++ b/Postbox/Postbox.swift @@ -86,6 +86,10 @@ public final class Modifier { self.postbox?.peerChatStateTable.set(id, state: state) } + public func updatePeerChatInterfaceState(_ id: PeerId, update: (PeerChatInterfaceState?) -> (PeerChatInterfaceState?)) { + self.postbox?.updatePeerChatInterfaceState(id, update: update) + } + public func getPeer(_ id: PeerId) -> Peer? { return self.postbox?.peerTable.get(id) } @@ -247,6 +251,7 @@ public final class Postbox { var contactsTable: ContactTable! var itemCollectionInfoTable: ItemCollectionInfoTable! var itemCollectionItemTable: ItemCollectionItemTable! + var peerChatInterfaceStateTable: PeerChatInterfaceStateTable! //temporary var peerRatingTable: RatingTable! @@ -358,6 +363,7 @@ public final class Postbox { self.peerPresenceTable = PeerPresenceTable(valueBox: self.valueBox, tableId: 20) self.itemCollectionInfoTable = ItemCollectionInfoTable(valueBox: self.valueBox, tableId: 21) self.itemCollectionItemTable = ItemCollectionItemTable(valueBox: self.valueBox, tableId: 22) + self.peerChatInterfaceStateTable = PeerChatInterfaceStateTable(valueBox: self.valueBox, tableId: 23) self.tables.append(self.keychainTable) self.tables.append(self.peerTable) @@ -381,6 +387,7 @@ public final class Postbox { self.tables.append(self.peerPresenceTable) self.tables.append(self.itemCollectionInfoTable) self.tables.append(self.itemCollectionItemTable) + self.tables.append(self.peerChatInterfaceStateTable) self.transactionStateVersion = self.metadataTable.transactionStateVersion() @@ -845,6 +852,11 @@ public final class Postbox { } } + fileprivate func updatePeerChatInterfaceState(_ id: PeerId, update: (PeerChatInterfaceState?) -> (PeerChatInterfaceState?)) { + let updatedState = update(self.peerChatInterfaceStateTable.get(id)) + self.peerChatInterfaceStateTable.set(id, state: updatedState) + } + fileprivate func replaceContactPeerIds(_ peerIds: Set) { self.contactsTable.replace(peerIds) @@ -920,8 +932,8 @@ public final class Postbox { } } - public func aroundUnreadMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType), NoError> { - return self.modify(userInteractive: true, { modifier -> Signal<(MessageHistoryView, ViewUpdateType), NoError> in + public func aroundUnreadMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.modify(userInteractive: true, { modifier -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in var index = MessageHistoryAnchorIndex(index: MessageIndex.upperBound(peerId: peerId), exact: true) if let maxReadIndex = self.messageHistoryTable.maxReadIndex(peerId) { index = maxReadIndex @@ -930,8 +942,8 @@ public final class Postbox { }) |> switchToLatest } - public func aroundIdMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, messageId: MessageId, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType), NoError> { - return self.modify { modifier -> Signal<(MessageHistoryView, ViewUpdateType), NoError> in + public func aroundIdMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, messageId: MessageId, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.modify { modifier -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in var index = MessageHistoryAnchorIndex(index: MessageIndex.upperBound(peerId: peerId), exact: true) if let anchorIndex = self.messageHistoryTable.anchorIndex(messageId) { index = anchorIndex @@ -940,8 +952,8 @@ public final class Postbox { } |> switchToLatest } - public func aroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageIndex, count: Int, anchorIndex: MessageIndex, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType), NoError> { - return self.modify { modifier -> Signal<(MessageHistoryView, ViewUpdateType), NoError> in + public func aroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageIndex, count: Int, anchorIndex: MessageIndex, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.modify { modifier -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in return self.syncAroundMessageHistoryViewForPeerId(peerId, index: index, count: count, anchorIndex: MessageHistoryAnchorIndex(index: anchorIndex, exact: true), unreadIndex: nil, fixedCombinedReadState: fixedCombinedReadState, tagMask: tagMask) } |> switchToLatest } @@ -972,7 +984,7 @@ public final class Postbox { } } - private func syncAroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageIndex, count: Int, anchorIndex: MessageHistoryAnchorIndex, unreadIndex: MessageIndex?, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType), NoError> { + private func syncAroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageIndex, count: Int, anchorIndex: MessageHistoryAnchorIndex, unreadIndex: MessageIndex?, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { let startTime = CFAbsoluteTimeGetCurrent() let (entries, earlier, later) = self.fetchAroundHistoryEntries(index, count: count, tagMask: tagMask) print("aroundMessageHistoryViewForPeerId fetchAroundHistoryEntries \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") @@ -989,8 +1001,10 @@ public final class Postbox { let (index, signal) = self.viewTracker.addMessageHistoryView(peerId, view: mutableView) - return (.single((MessageHistoryView(mutableView), initialUpdateType)) - |> then(signal)) + let initialData = self.initialMessageHistoryData(peerId: peerId) + + return (.single((MessageHistoryView(mutableView), initialUpdateType, initialData)) + |> then(signal |> map { ($0.0, $0.1, nil) })) |> afterDisposed { [weak self] in if let strongSelf = self { strongSelf.queue.async { @@ -1000,6 +1014,10 @@ public final class Postbox { } } + private func initialMessageHistoryData(peerId: PeerId) -> InitialMessageHistoryData { + return InitialMessageHistoryData(peer: self.peerTable.get(peerId), chatInterfaceState: self.peerChatInterfaceStateTable.get(peerId)) + } + public func messageIndexAtId(_ id: MessageId) -> Signal { return self.modify { modifier -> Signal in if let entry = self.messageHistoryIndexTable.get(id), case let .Message(index) = entry { @@ -1028,6 +1046,20 @@ public final class Postbox { } |> switchToLatest } + public func messagesAtIds(_ ids: [MessageId]) -> Signal<[Message], NoError> { + return self.modify { modifier -> Signal<[Message], NoError> in + var messages: [Message] = [] + for id in ids { + if let entry = self.messageHistoryIndexTable.get(id), case let .Message(index) = entry { + if let message = self.messageHistoryTable.getMessage(index) { + messages.append(self.renderIntermediateMessage(message)) + } + } + } + return .single(messages) + } |> switchToLatest + } + public func tailChatListView(_ count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { return self.aroundChatListView(MessageIndex.absoluteUpperBound(), count: count) }