From c6034ac794c65d562a89117c00adf453a54ddaf3 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 7 Oct 2016 19:14:00 +0300 Subject: [PATCH] no message --- Postbox.xcodeproj/project.pbxproj | 28 +++++++ Postbox/CachedMediaResource.swift | 4 + Postbox/CachedPeerData.swift | 6 ++ Postbox/CachedPeerDataTable.swift | 55 +++++++++++++ Postbox/ChatListView.swift | 68 +++++++++++----- Postbox/MediaBox.swift | 26 +++++- Postbox/MessageHistoryReadStateTable.swift | 2 +- Postbox/MessageHistoryView.swift | 51 ++++++++---- Postbox/PeerNotificationSettings.swift | 4 + Postbox/PeerNotificationSettingsTable.swift | 54 +++++++++++++ Postbox/PeerReadState.swift | 44 +++++++++- Postbox/PeerStatusTable.swift | 54 +++++++++++++ Postbox/PeerView.swift | 90 +++++++++++++++++++++ Postbox/Postbox.swift | 85 ++++++++++++++++--- Postbox/PostboxTransaction.swift | 13 ++- Postbox/SqliteValueBox.swift | 4 +- Postbox/ViewTracker.swift | 59 ++++++++++++-- 17 files changed, 590 insertions(+), 57 deletions(-) create mode 100644 Postbox/CachedMediaResource.swift create mode 100644 Postbox/CachedPeerData.swift create mode 100644 Postbox/CachedPeerDataTable.swift create mode 100644 Postbox/PeerNotificationSettings.swift create mode 100644 Postbox/PeerNotificationSettingsTable.swift create mode 100644 Postbox/PeerStatusTable.swift create mode 100644 Postbox/PeerView.swift diff --git a/Postbox.xcodeproj/project.pbxproj b/Postbox.xcodeproj/project.pbxproj index 2f8f41cb2a..4cf1bc56bc 100644 --- a/Postbox.xcodeproj/project.pbxproj +++ b/Postbox.xcodeproj/project.pbxproj @@ -16,6 +16,11 @@ D01F7D9B1CBEC390008765C9 /* MessageHistoryInvalidatedReadStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F7D9A1CBEC390008765C9 /* MessageHistoryInvalidatedReadStateTable.swift */; }; D01F7D9D1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F7D9C1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift */; }; D02EB8071D2B07F300D07ED3 /* OrderStatisticTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02EB8061D2B07F300D07ED3 /* OrderStatisticTreeTests.swift */; }; + D03120F81DA53FF4006A2A60 /* PeerStatusTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F71DA53FF4006A2A60 /* PeerStatusTable.swift */; }; + D03120FA1DA540F0006A2A60 /* CachedPeerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F91DA540F0006A2A60 /* CachedPeerData.swift */; }; + D03120FC1DA55427006A2A60 /* PeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */; }; + D03120FE1DA562E9006A2A60 /* CachedPeerDataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FD1DA562E9006A2A60 /* CachedPeerDataTable.swift */; }; + D03121001DA579A0006A2A60 /* PeerNotificationSettingsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FF1DA579A0006A2A60 /* PeerNotificationSettingsTable.swift */; }; D033A6F71C73D512006A2EAB /* MessageHistoryUnsentTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033A6F61C73D512006A2EAB /* MessageHistoryUnsentTable.swift */; }; D033A6F91C73E440006A2EAB /* UnsentMessageHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033A6F81C73E440006A2EAB /* UnsentMessageHistoryView.swift */; }; D03BCCF81C73561C0097A291 /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCF71C73561C0097A291 /* Table.swift */; }; @@ -134,6 +139,8 @@ D0D511041D64D91C00A97B8A /* IpcNotifier.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D511031D64D75200A97B8A /* IpcNotifier.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0D949F31D35302600740E02 /* RandomAccessResourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D949F21D35302600740E02 /* RandomAccessResourceTests.swift */; }; D0D949F51D35353900740E02 /* MappedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D949F41D35353900740E02 /* MappedFile.swift */; }; + D0DE76F91D9293B8002B8809 /* CachedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DE76F81D9293B8002B8809 /* CachedMediaResource.swift */; }; + D0DF0C8F1D81A350008AEB01 /* PeerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C8E1D81A350008AEB01 /* PeerView.swift */; }; D0E1DE151C5E1C6900C7826E /* ViewTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1DE141C5E1C6900C7826E /* ViewTracker.swift */; }; D0E3A7501B28A7E300A402D9 /* Postbox.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E3A74F1B28A7E300A402D9 /* Postbox.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0E3A7561B28A7E300A402D9 /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E3A74A1B28A7E300A402D9 /* Postbox.framework */; }; @@ -177,6 +184,11 @@ 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 = ""; }; D02EB8061D2B07F300D07ED3 /* OrderStatisticTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatisticTreeTests.swift; sourceTree = ""; }; + D03120F71DA53FF4006A2A60 /* PeerStatusTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerStatusTable.swift; sourceTree = ""; }; + D03120F91DA540F0006A2A60 /* CachedPeerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerData.swift; sourceTree = ""; }; + D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettings.swift; sourceTree = ""; }; + D03120FD1DA562E9006A2A60 /* CachedPeerDataTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerDataTable.swift; sourceTree = ""; }; + D03120FF1DA579A0006A2A60 /* PeerNotificationSettingsTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettingsTable.swift; sourceTree = ""; }; D033A6F61C73D512006A2EAB /* MessageHistoryUnsentTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryUnsentTable.swift; sourceTree = ""; }; D033A6F81C73E440006A2EAB /* UnsentMessageHistoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsentMessageHistoryView.swift; sourceTree = ""; }; D03BCCF71C73561C0097A291 /* Table.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Table.swift; sourceTree = ""; }; @@ -231,6 +243,8 @@ D0D511031D64D75200A97B8A /* IpcNotifier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IpcNotifier.h; sourceTree = ""; }; D0D949F21D35302600740E02 /* RandomAccessResourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomAccessResourceTests.swift; sourceTree = ""; }; D0D949F41D35353900740E02 /* MappedFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappedFile.swift; sourceTree = ""; }; + D0DE76F81D9293B8002B8809 /* CachedMediaResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedMediaResource.swift; sourceTree = ""; }; + D0DF0C8E1D81A350008AEB01 /* PeerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerView.swift; sourceTree = ""; }; D0E1DE141C5E1C6900C7826E /* ViewTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewTracker.swift; sourceTree = ""; }; D0E3A74A1B28A7E300A402D9 /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0E3A74E1B28A7E300A402D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -291,6 +305,7 @@ D055BD321B7D3D2D00F06C0A /* MediaBox.swift */, D05F09A51C9E9F9300BB6F96 /* MediaResourceStatus.swift */, D0CE63F51CA1CCB2002BC462 /* MediaResource.swift */, + D0DE76F81D9293B8002B8809 /* CachedMediaResource.swift */, D0FC1A3B1CA3FB310056AE9A /* MediaResourceData.swift */, D09ADF091D2E89F300C8208D /* RandomAccessMediaResourceContext.swift */, ); @@ -361,6 +376,9 @@ D0F9E86C1C5A0E5D00037222 /* MetadataTable.swift */, D0F9E86E1C5A0E7600037222 /* KeychainTable.swift */, D0F9E8701C5A0E9B00037222 /* PeerTable.swift */, + D03120FF1DA579A0006A2A60 /* PeerNotificationSettingsTable.swift */, + D03120FD1DA562E9006A2A60 /* CachedPeerDataTable.swift */, + D03120F71DA53FF4006A2A60 /* PeerStatusTable.swift */, D0C735271C864DF300BB3149 /* PeerChatStateTable.swift */, D0079F591D592E8B00A27A2C /* ContactTable.swift */, D0C674C71CBB11C600183765 /* MessageHistoryReadStateTable.swift */, @@ -382,6 +400,8 @@ D044CA2D1C618373002160FF /* ChatListHole.swift */, D0C674CB1CBB14A700183765 /* PeerReadState.swift */, D0079F6A1D5B3AAB00A27A2C /* PeerNameIndexRepresentation.swift */, + D03120F91DA540F0006A2A60 /* CachedPeerData.swift */, + D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */, ); name = Objects; sourceTree = ""; @@ -421,6 +441,7 @@ D0AB0B8B1D65D488002C78E7 /* MessageHistoryHolesView.swift */, D0AB0B8D1D65D49C002C78E7 /* ChatListHolesView.swift */, D0AB0B8F1D65D4AB002C78E7 /* UnsentMessageIndicesView.swift */, + D0DF0C8E1D81A350008AEB01 /* PeerView.swift */, ); name = Views; sourceTree = ""; @@ -740,9 +761,11 @@ D0E3A7821B28ADD000A402D9 /* Postbox.swift in Sources */, D0E3A79E1B28B50400A402D9 /* Message.swift in Sources */, D0C674C81CBB11C600183765 /* MessageHistoryReadStateTable.swift in Sources */, + D0DF0C8F1D81A350008AEB01 /* PeerView.swift in Sources */, D0079F651D5A457A00A27A2C /* ContactPeersView.swift in Sources */, D09ADF0A1D2E89F300C8208D /* RandomAccessMediaResourceContext.swift in Sources */, D08CEFB41D2AD8BE0015D3BC /* RedBlackTree.swift in Sources */, + D03120FE1DA562E9006A2A60 /* CachedPeerDataTable.swift in Sources */, D044CA2E1C618373002160FF /* ChatListHole.swift in Sources */, D044E1631B2AD677001EE087 /* MurMurHash32.m in Sources */, D0079F6B1D5B3AAB00A27A2C /* PeerNameIndexRepresentation.swift in Sources */, @@ -765,11 +788,13 @@ D033A6F91C73E440006A2EAB /* UnsentMessageHistoryView.swift in Sources */, D09ADF0C1D2EB83500C8208D /* OrderStatisticTable.swift in Sources */, D0CE63F61CA1CCB2002BC462 /* MediaResource.swift in Sources */, + D03121001DA579A0006A2A60 /* PeerNotificationSettingsTable.swift in Sources */, D0D510F91D63BCC200A97B8A /* IntermediateMessage.swift in Sources */, D0F9E86B1C59719800037222 /* ChatListView.swift in Sources */, D0D949F51D35353900740E02 /* MappedFile.swift in Sources */, D003E4E61B38DBDB00C22CBC /* MessageHistoryView.swift in Sources */, D0AB0B8E1D65D49C002C78E7 /* ChatListHolesView.swift in Sources */, + D03120FA1DA540F0006A2A60 /* CachedPeerData.swift in Sources */, D07516791B2EC90400AE42E0 /* SQLite-Bridging.m in Sources */, D0977FA01B8244D7009994B2 /* SqliteValueBox.swift in Sources */, D0F9E8651C58CB7F00037222 /* ChatListTable.swift in Sources */, @@ -781,11 +806,14 @@ D0FC1A3C1CA3FB310056AE9A /* MediaResourceData.swift in Sources */, D0F9E86D1C5A0E5D00037222 /* MetadataTable.swift in Sources */, D0E3A7841B28AE0900A402D9 /* Peer.swift in Sources */, + D0DE76F91D9293B8002B8809 /* CachedMediaResource.swift in Sources */, D0D510F61D63BBE100A97B8A /* MessageHistoryOperation.swift in Sources */, D0AB0B901D65D4AB002C78E7 /* UnsentMessageIndicesView.swift in Sources */, + D03120F81DA53FF4006A2A60 /* PeerStatusTable.swift in Sources */, D0F9E8731C5A1EE500037222 /* GlobalMessageIdsTable.swift in Sources */, D00EED1E1C81F28D00341DFF /* MessageHistoryTagsTable.swift in Sources */, D044CA2C1C617E2D002160FF /* MessageHistoryMetadataTable.swift in Sources */, + D03120FC1DA55427006A2A60 /* PeerNotificationSettings.swift in Sources */, D03BCCF81C73561C0097A291 /* Table.swift in Sources */, D0C735281C864DF300BB3149 /* PeerChatStateTable.swift in Sources */, D01F7D9B1CBEC390008765C9 /* MessageHistoryInvalidatedReadStateTable.swift in Sources */, diff --git a/Postbox/CachedMediaResource.swift b/Postbox/CachedMediaResource.swift new file mode 100644 index 0000000000..464695d4e4 --- /dev/null +++ b/Postbox/CachedMediaResource.swift @@ -0,0 +1,4 @@ + +public protocol CachedMediaResource { + var id: String { get } +} diff --git a/Postbox/CachedPeerData.swift b/Postbox/CachedPeerData.swift new file mode 100644 index 0000000000..3833f5576c --- /dev/null +++ b/Postbox/CachedPeerData.swift @@ -0,0 +1,6 @@ + +public protocol CachedPeerData: Coding { + var peerIds: Set { get } + + func isEqual(to: CachedPeerData) -> Bool +} diff --git a/Postbox/CachedPeerDataTable.swift b/Postbox/CachedPeerDataTable.swift new file mode 100644 index 0000000000..d3db7014f8 --- /dev/null +++ b/Postbox/CachedPeerDataTable.swift @@ -0,0 +1,55 @@ +import Foundation + +final class CachedPeerDataTable: Table { + private let sharedEncoder = Encoder() + private let sharedKey = ValueBoxKey(length: 8) + + private var cachedDatas: [PeerId: CachedPeerData] = [:] + private var updatedPeerIds = Set() + + override init(valueBox: ValueBox, tableId: Int32) { + super.init(valueBox: valueBox, tableId: tableId) + } + + private func key(_ id: PeerId) -> ValueBoxKey { + self.sharedKey.setInt64(0, value: id.toInt64()) + return self.sharedKey + } + + func set(id: PeerId, data: CachedPeerData) { + self.cachedDatas[id] = data + self.updatedPeerIds.insert(id) + } + + func get(_ id: PeerId) -> CachedPeerData? { + if let status = self.cachedDatas[id] { + return status + } + if let value = self.valueBox.get(self.tableId, key: self.key(id)) { + if let data = Decoder(buffer: value).decodeRootObject() as? CachedPeerData { + self.cachedDatas[id] = data + return data + } + } + return nil + } + + override func clearMemoryCache() { + self.cachedDatas.removeAll() + self.updatedPeerIds.removeAll() + } + + override func beforeCommit() { + for peerId in self.updatedPeerIds { + if let data = self.cachedDatas[peerId] { + self.sharedEncoder.reset() + self.sharedEncoder.encodeRootObject(data) + + self.valueBox.set(self.tableId, key: self.key(peerId), value: self.sharedEncoder.readBufferNoCopy()) + } + } + + self.updatedPeerIds.removeAll() + self.cachedDatas.removeAll() + } +} diff --git a/Postbox/ChatListView.swift b/Postbox/ChatListView.swift index 165353f36a..2652332ead 100644 --- a/Postbox/ChatListView.swift +++ b/Postbox/ChatListView.swift @@ -1,13 +1,13 @@ import Foundation public enum ChatListEntry: Comparable { - case MessageEntry(Message, Int) + case MessageEntry(Message, CombinedPeerReadState?, PeerNotificationSettings?) case HoleEntry(ChatListHole) case Nothing(MessageIndex) public var index: MessageIndex { switch self { - case let .MessageEntry(message, _): + case let .MessageEntry(message, _, _): return MessageIndex(message) case let .HoleEntry(hole): return hole.index @@ -19,8 +19,15 @@ public enum ChatListEntry: Comparable { public func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool { if lhs.index == rhs.index { - if case let .MessageEntry(_, lhsCount) = lhs, case let .MessageEntry(_, rhsCount) = rhs { - if lhsCount != rhsCount { + if case let .MessageEntry(_, lhsReadState, lhsSettings) = lhs, case let .MessageEntry(_, rhsReadState, rhsSettings) = rhs { + if lhsReadState != rhsReadState { + return false + } + if let lhsSettings = lhsSettings, let rhsSettings = rhsSettings { + if !lhsSettings.isEqual(to: rhsSettings) { + return false + } + } else if (lhsSettings != nil) != (rhsSettings != nil) { return false } } @@ -34,16 +41,16 @@ public func <(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool { } enum MutableChatListEntry: Equatable { - case IntermediateMessageEntry(IntermediateMessage, CombinedPeerReadState?) - case MessageEntry(Message, CombinedPeerReadState?) + case IntermediateMessageEntry(IntermediateMessage, CombinedPeerReadState?, PeerNotificationSettings?) + case MessageEntry(Message, CombinedPeerReadState?, PeerNotificationSettings?) case HoleEntry(ChatListHole) case Nothing(MessageIndex) var index: MessageIndex { switch self { - case let .IntermediateMessageEntry(message, _): + case let .IntermediateMessageEntry(message, _, _): return MessageIndex(id: message.id, timestamp: message.timestamp) - case let .MessageEntry(message, _): + case let .MessageEntry(message, _, _): return MessageIndex(message) case let .HoleEntry(hole): return hole.index @@ -131,12 +138,12 @@ final class MutableChatListView { } } - func replay(_ operations: [ChatListOperation], context: MutableChatListViewReplayContext) -> Bool { + func replay(_ operations: [ChatListOperation], updatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], context: MutableChatListViewReplayContext) -> Bool { var hasChanges = false for operation in operations { switch operation { case let .InsertMessage(message, combinedReadState): - if self.add(.IntermediateMessageEntry(message, combinedReadState)) { + if self.add(.IntermediateMessageEntry(message, combinedReadState, nil)) { hasChanges = true } case let .InsertNothing(index): @@ -157,6 +164,22 @@ final class MutableChatListView { } } } + for i in 0 ..< self.entries.count { + switch self.entries[i] { + case let .IntermediateMessageEntry(message, readState, _): + if let settings = updatedPeerNotificationSettings[message.id.peerId] { + self.entries[i] = .IntermediateMessageEntry(message, readState, settings) + hasChanges = true + } + case let .MessageEntry(message, readState, _): + if let settings = updatedPeerNotificationSettings[message.id.peerId] { + self.entries[i] = .MessageEntry(message, readState, settings) + hasChanges = true + } + default: + continue + } + } return hasChanges } @@ -392,10 +415,19 @@ final class MutableChatListView { return hasChanges } - func render(_ renderMessage: (IntermediateMessage) -> Message) { + func render(_ renderMessage: (IntermediateMessage) -> Message, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?) { for i in 0 ..< self.entries.count { - if case let .IntermediateMessageEntry(message, combinedReadState) = self.entries[i] { - self.entries[i] = .MessageEntry(renderMessage(message), combinedReadState) + switch self.entries[i] { + case let .IntermediateMessageEntry(message, combinedReadState, notificationSettings): + let updatedNotificationSettings: PeerNotificationSettings? + if let notificationSettings = notificationSettings { + updatedNotificationSettings = notificationSettings + } else { + updatedNotificationSettings = getPeerNotificationSettings(message.id.peerId) + } + self.entries[i] = .MessageEntry(renderMessage(message), combinedReadState, updatedNotificationSettings) + default: + break } } } @@ -410,14 +442,8 @@ public final class ChatListView { var entries: [ChatListEntry] = [] for entry in mutableView.entries { switch entry { - case let .MessageEntry(message, combinedReadState): - var unreadCount: Int32 = 0 - if let combinedReadState = combinedReadState { - for (_, state) in combinedReadState.states { - unreadCount += state.count - } - } - entries.append(.MessageEntry(message, Int(unreadCount))) + case let .MessageEntry(message, combinedReadState, notificationSettings): + entries.append(.MessageEntry(message, combinedReadState, notificationSettings)) case let .Nothing(index): entries.append(.Nothing(index)) case let .HoleEntry(hole): diff --git a/Postbox/MediaBox.swift b/Postbox/MediaBox.swift index 92cf175b78..e0d92424a7 100644 --- a/Postbox/MediaBox.swift +++ b/Postbox/MediaBox.swift @@ -42,6 +42,7 @@ public final class MediaBox { private let statusQueue = Queue() private let concurrentQueue = Queue.concurrentDefaultQueue() private let dataQueue = Queue() + private let cacheQueue = Queue() private var statusContexts: [String: ResourceStatusContext] = [:] private var dataContexts: [String: ResourceDataContext] = [:] @@ -77,7 +78,30 @@ public final class MediaBox { } private func streamingPathForId(_ id: String) -> String { - return "\(self.basePath)/\(id)_stream" + return "\(self.basePath)/\(fileNameForId(id))_stream" + } + + private func cachePathForId(_ id: String) -> String { + return "\(self.basePath)/cache/\(fileNameForId(id))" + } + + public func cachedResource(_ resource: CachedMediaResource) -> Signal { + return Signal { subscriber in + self.concurrentQueue.async { + let path = self.cachePathForId(resource.id) + let currentSize = fileSize(path) + subscriber.putNext(MediaResourceData(path: path, size: currentSize)) + subscriber.putCompletion() + } + + return EmptyDisposable + } + } + + public func cacheResource(_ resource: CachedMediaResource, data: Data) { + self.cacheQueue.async { + let _ = try? data.write(to: URL(fileURLWithPath: self.cachePathForId(resource.id)), options: [.atomic]) + } } public func resourceStatus(_ resource: MediaResource) -> Signal { diff --git a/Postbox/MessageHistoryReadStateTable.swift b/Postbox/MessageHistoryReadStateTable.swift index 166ab72382..a6555d9c6a 100644 --- a/Postbox/MessageHistoryReadStateTable.swift +++ b/Postbox/MessageHistoryReadStateTable.swift @@ -233,7 +233,7 @@ final class MessageHistoryReadStateTable: Table { func applyOutgoingMaxReadId(_ messageId: MessageId) -> (CombinedPeerReadState?, Bool) { if let states = self.get(messageId.peerId), let state = states.namespaces[messageId.namespace] { if state.maxOutgoingReadId < messageId.id { - states.namespaces[messageId.namespace] = PeerReadState(maxIncomingReadId: state.maxIncomingReadId, maxOutgoingReadId: state.maxOutgoingReadId, maxKnownId: state.maxKnownId, count: state.count) + states.namespaces[messageId.namespace] = PeerReadState(maxIncomingReadId: state.maxIncomingReadId, maxOutgoingReadId: messageId.id, maxKnownId: state.maxKnownId, count: state.count) self.updatedPeerIds.insert(messageId.peerId) return (CombinedPeerReadState(states: states.namespaces.map({$0})), false) } diff --git a/Postbox/MessageHistoryView.swift b/Postbox/MessageHistoryView.swift index 87115c3ad9..77230a6353 100644 --- a/Postbox/MessageHistoryView.swift +++ b/Postbox/MessageHistoryView.swift @@ -74,12 +74,12 @@ public func ==(lhs: MessageHistoryEntryLocation, rhs: MessageHistoryEntryLocatio } public enum MessageHistoryEntry: Comparable { - case MessageEntry(Message, MessageHistoryEntryLocation?) + case MessageEntry(Message, Bool, MessageHistoryEntryLocation?) case HoleEntry(MessageHistoryHole, MessageHistoryEntryLocation?) public var index: MessageIndex { switch self { - case let .MessageEntry(message, _): + case let .MessageEntry(message, _, _): return MessageIndex(id: message.id, timestamp: message.timestamp) case let .HoleEntry(hole, _): return hole.maxIndex @@ -89,12 +89,12 @@ public enum MessageHistoryEntry: Comparable { public func ==(lhs: MessageHistoryEntry, rhs: MessageHistoryEntry) -> Bool { switch lhs { - case let .MessageEntry(lhsMessage, lhsLocation): + case let .MessageEntry(lhsMessage, lhsRead, lhsLocation): switch rhs { case .HoleEntry: return false - case let .MessageEntry(rhsMessage, rhsLocation): - if MessageIndex(lhsMessage) == MessageIndex(rhsMessage) && lhsMessage.flags == rhsMessage.flags && lhsLocation == rhsLocation { + case let .MessageEntry(rhsMessage, rhsRead, rhsLocation): + if MessageIndex(lhsMessage) == MessageIndex(rhsMessage) && lhsMessage.flags == rhsMessage.flags && lhsLocation == rhsLocation && lhsRead == rhsRead { return true } return false @@ -128,6 +128,7 @@ final class MutableMessageHistoryView { let tagMask: MessageTags? fileprivate var anchorIndex: MessageHistoryAnchorIndex fileprivate let combinedReadState: CombinedPeerReadState? + fileprivate var transientReadState: CombinedPeerReadState? fileprivate var earlier: MutableMessageHistoryEntry? fileprivate var later: MutableMessageHistoryEntry? fileprivate var entries: [MutableMessageHistoryEntry] @@ -137,6 +138,7 @@ final class MutableMessageHistoryView { self.id = id self.anchorIndex = anchorIndex self.combinedReadState = combinedReadState + self.transientReadState = combinedReadState self.earlier = earlier self.entries = entries self.later = later @@ -267,7 +269,7 @@ final class MutableMessageHistoryView { } case let .UpdateReadState(combinedReadState): hasChanges = true - //self.combinedReadState = combinedReadState + self.transientReadState = combinedReadState case let .UpdateEmbeddedMedia(index, embeddedMediaData): for i in 0 ..< self.entries.count { if case let .IntermediateMessageEntry(message, _) = self.entries[i] , MessageIndex(message) == index { @@ -556,14 +558,33 @@ public final class MessageHistoryView { self.anchorIndex = mutableView.anchorIndex.index var entries: [MessageHistoryEntry] = [] - for entry in mutableView.entries { - switch entry { - case let .HoleEntry(hole, location): - entries.append(.HoleEntry(hole, location)) - case let .MessageEntry(message, location): - entries.append(.MessageEntry(message, location)) - case .IntermediateMessageEntry: - assertionFailure("unexpected IntermediateMessageEntry in MessageHistoryView.init()") + if let transientReadState = mutableView.transientReadState { + for entry in mutableView.entries { + switch entry { + case let .HoleEntry(hole, location): + entries.append(.HoleEntry(hole, location)) + case let .MessageEntry(message, location): + let read: Bool + if message.flags.contains(.Incoming) { + read = false + } else { + read = transientReadState.isOutgoingMessageIdRead(message.id) + } + entries.append(.MessageEntry(message, read, location)) + case .IntermediateMessageEntry: + assertionFailure("unexpected IntermediateMessageEntry in MessageHistoryView.init()") + } + } + } else { + for entry in mutableView.entries { + switch entry { + case let .HoleEntry(hole, location): + entries.append(.HoleEntry(hole, location)) + case let .MessageEntry(message, location): + entries.append(.MessageEntry(message, false, location)) + case .IntermediateMessageEntry: + assertionFailure("unexpected IntermediateMessageEntry in MessageHistoryView.init()") + } } } self.entries = entries @@ -597,7 +618,7 @@ public final class MessageHistoryView { } if let _ = maxNamespaceIndex , index + 1 < entries.count { for i in index + 1 ..< entries.count { - if case let .MessageEntry(message, _) = entries[i] , !message.flags.contains(.Incoming) { + if case let .MessageEntry(message, _, _) = entries[i] , !message.flags.contains(.Incoming) { maxNamespaceIndex = MessageIndex(message) } else { break diff --git a/Postbox/PeerNotificationSettings.swift b/Postbox/PeerNotificationSettings.swift new file mode 100644 index 0000000000..32ad5004c5 --- /dev/null +++ b/Postbox/PeerNotificationSettings.swift @@ -0,0 +1,4 @@ + +public protocol PeerNotificationSettings: Coding { + func isEqual(to: PeerNotificationSettings) -> Bool +} diff --git a/Postbox/PeerNotificationSettingsTable.swift b/Postbox/PeerNotificationSettingsTable.swift new file mode 100644 index 0000000000..21a7c891d7 --- /dev/null +++ b/Postbox/PeerNotificationSettingsTable.swift @@ -0,0 +1,54 @@ +import Foundation + +final class PeerNotificationSettingsTable: Table { + private let sharedEncoder = Encoder() + private let sharedKey = ValueBoxKey(length: 8) + + private var cachedSettings: [PeerId: PeerNotificationSettings] = [:] + private var updatedPeerIds = Set() + + override init(valueBox: ValueBox, tableId: Int32) { + super.init(valueBox: valueBox, tableId: tableId) + } + + private func key(_ id: PeerId) -> ValueBoxKey { + self.sharedKey.setInt64(0, value: id.toInt64()) + return self.sharedKey + } + + func set(id: PeerId, settings: PeerNotificationSettings) { + self.cachedSettings[id] = settings + self.updatedPeerIds.insert(id) + } + + func get(_ id: PeerId) -> PeerNotificationSettings? { + if let settings = self.cachedSettings[id] { + return settings + } + if let value = self.valueBox.get(self.tableId, key: self.key(id)) { + if let settings = Decoder(buffer: value).decodeRootObject() as? PeerNotificationSettings { + self.cachedSettings[id] = settings + return settings + } + } + return nil + } + + override func clearMemoryCache() { + self.cachedSettings.removeAll() + self.updatedPeerIds.removeAll() + } + + override func beforeCommit() { + for peerId in self.updatedPeerIds { + if let settings = self.cachedSettings[peerId] { + self.sharedEncoder.reset() + self.sharedEncoder.encodeRootObject(settings) + + self.valueBox.set(self.tableId, key: self.key(peerId), value: self.sharedEncoder.readBufferNoCopy()) + } + } + + self.updatedPeerIds.removeAll() + } +} diff --git a/Postbox/PeerReadState.swift b/Postbox/PeerReadState.swift index ee3d20ba52..a0fa71469c 100644 --- a/Postbox/PeerReadState.swift +++ b/Postbox/PeerReadState.swift @@ -21,13 +21,53 @@ public func ==(lhs: PeerReadState, rhs: PeerReadState) -> Bool { return lhs.maxIncomingReadId == rhs.maxIncomingReadId && lhs.maxOutgoingReadId == rhs.maxOutgoingReadId && lhs.maxKnownId == rhs.maxKnownId && lhs.count == rhs.count } -public struct CombinedPeerReadState { +public struct CombinedPeerReadState: Equatable { let states: [(MessageId.Namespace, PeerReadState)] - var count: Int32 { + public var count: Int32 { var result: Int32 = 0 for (_, state) in self.states { result += state.count } return result } + + public static func ==(lhs: CombinedPeerReadState, rhs: CombinedPeerReadState) -> Bool { + if lhs.states.count != rhs.states.count { + return false + } + for (lhsNamespace, lhsState) in lhs.states { + var rhsFound = false + inner: for (rhsNamespace, rhsState) in rhs.states { + if rhsNamespace == lhsNamespace { + if lhsState != rhsState { + return false + } + rhsFound = true + break inner + } + } + if !rhsFound { + return false + } + } + return true + } + + public func isOutgoingMessageIdRead(_ id: MessageId) -> Bool { + for (namespace, readState) in self.states { + if namespace == id.namespace { + return readState.maxOutgoingReadId >= id.id + } + } + return false + } + + public func isIncomingMessageIdRead(_ id: MessageId) -> Bool { + for (namespace, readState) in self.states { + if namespace == id.namespace { + return readState.maxIncomingReadId >= id.id + } + } + return false + } } diff --git a/Postbox/PeerStatusTable.swift b/Postbox/PeerStatusTable.swift new file mode 100644 index 0000000000..ab50d10809 --- /dev/null +++ b/Postbox/PeerStatusTable.swift @@ -0,0 +1,54 @@ +import Foundation + +final class PeerStatusTable: Table { + private let sharedEncoder = Encoder() + private let sharedKey = ValueBoxKey(length: 8) + + private var cachedStatuses: [PeerId: Coding] = [:] + private var updatedPeerIds = Set() + + override init(valueBox: ValueBox, tableId: Int32) { + super.init(valueBox: valueBox, tableId: tableId) + } + + private func key(_ id: PeerId) -> ValueBoxKey { + self.sharedKey.setInt64(0, value: id.toInt64()) + return self.sharedKey + } + + func set(id: PeerId, status: Coding) { + self.cachedStatuses[id] = status + self.updatedPeerIds.insert(id) + } + + func get(_ id: PeerId) -> Coding? { + if let status = self.cachedStatuses[id] { + return status + } + if let value = self.valueBox.get(self.tableId, key: self.key(id)) { + if let status = Decoder(buffer: value).decodeRootObject() { + self.cachedStatuses[id] = status + return status + } + } + return nil + } + + override func clearMemoryCache() { + self.cachedStatuses.removeAll() + self.updatedPeerIds.removeAll() + } + + override func beforeCommit() { + for peerId in self.updatedPeerIds { + if let status = self.cachedStatuses[peerId] { + self.sharedEncoder.reset() + self.sharedEncoder.encodeRootObject(status) + + self.valueBox.set(self.tableId, key: self.key(peerId), value: self.sharedEncoder.readBufferNoCopy()) + } + } + + self.updatedPeerIds.removeAll() + } +} diff --git a/Postbox/PeerView.swift b/Postbox/PeerView.swift new file mode 100644 index 0000000000..39d004e2cb --- /dev/null +++ b/Postbox/PeerView.swift @@ -0,0 +1,90 @@ +import Foundation + +final class MutablePeerView { + let peerId: PeerId + var notificationSettings: PeerNotificationSettings? + var cachedData: CachedPeerData? + var peers: [PeerId: Peer] = [:] + + init(peerId: PeerId, notificationSettings: PeerNotificationSettings?, cachedData: CachedPeerData?, getPeer: (PeerId) -> Peer?) { + self.peerId = peerId + self.notificationSettings = notificationSettings + self.cachedData = cachedData + var peerIds = Set() + peerIds.insert(peerId) + if let cachedData = cachedData { + peerIds.formUnion(cachedData.peerIds) + } + for id in peerIds { + if let peer = getPeer(id) { + self.peers[id] = peer + } + } + } + + func replay(updatedPeers: [PeerId: Peer], updatedNotificationSettings: [PeerId: PeerNotificationSettings], updatedCachedPeerData: [PeerId: CachedPeerData], getPeer: (PeerId) -> Peer?) -> Bool { + var updated = false + + if let cachedData = updatedCachedPeerData[self.peerId], self.cachedData == nil || self.cachedData!.peerIds != cachedData.peerIds { + self.cachedData = cachedData + updated = true + + var peerIds = Set() + peerIds.insert(self.peerId) + peerIds.formUnion(cachedData.peerIds) + + for id in peerIds { + if let peer = updatedPeers[id] { + self.peers[id] = peer + } else if let peer = getPeer(id) { + self.peers[id] = peer + } + } + + var removePeerIds: [PeerId] = [] + for peerId in self.peers.keys { + if !peerIds.contains(peerId) { + removePeerIds.append(peerId) + } + } + + for peerId in removePeerIds { + self.peers.removeValue(forKey: peerId) + } + } else { + var peerIds = Set() + peerIds.insert(self.peerId) + if let cachedData = self.cachedData { + peerIds.formUnion(cachedData.peerIds) + } + + for id in peerIds { + if let peer = updatedPeers[id] { + self.peers[id] = peer + updated = true + } + } + } + + if let notificationSettings = updatedNotificationSettings[self.peerId] { + self.notificationSettings = notificationSettings + updated = true + } + + return updated + } +} + +public final class PeerView { + public let peerId: PeerId + public let cachedData: CachedPeerData? + public let notificationSettings: PeerNotificationSettings? + public let peers: [PeerId: Peer] + + init(_ mutableView: MutablePeerView) { + self.peerId = mutableView.peerId + self.cachedData = mutableView.cachedData + self.notificationSettings = mutableView.notificationSettings + self.peers = mutableView.peers + } +} diff --git a/Postbox/Postbox.swift b/Postbox/Postbox.swift index ec26066924..2dc54776f8 100644 --- a/Postbox/Postbox.swift +++ b/Postbox/Postbox.swift @@ -94,10 +94,22 @@ public final class Modifier { return self.postbox?.readStateTable.getCombinedState(id)?.states } + public func getPeerNotificationSettings(_ id: PeerId) -> PeerNotificationSettings? { + return self.postbox?.peerNotificationSettingsTable.get(id) + } + public func updatePeers(_ peers: [Peer], update: (Peer, Peer) -> Peer?) { self.postbox?.updatePeers(peers, update: update) } + public func updatePeerNotificationSettings(_ notificationSettings: [PeerId: PeerNotificationSettings]) { + self.postbox?.updatePeerNotificationSettings(notificationSettings) + } + + public func updatePeerCachedData(peerIds: Set, update: (PeerId, CachedPeerData?) -> CachedPeerData?) { + self.postbox?.updatePeerCachedData(peerIds: peerIds, update: update) + } + public func replaceContactPeerIds(_ peerIds: Set) { self.postbox?.replaceContactPeerIds(peerIds) } @@ -180,6 +192,9 @@ public final class Postbox { private var currentRemovedHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]] = [:] private var currentFilledHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]] = [:] private var currentUpdatedPeers: [PeerId: Peer] = [:] + private var currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings] = [:] + private var currentUpdatedCachedPeerData: [PeerId: CachedPeerData] = [:] + private var currentReplaceChatListHoles: [(MessageIndex, ChatListHole?)] = [] private var currentReplacedContactPeerIds: Set? private var currentUpdatedMasterClientId: Int64? @@ -200,6 +215,9 @@ public final class Postbox { var metadataTable: MetadataTable! var keychainTable: KeychainTable! var peerTable: PeerTable! + var peerNotificationSettingsTable: PeerNotificationSettingsTable! + var cachedPeerDataTable: CachedPeerDataTable! + var peerStatusTable: PeerStatusTable! var globalMessageIdsTable: GlobalMessageIdsTable! var messageHistoryIndexTable: MessageHistoryIndexTable! var messageHistoryTable: MessageHistoryTable! @@ -296,7 +314,7 @@ public final class Postbox { self.metadataTable = MetadataTable(valueBox: self.valueBox, tableId: 0) let userVersion: Int32? = self.metadataTable.userVersion() - let currentUserVersion: Int32 = 8 + let currentUserVersion: Int32 = 10 if userVersion != currentUserVersion { self.valueBox.drop() @@ -320,6 +338,9 @@ public final class Postbox { self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, tableId: 13) self.contactsTable = ContactTable(valueBox: self.valueBox, tableId: 16) self.peerRatingTable = RatingTable(valueBox: self.valueBox, tableId: 17) + self.cachedPeerDataTable = CachedPeerDataTable(valueBox: self.valueBox, tableId: 18) + self.peerNotificationSettingsTable = PeerNotificationSettingsTable(valueBox: self.valueBox, tableId: 19) + self.peerStatusTable = PeerStatusTable(valueBox: self.valueBox, tableId: 20) self.tables.append(self.keychainTable) self.tables.append(self.peerTable) @@ -338,11 +359,18 @@ public final class Postbox { self.tables.append(self.peerChatStateTable) self.tables.append(self.contactsTable) self.tables.append(self.peerRatingTable) + self.tables.append(self.peerNotificationSettingsTable) + self.tables.append(self.cachedPeerDataTable) + self.tables.append(self.peerStatusTable) self.transactionStateVersion = self.metadataTable.transactionStateVersion() self.viewTracker = ViewTracker(queue: self.queue, fetchEarlierHistoryEntries: self.fetchEarlierHistoryEntries, fetchLaterHistoryEntries: self.fetchLaterHistoryEntries, fetchEarlierChatEntries: self.fetchEarlierChatEntries, fetchLaterChatEntries: self.fetchLaterChatEntries, fetchAnchorIndex: self.fetchAnchorIndex, renderMessage: self.renderIntermediateMessage, getPeer: { peerId in return self.peerTable.get(peerId) + }, getPeerNotificationSettings: { peerId in + return self.peerNotificationSettingsTable.get(peerId) + }, getCachedPeerData: { peerId in + return self.cachedPeerDataTable.get(peerId) }, unsentMessageIndices: self.messageHistoryUnsentTable!.get(), synchronizePeerReadStateOperations: self.synchronizeReadStateTable!.get()) print("(Postbox initialization took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") @@ -611,7 +639,7 @@ public final class Postbox { for entry in intermediateEntries { switch entry { case let .Message(message): - entries.append(.IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId))) + entries.append(.IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId), self.peerNotificationSettingsTable.get(message.id.peerId))) case let .Hole(hole): entries.append(.HoleEntry(hole)) case let .Nothing(index): @@ -622,7 +650,7 @@ public final class Postbox { if let intermediateLower = intermediateLower { switch intermediateLower { case let .Message(message): - lower = .IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId)) + lower = .IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId), self.peerNotificationSettingsTable.get(message.id.peerId)) case let .Hole(hole): lower = .HoleEntry(hole) case let .Nothing(index): @@ -633,7 +661,7 @@ public final class Postbox { if let intermediateUpper = intermediateUpper { switch intermediateUpper { case let .Message(message): - upper = .IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId)) + upper = .IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId), self.peerNotificationSettingsTable.get(message.id.peerId)) case let .Hole(hole): upper = .HoleEntry(hole) case let .Nothing(index): @@ -650,7 +678,7 @@ public final class Postbox { for entry in intermediateEntries { switch entry { case let .Message(message): - entries.append(.IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId))) + entries.append(.IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId), self.peerNotificationSettingsTable.get(message.id.peerId))) case let .Hole(hole): entries.append(.HoleEntry(hole)) case let .Nothing(index): @@ -666,7 +694,7 @@ public final class Postbox { for entry in intermediateEntries { switch entry { case let .Message(message): - entries.append(.IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId))) + entries.append(.IntermediateMessageEntry(message, self.readStateTable.getCombinedState(message.id.peerId), self.peerNotificationSettingsTable.get(message.id.peerId))) case let .Nothing(index): entries.append(.Nothing(index)) case let .Hole(index): @@ -708,7 +736,7 @@ public final class Postbox { self.chatListTable.replaceHole(index, hole: hole, operations: &chatListOperations) } - let transaction = PostboxTransaction(currentOperationsByPeerId: self.currentOperationsByPeerId, peerIdsWithFilledHoles: self.currentFilledHolesByPeerId, removedHolesByPeerId: self.currentRemovedHolesByPeerId, chatListOperations: chatListOperations, currentUpdatedPeers: self.currentUpdatedPeers, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, updatedMedia: self.currentUpdatedMedia, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentUpdatedMasterClientId: currentUpdatedMasterClientId) + let transaction = PostboxTransaction(currentOperationsByPeerId: self.currentOperationsByPeerId, peerIdsWithFilledHoles: self.currentFilledHolesByPeerId, removedHolesByPeerId: self.currentRemovedHolesByPeerId, chatListOperations: chatListOperations, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, updatedMedia: self.currentUpdatedMedia, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentUpdatedMasterClientId: currentUpdatedMasterClientId) var updatedTransactionState: Int64? var updatedMasterClientId: Int64? if !transaction.isEmpty { @@ -732,6 +760,8 @@ public final class Postbox { self.currentUpdatedMedia.removeAll() self.currentReplacedContactPeerIds = nil self.currentUpdatedMasterClientId = nil + self.currentUpdatedPeerNotificationSettings.removeAll() + self.currentUpdatedCachedPeerData.removeAll() for table in self.tables { table.beforeCommit() @@ -763,6 +793,26 @@ public final class Postbox { } } + fileprivate func updatePeerNotificationSettings(_ notificationSettings: [PeerId: PeerNotificationSettings]) { + for (peerId, settings) in notificationSettings { + let currentSettings = self.peerNotificationSettingsTable.get(peerId) + if currentSettings == nil || !(currentSettings!.isEqual(to: settings)) { + self.peerNotificationSettingsTable.set(id: peerId, settings: settings) + self.currentUpdatedPeerNotificationSettings[peerId] = settings + } + } + } + + fileprivate func updatePeerCachedData(peerIds: Set, update: (PeerId, CachedPeerData?) -> CachedPeerData?) { + for peerId in peerIds { + let currentData = self.cachedPeerDataTable.get(peerId) + if let updatedData = update(peerId, currentData) { + self.cachedPeerDataTable.set(id: peerId, data: updatedData) + self.currentUpdatedCachedPeerData[peerId] = updatedData + } + } + } + fileprivate func replaceContactPeerIds(_ peerIds: Set) { self.contactsTable.replace(peerIds) @@ -937,7 +987,7 @@ public final class Postbox { let (entries, earlier, later) = self.fetchAroundChatEntries(index, count: count) let mutableView = MutableChatListView(earlier: earlier, entries: entries, later: later, count: count) - mutableView.render(self.renderIntermediateMessage) + mutableView.render(self.renderIntermediateMessage, getPeerNotificationSettings: { self.peerNotificationSettingsTable.get($0) }) let (index, signal) = self.viewTracker.addChatListView(mutableView) @@ -1012,7 +1062,24 @@ public final class Postbox { } |> switchToLatest } - public func peerWithId(_ id: PeerId) -> Signal { + 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), getPeer: { self.peerTable.get($0) }) + let (index, signal) = self.viewTracker.addPeerView(view) + + return (.single(PeerView(view)) + |> then(signal)) + |> afterDisposed { [weak self] in + if let strongSelf = self { + strongSelf.queue.async { + strongSelf.viewTracker.removePeerView(index) + } + } + } + } |> switchToLatest + } + + public func loadedPeerWithId(_ id: PeerId) -> Signal { return self.modify { modifier -> Signal in if let peer = self.peerTable.get(id) { return .single(peer) diff --git a/Postbox/PostboxTransaction.swift b/Postbox/PostboxTransaction.swift index 764c343f5a..4ef39497fc 100644 --- a/Postbox/PostboxTransaction.swift +++ b/Postbox/PostboxTransaction.swift @@ -6,6 +6,9 @@ final class PostboxTransaction { let removedHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]] let chatListOperations: [ChatListOperation] let currentUpdatedPeers: [PeerId: Peer] + let currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings] + let currentUpdatedCachedPeerData: [PeerId: CachedPeerData] + let unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation] let updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?] let updatedMedia: [MediaId: Media?] @@ -28,6 +31,12 @@ final class PostboxTransaction { if !currentUpdatedPeers.isEmpty { return false } + if !currentUpdatedPeerNotificationSettings.isEmpty { + return false + } + if !currentUpdatedCachedPeerData.isEmpty { + return false + } if !unsentMessageOperations.isEmpty { return false } @@ -46,12 +55,14 @@ final class PostboxTransaction { return true } - init(currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], peerIdsWithFilledHoles: [PeerId: [MessageIndex: HoleFillDirection]], removedHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]], chatListOperations: [ChatListOperation], currentUpdatedPeers: [PeerId: Peer], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], updatedMedia: [MediaId: Media?], replaceContactPeerIds: Set?, currentUpdatedMasterClientId: Int64?) { + init(currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], peerIdsWithFilledHoles: [PeerId: [MessageIndex: HoleFillDirection]], removedHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]], chatListOperations: [ChatListOperation], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], updatedMedia: [MediaId: Media?], replaceContactPeerIds: Set?, currentUpdatedMasterClientId: Int64?) { self.currentOperationsByPeerId = currentOperationsByPeerId self.peerIdsWithFilledHoles = peerIdsWithFilledHoles self.removedHolesByPeerId = removedHolesByPeerId self.chatListOperations = chatListOperations self.currentUpdatedPeers = currentUpdatedPeers + self.currentUpdatedPeerNotificationSettings = currentUpdatedPeerNotificationSettings; + self.currentUpdatedCachedPeerData = currentUpdatedCachedPeerData self.unsentMessageOperations = unsentMessageOperations self.updatedSynchronizePeerReadStateOperations = updatedSynchronizePeerReadStateOperations self.updatedMedia = updatedMedia diff --git a/Postbox/SqliteValueBox.swift b/Postbox/SqliteValueBox.swift index 79d90bdf20..12ffba1c27 100644 --- a/Postbox/SqliteValueBox.swift +++ b/Postbox/SqliteValueBox.swift @@ -121,7 +121,7 @@ public final class SqliteValueBox: ValueBox { //database.execute("PRAGMA wal_autocheckpoint=200") database.execute("PRAGMA journal_size_limit=1536") - var statement: OpaquePointer? = nil + /*var statement: OpaquePointer? = nil sqlite3_prepare_v2(database.handle, "PRAGMA integrity_check", -1, &statement, nil) let preparedStatement = SqlitePreparedStatement(statement: statement) while preparedStatement.step() { @@ -132,7 +132,7 @@ public final class SqliteValueBox: ValueBox { //let value = preparedStatement.stringAt(0) //print("integrity_check: \(value)") } - preparedStatement.destroy() + preparedStatement.destroy()*/ sqlite3_busy_timeout(database.handle, 10000000) diff --git a/Postbox/ViewTracker.swift b/Postbox/ViewTracker.swift index d994ce8436..c339fa0f00 100644 --- a/Postbox/ViewTracker.swift +++ b/Postbox/ViewTracker.swift @@ -21,6 +21,8 @@ final class ViewTracker { private let fetchAnchorIndex: (MessageId) -> MessageHistoryAnchorIndex? private let renderMessage: (IntermediateMessage) -> Message private let getPeer: (PeerId) -> Peer? + private let getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings? + private let getCachedPeerData: (PeerId) -> CachedPeerData? private var chatListViews = Bag<(MutableChatListView, ValuePipe<(ChatListView, ViewUpdateType)>)>() private var messageHistoryViews: [PeerId: Bag<(MutableMessageHistoryView, ValuePipe<(MessageHistoryView, ViewUpdateType)>)>] = [:] @@ -39,7 +41,9 @@ final class ViewTracker { private var synchronizeReadStatesView: MutableSynchronizePeerReadStatesView private let synchronizePeerReadStatesViewSubscribers = Bag>() - init(queue: Queue, fetchEarlierHistoryEntries: @escaping (PeerId, MessageIndex?, Int, MessageTags?) -> [MutableMessageHistoryEntry], fetchLaterHistoryEntries: @escaping (PeerId, MessageIndex?, Int, MessageTags?) -> [MutableMessageHistoryEntry], fetchEarlierChatEntries: @escaping (MessageIndex?, Int) -> [MutableChatListEntry], fetchLaterChatEntries: @escaping (MessageIndex?, Int) -> [MutableChatListEntry], fetchAnchorIndex: @escaping (MessageId) -> MessageHistoryAnchorIndex?, renderMessage: @escaping (IntermediateMessage) -> Message, getPeer: @escaping (PeerId) -> Peer?, unsentMessageIndices: [MessageIndex], synchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation]) { + private var peerViews = Bag<(MutablePeerView, ValuePipe)>() + + init(queue: Queue, fetchEarlierHistoryEntries: @escaping (PeerId, MessageIndex?, Int, MessageTags?) -> [MutableMessageHistoryEntry], fetchLaterHistoryEntries: @escaping (PeerId, MessageIndex?, Int, MessageTags?) -> [MutableMessageHistoryEntry], fetchEarlierChatEntries: @escaping (MessageIndex?, Int) -> [MutableChatListEntry], fetchLaterChatEntries: @escaping (MessageIndex?, Int) -> [MutableChatListEntry], fetchAnchorIndex: @escaping (MessageId) -> MessageHistoryAnchorIndex?, renderMessage: @escaping (IntermediateMessage) -> Message, getPeer: @escaping (PeerId) -> Peer?, getPeerNotificationSettings: @escaping (PeerId) -> PeerNotificationSettings?, getCachedPeerData: @escaping (PeerId) -> CachedPeerData?, unsentMessageIndices: [MessageIndex], synchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation]) { self.queue = queue self.fetchEarlierHistoryEntries = fetchEarlierHistoryEntries self.fetchLaterHistoryEntries = fetchLaterHistoryEntries @@ -48,6 +52,8 @@ final class ViewTracker { self.fetchAnchorIndex = fetchAnchorIndex self.renderMessage = renderMessage self.getPeer = getPeer + self.getPeerNotificationSettings = getPeerNotificationSettings + self.getCachedPeerData = getCachedPeerData self.unsentMessageView = UnsentMessageHistoryView(indices: unsentMessageIndices) self.synchronizeReadStatesView = MutableSynchronizePeerReadStatesView(operations: synchronizePeerReadStateOperations) @@ -146,6 +152,17 @@ final class ViewTracker { } } + func addPeerView(_ view: MutablePeerView) -> (Bag<(MutablePeerView, ValuePipe)>.Index, Signal) { + let record = (view, ValuePipe()) + let index = self.peerViews.add(record) + + return (index, record.1.signal()) + } + + func removePeerView(_ index: Bag<(MutablePeerView, ValuePipe)>.Index) { + self.peerViews.remove(index) + } + func refreshViewsDueToExternalTransaction(fetchAroundChatEntries: (_ index: MessageIndex, _ count: Int) -> (entries: [MutableChatListEntry], earlier: MutableChatListEntry?, later: MutableChatListEntry?), fetchAroundHistoryEntries: (_ index: MessageIndex, _ count: Int, _ tagMask: MessageTags?) -> (entries: [MutableMessageHistoryEntry], lower: MutableMessageHistoryEntry?, upper: MutableMessageHistoryEntry?), fetchUnsendMessageIndices: () -> [MessageIndex], fetchSynchronizePeerReadStateOperations: () -> [PeerId: PeerReadStateSynchronizationOperation]) { var updateTrackedHolesPeerIds: [PeerId] = [] @@ -164,7 +181,7 @@ final class ViewTracker { for (mutableView, pipe) in self.chatListViews.copyItems() { if mutableView.refreshDueToExternalTransaction(fetchAroundChatEntries: fetchAroundChatEntries) { - mutableView.render(self.renderMessage) + mutableView.render(self.renderMessage, getPeerNotificationSettings: self.getPeerNotificationSettings) pipe.putNext((ChatListView(mutableView), .Generic)) } } @@ -180,6 +197,32 @@ final class ViewTracker { if self.synchronizeReadStatesView.refreshDueToExternalTransaction(fetchSynchronizePeerReadStateOperations: fetchSynchronizePeerReadStateOperations) { self.synchronizeReadStateViewUpdated() } + + for (mutableView, pipe) in self.peerViews.copyItems() { + var updatedPeers: [PeerId: Peer] = [:] + if let peer = self.getPeer(mutableView.peerId) { + updatedPeers[peer.id] = peer + } + + var updatedNotificationSettings: [PeerId: PeerNotificationSettings] = [:] + if let notificationSettings = self.getPeerNotificationSettings(mutableView.peerId) { + updatedNotificationSettings[mutableView.peerId] = notificationSettings + } + + var updatedCachedPeerData: [PeerId: CachedPeerData] = [:] + if let cachedPeerData = self.getCachedPeerData(mutableView.peerId) { + updatedCachedPeerData[mutableView.peerId] = cachedPeerData + for peerIds in cachedPeerData.peerIds { + if let peer = self.getPeer(mutableView.peerId) { + updatedPeers[peer.id] = peer + } + } + } + + if mutableView.replay(updatedPeers: updatedPeers, updatedNotificationSettings: updatedNotificationSettings, updatedCachedPeerData: updatedCachedPeerData, getPeer: self.getPeer) { + pipe.putNext(PeerView(mutableView)) + } + } } func updateViews(transaction: PostboxTransaction) { @@ -232,12 +275,12 @@ final class ViewTracker { } } - if transaction.chatListOperations.count != 0 { + if !transaction.chatListOperations.isEmpty || !transaction.currentUpdatedPeerNotificationSettings.isEmpty { for (mutableView, pipe) in self.chatListViews.copyItems() { let context = MutableChatListViewReplayContext() - if mutableView.replay(transaction.chatListOperations, context: context) { + if mutableView.replay(transaction.chatListOperations, updatedPeerNotificationSettings: transaction.currentUpdatedPeerNotificationSettings, context: context) { mutableView.complete(context: context, fetchEarlier: self.fetchEarlierChatEntries, fetchLater: self.fetchLaterChatEntries) - mutableView.render(self.renderMessage) + mutableView.render(self.renderMessage, getPeerNotificationSettings: self.getPeerNotificationSettings) pipe.putNext((ChatListView(mutableView), .Generic)) } } @@ -270,6 +313,12 @@ final class ViewTracker { } } } + + for (mutableView, pipe) in self.peerViews.copyItems() { + if mutableView.replay(updatedPeers: transaction.currentUpdatedPeers, updatedNotificationSettings: transaction.currentUpdatedPeerNotificationSettings, updatedCachedPeerData: transaction.currentUpdatedCachedPeerData, getPeer: self.getPeer) { + pipe.putNext(PeerView(mutableView)) + } + } } private func updateTrackedChatListHoles() {