Swiftgram/submodules/TelegramCore/Sources/ContactManagement.swift
2019-10-29 00:13:20 +04:00

184 lines
7.2 KiB
Swift

import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import TelegramApiMac
#else
import TelegramApi
import Postbox
import SwiftSignalKit
#endif
import SyncCore
private func md5(_ data: Data) -> Data {
return data.withUnsafeBytes { bytes -> Data in
return CryptoMD5(bytes, Int32(data.count))
}
}
private func updatedRemoteContactPeers(network: Network, hash: Int32) -> Signal<([Peer], [PeerId: PeerPresence], Int32)?, NoError> {
return network.request(Api.functions.contacts.getContacts(hash: hash), automaticFloodWait: false)
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.contacts.Contacts?, NoError> in
return .single(nil)
}
|> map { result -> ([Peer], [PeerId: PeerPresence], Int32)? in
guard let result = result else {
return nil
}
switch result {
case .contactsNotModified:
return nil
case let .contacts(_, savedCount, users):
var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:]
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
if let presence = TelegramUserPresence(apiUser: user) {
peerPresences[telegramUser.id] = presence
}
}
return (peers, peerPresences, savedCount)
}
}
}
private func hashForCountAndIds(count: Int32, ids: [Int32]) -> Int32 {
var acc: Int64 = 0
acc = (acc &* 20261) &+ Int64(count)
for id in ids {
acc = (acc &* 20261) &+ Int64(id)
acc = acc & Int64(0x7FFFFFFF)
}
return Int32(acc & Int64(0x7FFFFFFF))
}
func syncContactsOnce(network: Network, postbox: Postbox, accountPeerId: PeerId) -> Signal<Never, NoError> {
let initialContactPeerIdsHash = postbox.transaction { transaction -> Int32 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 }).sorted())
}
let updatedPeers = initialContactPeerIdsHash
|> mapToSignal { hash -> Signal<([Peer], [PeerId: PeerPresence], Int32)?, NoError> in
return updatedRemoteContactPeers(network: network, hash: hash)
}
let appliedUpdatedPeers = updatedPeers
|> mapToSignal { peersAndPresences -> Signal<Never, NoError> in
if let (peers, peerPresences, totalCount) = peersAndPresences {
return postbox.transaction { transaction -> Signal<Void, NoError> in
let previousIds = transaction.getContactPeerIds()
let wasEmpty = previousIds.isEmpty
transaction.replaceRemoteContactCount(totalCount)
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
if wasEmpty {
var insertSignal: Signal<Void, NoError> = .complete()
for s in stride(from: 0, to: peers.count, by: 500) {
let partPeers = Array(peers[s ..< min(s + 500, peers.count)])
let partSignal = postbox.transaction { transaction -> Void in
updatePeers(transaction: transaction, peers: partPeers, update: { return $1 })
var updatedIds = transaction.getContactPeerIds()
updatedIds.formUnion(partPeers.map { $0.id })
transaction.replaceContactPeerIds(updatedIds)
}
|> delay(0.1, queue: Queue.concurrentDefaultQueue())
insertSignal = insertSignal |> then(partSignal)
}
return insertSignal
} else {
transaction.replaceContactPeerIds(Set(peers.map { $0.id }))
return .complete()
}
}
|> switchToLatest
|> ignoreValues
} else {
return .complete()
}
}
return appliedUpdatedPeers
}
public func deleteContactPeerInteractively(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Signal<Never, NoError> 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<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Void, NoError> in
if let updates = updates {
account.stateManager.addUpdates(updates)
}
return account.postbox.transaction { transaction -> Void in
var peerIds = transaction.getContactPeerIds()
if peerIds.contains(peerId) {
peerIds.remove(peerId)
transaction.replaceContactPeerIds(peerIds)
}
}
}
|> ignoreValues
} else {
return .complete()
}
}
|> switchToLatest
}
public func deleteAllContacts(account: Account) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> [Api.InputUser] in
return transaction.getContactPeerIds().compactMap(transaction.getPeer).compactMap({ apiInputUser($0) }).compactMap({ $0 })
}
|> mapToSignal { users -> Signal<Never, NoError> in
let deleteContacts = account.network.request(Api.functions.contacts.deleteContacts(id: users))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
let deleteImported = account.network.request(Api.functions.contacts.resetSaved())
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
return combineLatest(deleteContacts, deleteImported)
|> mapToSignal { updates, _ -> Signal<Never, NoError> in
return account.postbox.transaction { transaction -> Void in
transaction.replaceContactPeerIds(Set())
transaction.clearDeviceContactImportInfoIdentifiers()
}
|> mapToSignal { _ -> Signal<Void, NoError> in
account.restartContactManagement()
if let updates = updates {
account.stateManager.addUpdates(updates)
}
return .complete()
}
|> ignoreValues
}
}
}
public func resetSavedContacts(network: Network) -> Signal<Void, NoError> {
return network.request(Api.functions.contacts.resetSaved())
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}