import Foundation public struct PeerGroupUnreadCounters: PostboxCoding, Equatable { public var messageCount: Int32 public var chatCount: Int32 public init(messageCount: Int32, chatCount: Int32) { self.messageCount = messageCount self.chatCount = chatCount } public init(decoder: PostboxDecoder) { self.messageCount = decoder.decodeInt32ForKey("m", orElse: 0) self.chatCount = decoder.decodeInt32ForKey("c", orElse: 0) } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.messageCount, forKey: "m") encoder.encodeInt32(self.chatCount, forKey: "c") } } public struct PeerGroupUnreadCountersSummary: PostboxCoding, Equatable { public var all: PeerGroupUnreadCounters public init(all: PeerGroupUnreadCounters) { self.all = all } public init(decoder: PostboxDecoder) { self.all = decoder.decodeObjectForKey("a", decoder: { PeerGroupUnreadCounters(decoder: $0) }) as! PeerGroupUnreadCounters } public func encode(_ encoder: PostboxEncoder) { encoder.encodeObject(self.all, forKey: "a") } } public struct PeerGroupUnreadCountersCombinedSummary: PostboxCoding, Equatable { public enum CountingCategory { case chats case messages } public enum MuteCategory { case all } public var namespaces: [MessageId.Namespace: PeerGroupUnreadCountersSummary] public init(namespaces: [MessageId.Namespace: PeerGroupUnreadCountersSummary]) { self.namespaces = namespaces } public init(decoder: PostboxDecoder) { self.namespaces = decoder.decodeObjectDictionaryForKey("n", keyDecoder: { $0.decodeInt32ForKey("k", orElse: 0) }, valueDecoder: { PeerGroupUnreadCountersSummary(decoder: $0) }) } public func encode(_ encoder: PostboxEncoder) { encoder.encodeObjectDictionary(self.namespaces, forKey: "n", keyEncoder: { $1.encodeInt32($0, forKey: "k") }) } public func count(countingCategory: CountingCategory, mutedCategory: MuteCategory) -> Int32 { var result: Int32 = 0 for (_, summary) in self.namespaces { switch mutedCategory { case .all: switch countingCategory { case .chats: result = result &+ summary.all.chatCount case .messages: result = result &+ summary.all.messageCount } } } return result } } public enum ChatListTotalUnreadStateCategory: Int32 { case filtered = 0 case raw = 1 } public enum ChatListTotalUnreadStateStats: Int32 { case messages = 0 case chats = 1 } public struct ChatListTotalUnreadState: PostboxCoding, Equatable { public var absoluteCounters: [PeerSummaryCounterTags: ChatListTotalUnreadCounters] public var filteredCounters: [PeerSummaryCounterTags: ChatListTotalUnreadCounters] public init(absoluteCounters: [PeerSummaryCounterTags: ChatListTotalUnreadCounters], filteredCounters: [PeerSummaryCounterTags: ChatListTotalUnreadCounters]) { self.absoluteCounters = absoluteCounters self.filteredCounters = filteredCounters } public init(decoder: PostboxDecoder) { self.absoluteCounters = decoder.decodeObjectDictionaryForKey("ad", keyDecoder: { decoder in return PeerSummaryCounterTags(rawValue: decoder.decodeInt32ForKey("k", orElse: 0)) }, valueDecoder: { decoder in return ChatListTotalUnreadCounters(decoder: decoder) }) self.filteredCounters = decoder.decodeObjectDictionaryForKey("fd", keyDecoder: { decoder in return PeerSummaryCounterTags(rawValue: decoder.decodeInt32ForKey("k", orElse: 0)) }, valueDecoder: { decoder in return ChatListTotalUnreadCounters(decoder: decoder) }) } public func encode(_ encoder: PostboxEncoder) { encoder.encodeObjectDictionary(self.absoluteCounters, forKey: "ad", keyEncoder: { key, encoder in encoder.encodeInt32(key.rawValue, forKey: "k") }) encoder.encodeObjectDictionary(self.filteredCounters, forKey: "fd", keyEncoder: { key, encoder in encoder.encodeInt32(key.rawValue, forKey: "k") }) } public func count(for category: ChatListTotalUnreadStateCategory, in statsType: ChatListTotalUnreadStateStats, with tags: PeerSummaryCounterTags) -> Int32 { let counters: [PeerSummaryCounterTags: ChatListTotalUnreadCounters] switch category { case .raw: counters = self.absoluteCounters case .filtered: counters = self.filteredCounters } var result: Int32 = 0 for tag in tags { if let category = counters[tag] { switch statsType { case .messages: result = result &+ category.messageCount case .chats: result = result &+ category.chatCount } } } return result } } final class GroupMessageStatsTable: Table { private var cachedEntries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary]? private var updatedGroupIds = Set() static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .int64, compactValuesOnCreation: true) } private func preloadCache() { if self.cachedEntries == nil { var entries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary] = [:] self.valueBox.scanInt64(self.table, values: { key, value in let groupIdValue: Int32 = Int32(clamping: key) let groupId = PeerGroupId(rawValue: groupIdValue) let state = PeerGroupUnreadCountersCombinedSummary(decoder: PostboxDecoder(buffer: value)) entries[groupId] = state return true }) self.cachedEntries = entries } } func removeAll() { self.preloadCache() for groupId in self.cachedEntries!.keys { self.set(groupId: groupId, summary: PeerGroupUnreadCountersCombinedSummary(namespaces: [:])) } } func get(groupId: PeerGroupId) -> PeerGroupUnreadCountersCombinedSummary { self.preloadCache() if let state = self.cachedEntries?[groupId] { return state } else { return PeerGroupUnreadCountersCombinedSummary(namespaces: [:]) } } func set(groupId: PeerGroupId, summary: PeerGroupUnreadCountersCombinedSummary) { self.preloadCache() let previousSummary = self.get(groupId: groupId) if previousSummary != summary { self.cachedEntries![groupId] = summary self.updatedGroupIds.insert(groupId) } } override func clearMemoryCache() { self.cachedEntries = nil assert(self.updatedGroupIds.isEmpty) } override func beforeCommit() { if !self.updatedGroupIds.isEmpty { if let cachedEntries = self.cachedEntries { let sharedKey = ValueBoxKey(length: 8) let sharedEncoder = PostboxEncoder() for groupId in self.updatedGroupIds { sharedKey.setInt64(0, value: Int64(groupId.rawValue)) sharedEncoder.reset() if let state = cachedEntries[groupId] { state.encode(sharedEncoder) self.valueBox.set(self.table, key: sharedKey, value: sharedEncoder.readBufferNoCopy()) } else { self.valueBox.remove(self.table, key: sharedKey, secure: false) } } } else { assertionFailure() } self.updatedGroupIds.removeAll() } } }