Swiftgram/Telegram-iOS/LegacyChatImport.swift
2019-06-06 23:17:31 +01:00

791 lines
42 KiB
Swift

import Foundation
import TelegramCore
import SwiftSignalKit
import Postbox
import TelegramUI
import LegacyComponents
private let reportedLayer_hash: Int32 = -717538193
private let layer_hash: Int32 = 849537378
private let seq_out_hash: Int32 = -737765753
private let seq_in_hash: Int32 = -7646011
private let defaultPrime: Data = {
let bytes: [UInt8] = [
0xc7, 0x1c, 0xae, 0xb9, 0xc6, 0xb1, 0xc9, 0x04, 0x8e, 0x6c, 0x52, 0x2f,
0x70, 0xf1, 0x3f, 0x73, 0x98, 0x0d, 0x40, 0x23, 0x8e, 0x3e, 0x21, 0xc1,
0x49, 0x34, 0xd0, 0x37, 0x56, 0x3d, 0x93, 0x0f, 0x48, 0x19, 0x8a, 0x0a,
0xa7, 0xc1, 0x40, 0x58, 0x22, 0x94, 0x93, 0xd2, 0x25, 0x30, 0xf4, 0xdb,
0xfa, 0x33, 0x6f, 0x6e, 0x0a, 0xc9, 0x25, 0x13, 0x95, 0x43, 0xae, 0xd4,
0x4c, 0xce, 0x7c, 0x37, 0x20, 0xfd, 0x51, 0xf6, 0x94, 0x58, 0x70, 0x5a,
0xc6, 0x8c, 0xd4, 0xfe, 0x6b, 0x6b, 0x13, 0xab, 0xdc, 0x97, 0x46, 0x51,
0x29, 0x69, 0x32, 0x84, 0x54, 0xf1, 0x8f, 0xaf, 0x8c, 0x59, 0x5f, 0x64,
0x24, 0x77, 0xfe, 0x96, 0xbb, 0x2a, 0x94, 0x1d, 0x5b, 0xcd, 0x1d, 0x4a,
0xc8, 0xcc, 0x49, 0x88, 0x07, 0x08, 0xfa, 0x9b, 0x37, 0x8e, 0x3c, 0x4f,
0x3a, 0x90, 0x60, 0xbe, 0xe6, 0x7c, 0xf9, 0xa4, 0xa4, 0xa6, 0x95, 0x81,
0x10, 0x51, 0x90, 0x7e, 0x16, 0x27, 0x53, 0xb5, 0x6b, 0x0f, 0x6b, 0x41,
0x0d, 0xba, 0x74, 0xd8, 0xa8, 0x4b, 0x2a, 0x14, 0xb3, 0x14, 0x4e, 0x0e,
0xf1, 0x28, 0x47, 0x54, 0xfd, 0x17, 0xed, 0x95, 0x0d, 0x59, 0x65, 0xb4,
0xb9, 0xdd, 0x46, 0x58, 0x2d, 0xb1, 0x17, 0x8d, 0x16, 0x9c, 0x6b, 0xc4,
0x65, 0xb0, 0xd6, 0xff, 0x9c, 0xa3, 0x92, 0x8f, 0xef, 0x5b, 0x9a, 0xe4,
0xe4, 0x18, 0xfc, 0x15, 0xe8, 0x3e, 0xbe, 0xa0, 0xf8, 0x7f, 0xa9, 0xff,
0x5e, 0xed, 0x70, 0x05, 0x0d, 0xed, 0x28, 0x49, 0xf4, 0x7b, 0xf9, 0x59,
0xd9, 0x56, 0x85, 0x0c, 0xe9, 0x29, 0x85, 0x1f, 0x0d, 0x81, 0x15, 0xf6,
0x35, 0xb1, 0x05, 0xee, 0x2e, 0x4e, 0x15, 0xd0, 0x4b, 0x24, 0x54, 0xbf,
0x6f, 0x4f, 0xad, 0xf0, 0x34, 0xb1, 0x04, 0x03, 0x11, 0x9c, 0xd8, 0xe3,
0xb9, 0x2f, 0xcc, 0x5b
]
var data = Data(count: bytes.count)
data.withUnsafeMutableBytes { (dst: UnsafeMutablePointer<UInt8>) -> Void in
for i in 0 ..< bytes.count {
dst.advanced(by: i).pointee = bytes[i]
}
}
return data
}()
@objc(TGEncryptionKeyData) private final class TGEncryptionKeyData: NSObject, NSCoding {
let keyId: Int64
let key: Data
let firstSeqOut: Int32
init?(coder aDecoder: NSCoder) {
self.keyId = aDecoder.decodeInt64(forKey: "keyId")
self.key = (aDecoder.decodeObject(forKey: "key") as? Data) ?? Data()
self.firstSeqOut = aDecoder.decodeInt32(forKey: "firstSeqOut")
}
func encode(with aCoder: NSCoder) {
assertionFailure()
}
}
private struct SecretChatData {
let accessHash: Int64
let handshakeState: Int32
let rekeyState: SecretChatRekeySessionState?
}
private func readSecretChatParticipantData(accountPeerId: PeerId, data: Data) -> (SecretChatRole, PeerId)? {
let reader = BufferReader(Buffer(data: data))
guard reader.readInt32() == Int32(bitPattern: 0xabcdef12) else {
return nil
}
guard let formatVersion = reader.readInt32(), formatVersion >= 2 else {
return nil
}
reader.skip(4)
guard let adminId = reader.readInt32() else {
return nil
}
guard let count = reader.readInt32() else {
return nil
}
var ids: [Int32] = []
for _ in 0 ..< Int(count) {
guard let id = reader.readInt32() else {
return nil
}
reader.skip(4)
reader.skip(4)
ids.append(id)
}
guard let otherPeerId = ids.first else {
return nil
}
return (adminId == accountPeerId.id ? .creator : .participant, PeerId(namespace: Namespaces.Peer.CloudUser, id: otherPeerId))
}
private func readSecretChatData(reader: BufferReader) -> SecretChatData? {
guard let version = reader.readBytesAsInt32(1) else {
return nil
}
if version != 3 {
return nil
}
reader.skip(8)
guard let accessHash = reader.readInt64() else {
return nil
}
guard let _ = reader.readInt64() else {
return nil
}
guard let handshakeState = reader.readInt32() else {
return nil
}
guard let currentRekeyExchangeId = reader.readInt64() else {
return nil
}
guard let currentRekeyIsInitiatedByLocalClient = reader.readBytesAsInt32(1) else {
return nil
}
guard let currentRekeyNumberLength = reader.readInt32() else {
return nil
}
var currentRekeyNumber: Data?
if currentRekeyNumberLength > 0 {
guard let value = reader.readBuffer(Int(currentRekeyNumberLength))?.makeData() else {
return nil
}
currentRekeyNumber = value
}
guard let currentRekeyKeyLength = reader.readInt32() else {
return nil
}
var currentRekeyKey: Data?
if currentRekeyKeyLength > 0 {
guard let value = reader.readBuffer(Int(currentRekeyKeyLength))?.makeData() else {
return nil
}
currentRekeyKey = value
}
guard let currentRekeyKeyId = reader.readInt64() else {
return nil
}
var rekeyState: SecretChatRekeySessionState?
if currentRekeyExchangeId != 0 {
let innerState: SecretChatRekeySessionData?
if currentRekeyIsInitiatedByLocalClient != 0, let currentRekeyNumber = currentRekeyNumber {
innerState = .requested(a: MemoryBuffer(data: currentRekeyNumber), config: SecretChatEncryptionConfig(g: 3, p: MemoryBuffer(data: defaultPrime), version: 0))
} else if currentRekeyIsInitiatedByLocalClient == 0, let currentRekeyKey = currentRekeyKey, currentRekeyKeyId != 0 {
innerState = .accepted(key: MemoryBuffer(data: currentRekeyKey), keyFingerprint: currentRekeyKeyId)
} else {
innerState = nil
}
if let innerState = innerState {
rekeyState = SecretChatRekeySessionState(id: currentRekeyExchangeId, data: innerState)
}
}
return SecretChatData(accessHash: accessHash, handshakeState: handshakeState, rekeyState: rekeyState)
}
let registeredAttachmentParsers: Bool = {
let parsers: [(Int32, TGMediaAttachmentParser)] = [
(TGActionMediaAttachmentType, TGActionMediaAttachment()),
(TGImageMediaAttachmentType, TGImageMediaAttachment()),
(TGLocationMediaAttachmentType, TGLocationMediaAttachment()),
(TGVideoMediaAttachmentType, TGVideoMediaAttachment()),
(Int32(bitPattern: 0xB90A5663), TGContactMediaAttachment()),
(Int32(bitPattern: 0xE6C64318), TGDocumentMediaAttachment()),
(TGAudioMediaAttachmentType, TGAudioMediaAttachment()),
(Int32(bitPattern: 0x8C2E3CCE), TGMessageEntitiesAttachment()),
(Int32(bitPattern: 0x944DE6B6), TGLocalMessageMetaMediaAttachment()),
(TGAuthorSignatureMediaAttachmentType, TGAuthorSignatureMediaAttachment()),
(TGInvoiceMediaAttachmentType, TGInvoiceMediaAttachment()),
(TGGameAttachmentType, TGGameMediaAttachment()),
(Int32(bitPattern: 0xA3F4C8F5), TGViaUserAttachment()),
(TGBotContextResultAttachmentType, TGBotContextResultAttachment()),
(TGReplyMarkupAttachmentType, TGReplyMarkupAttachment()),
(TGWebPageMediaAttachmentType, TGWebPageMediaAttachment()),
(TGReplyMessageMediaAttachmentType, TGReplyMessageMediaAttachment()),
(TGAudioMediaAttachmentType, TGAudioMediaAttachment()),
(Int32(bitPattern: 0xaa1050c1), TGForwardedMessageMediaAttachment())
]
for (id, parser) in parsers {
TGMessage.registerMediaAttachmentParser(id, parser: parser)
}
return true
}()
private func parseSecretChatData(peerId: PeerId, data: Data, unreadCount: Int32) -> (SecretChatData, [MessageId.Namespace: PeerReadState], Int32)? {
let reader = BufferReader(Buffer(data: data))
guard let magic = reader.readInt32() else {
return nil
}
var version: Int32 = 1
if magic == 0x7acde441 {
guard let value = reader.readInt32() else {
return nil
}
version = value
}
if version < 2 {
return nil
}
for _ in 0 ..< 3 {
guard let length = reader.readInt32() else {
return nil
}
reader.skip(Int(length))
}
guard let hasEncryptedData = reader.readBytesAsInt32(1), hasEncryptedData == 1 else {
return nil
}
guard let secretChatData = readSecretChatData(reader: reader) else {
return nil
}
reader.skip(4)
reader.skip(4)
reader.skip(8)
reader.skip(4)
reader.skip(4)
reader.skip(4)
reader.skip(4)
guard let maxReadDate = reader.readInt32() else {
return nil
}
guard let maxOutgoingReadDate = reader.readInt32() else {
return nil
}
guard let messageDate = reader.readInt32() else {
return nil
}
guard let minMessageDate = reader.readInt32() else {
return nil
}
let readStates: [MessageId.Namespace: PeerReadState] = [
Namespaces.Message.SecretIncoming: .indexBased(maxIncomingReadIndex: MessageIndex(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: 1), timestamp: maxReadDate), maxOutgoingReadIndex: MessageIndex.lowerBound(peerId: peerId), count: 0, markedUnread: false),
Namespaces.Message.Local: .indexBased(maxIncomingReadIndex: MessageIndex.lowerBound(peerId: peerId), maxOutgoingReadIndex: MessageIndex(id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 1), timestamp: maxOutgoingReadDate), count: 0, markedUnread: false)
]
return (secretChatData, readStates, max(messageDate, minMessageDate))
}
private enum CustomPropertyKey {
case string(String)
case hash(Int32)
}
private func loadLegacyPeerCustomProperyData(database: SqliteInterface, peerId: Int64, key: CustomPropertyKey) -> Data? {
var propertiesData: Data?
database.select("SELECT custom_properties FROM peers_v29 WHERE pid=\(peerId)", { cursor in
propertiesData = cursor.getData(at: 0)
return false
})
if let propertiesData = propertiesData {
let keyHash: Int32
switch key {
case let .string(string):
keyHash = HashFunctions.murMurHash32(string)
case let .hash(hash):
keyHash = hash
}
let reader = BufferReader(Buffer(data: propertiesData))
guard let _ = reader.readInt32() else {
return nil
}
guard let count = reader.readInt32() else {
return nil
}
for _ in 0 ..< Int(count) {
guard let valueKey = reader.readInt32() else {
return nil
}
guard let valueLength = reader.readInt32() else {
return nil
}
if valueKey == keyHash {
return reader.readBuffer(Int(valueLength))?.makeData()
}
reader.skip(Int(valueLength))
}
}
return nil
}
private func loadLegacyPeerCustomProperyInt32(database: SqliteInterface, peerId: Int64, key: CustomPropertyKey) -> Int32? {
guard let data = loadLegacyPeerCustomProperyData(database: database, peerId: peerId, key: key), data.count == 4 else {
return nil
}
var result: Int32 = 0
withUnsafeMutablePointer(to: &result, { bytes -> Void in
data.copyBytes(to: UnsafeMutableRawPointer(bytes).assumingMemoryBound(to: UInt8.self), from: 0 ..< 4)
})
return result
}
private func loadLegacyMessages(account: TemporaryAccount, basePath: String, accountPeerId: PeerId, peerId: PeerId, userPeerId: PeerId, database: SqliteInterface, conversationId: Int64, expectedTotalCount: Int32) -> Signal<Float, NoError> {
return Signal { subscriber in
subscriber.putNext(0.0)
var copyLocalFiles: [(MediaResource, String)] = []
var messages: [StoreMessage] = []
Logger.shared.log("loadLegacyMessages", "begin peerId \(peerId) conversationId \(conversationId) count \(expectedTotalCount)")
database.select("CREATE INDEX IF NOT EXISTS random_ids_mid ON random_ids_v29 (mid)", { _ in
return true
})
var messageIndex: Int32 = -1
let reportBase = max(1, expectedTotalCount / 100)
database.select("SELECT mid, message, media, from_id, dstate, date, flags, localMid, content_properties FROM messages_v29 WHERE cid=\(conversationId)", { cursor in
messageIndex += 1
#if DEBUG
//usleep(500000)
#endif
if messageIndex % reportBase == 0 {
subscriber.putNext(min(1.0, Float(messageIndex) / Float(expectedTotalCount)))
}
let messageId = cursor.getInt32(at: 0)
//Logger.shared.log("loadLegacyMessages", "import message \(messageId)")
var globallyUniqueId: Int64?
database.select("SELECT random_id FROM random_ids_v29 where mid=\(messageId)", { innerCursor in
globallyUniqueId = innerCursor.getInt64(at: 0)
return false
})
let text = cursor.getString(at: 1)
let fromId = cursor.getInt64(at: 3)
let deliveryState = cursor.getInt32(at: 4)
let timestamp = cursor.getInt32(at: 5)
let autoremoveTimeout = cursor.getInt32(at: 7)
let contentPropertiesData = cursor.getData(at: 8)
let parsedAuthorId: PeerId
let parsedId: StoreMessageId
var parsedFlags: StoreMessageFlags = []
var parsedAttributes: [MessageAttribute] = []
var parsedMedia: [Media] = []
var parsedGroupingKey: Int64?
if fromId == accountPeerId.id {
parsedAuthorId = accountPeerId
parsedId = .Partial(peerId, Namespaces.Message.Local)
} else {
parsedAuthorId = userPeerId
parsedId = .Partial(peerId, Namespaces.Message.SecretIncoming)
parsedFlags.insert(.Incoming)
}
if deliveryState != 0 {
return true
}
if !contentPropertiesData.isEmpty {
if let contentProperties = TGMessage.parseContentProperties(contentPropertiesData) {
for (_, value) in contentProperties {
if let value = value as? TGMessageGroupedIdContentProperty {
parsedGroupingKey = value.groupedId
}
}
}
}
//Logger.shared.log("loadLegacyMessages", "message \(messageId) read content properties")
let media = cursor.getData(at: 2)
if let mediaList = TGMessage.parseMediaAttachments(media) {
for item in mediaList {
if let item = item as? TGImageMediaAttachment {
let mediaId = MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64())
var representations: [TelegramMediaImageRepresentation] = []
if let allSizes = item.imageInfo?.allSizes() as? [String: NSValue] {
for (imageUrl, sizeValue) in allSizes {
var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64())
var resourcePath: String?
if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: imageUrl, type: .image) {
resource = updatedResource
copyLocalFiles.append((updatedResource, path))
resourcePath = path
} else if imageUrl.hasPrefix("file://"), let path = URL(string: imageUrl)?.path {
copyLocalFiles.append((resource, path))
resourcePath = path
}
var dimensions = sizeValue.cgSizeValue
if let resourcePath = resourcePath, let image = UIImage(contentsOfFile: resourcePath) {
dimensions = image.size
}
representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource))
}
}
if item.localImageId != 0 {
let fullSizePath = basePath + "/Documents/files/image-local-\(String(item.localImageId, radix: 16))/image.jpg"
if let image = UIImage(contentsOfFile: fullSizePath) {
let resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64())
copyLocalFiles.append((resource, fullSizePath))
representations.append(TelegramMediaImageRepresentation(dimensions: image.size, resource: resource))
}
}
parsedMedia.append(TelegramMediaImage(imageId: mediaId, representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil))
} else if let item = item as? TGVideoMediaAttachment {
let mediaId = MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64())
var representations: [TelegramMediaImageRepresentation] = []
if let allSizes = item.thumbnailInfo?.allSizes() as? [String: NSValue] {
for (imageUrl, sizeValue) in allSizes {
var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64())
if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: imageUrl, type: .image) {
resource = updatedResource
copyLocalFiles.append((updatedResource, path))
} else if imageUrl.hasPrefix("file://"), let path = URL(string: imageUrl)?.path {
copyLocalFiles.append((resource, path))
}
representations.append(TelegramMediaImageRepresentation(dimensions: sizeValue.cgSizeValue, resource: resource))
}
}
var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64())
var attributes: [TelegramMediaFileAttribute] = []
attributes.append(.Video(duration: Int(item.duration), size: item.dimensions, flags: item.roundMessage ? .instantRoundVideo : []))
var size: Int32 = 0
if let videoUrl = item.videoInfo?.url(withQuality: 1, actualQuality: nil, actualSize: &size) {
if let path = pathFromLegacyLocalVideoUrl(basePath: basePath, url: videoUrl) {
copyLocalFiles.append((resource, path))
} else if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: videoUrl, type: .video) {
resource = updatedResource
copyLocalFiles.append((updatedResource, path))
} else if videoUrl.hasPrefix("file://"), let path = URL(string: videoUrl)?.path {
copyLocalFiles.append((resource, path))
}
}
parsedMedia.append(TelegramMediaFile(fileId: mediaId, partialReference: nil, resource: resource, previewRepresentations: representations, immediateThumbnailData: nil, mimeType: "video/mp4", size: size == 0 ? nil : Int(size), attributes: attributes))
} else if let item = item as? TGAudioMediaAttachment {
let mediaId = MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64())
let representations: [TelegramMediaImageRepresentation] = []
var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64())
var attributes: [TelegramMediaFileAttribute] = []
attributes.append(.Audio(isVoice: true, duration: Int(item.duration), title: nil, performer: nil, waveform: nil))
let size: Int32 = item.fileSize
let audioUrl = item.audioUri ?? ""
if let path = pathFromLegacyLocalVideoUrl(basePath: basePath, url: audioUrl) {
copyLocalFiles.append((resource, path))
} else if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: audioUrl, type: .audio) {
resource = updatedResource
copyLocalFiles.append((updatedResource, path))
} else if audioUrl.hasPrefix("file://"), let path = URL(string: audioUrl)?.path {
copyLocalFiles.append((resource, path))
}
parsedMedia.append(TelegramMediaFile(fileId: mediaId, partialReference: nil, resource: resource, previewRepresentations: representations, immediateThumbnailData: nil, mimeType: "audio/ogg", size: size == 0 ? nil : Int(size), attributes: attributes))
} else if let item = item as? TGDocumentMediaAttachment {
let mediaId = MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64())
var representations: [TelegramMediaImageRepresentation] = []
if let allSizes = (item.thumbnailInfo?.allSizes()) as? [String: NSValue] {
for (imageUrl, sizeValue) in allSizes {
var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64())
if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: imageUrl, type: .image) {
resource = updatedResource
copyLocalFiles.append((updatedResource, path))
} else if imageUrl.hasPrefix("file://"), let path = URL(string: imageUrl)?.path {
copyLocalFiles.append((resource, path))
} else if let updatedResource = resourceFromLegacyImageUrl(imageUrl) {
resource = updatedResource
copyLocalFiles.append((resource, pathFromLegacyImageUrl(basePath: basePath, url: imageUrl)))
}
representations.append(TelegramMediaImageRepresentation(dimensions: sizeValue.cgSizeValue, resource: resource))
}
}
var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64())
var attributes: [TelegramMediaFileAttribute] = []
var fileName = "file"
if let itemAttributes = item.attributes {
for attribute in itemAttributes {
if let attribute = attribute as? TGDocumentAttributeFilename {
attributes.append(.FileName(fileName: attribute.filename ?? "file"))
fileName = attribute.filename ?? "file"
} else if let attribute = attribute as? TGDocumentAttributeAudio {
let title = attribute.title ?? ""
let performer = attribute.performer ?? ""
var waveform: MemoryBuffer?
if let data = attribute.waveform {
waveform = MemoryBuffer(data: data.bitstream()!)
}
attributes.append(.Audio(isVoice: attribute.isVoice, duration: Int(attribute.duration), title: title.isEmpty ? nil : title, performer: performer.isEmpty ? nil : performer, waveform: waveform))
} else if let _ = attribute as? TGDocumentAttributeAnimated {
attributes.append(.Animated)
} else if let attribute = attribute as? TGDocumentAttributeVideo {
attributes.append(.Video(duration: Int(attribute.duration), size: attribute.size, flags: attribute.isRoundMessage ? .instantRoundVideo : []))
} else if let attribute = attribute as? TGDocumentAttributeSticker {
var packReference: StickerPackReference?
if let reference = attribute.packReference as? TGStickerPackIdReference {
packReference = .id(id: reference.packId, accessHash: reference.packAccessHash)
} else if let reference = attribute.packReference as? TGStickerPackShortnameReference {
packReference = .name(reference.shortName ?? "")
}
attributes.append(.Sticker(displayText: attribute.alt ?? "", packReference: packReference, maskData: nil))
} else if let attribute = attribute as? TGDocumentAttributeImageSize {
attributes.append(.ImageSize(size: attribute.size))
}
}
}
let documentUri = item.documentUri ?? ""
let size: Int32 = item.size
if documentUri.hasPrefix("file://"), let path = URL(string: documentUri)?.path {
copyLocalFiles.append((resource, path))
} else if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: documentUri, type: .document(fileName: fileName)) {
resource = updatedResource
copyLocalFiles.append((resource, path))
} else if item.localDocumentId != 0 {
copyLocalFiles.append((resource, pathFromLegacyFile(basePath: basePath, fileId: item.localDocumentId, isLocal: true, fileName: TGDocumentMediaAttachment.safeFileName(forFileName: fileName) ?? "")))
} else if item.documentId != 0 {
copyLocalFiles.append((resource, pathFromLegacyFile(basePath: basePath, fileId: item.documentId, isLocal: false, fileName: TGDocumentMediaAttachment.safeFileName(forFileName: fileName) ?? "")))
}
parsedMedia.append(TelegramMediaFile(fileId: mediaId, partialReference: nil, resource: resource, previewRepresentations: representations, immediateThumbnailData: nil, mimeType: item.mimeType ?? "application/octet-stream", size: size == 0 ? nil : Int(size), attributes: attributes))
} else if let item = item as? TGActionMediaAttachment {
if item.actionType == TGMessageActionEncryptedChatMessageLifetime, let actionData = item.actionData, let timeout = actionData["messageLifetime"] as? Int32 {
parsedMedia.append(TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(timeout)))
}
} else if let item = item as? TGContactMediaAttachment {
parsedMedia.append(TelegramMediaContact(firstName: item.firstName ?? "", lastName: item.lastName ?? "", phoneNumber: item.phoneNumber ?? "", peerId: nil, vCardData: nil))
} else if let item = item as? TGLocationMediaAttachment {
var venue: MapVenue?
if let v = item.venue {
venue = MapVenue(title: v.title ?? "", address: v.address ?? "", provider: v.provider == "" ? nil : v.provider, id: v.venueId == "" ? nil : v.venueId, type: v.type == "" ? nil : v.type)
}
parsedMedia.append(TelegramMediaMap(latitude: item.latitude, longitude: item.longitude, geoPlace: nil, venue: venue, liveBroadcastingTimeout: nil))
}
}
}
//Logger.shared.log("loadLegacyMessages", "message \(messageId) read media")
if autoremoveTimeout != 0 {
var countdownBeginTime: Int32?
database.select("SELECT date FROM selfdestruct_v29 where mid=\(messageId)", { innerCursor in
countdownBeginTime = innerCursor.getInt32(at: 0) - autoremoveTimeout
return false
})
parsedAttributes.append(AutoremoveTimeoutMessageAttribute(timeout: autoremoveTimeout, countdownBeginTime: countdownBeginTime))
}
let (parsedTags, parsedGlobalTags) = tagsForStoreMessage(incoming: parsedFlags.contains(.Incoming), attributes: parsedAttributes, media: parsedMedia, textEntities: nil)
messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia))
//Logger.shared.log("loadLegacyMessages", "message \(messageId) completed")
return true
})
let disposable = (account.postbox.transaction { transaction -> Void in
//Logger.shared.log("loadLegacyMessages", "conversation \(conversationId) storing messages")
let _ = transaction.addMessages(messages, location: .UpperHistoryBlock)
//Logger.shared.log("loadLegacyMessages", "conversation \(conversationId) copying \(copyLocalFiles.count) files")
for (resource, path) in copyLocalFiles {
account.postbox.mediaBox.copyResourceData(resource.id, fromTempPath: path)
}
Logger.shared.log("loadLegacyMessages", "conversation \(conversationId) done")
}).start(completed: {
subscriber.putCompletion()
})
return disposable
}
}
private func importChannelBroadcastPreferences(account: TemporaryAccount, basePath: String, database: SqliteInterface) -> Signal<Never, NoError> {
return deferred { () -> Signal<Never, NoError> in
var peerIds: [Int64] = []
database.select("SELECT cid FROM channel_conversations_v29", { cursor in
peerIds.append(cursor.getInt64(at: 0))
return true
})
var peerIdsWithMutedMessages: [Int64] = []
for peerId in peerIds {
if let data = loadLegacyPeerCustomProperyData(database: database, peerId: peerId, key: .hash(0x374BF349)), !data.isEmpty {
let reader = BufferReader(Buffer(data: data))
guard let version = reader.readBytesAsInt32(1) else {
continue
}
guard let _ = reader.readBytesAsInt32(1) else {
continue
}
if version >= 2 {
guard let messagesMuted = reader.readBytesAsInt32(1) else {
continue
}
if messagesMuted == 1 {
peerIdsWithMutedMessages.append(peerId)
}
}
}
}
return account.postbox.transaction { transaction -> Void in
for peerId in peerIdsWithMutedMessages {
let channelId = Int32(clamping: Int64(Int32.min) &* 2 &- peerId)
transaction.updatePeerChatInterfaceState(PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), update: { current in
let state = (current as? ChatInterfaceState ?? ChatInterfaceState()).withUpdatedSilentPosting(true)
return state
})
}
}
|> ignoreValues
}
}
func loadLegacySecretChats(account: TemporaryAccount, basePath: String, accountPeerId: PeerId, database: SqliteInterface) -> Signal<Float, NoError> {
return deferred { () -> Signal<Float, NoError> in
var peerIdToConversationId: [PeerId: Int64] = [:]
database.select("SELECT encrypted_id, cid FROM encrypted_cids_v29", { cursor in
peerIdToConversationId[PeerId(namespace: Namespaces.Peer.SecretChat, id: cursor.getInt32(at: 0))] = cursor.getInt64(at: 1)
return true
})
var chatInfos: [(TelegramSecretChat, SecretChatState, Int32?, [MessageId.Namespace: PeerReadState], Int64)] = []
for (peerId, conversationId) in peerIdToConversationId {
database.select("SELECT chat_photo, unread_count, participants, date FROM convesations_v29 WHERE cid=\(conversationId)", { cursor in
guard let (secretChatData, readStates, minMessageDate) = parseSecretChatData(peerId: peerId, data: cursor.getData(at: 0), unreadCount: cursor.getInt32(at: 1)) else {
return false
}
guard let (role, userPeerId) = readSecretChatParticipantData(accountPeerId: accountPeerId, data: cursor.getData(at: 2)) else {
return false
}
let chatMessageDate = cursor.getInt32(at: 3)
let messageDate = min(minMessageDate, chatMessageDate)
let messageLifetime = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .string("messageLifetime")) ?? 0
let state: SecretChatState
var seqOut: Int32?
switch secretChatData.handshakeState {
case 1: //requested
guard let a = loadLegacyPeerCustomProperyData(database: database, peerId: conversationId, key: .string("a")), !a.isEmpty else {
return false
}
state = SecretChatState(role: .creator, embeddedState: .handshake(.requested(g: 3, p: MemoryBuffer(data: defaultPrime), a: MemoryBuffer(data: a))), keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil)
case 2: //accepting
return false
case 3: //terminated
state = SecretChatState(role: .creator, embeddedState: .terminated, keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil)
case 4:
guard let sha1Fingerprint = loadLegacyPeerCustomProperyData(database: database, peerId: conversationId, key: .string("encryptionKeySha1")) else {
return false
}
guard let sha256Fingerprint = loadLegacyPeerCustomProperyData(database: database, peerId: conversationId, key: .string("encryptionKeySha256")) else {
return false
}
guard let keysData = loadLegacyPeerCustomProperyData(database: database, peerId: conversationId, key: .string("encryptionKeys")) else {
return false
}
guard let keysArray = NSKeyedUnarchiver.unarchiveObject(with: keysData) as? [TGEncryptionKeyData] else {
return false
}
let parsedKeys: [SecretChatKey] = keysArray.map({ key in
return SecretChatKey(fingerprint: key.keyId, key: MemoryBuffer(data: key.key), validity: .sequenceBasedIndexRange(fromCanonicalIndex: key.firstSeqOut), useCount: 1)
})
let requestedLayerValue = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .hash(reportedLayer_hash)) ?? 0
let appliedSeqInValue = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .hash(seq_in_hash)) ?? 0
guard let seqOutValue = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .hash(seq_out_hash)) else {
return false
}
seqOut = seqOutValue
guard let activeLayerValue = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .hash(layer_hash)) else {
return false
}
guard let activeLayer = SecretChatSequenceBasedLayer(rawValue: activeLayerValue) else {
return false
}
let rekeyState: SecretChatRekeySessionState? = secretChatData.rekeyState
let embeddedState: SecretChatEmbeddedState = .sequenceBasedLayer(SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: activeLayer, locallyRequestedLayer: requestedLayerValue == 0 ? nil : requestedLayerValue, remotelyRequestedLayer: nil), rekeyState: rekeyState, baseIncomingOperationIndex: 0, baseOutgoingOperationIndex: 0, topProcessedCanonicalIncomingOperationIndex: appliedSeqInValue == 0 ? nil : max(0, appliedSeqInValue - 1)))
state = SecretChatState(role: role, embeddedState: embeddedState, keychain: SecretChatKeychain(keys: parsedKeys), keyFingerprint: SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Fingerprint), sha256: SecretChatKeySha256Fingerprint(digest: sha256Fingerprint)), messageAutoremoveTimeout: messageLifetime == 0 ? nil : messageLifetime)
default:
return false
}
let secretChat = TelegramSecretChat(id: peerId, creationDate: messageDate, regularPeerId: userPeerId, accessHash: secretChatData.accessHash, role: role, embeddedState: state.embeddedState.peerState, messageAutoremoveTimeout: messageLifetime == 0 ? nil : messageLifetime)
chatInfos.append((secretChat, state, seqOut, readStates, conversationId))
return false
})
}
var userPeers: [PeerId: Peer] = [:]
var presences: [PeerId: PeerPresence] = [:]
for info in chatInfos {
if let (peer, presence) = loadLegacyUser(database: database, id: info.0.regularPeerId.id) {
userPeers[peer.id] = peer
presences[peer.id] = presence
}
}
let storedChats = account.postbox.transaction { transaction -> Void in
updatePeers(transaction: transaction, peers: Array(userPeers.values), update: { _, updated in
return updated
})
transaction.updatePeerPresencesInternal(presences: presences, merge: { _, updated in return updated })
for (peer, state, seqOutValue, readStates, _) in chatInfos {
if userPeers[peer.regularPeerId] == nil {
continue
}
updatePeers(transaction: transaction, peers: [peer], update: { _, updated in
return updated
})
transaction.setPeerChatState(peer.id, state: state)
switch state.embeddedState {
case .sequenceBasedLayer:
if let seqOutValue = seqOutValue {
transaction.operationLogResetIndices(peerId: peer.id, tag: OperationLogTags.SecretOutgoing, nextTagLocalIndex: seqOutValue + 1)
}
default:
break
}
transaction.resetIncomingReadStates([peer.id: readStates])
}
}
|> ignoreValues
let _ = registeredAttachmentParsers
var countByConversationId: [Int64: Int32] = [:]
var totalCount: Int32 = 0
for info in chatInfos {
database.select("SELECT COUNT(*) FROM messages_v29 WHERE cid=\(info.4)", { cursor in
let count = cursor.getInt32(at: 0)
countByConversationId[info.4] = count
totalCount += count
return true
})
}
var storedMessagesSignals: Signal<Float, NoError> = .single(0.0)
var cumulativeCount: Int32 = 0
for info in chatInfos {
let localBaseline = cumulativeCount
let localCount = countByConversationId[info.4] ?? 0
storedMessagesSignals = storedMessagesSignals
|> then(
loadLegacyMessages(account: account, basePath: basePath, accountPeerId: accountPeerId, peerId: info.0.id, userPeerId: info.0.regularPeerId, database: database, conversationId: info.4, expectedTotalCount: localCount)
|> map { localProgress -> Float in
if totalCount <= 0 {
return 0.0
}
let globalCount = localBaseline + Int32(localProgress * Float(localCount))
return Float(globalCount) / Float(totalCount)
}
)
cumulativeCount += countByConversationId[info.4] ?? 0
}
return storedChats
|> map { _ -> Float in return 0.0 }
|> then(storedMessagesSignals)
}
}