diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index d2d3d83288..6b402b8ac7 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -350,7 +350,7 @@ public func accountTransaction(rootPath: String, id: AccountRecordId, encrypt } } -public func accountWithId(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal { +public func accountWithId(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, backupData: AccountBackupData?, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters) @@ -366,7 +366,18 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw } |> mapToSignal { localizationSettings, proxySettings -> Signal in return postbox.transaction { transaction -> (PostboxCoding?, LocalizationSettings?, ProxySettings?, NetworkSettings?) in - return (transaction.getState(), localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings) as? NetworkSettings) + var state = transaction.getState() + if state == nil, let backupData = backupData { + let backupState = AuthorizedAccountState(isTestingEnvironment: beginWithTestingEnvironment, masterDatacenterId: backupData.masterDatacenterId, peerId: PeerId(backupData.peerId), state: nil) + state = backupState + let dict = NSMutableDictionary() + dict.setObject(MTDatacenterAuthInfo(authKey: backupData.masterDatacenterKey, authKeyId: backupData.masterDatacenterKeyId, saltSet: [], authKeyAttributes: [:], mainTempAuthKey: nil, mediaTempAuthKey: nil), forKey: backupData.masterDatacenterId as NSNumber) + let data = NSKeyedArchiver.archivedData(withRootObject: dict) + transaction.setState(backupState) + transaction.setKeychainEntry(data, forKey: "persistent:datacenterAuthInfoById") + } + + return (state, localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings) as? NetworkSettings) } |> mapToSignal { (accountState, localizationSettings, proxySettings, networkSettings) -> Signal in let keychain = makeExclusiveKeychain(id: id, postbox: postbox) @@ -911,6 +922,62 @@ public func decryptedNotificationPayload(account: Account, data: Data) -> Signal } } +public struct AccountBackupData: Codable, Equatable { + public var masterDatacenterId: Int32 + public var peerId: Int64 + public var masterDatacenterKey: Data + public var masterDatacenterKeyId: Int64 +} + +public final class AccountBackupDataAttribute: AccountRecordAttribute, Equatable { + public let data: AccountBackupData + + public init(data: AccountBackupData) { + self.data = data + } + + public init(decoder: PostboxDecoder) { + self.data = try! JSONDecoder().decode(AccountBackupData.self, from: decoder.decodeDataForKey("data")!) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeData(try! JSONEncoder().encode(self.data), forKey: "data") + } + + public static func ==(lhs: AccountBackupDataAttribute, rhs: AccountBackupDataAttribute) -> Bool { + return lhs.data == rhs.data + } + + public func isEqual(to: AccountRecordAttribute) -> Bool { + if let to = to as? AccountBackupDataAttribute { + return self == to + } else { + return false + } + } +} + +public func accountBackupData(postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> AccountBackupData? in + guard let state = transaction.getState() as? AuthorizedAccountState else { + return nil + } + guard let authInfoData = transaction.keychainEntryForKey("persistent:datacenterAuthInfoById") else { + return nil + } + guard let authInfo = NSKeyedUnarchiver.unarchiveObject(with: authInfoData) as? NSDictionary else { + return nil + } + guard let datacenterAuthInfo = authInfo.object(forKey: state.masterDatacenterId as NSNumber) as? MTDatacenterAuthInfo else { + return nil + } + guard let authKey = datacenterAuthInfo.authKey else { + return nil + } + return AccountBackupData(masterDatacenterId: state.masterDatacenterId, peerId: state.peerId.toInt64(), masterDatacenterKey: authKey, masterDatacenterKeyId: datacenterAuthInfo.authKeyId) + } +} + public class Account { public let id: AccountRecordId public let basePath: String diff --git a/TelegramCore/AccountManager.swift b/TelegramCore/AccountManager.swift index c6f612f6f5..74b4816c19 100644 --- a/TelegramCore/AccountManager.swift +++ b/TelegramCore/AccountManager.swift @@ -139,6 +139,7 @@ private var declaredEncodables: Void = { declareEncodable(CloudDocumentSizeMediaResource.self, f: { CloudDocumentSizeMediaResource(decoder: $0) }) declareEncodable(CloudPeerPhotoSizeMediaResource.self, f: { CloudPeerPhotoSizeMediaResource(decoder: $0) }) declareEncodable(CloudStickerPackThumbnailMediaResource.self, f: { CloudStickerPackThumbnailMediaResource(decoder: $0) }) + declareEncodable(AccountBackupDataAttribute.self, f: { AccountBackupDataAttribute(decoder: $0) }) return }() @@ -219,7 +220,7 @@ public func currentAccount(allocateIfNotExists: Bool, networkArguments: NetworkI return false } }) - return accountWithId(accountManager: manager, networkArguments: networkArguments, id: record.0, encryptionParameters: encryptionParameters, supplementary: supplementary, rootPath: rootPath, beginWithTestingEnvironment: beginWithTestingEnvironment, auxiliaryMethods: auxiliaryMethods) + return accountWithId(accountManager: manager, networkArguments: networkArguments, id: record.0, encryptionParameters: encryptionParameters, supplementary: supplementary, rootPath: rootPath, beginWithTestingEnvironment: beginWithTestingEnvironment, backupData: nil, auxiliaryMethods: auxiliaryMethods) |> mapToSignal { accountResult -> Signal in let postbox: Postbox let initialKind: AccountKind @@ -385,7 +386,7 @@ private func cleanupAccount(networkArguments: NetworkInitializationArguments, ac return false } }) - return accountWithId(accountManager: accountManager, networkArguments: networkArguments, id: id, encryptionParameters: encryptionParameters, supplementary: true, rootPath: rootPath, beginWithTestingEnvironment: beginWithTestingEnvironment, auxiliaryMethods: auxiliaryMethods) + return accountWithId(accountManager: accountManager, networkArguments: networkArguments, id: id, encryptionParameters: encryptionParameters, supplementary: true, rootPath: rootPath, beginWithTestingEnvironment: beginWithTestingEnvironment, backupData: nil, auxiliaryMethods: auxiliaryMethods) |> mapToSignal { account -> Signal in switch account { case .upgrading: diff --git a/TelegramCore/HistoryViewChannelStateValidation.swift b/TelegramCore/HistoryViewChannelStateValidation.swift index 2ed388f360..eb691ad359 100644 --- a/TelegramCore/HistoryViewChannelStateValidation.swift +++ b/TelegramCore/HistoryViewChannelStateValidation.swift @@ -316,6 +316,7 @@ private func validateBatch(postbox: Postbox, network: Network, accountPeerId: Pe switch historyState { case let .channel(peerId, _): let hash = hashForMessages(previousMessages, withChannelIds: false) + Logger.shared.log("HistoryValidation", "validate batch for \(peerId): \(previousMessages.map({ $0.id }))") if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { let requestSignal: Signal if let tag = tag { @@ -358,34 +359,6 @@ private func validateBatch(postbox: Postbox, network: Network, accountPeerId: Pe } else { return .complete() } - /*case let .group(groupId, _): - signal = .single(.notModified) - let hash = hashForMessages(previousMessages, withChannelIds: true) - let upperIndex = MessageIndex(previousMessages[previousMessages.count - 1]) - let minIndex = MessageIndex(previousMessages[0]).predecessor() - - let upperInputPeer: Api.Peer = groupBoundaryPeer(upperIndex.id.peerId, accountPeerId: accountPeerId) - let lowerInputPeer: Api.Peer = groupBoundaryPeer(minIndex.id.peerId, accountPeerId: accountPeerId) - - var flags: Int32 = 0 - flags |= (1 << 0) - - let offsetPosition: Api.FeedPosition = .feedPosition(date: upperIndex.timestamp, peer: upperInputPeer, id: upperIndex.id.id) - let addOffset: Int32 = -1 - let minPosition: Api.FeedPosition = .feedPosition(date: minIndex.timestamp, peer: lowerInputPeer, id: minIndex.id.id) - - flags |= (1 << 0) - flags |= (1 << 2) - - signal = network.request(Api.functions.channels.getFeed(flags: flags, feedId: groupId.rawValue, offsetPosition: offsetPosition, addOffset: addOffset, limit: 200, maxPosition: nil, minPosition: minPosition, hash: hash)) - |> map { result -> ValidatedMessages in - switch result { - case let .feedMessages(_, _, _, _, messages, chats, users): - return .messages(messages, chats, users, nil) - case .feedMessagesNotModified: - return .notModified - } - }*/ } return signal @@ -394,37 +367,81 @@ private func validateBatch(postbox: Postbox, network: Network, accountPeerId: Pe return .single(nil) } |> mapToSignal { result -> Signal in - return postbox.transaction { transaction -> Void in - if let result = result { - switch result { - case let .messages(messages, chats, users, channelPts): - var storeMessages: [StoreMessage] = [] + guard let result = result else { + return .complete() + } + switch result { + case let .messages(messages, chats, users, channelPts): + var storeMessages: [StoreMessage] = [] + + for message in messages { + if let storeMessage = StoreMessage(apiMessage: message) { + var attributes = storeMessage.attributes - for message in messages { - if let storeMessage = StoreMessage(apiMessage: message) { - var attributes = storeMessage.attributes - - if let channelPts = channelPts { - attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts)) - } - - switch historyState { - case .channel: - break - /*case let .group(_, groupState): - attributes.append(PeerGroupMessageStateVersionAttribute(stateIndex: groupState.stateIndex))*/ - } - - storeMessages.append(storeMessage.withUpdatedAttributes(attributes)) - } + if let channelPts = channelPts { + attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts)) } - /*if case .group = historyState { - let prevHash = hashForMessages(previousMessages, withChannelIds: true) - let updatedHash = hashForMessages(storeMessages, withChannelIds: true) - print("\(updatedHash) != \(prevHash)") - }*/ - + storeMessages.append(storeMessage.withUpdatedAttributes(attributes)) + } + } + + var validMessageIds = Set() + for message in storeMessages { + if case let .Id(id) = message.id { + validMessageIds.insert(id) + } + } + + var maybeRemovedMessageIds: [MessageId] = [] + for id in previous.keys { + if !validMessageIds.contains(id) { + maybeRemovedMessageIds.append(id) + } + } + + let actuallyRemovedMessagesSignal: Signal, NoError> + if maybeRemovedMessageIds.isEmpty { + actuallyRemovedMessagesSignal = .single(Set()) + } else { + actuallyRemovedMessagesSignal = postbox.transaction { transaction -> Signal, NoError> in + switch historyState { + case let .channel(peerId, _): + if let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) { + return network.request(Api.functions.channels.getMessages(channel: inputChannel, id: maybeRemovedMessageIds.map({ Api.InputMessage.inputMessageID(id: $0.id) }))) + |> map { result -> Set in + let apiMessages: [Api.Message] + switch result { + case let .channelMessages(_, _, _, messages, _, _): + apiMessages = messages + case let .messages(messages, _, _): + apiMessages = messages + case let .messagesSlice(_, _, messages, _, _): + apiMessages = messages + case .messagesNotModified: + return Set() + } + var ids = Set() + for message in apiMessages { + if let id = message.id { + ids.insert(id) + } + } + return ids + } + |> `catch` { _ -> Signal, NoError> in + return .single(Set(maybeRemovedMessageIds)) + } + } + } + return .single(Set(maybeRemovedMessageIds)) + } + |> switchToLatest + } + + return actuallyRemovedMessagesSignal + |> mapToSignal { removedMessageIds -> Signal in + return postbox.transaction { transaction -> Void in var validMessageIds = Set() for message in storeMessages { if case let .Id(id) = message.id { @@ -470,22 +487,7 @@ private func validateBatch(postbox: Postbox, network: Network, accountPeerId: Pe attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts)) } - switch historyState { - case .channel: - break - /*case let .group(_, groupState): - for i in (0 ..< attributes.count).reversed() { - if let _ = attributes[i] as? PeerGroupMessageStateVersionAttribute { - attributes.remove(at: i) - } - } - attributes.append(PeerGroupMessageStateVersionAttribute(stateIndex: groupState.stateIndex))*/ - } - - var updatedFlags = StoreMessageFlags(currentMessage.flags) - /*if case .group = historyState { - updatedFlags.insert(.CanBeGroupedIntoFeed) - }*/ + let updatedFlags = StoreMessageFlags(currentMessage.flags) return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) } @@ -493,17 +495,14 @@ private func validateBatch(postbox: Postbox, network: Network, accountPeerId: Pe if previous[id] == nil { print("\(id) missing") - /*if case let .group(groupId, _) = historyState { - let _ = transaction.addMessagesToGroupFeedIndex(groupId: groupId, ids: [id]) - }*/ } } else { let _ = transaction.addMessages([message], location: .Random) } } } - - for id in previous.keys { + + for id in removedMessageIds { if !validMessageIds.contains(id) { if let tag = tag { transaction.updateMessage(id, update: { currentMessage in @@ -519,43 +518,38 @@ private func validateBatch(postbox: Postbox, network: Network, accountPeerId: Pe switch historyState { case .channel: deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [id]) - /*case let .group(groupId, _): - transaction.removeMessagesFromGroupFeedIndex(groupId: groupId, ids: [id])*/ + Logger.shared.log("HistoryValidation", "deleting message \(id) in \(id.peerId)") } } } } - case .notModified: - for id in previous.keys { - transaction.updateMessage(id, update: { currentMessage in - var storeForwardInfo: StoreMessageForwardInfo? - if let forwardInfo = currentMessage.forwardInfo { - storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature) - } - var attributes = currentMessage.attributes - for i in (0 ..< attributes.count).reversed() { - switch historyState { - case .channel: - if let _ = attributes[i] as? ChannelMessageStateVersionAttribute { - attributes.remove(at: i) - } - /*case .group: - if let _ = attributes[i] as? PeerGroupMessageStateVersionAttribute { - attributes.remove(at: i) - }*/ - } - } - switch historyState { - case let .channel(_, channelState): - attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts)) - /*case let .group(_, groupState): - attributes.append(PeerGroupMessageStateVersionAttribute(stateIndex: groupState.stateIndex))*/ - } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) - }) - } + } + } + case .notModified: + return postbox.transaction { transaction -> Void in + for id in previous.keys { + transaction.updateMessage(id, update: { currentMessage in + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature) + } + var attributes = currentMessage.attributes + for i in (0 ..< attributes.count).reversed() { + switch historyState { + case .channel: + if let _ = attributes[i] as? ChannelMessageStateVersionAttribute { + attributes.remove(at: i) + } + } + } + switch historyState { + case let .channel(_, channelState): + attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts)) + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } } - } } } } |> switchToLatest