Swiftgram/submodules/Postbox/Sources/UnorderedItemListTable.swift
2019-11-01 17:11:12 +04:00

241 lines
9.7 KiB
Swift

import Foundation
public struct UnorderedItemListEntryInfo {
public let hashValue: Int64
public init(hashValue: Int64) {
self.hashValue = hashValue
}
}
public struct UnorderedItemListEntry {
public let id: ValueBoxKey
public let info: UnorderedItemListEntryInfo
public let contents: PostboxCoding
public init(id: ValueBoxKey, info: UnorderedItemListEntryInfo, contents: PostboxCoding) {
self.id = id
self.info = info
self.contents = contents
}
}
public struct UnorderedItemListEntryTag {
public let value: ValueBoxKey
public init(value: ValueBoxKey) {
self.value = value
}
}
public protocol UnorderedItemListTagMetaInfo: PostboxCoding {
func isEqual(to: UnorderedItemListTagMetaInfo) -> Bool
}
private enum UnorderedItemListTableKeyspace: UInt8 {
case metaInfo = 0
case entries = 1
}
private func extractEntryKey(tagLength: Int, key: ValueBoxKey) -> ValueBoxKey {
let result = ValueBoxKey(length: key.length - tagLength - 1)
memcpy(result.memory, key.memory.advanced(by: 1 + tagLength), result.length)
return result
}
private func extractEntryInfo(_ value: ReadBuffer) -> UnorderedItemListEntryInfo {
var hashValue: Int64 = 0
value.read(&hashValue, offset: 0, length: 8)
return UnorderedItemListEntryInfo(hashValue: hashValue)
}
final class UnorderedItemListTable: Table {
static func tableSpec(_ id: Int32) -> ValueBoxTable {
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
}
private func metaInfoKey(tag: UnorderedItemListEntryTag) -> ValueBoxKey {
let tagValue = tag.value
let key = ValueBoxKey(length: 1 + tagValue.length)
key.setUInt8(0, value: UnorderedItemListTableKeyspace.metaInfo.rawValue)
memcpy(key.memory.advanced(by: 1), tagValue.memory, tagValue.length)
return key
}
private func entryKey(tag: UnorderedItemListEntryTag, id: ValueBoxKey) -> ValueBoxKey {
let tagValue = tag.value
let key = ValueBoxKey(length: 1 + tagValue.length + id.length)
key.setUInt8(0, value: UnorderedItemListTableKeyspace.entries.rawValue)
memcpy(key.memory.advanced(by: 1), tagValue.memory, tagValue.length)
memcpy(key.memory.advanced(by: 1 + tagValue.length), id.memory, id.length)
return key
}
private func entryLowerBoundKey(tag: UnorderedItemListEntryTag) -> ValueBoxKey {
let tagValue = tag.value
let key = ValueBoxKey(length: 1 + tagValue.length)
key.setUInt8(0, value: UnorderedItemListTableKeyspace.entries.rawValue)
memcpy(key.memory.advanced(by: 1), tagValue.memory, tagValue.length)
return key
}
private func entryUpperBoundKey(tag: UnorderedItemListEntryTag) -> ValueBoxKey {
let tagValue = tag.value.successor
let key = ValueBoxKey(length: 1 + tagValue.length)
key.setUInt8(0, value: UnorderedItemListTableKeyspace.entries.rawValue)
memcpy(key.memory.advanced(by: 1), tagValue.memory, tagValue.length)
return key
}
private func getMetaInfo(tag: UnorderedItemListEntryTag) -> UnorderedItemListTagMetaInfo? {
if let value = self.valueBox.get(self.table, key: self.metaInfoKey(tag: tag)), let info = PostboxDecoder(buffer: value).decodeRootObject() as? UnorderedItemListTagMetaInfo {
return info
} else {
return nil
}
}
private func setMetaInfo(tag: UnorderedItemListEntryTag, info: UnorderedItemListTagMetaInfo) {
let encoder = PostboxEncoder()
encoder.encodeRootObject(info)
self.valueBox.set(self.table, key: self.metaInfoKey(tag: tag), value: encoder.readBufferNoCopy())
}
private func getEntryInfos(tag: UnorderedItemListEntryTag) -> [ValueBoxKey: UnorderedItemListEntryInfo] {
var result: [ValueBoxKey: UnorderedItemListEntryInfo] = [:]
let tagLength = tag.value.length
self.valueBox.range(self.table, start: self.entryLowerBoundKey(tag: tag), end: self.entryUpperBoundKey(tag: tag), values: { key, value in
result[extractEntryKey(tagLength: tagLength, key: key)] = extractEntryInfo(value)
return true
}, limit: 0)
return result
}
private func getEntry(tag: UnorderedItemListEntryTag, id: ValueBoxKey) -> UnorderedItemListEntry? {
if let value = self.valueBox.get(self.table, key: self.entryKey(tag: tag, id: id)) {
var hashValue: Int64 = 0
value.read(&hashValue, offset: 0, length: 8)
let tempBuffer = MemoryBuffer(memory: value.memory.advanced(by: 8), capacity: value.length - 8, length: value.length - 8, freeWhenDone: false)
let contents = withExtendedLifetime(tempBuffer, {
return PostboxDecoder(buffer: tempBuffer).decodeRootObject()
})
if let contents = contents {
let entry = UnorderedItemListEntry(id: id, info: UnorderedItemListEntryInfo(hashValue: hashValue), contents: contents)
return entry
} else {
assertionFailure()
return nil
}
} else {
return nil
}
}
private func setEntry(tag: UnorderedItemListEntryTag, entry: UnorderedItemListEntry, sharedBuffer: WriteBuffer, sharedEncoder: PostboxEncoder) {
sharedBuffer.reset()
sharedEncoder.reset()
var hashValue: Int64 = entry.info.hashValue
sharedBuffer.write(&hashValue, offset: 0, length: 8)
sharedEncoder.encodeRootObject(entry.contents)
let tempBuffer = sharedEncoder.readBufferNoCopy()
withExtendedLifetime(tempBuffer, {
sharedBuffer.write(tempBuffer.memory, offset: 0, length: tempBuffer.length)
})
self.valueBox.set(self.table, key: self.entryKey(tag: tag, id: entry.id), value: sharedBuffer)
}
func scan(tag: UnorderedItemListEntryTag, _ f: (UnorderedItemListEntry) -> Void) {
let tagLength = tag.value.length
self.valueBox.range(self.table, start: self.entryLowerBoundKey(tag: tag), end: self.entryUpperBoundKey(tag: tag), values: { key, value in
let entryKey = extractEntryKey(tagLength: tagLength, key: key)
var hashValue: Int64 = 0
value.read(&hashValue, offset: 0, length: 8)
let tempBuffer = MemoryBuffer(memory: value.memory.advanced(by: 8), capacity: value.length - 8, length: value.length - 8, freeWhenDone: false)
let contents = withExtendedLifetime(tempBuffer, {
return PostboxDecoder(buffer: tempBuffer).decodeRootObject()
})
if let contents = contents {
f(UnorderedItemListEntry(id: entryKey, info: UnorderedItemListEntryInfo(hashValue: hashValue), contents: contents))
} else {
assertionFailure()
}
return true
}, limit: 0)
}
func difference(tag: UnorderedItemListEntryTag, updatedEntryInfos: [ValueBoxKey: UnorderedItemListEntryInfo]) -> (metaInfo: UnorderedItemListTagMetaInfo?, added: [ValueBoxKey], removed: [UnorderedItemListEntry], updated: [UnorderedItemListEntry]) {
let currentEntryInfos = self.getEntryInfos(tag: tag)
var currentInfoIds = Set<ValueBoxKey>()
for key in currentEntryInfos.keys {
currentInfoIds.insert(key)
}
var updatedInfoIds = Set<ValueBoxKey>()
for key in updatedEntryInfos.keys {
updatedInfoIds.insert(key)
}
let addedKeys = updatedInfoIds.subtracting(currentInfoIds)
let added: [ValueBoxKey] = Array(addedKeys)
let removedKeys = currentInfoIds.subtracting(updatedInfoIds)
var removed: [UnorderedItemListEntry] = []
for key in removedKeys {
if let entry = self.getEntry(tag: tag, id: key) {
removed.append(entry)
} else {
assertionFailure()
}
}
var updated: [UnorderedItemListEntry] = []
for (key, info) in updatedEntryInfos {
if !addedKeys.contains(key) {
if let currentInfo = currentEntryInfos[key] {
if info.hashValue != currentInfo.hashValue {
if let entry = self.getEntry(tag: tag, id: key) {
updated.append(entry)
} else {
assertionFailure()
}
}
} else {
assertionFailure()
}
}
}
return (self.getMetaInfo(tag: tag), added, removed, updated)
}
func applyDifference(tag: UnorderedItemListEntryTag, previousInfo: UnorderedItemListTagMetaInfo?, updatedInfo: UnorderedItemListTagMetaInfo, setItems: [UnorderedItemListEntry], removeItemIds: [ValueBoxKey]) -> Bool {
let currentInfo = self.getMetaInfo(tag: tag)
if let currentInfo = currentInfo, let previousInfo = previousInfo {
if !currentInfo.isEqual(to: previousInfo) {
return false
}
} else if (currentInfo != nil) != (previousInfo != nil) {
return false
}
self.setMetaInfo(tag: tag, info: updatedInfo)
let sharedBuffer = WriteBuffer()
let sharedEncoder = PostboxEncoder()
for entry in setItems {
self.setEntry(tag: tag, entry: entry, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder)
}
for id in removeItemIds {
self.valueBox.remove(self.table, key: self.entryKey(tag: tag, id: id), secure: false)
}
return true
}
}