Swiftgram/submodules/Postbox/Sources/PostboxUpgrade_19to20.swift
2021-03-24 19:35:52 +04:00

455 lines
21 KiB
Swift

import Foundation
private struct PrivatePeerId: Hashable {
typealias Namespace = Int32
typealias Id = Int32
let namespace: Namespace
let id: Id
init(namespace: Namespace, id: Id) {
self.namespace = namespace
self.id = id
}
init(_ n: Int64) {
self.namespace = Int32((n >> 32) & 0x7fffffff)
self.id = Int32(bitPattern: UInt32(n & 0xffffffff))
}
func toInt64() -> Int64 {
return (Int64(self.namespace) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: self.id)))
}
}
private func localChatListPinningIndexFromKeyValue(_ value: UInt16) -> UInt16? {
if value == 0 {
return nil
} else {
return UInt16.max - 1 - value
}
}
private func extractChatListKey(_ key: ValueBoxKey) -> (groupId: Int32?, pinningIndex: UInt16?, index: MessageIndex, type: Int8) {
let groupIdValue = key.getInt32(0)
return (
groupId: groupIdValue == 0 ? nil : groupIdValue,
pinningIndex: localChatListPinningIndexFromKeyValue(key.getUInt16(4)),
index: MessageIndex(
id: MessageId(
peerId: PeerId(key.getInt64(4 + 2 + 4 + 1 + 4)),
namespace: Int32(key.getInt8(4 + 2 + 4)),
id: key.getInt32(4 + 2 + 4 + 1)
),
timestamp: key.getInt32(4 + 2)
),
type: key.getInt8(4 + 2 + 4 + 1 + 4 + 8)
)
}
private struct MessageDataFlags: OptionSet {
var rawValue: Int8
init() {
self.rawValue = 0
}
init(rawValue: Int8) {
self.rawValue = rawValue
}
static let hasGloballyUniqueId = MessageDataFlags(rawValue: 1 << 0)
static let hasGlobalTags = MessageDataFlags(rawValue: 1 << 1)
static let hasGroupingKey = MessageDataFlags(rawValue: 1 << 2)
static let hasGroupInfo = MessageDataFlags(rawValue: 1 << 3)
static let hasLocalTags = MessageDataFlags(rawValue: 1 << 4)
}
private enum MediaEntryType: Int8 {
case Direct
case MessageReference
}
private func mediaTableKey(_ id: MediaId) -> ValueBoxKey {
let key = ValueBoxKey(length: 4 + 8)
key.setInt32(0, value: id.namespace)
key.setInt64(4, value: id.id)
return key
}
private func removeMediaReference(valueBox: ValueBox, table: ValueBoxTable, id: MediaId, sharedWriteBuffer: WriteBuffer = WriteBuffer()) {
guard let value = valueBox.get(table, key: mediaTableKey(id)) else {
return
}
var type: Int8 = 0
value.read(&type, offset: 0, length: 1)
if type == MediaEntryType.Direct.rawValue {
var dataLength: Int32 = 0
value.read(&dataLength, offset: 0, length: 4)
value.skip(Int(dataLength))
sharedWriteBuffer.reset()
sharedWriteBuffer.write(value.memory, offset: 0, length: value.offset)
var messageReferenceCount: Int32 = 0
value.read(&messageReferenceCount, offset: 0, length: 4)
messageReferenceCount -= 1
sharedWriteBuffer.write(&messageReferenceCount, offset: 0, length: 4)
if messageReferenceCount <= 0 {
valueBox.remove(table, key: mediaTableKey(id), secure: false)
} else {
withExtendedLifetime(sharedWriteBuffer, {
valueBox.set(table, key: mediaTableKey(id), value: sharedWriteBuffer.readBufferNoCopy())
})
}
} else if type == MediaEntryType.MessageReference.rawValue {
var idPeerId: Int64 = 0
var idNamespace: Int32 = 0
var idId: Int32 = 0
var idTimestamp: Int32 = 0
value.read(&idPeerId, offset: 0, length: 8)
value.read(&idNamespace, offset: 0, length: 4)
value.read(&idId, offset: 0, length: 4)
value.read(&idTimestamp, offset: 0, length: 4)
valueBox.remove(table, key: mediaTableKey(id), secure: false)
} else {
assertionFailure()
}
}
func postboxUpgrade_19to20(metadataTable: MetadataTable, valueBox: ValueBox, progress: (Float) -> Void) {
postboxLog("Upgrade 19->20 started")
let startTime = CFAbsoluteTimeGetCurrent()
let messageHistoryIndexTable = ValueBoxTable(id: 4, keyType: .binary, compactValuesOnCreation: true)
let messageHistoryTable = ValueBoxTable(id: 7, keyType: .binary, compactValuesOnCreation: false)
let messageHistoryMetadataTable = ValueBoxTable(id: 10, keyType: .binary, compactValuesOnCreation: true)
let chatListIndexTable = ValueBoxTable(id: 8, keyType: .int64, compactValuesOnCreation: false)
let chatListTable = ValueBoxTable(id: 9, keyType: .binary, compactValuesOnCreation: true)
let globalMessageIdsTable = ValueBoxTable(id: 3, keyType: .int64, compactValuesOnCreation: false)
let messageHistoryTagsTable = ValueBoxTable(id: 12, keyType: .binary, compactValuesOnCreation: true)
let globalMessageHistoryTagsTable = ValueBoxTable(id: 39, keyType: .binary, compactValuesOnCreation: true)
let localMessageHistoryTagsTable = ValueBoxTable(id: 52, keyType: .binary, compactValuesOnCreation: true)
let mediaTable = ValueBoxTable(id: 6, keyType: .binary, compactValuesOnCreation: false)
var totalMessageCount = 0
let absoluteLowerBound = ValueBoxKey(length: 8)
absoluteLowerBound.setInt64(0, value: 0)
let absoluteUpperBound = ValueBoxKey(length: 8)
absoluteUpperBound.setInt64(0, value: Int64.max - 1)
let sharedMessageHistoryKey = ValueBoxKey(length: 8 + 4 + 4 + 4)
let sharedPeerHistoryInitializedKey = ValueBoxKey(length: 8 + 1)
let sharedChatListIndexKey = ValueBoxKey(length: 8)
let sharedGlobalIdsKey = ValueBoxKey(length: 8)
let sharedMessageHistoryTagsKey = ValueBoxKey(length: 8 + 4 + 4 + 4 + 4)
let sharedGlobalTagsKey = ValueBoxKey(length: 4 + 4 + 4 + 4 + 8)
let sharedLocalTagsKey = ValueBoxKey(length: 4 + 4 + 4 + 8)
let sharedMediaWriteBuffer = WriteBuffer()
var matchingPeerIds: [PrivatePeerId] = []
var expectedTotalCount = 0
let currentLowerBound: ValueBoxKey = absoluteLowerBound
while true {
var currentPeerId: PrivatePeerId?
valueBox.range(messageHistoryIndexTable, start: currentLowerBound, end: absoluteUpperBound, keys: {
key in
currentPeerId = PrivatePeerId(key.getInt64(0))
return true
}, limit: 1)
if let currentPeerId = currentPeerId {
if currentPeerId.namespace == 0 || currentPeerId.namespace == 1 { // CloudUser || CloudGroup
matchingPeerIds.append(currentPeerId)
let nextLowerBound = ValueBoxKey(length: 8)
nextLowerBound.setInt64(0, value: currentPeerId.toInt64() + 1)
expectedTotalCount += valueBox.count(messageHistoryIndexTable, start: currentLowerBound, end: nextLowerBound)
}
currentLowerBound.setInt64(0, value: currentPeerId.toInt64() + 1)
} else {
break
}
}
var messageIndex = -1
let reportBase = max(1, expectedTotalCount / 100)
for peerId in matchingPeerIds {
let peerCloudLowerBound = ValueBoxKey(length: 8 + 4)
peerCloudLowerBound.setInt64(0, value: peerId.toInt64())
peerCloudLowerBound.setInt32(8, value: 0) // Cloud
valueBox.range(messageHistoryIndexTable, start: peerCloudLowerBound, end: peerCloudLowerBound.successor, values: { key, indexValue in
totalMessageCount += 1
if messageIndex % reportBase == 0 {
progress(min(1.0, Float(messageIndex) / Float(expectedTotalCount)))
}
messageIndex += 1
var flags: Int8 = 0
indexValue.read(&flags, offset: 0, length: 1)
var timestamp: Int32 = 0
indexValue.read(&timestamp, offset: 0, length: 4)
let id = key.getInt32(8 + 4)
let HistoryEntryTypeMask: Int8 = 1
let HistoryEntryTypeMessage: Int8 = 0
if (flags & HistoryEntryTypeMask) == HistoryEntryTypeMessage {
sharedGlobalIdsKey.setInt64(0, value: Int64(id))
valueBox.remove(globalMessageIdsTable, key: sharedGlobalIdsKey, secure: false)
}
sharedMessageHistoryKey.setInt64(0, value: peerId.toInt64())
sharedMessageHistoryKey.setInt32(8, value: timestamp)
sharedMessageHistoryKey.setInt32(8 + 4, value: 0)
sharedMessageHistoryKey.setInt32(8 + 4 + 4, value: id)
if let value = valueBox.get(messageHistoryTable, key: sharedMessageHistoryKey) {
var type: Int8 = 0
value.read(&type, offset: 0, length: 1)
if type == 0 {
var stableId: UInt32 = 0
value.read(&stableId, offset: 0, length: 4)
var stableVersion: UInt32 = 0
value.read(&stableVersion, offset: 0, length: 4)
var dataFlagsValue: Int8 = 0
value.read(&dataFlagsValue, offset: 0, length: 1)
let dataFlags = MessageDataFlags(rawValue: dataFlagsValue)
if dataFlags.contains(.hasGloballyUniqueId) {
var globallyUniqueIdValue: Int64 = 0
value.read(&globallyUniqueIdValue, offset: 0, length: 8)
}
var globalTags: GlobalMessageTags = []
if dataFlags.contains(.hasGlobalTags) {
var globalTagsValue: UInt32 = 0
value.read(&globalTagsValue, offset: 0, length: 4)
globalTags = GlobalMessageTags(rawValue: globalTagsValue)
}
if dataFlags.contains(.hasGroupingKey) {
var groupingKeyValue: Int64 = 0
value.read(&groupingKeyValue, offset: 0, length: 8)
}
if dataFlags.contains(.hasGroupInfo) {
var stableIdValue: UInt32 = 0
value.read(&stableIdValue, offset: 0, length: 4)
}
var localTags: LocalMessageTags = []
if dataFlags.contains(.hasLocalTags) {
var localTagsValue: UInt32 = 0
value.read(&localTagsValue, offset: 0, length: 4)
localTags = LocalMessageTags(rawValue: localTagsValue)
}
var flagsValue: UInt32 = 0
value.read(&flagsValue, offset: 0, length: 4)
var tagsValue: UInt32 = 0
value.read(&tagsValue, offset: 0, length: 4)
let tags = MessageTags(rawValue: tagsValue)
var forwardInfoFlags: Int8 = 0
value.read(&forwardInfoFlags, offset: 0, length: 1)
if forwardInfoFlags != 0 {
var forwardAuthorId: Int64 = 0
var forwardDate: Int32 = 0
value.read(&forwardAuthorId, offset: 0, length: 8)
value.read(&forwardDate, offset: 0, length: 4)
if (forwardInfoFlags & (1 << 1)) != 0 {
var forwardSourceIdValue: Int64 = 0
value.read(&forwardSourceIdValue, offset: 0, length: 8)
}
if (forwardInfoFlags & (1 << 2)) != 0 {
var forwardSourceMessagePeerId: Int64 = 0
var forwardSourceMessageNamespace: Int32 = 0
var forwardSourceMessageIdId: Int32 = 0
value.read(&forwardSourceMessagePeerId, offset: 0, length: 8)
value.read(&forwardSourceMessageNamespace, offset: 0, length: 4)
value.read(&forwardSourceMessageIdId, offset: 0, length: 4)
}
if (forwardInfoFlags & (1 << 3)) != 0 {
var signatureLength: Int32 = 0
value.read(&signatureLength, offset: 0, length: 4)
value.skip(Int(signatureLength))
}
}
var hasAuthor: Int8 = 0
value.read(&hasAuthor, offset: 0, length: 1)
if hasAuthor == 1 {
var varAuthorId: Int64 = 0
value.read(&varAuthorId, offset: 0, length: 8)
}
var textLength: Int32 = 0
value.read(&textLength, offset: 0, length: 4)
value.skip(Int(textLength))
var attributeCount: Int32 = 0
value.read(&attributeCount, offset: 0, length: 4)
for _ in 0 ..< attributeCount {
var attributeLength: Int32 = 0
value.read(&attributeLength, offset: 0, length: 4)
value.skip(Int(attributeLength))
}
let embeddedMediaOffset = value.offset
var embeddedMediaCount: Int32 = 0
value.read(&embeddedMediaCount, offset: 0, length: 4)
for _ in 0 ..< embeddedMediaCount {
var mediaLength: Int32 = 0
value.read(&mediaLength, offset: 0, length: 4)
value.skip(Int(mediaLength))
}
let embeddedMediaLength = value.offset - embeddedMediaOffset
let embeddedMediaBytes = malloc(embeddedMediaLength)!
memcpy(embeddedMediaBytes, value.memory + embeddedMediaOffset, embeddedMediaLength)
let embeddedMediaData = ReadBuffer(memory: embeddedMediaBytes, length: embeddedMediaLength, freeWhenDone: true)
var referencedMediaIds: [MediaId] = []
var referencedMediaIdsCount: Int32 = 0
value.read(&referencedMediaIdsCount, offset: 0, length: 4)
for _ in 0 ..< referencedMediaIdsCount {
var idNamespace: Int32 = 0
var idId: Int64 = 0
value.read(&idNamespace, offset: 0, length: 4)
value.read(&idId, offset: 0, length: 8)
referencedMediaIds.append(MediaId(namespace: idNamespace, id: idId))
}
for tag in tags {
sharedMessageHistoryTagsKey.setInt64(0, value: peerId.toInt64())
sharedMessageHistoryTagsKey.setUInt32(8, value: tag.rawValue)
sharedMessageHistoryTagsKey.setInt32(8 + 4, value: timestamp)
sharedMessageHistoryTagsKey.setInt32(8 + 4 + 4, value: 0)
sharedMessageHistoryTagsKey.setInt32(8 + 4 + 4 + 4, value: id)
valueBox.remove(messageHistoryTagsTable, key: sharedMessageHistoryTagsKey, secure: false)
}
for tag in globalTags {
sharedGlobalTagsKey.setUInt32(0, value: tag.rawValue)
sharedGlobalTagsKey.setInt32(4, value: timestamp)
sharedGlobalTagsKey.setInt32(4 + 4, value: 0)
sharedGlobalTagsKey.setInt32(4 + 4 + 4, value: id)
sharedGlobalTagsKey.setInt64(4 + 4 + 4 + 4, value: peerId.toInt64())
valueBox.remove(globalMessageHistoryTagsTable, key: sharedGlobalTagsKey, secure: false)
}
for tag in localTags {
sharedLocalTagsKey.setUInt32(0, value: tag.rawValue)
sharedLocalTagsKey.setInt32(4, value: 0)
sharedLocalTagsKey.setInt32(4 + 4, value: id)
sharedLocalTagsKey.setInt64(4 + 4 + 4, value: peerId.toInt64())
valueBox.remove(localMessageHistoryTagsTable, key: sharedLocalTagsKey, secure: false)
}
for mediaId in referencedMediaIds {
removeMediaReference(valueBox: valueBox, table: mediaTable, id: mediaId, sharedWriteBuffer: sharedMediaWriteBuffer)
}
if embeddedMediaData.length > 4 {
var embeddedMediaCount: Int32 = 0
embeddedMediaData.read(&embeddedMediaCount, offset: 0, length: 4)
for _ in 0 ..< embeddedMediaCount {
var mediaLength: Int32 = 0
embeddedMediaData.read(&mediaLength, offset: 0, length: 4)
if let media = PostboxDecoder(buffer: MemoryBuffer(memory: embeddedMediaData.memory + embeddedMediaData.offset, capacity: Int(mediaLength), length: Int(mediaLength), freeWhenDone: false)).decodeRootObject() as? Media {
if let mediaId = media.id {
removeMediaReference(valueBox: valueBox, table: mediaTable, id: mediaId, sharedWriteBuffer: sharedMediaWriteBuffer)
}
}
embeddedMediaData.skip(Int(mediaLength))
}
}
} else {
var stableId: UInt32 = 0
value.read(&stableId, offset: 0, length: 4)
var minId: Int32 = 0
value.read(&minId, offset: 0, length: 4)
var tags: UInt32 = 0
value.read(&tags, offset: 0, length: 4)
for tag in MessageTags(rawValue: tags) {
sharedMessageHistoryTagsKey.setInt64(0, value: peerId.toInt64())
sharedMessageHistoryTagsKey.setUInt32(8, value: tag.rawValue)
sharedMessageHistoryTagsKey.setInt32(8 + 4, value: timestamp)
sharedMessageHistoryTagsKey.setInt32(8 + 4 + 4, value: 0)
sharedMessageHistoryTagsKey.setInt32(8 + 4 + 4 + 4, value: id)
valueBox.remove(messageHistoryTagsTable, key: sharedMessageHistoryTagsKey, secure: false)
}
}
valueBox.remove(messageHistoryTable, key: sharedMessageHistoryKey, secure: false)
}
return true
}, limit: 0)
valueBox.removeRange(messageHistoryIndexTable, start: peerCloudLowerBound, end: peerCloudLowerBound.successor)
sharedPeerHistoryInitializedKey.setInt64(0, value: peerId.toInt64())
sharedPeerHistoryInitializedKey.setInt8(8, value: 1)
valueBox.remove(messageHistoryMetadataTable, key: sharedPeerHistoryInitializedKey, secure: false)
sharedChatListIndexKey.setInt64(0, value: peerId.toInt64())
valueBox.remove(chatListIndexTable, key: sharedChatListIndexKey, secure: false)
}
var removeChatListKeys: [ValueBoxKey] = []
valueBox.scan(chatListTable, keys: { key in
let (_, _, index, type) = extractChatListKey(key)
if index.id.peerId.namespace._internalGetInt32Value() != 3 { // Secret Chat
sharedChatListIndexKey.setInt64(0, value: index.id.peerId.toInt64())
valueBox.remove(chatListIndexTable, key: sharedChatListIndexKey, secure: false)
removeChatListKeys.append(key)
} else if type == 2 { // Hole
removeChatListKeys.append(key)
}
return true
})
for key in removeChatListKeys {
valueBox.remove(chatListTable, key: key, secure: false)
}
let chatListInitializedKey = ValueBoxKey(length: 1)
chatListInitializedKey.setInt8(0, value: 0)
valueBox.remove(messageHistoryMetadataTable, key: chatListInitializedKey, secure: false)
let shouldReindexUnreadCountsKey = ValueBoxKey(length: 1)
shouldReindexUnreadCountsKey.setInt8(0, value: 8)
valueBox.set(messageHistoryMetadataTable, key: shouldReindexUnreadCountsKey, value: MemoryBuffer())
let endTime = CFAbsoluteTimeGetCurrent()
postboxLog("Upgrade 19->20 (\(totalMessageCount) messages) took \(endTime - startTime) s")
metadataTable.setUserVersion(20)
}