import Foundation import TelegramApi import Postbox import SwiftSignalKit import CryptoUtils private func md5(_ data: Data) -> Data { return data.withUnsafeBytes { rawBytes -> Data in let bytes = rawBytes.baseAddress! return CryptoMD5(bytes, Int32(data.count)) } } private func updatedRemoteContactPeers(network: Network, hash: Int64) -> Signal<(AccumulatedPeers, Int32)?, NoError> { return network.request(Api.functions.contacts.getContacts(hash: hash), automaticFloodWait: false) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } |> map { result -> (AccumulatedPeers, Int32)? in guard let result = result else { return nil } switch result { case .contactsNotModified: return nil case let .contacts(_, savedCount, users): return (AccumulatedPeers(users: users), savedCount) } } } private func hashForCountAndIds(count: Int32, ids: [Int64]) -> Int64 { var acc: UInt64 = 0 combineInt64Hash(&acc, with: UInt64(count)) for id in ids { combineInt64Hash(&acc, with: UInt64(bitPattern: id)) } return finalizeInt64Hash(acc) } func syncContactsOnce(network: Network, postbox: Postbox, accountPeerId: PeerId) -> Signal { let initialContactPeerIdsHash = postbox.transaction { transaction -> Int64 in let contactPeerIds = transaction.getContactPeerIds() let totalCount = transaction.getRemoteContactCount() let peerIds = Set(contactPeerIds.filter({ $0.namespace == Namespaces.Peer.CloudUser })) return hashForCountAndIds(count: totalCount, ids: peerIds.map({ $0.id._internalGetInt64Value() }).sorted()) } let updatedPeers = initialContactPeerIdsHash |> mapToSignal { hash -> Signal<(AccumulatedPeers, Int32)?, NoError> in return updatedRemoteContactPeers(network: network, hash: hash) } let appliedUpdatedPeers = updatedPeers |> mapToSignal { peersAndPresences -> Signal in if let (peers, totalCount) = peersAndPresences { return postbox.transaction { transaction -> Signal in let previousIds = transaction.getContactPeerIds() let wasEmpty = previousIds.isEmpty transaction.replaceRemoteContactCount(totalCount) if wasEmpty { let users = Array(peers.users.values) var insertSignal: Signal = .complete() for s in stride(from: 0, to: users.count, by: 500) { let partUsers = Array(users[s ..< min(s + 500, users.count)]) let partSignal = postbox.transaction { transaction -> Void in updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: partUsers)) var updatedIds = transaction.getContactPeerIds() updatedIds.formUnion(partUsers.map { $0.peerId }) transaction.replaceContactPeerIds(updatedIds) } |> delay(0.1, queue: Queue.concurrentDefaultQueue()) insertSignal = insertSignal |> then(partSignal) } return insertSignal } else { transaction.replaceContactPeerIds(Set(peers.users.keys)) return .complete() } } |> switchToLatest |> ignoreValues } else { return .complete() } } return appliedUpdatedPeers } func _internal_deleteContactPeerInteractively(account: Account, peerId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) { return account.network.request(Api.functions.contacts.deleteContacts(id: [inputUser])) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } |> mapToSignal { updates -> Signal in if let updates = updates { account.stateManager.addUpdates(updates) } return account.postbox.transaction { transaction -> Void in if let user = peer as? TelegramUser { _internal_updatePeerIsContact(transaction: transaction, user: user, isContact: false) } var peerIds = transaction.getContactPeerIds() if peerIds.contains(peerId) { peerIds.remove(peerId) transaction.replaceContactPeerIds(peerIds) } } } |> ignoreValues } else { return .complete() } } |> switchToLatest } func _internal_deleteContacts(account: Account, peerIds: [PeerId]) -> Signal { return account.postbox.transaction { transaction -> Signal in let users = peerIds.compactMap { transaction.getPeer($0) } let inputUsers: [Api.InputUser] = users.compactMap { apiInputUser($0) } if !inputUsers.isEmpty { return account.network.request(Api.functions.contacts.deleteContacts(id: inputUsers)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } |> mapToSignal { updates -> Signal in if let updates = updates { account.stateManager.addUpdates(updates) } return account.postbox.transaction { transaction -> Void in for user in users { if let user = user as? TelegramUser { _internal_updatePeerIsContact(transaction: transaction, user: user, isContact: false) } } let updatedContactPeerIds = transaction.getContactPeerIds().filter { !peerIds.contains($0) } transaction.replaceContactPeerIds(updatedContactPeerIds) } } |> ignoreValues } else { return .complete() } } |> switchToLatest } func _internal_deleteAllContacts(account: Account) -> Signal { return account.postbox.transaction { transaction -> [Api.InputUser] in return transaction.getContactPeerIds().compactMap(transaction.getPeer).compactMap({ apiInputUser($0) }).compactMap({ $0 }) } |> mapToSignal { users -> Signal in let deleteContacts = account.network.request(Api.functions.contacts.deleteContacts(id: users)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } let deleteImported = account.network.request(Api.functions.contacts.resetSaved()) |> `catch` { _ -> Signal in return .single(.boolFalse) } return combineLatest(deleteContacts, deleteImported) |> mapToSignal { updates, _ -> Signal in return account.postbox.transaction { transaction -> Void in transaction.replaceContactPeerIds(Set()) transaction.clearDeviceContactImportInfoIdentifiers() } |> mapToSignal { _ -> Signal in account.restartContactManagement() if let updates = updates { account.stateManager.addUpdates(updates) } return .complete() } |> ignoreValues } } } func _internal_resetSavedContacts(network: Network) -> Signal { return network.request(Api.functions.contacts.resetSaved()) |> `catch` { _ -> Signal in return .single(.boolFalse) } |> mapToSignal { _ -> Signal in return .complete() } }