import Foundation public struct StoredMessageHistoryThreadInfo: Equatable, PostboxCoding { public struct Summary: Equatable, PostboxCoding { public var totalUnreadCount: Int32 public var mutedUntil: Int32? public init(totalUnreadCount: Int32, mutedUntil: Int32?) { self.totalUnreadCount = totalUnreadCount self.mutedUntil = mutedUntil } public init(decoder: PostboxDecoder) { self.totalUnreadCount = decoder.decodeInt32ForKey("u", orElse: 0) self.mutedUntil = decoder.decodeOptionalInt32ForKey("m") } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.totalUnreadCount, forKey: "u") if let mutedUntil = self.mutedUntil { encoder.encodeInt32(mutedUntil, forKey: "m") } else { encoder.encodeNil(forKey: "m") } } } public var data: CodableEntry public var summary: Summary public init(data: CodableEntry, summary: Summary) { self.data = data self.summary = summary } public init(decoder: PostboxDecoder) { self.data = CodableEntry(data: decoder.decodeDataForKey("d")!) self.summary = decoder.decodeObjectForKey("s", decoder: { return Summary(decoder: $0) }) as! Summary } public func encode(_ encoder: PostboxEncoder) { encoder.encodeData(self.data.data, forKey: "d") encoder.encodeObject(self.summary, forKey: "s") } } private func extractKey(_ key: ValueBoxKey) -> MessageIndex { return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8), id: key.getInt32(8 + 8 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4)) } class MessageHistoryThreadReverseIndexTable: Table { static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) } private let sharedKey = ValueBoxKey(length: 8 + 8) override init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool) { super.init(valueBox: valueBox, table: table, useCaches: useCaches) } private func key(peerId: PeerId, threadId: Int64, key: ValueBoxKey) -> ValueBoxKey { key.setInt64(0, value: peerId.toInt64()) key.setInt64(8, value: threadId) return key } func get(peerId: PeerId, threadId: Int64) -> MessageIndex? { if let value = self.valueBox.get(self.table, key: self.key(peerId: peerId, threadId: threadId, key: self.sharedKey)) { var result: MessageIndex? withExtendedLifetime(value, { let readBuffer = ReadBuffer(memoryBufferNoCopy: value) var namespace: Int32 = 0 readBuffer.read(&namespace, offset: 0, length: 4) var id: Int32 = 0 readBuffer.read(&id, offset: 0, length: 4) var timestamp: Int32 = 0 readBuffer.read(×tamp, offset: 0, length: 4) result = MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: id), timestamp: timestamp) }) return result } else { return nil } } func set(peerId: PeerId, threadId: Int64, timestamp: Int32, namespace: MessageId.Namespace, id: MessageId.Id, hasValue: Bool) { if hasValue { let buffer = WriteBuffer() var namespace = namespace buffer.write(&namespace, length: 4) var id = id buffer.write(&id, length: 4) var timestamp = timestamp buffer.write(×tamp, length: 4) withExtendedLifetime(buffer, { self.valueBox.set(self.table, key: self.key(peerId: peerId, threadId: threadId, key: self.sharedKey), value: buffer.readBufferNoCopy()) }) } else { self.valueBox.remove(self.table, key: self.key(peerId: peerId, threadId: threadId, key: self.sharedKey), secure: false) } } } class MessageHistoryThreadIndexTable: Table { static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) } private struct UpdatedEntry { var value: StoredMessageHistoryThreadInfo? } enum IndexBoundary { case lowerBound case upperBound case index(StoredPeerThreadCombinedState.Index) } private let reverseIndexTable: MessageHistoryThreadReverseIndexTable private let seedConfiguration: SeedConfiguration private let sharedKey = ValueBoxKey(length: 8 + 4 + 8 + 4 + 4) private var updatedInfoItems: [MessageHistoryThreadsTable.ItemId: UpdatedEntry] = [:] init(valueBox: ValueBox, table: ValueBoxTable, reverseIndexTable: MessageHistoryThreadReverseIndexTable, seedConfiguration: SeedConfiguration, useCaches: Bool) { self.reverseIndexTable = reverseIndexTable self.seedConfiguration = seedConfiguration super.init(valueBox: valueBox, table: table, useCaches: useCaches) } private func key(peerId: PeerId, timestamp: Int32, threadId: Int64, namespace: MessageId.Namespace, id: MessageId.Id, key: ValueBoxKey) -> ValueBoxKey { key.setInt64(0, value: peerId.toInt64()) key.setInt32(8, value: timestamp) key.setInt64(8 + 4, value: threadId) key.setInt32(8 + 4 + 8, value: namespace) key.setInt32(8 + 4 + 8 + 4, value: id) return key } private static func extract(key: ValueBoxKey) -> (threadId: Int64, index: MessageIndex) { return ( threadId: key.getInt64(8 + 4), index: MessageIndex( id: MessageId( peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 4 + 8), id: key.getInt32(8 + 4 + 8 + 4) ), timestamp: key.getInt32(8) ) ) } private func lowerBound(peerId: PeerId) -> ValueBoxKey { let key = ValueBoxKey(length: 8) key.setInt64(0, value: peerId.toInt64()) return key } private func upperBound(peerId: PeerId) -> ValueBoxKey { return self.lowerBound(peerId: peerId).successor } func get(peerId: PeerId, threadId: Int64) -> StoredMessageHistoryThreadInfo? { if let updated = self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] { return updated.value } else { if let itemIndex = self.reverseIndexTable.get(peerId: peerId, threadId: threadId) { if let value = self.valueBox.get(self.table, key: self.key(peerId: itemIndex.id.peerId, timestamp: itemIndex.timestamp, threadId: threadId, namespace: itemIndex.id.namespace, id: itemIndex.id.id, key: self.sharedKey)) { if value.length != 0 { let decoder = PostboxDecoder(buffer: value) let state = StoredMessageHistoryThreadInfo(decoder: decoder) return state } else { return nil } } else { return nil } } else { return nil } } } func set(peerId: PeerId, threadId: Int64, info: StoredMessageHistoryThreadInfo?) { self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] = UpdatedEntry(value: info) } func replay(threadsTable: MessageHistoryThreadsTable, namespaces: Set, updatedIds: Set) -> Set { var peerIds = Set() for itemId in updatedIds.union(Set(self.updatedInfoItems.keys)) { let topIndex = threadsTable.getTop(peerId: itemId.peerId, threadId: itemId.threadId, namespaces: namespaces) let previousIndex = self.reverseIndexTable.get(peerId: itemId.peerId, threadId: itemId.threadId) if topIndex != previousIndex || self.updatedInfoItems[itemId] != nil { peerIds.insert(itemId.peerId) var info: ReadBuffer? if let previousIndex = previousIndex { let previousKey = self.key(peerId: itemId.peerId, timestamp: previousIndex.timestamp, threadId: itemId.threadId, namespace: previousIndex.id.namespace, id: previousIndex.id.id, key: self.sharedKey) if let previousValue = self.valueBox.get(self.table, key: previousKey) { if previousValue.length != 0 { info = previousValue } } else { assert(false) } self.valueBox.remove(self.table, key: previousKey, secure: true) } if let updatedInfo = self.updatedInfoItems[itemId] { if let value = updatedInfo.value { let encoder = PostboxEncoder() value.encode(encoder) info = encoder.makeReadBufferAndReset() } else { info = nil } } if info == nil { if let value = self.seedConfiguration.automaticThreadIndexInfo(itemId.peerId, itemId.threadId) { let encoder = PostboxEncoder() value.encode(encoder) info = encoder.makeReadBufferAndReset() } } if let topIndex = topIndex, let info = info { if let previousIndex = previousIndex { self.reverseIndexTable.set(peerId: itemId.peerId, threadId: itemId.threadId, timestamp: previousIndex.timestamp, namespace: previousIndex.id.namespace, id: previousIndex.id.id, hasValue: false) } self.reverseIndexTable.set(peerId: itemId.peerId, threadId: itemId.threadId, timestamp: topIndex.timestamp, namespace: topIndex.id.namespace, id: topIndex.id.id, hasValue: true) self.valueBox.set(self.table, key: self.key(peerId: itemId.peerId, timestamp: topIndex.timestamp, threadId: itemId.threadId, namespace: topIndex.id.namespace, id: topIndex.id.id, key: self.sharedKey), value: info) } else { if let previousIndex = previousIndex { self.reverseIndexTable.set(peerId: itemId.peerId, threadId: itemId.threadId, timestamp: previousIndex.timestamp, namespace: previousIndex.id.namespace, id: previousIndex.id.id, hasValue: false) self.valueBox.remove(self.table, key: self.key(peerId: itemId.peerId, timestamp: previousIndex.timestamp, threadId: itemId.threadId, namespace: previousIndex.id.namespace, id: previousIndex.id.id, key: self.sharedKey), secure: true) } } } } return peerIds } func fetch(peerId: PeerId, namespace: MessageId.Namespace, start: IndexBoundary, end: IndexBoundary, limit: Int) -> [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] { let startKey: ValueBoxKey switch start { case let .index(index): startKey = self.key(peerId: peerId, timestamp: index.timestamp, threadId: index.threadId, namespace: namespace, id: index.messageId, key: ValueBoxKey(length: self.sharedKey.length)) case .lowerBound: startKey = self.lowerBound(peerId: peerId) case .upperBound: startKey = self.upperBound(peerId: peerId) } let endKey: ValueBoxKey switch end { case let .index(index): endKey = self.key(peerId: peerId, timestamp: index.timestamp, threadId: index.threadId, namespace: namespace, id: index.messageId, key: ValueBoxKey(length: self.sharedKey.length)) case .lowerBound: endKey = self.lowerBound(peerId: peerId) case .upperBound: endKey = self.upperBound(peerId: peerId) } var result: [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] = [] self.valueBox.range(self.table, start: startKey, end: endKey, values: { key, value in let keyData = MessageHistoryThreadIndexTable.extract(key: key) if value.length == 0 { return true } let decoder = PostboxDecoder(buffer: value) let state = StoredMessageHistoryThreadInfo(decoder: decoder) result.append((keyData.threadId, keyData.index, state)) return true }, limit: limit) return result } func getAll(peerId: PeerId) -> [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] { var result: [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] = [] self.valueBox.range(self.table, start: self.upperBound(peerId: peerId), end: self.lowerBound(peerId: peerId), values: { key, value in let keyData = MessageHistoryThreadIndexTable.extract(key: key) if value.length == 0 { return true } let decoder = PostboxDecoder(buffer: value) let state = StoredMessageHistoryThreadInfo(decoder: decoder) result.append((keyData.threadId, keyData.index, state)) return true }, limit: 100000) return result } func getCount(peerId: PeerId) -> Int { return self.valueBox.count(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId)) } override func beforeCommit() { super.beforeCommit() self.updatedInfoItems.removeAll() } } class MessageHistoryThreadPinnedTable: Table { static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) } private let sharedKey = ValueBoxKey(length: 8 + 4 + 8) override init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool) { super.init(valueBox: valueBox, table: table, useCaches: useCaches) } private func key(peerId: PeerId, index: Int32, threadId: Int64) -> ValueBoxKey { self.sharedKey.setInt64(0, value: peerId.toInt64()) self.sharedKey.setInt32(8, value: index) self.sharedKey.setInt64(8 + 4, value: threadId) return self.sharedKey } private static func extract(key: ValueBoxKey) -> (peerId: PeerId, threadId: Int64) { return ( peerId: PeerId(key.getInt64(0)), threadId: key.getInt64(8 + 4) ) } private func lowerBound(peerId: PeerId) -> ValueBoxKey { let key = ValueBoxKey(length: 8) key.setInt64(0, value: peerId.toInt64()) return key } private func upperBound(peerId: PeerId) -> ValueBoxKey { return self.lowerBound(peerId: peerId).successor } func get(peerId: PeerId) -> [Int64] { var result: [Int64] = [] self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), keys: { key in result.append(MessageHistoryThreadPinnedTable.extract(key: key).threadId) return true }, limit: 0) return result } func set(peerId: PeerId, threadIds: [Int64]) { self.valueBox.removeRange(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId)) for i in 0 ..< threadIds.count { self.valueBox.set(self.table, key: self.key(peerId: peerId, index: Int32(i), threadId: threadIds[i]), value: MemoryBuffer()) } } override func beforeCommit() { super.beforeCommit() } }