From 48cb4942c69c19303155fa2f63e77ce9688e26cb Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 6 Oct 2018 01:18:17 +0400 Subject: [PATCH] More robust presence management --- TelegramCore.xcodeproj/project.pbxproj | 6 ++ TelegramCore/Account.swift | 74 +++++++------- TelegramCore/AccountIntermediateState.swift | 2 +- .../AccountStateManagementUtils.swift | 5 +- TelegramCore/ManagedAccountPresence.swift | 98 +++++++++++++++++++ 5 files changed, 147 insertions(+), 38 deletions(-) create mode 100644 TelegramCore/ManagedAccountPresence.swift diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 37eaf63f77..bab8f79321 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -73,6 +73,8 @@ D001F3F61E128A1C007A8C60 /* PendingMessageUploadedContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09BB6B51DB0428000A905C0 /* PendingMessageUploadedContent.swift */; }; D001F3F71E128A1C007A8C60 /* ApplyUpdateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9221DD5E9A200E8160F /* ApplyUpdateMessage.swift */; }; D003702B1DA42586004308D3 /* PhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D003702A1DA42586004308D3 /* PhoneNumber.swift */; }; + D00422D321677F4500719B67 /* ManagedAccountPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00422D221677F4500719B67 /* ManagedAccountPresence.swift */; }; + D00422D421677F4500719B67 /* ManagedAccountPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00422D221677F4500719B67 /* ManagedAccountPresence.swift */; }; D00BDA191EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00BDA181EE593D600C64C5E /* TelegramChannelAdminRights.swift */; }; D00BDA1A1EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00BDA181EE593D600C64C5E /* TelegramChannelAdminRights.swift */; }; D00BDA1C1EE5952A00C64C5E /* TelegramChannelBannedRights.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00BDA1B1EE5952A00C64C5E /* TelegramChannelBannedRights.swift */; }; @@ -779,6 +781,7 @@ C2FD33E31E687BF1008D13D4 /* PeerPhotoUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPhotoUpdater.swift; sourceTree = ""; }; C2FD33EA1E696C78008D13D4 /* GroupsInCommon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupsInCommon.swift; sourceTree = ""; }; D003702A1DA42586004308D3 /* PhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = ""; }; + D00422D221677F4500719B67 /* ManagedAccountPresence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAccountPresence.swift; sourceTree = ""; }; D00BDA181EE593D600C64C5E /* TelegramChannelAdminRights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannelAdminRights.swift; sourceTree = ""; }; D00BDA1B1EE5952A00C64C5E /* TelegramChannelBannedRights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannelBannedRights.swift; sourceTree = ""; }; D00C7CCB1E3620C30080C3D5 /* CachedChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedChannelParticipants.swift; sourceTree = ""; }; @@ -1467,6 +1470,7 @@ D048B4AB20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift */, D0467D0A20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */, D0467D1420D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift */, + D00422D221677F4500719B67 /* ManagedAccountPresence.swift */, ); name = State; sourceTree = ""; @@ -2209,6 +2213,7 @@ D0DA1D321F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */, D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */, C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */, + D00422D321677F4500719B67 /* ManagedAccountPresence.swift in Sources */, D03B0D0A1D62255C00955575 /* Holes.swift in Sources */, D05464972073872C002ECC1E /* SecureIdBankStatementValue.swift in Sources */, D0B843CB1DA7FF30005F29E1 /* NBPhoneNumberUtil.m in Sources */, @@ -2747,6 +2752,7 @@ D0575AF21E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift in Sources */, D0528E661E65C82400E2FEF5 /* UpdateContactName.swift in Sources */, D0EE7FC82098853100981319 /* SecureIdTemporaryRegistrationValue.swift in Sources */, + D00422D421677F4500719B67 /* ManagedAccountPresence.swift in Sources */, D023E67921540624008C27D1 /* UpdateMessageMedia.swift in Sources */, D053B4191F18DE5000E2D58A /* AuthorSignatureMessageAttribute.swift in Sources */, D0F7B1E81E045C87007EB8A5 /* PeerParticipants.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index e2c9928dda..5189754734 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -874,10 +874,10 @@ public class Account { private(set) var mediaReferenceRevalidationContext: MediaReferenceRevalidationContext! private var peerInputActivityManager: PeerInputActivityManager! private var localInputActivityManager: PeerInputActivityManager! + private var accountPresenceManager: AccountPresenceManager! fileprivate let managedContactsDisposable = MetaDisposable() fileprivate let managedStickerPacksDisposable = MetaDisposable() private let becomeMasterDisposable = MetaDisposable() - private let updatedPresenceDisposable = MetaDisposable() private let managedServiceViewsDisposable = MetaDisposable() private let managedOperationsDisposable = DisposableSet() @@ -948,6 +948,8 @@ public class Account { self?.stateManager.addUpdates(updates) }) self.localInputActivityManager = PeerInputActivityManager() + self.accountPresenceManager = AccountPresenceManager(shouldKeepOnlinePresence: self.shouldKeepOnlinePresence.get(), network: network) + self.viewTracker = AccountViewTracker(account: self) self.messageMediaPreuploadManager = MessageMediaPreuploadManager() self.mediaReferenceRevalidationContext = MediaReferenceRevalidationContext() @@ -1146,16 +1148,17 @@ public class Account { let importantBackgroundOperations: [Signal] = [ managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] }, - self.pendingMessageManager.hasPendingMessages |> map { $0 ? AccountRunningImportantTasks.pendingMessages : [] } + self.pendingMessageManager.hasPendingMessages |> map { $0 ? AccountRunningImportantTasks.pendingMessages : [] }, + self.accountPresenceManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] } ] let importantBackgroundOperationsRunning = combineLatest(importantBackgroundOperations) - |> deliverOn(Queue()) - |> map { values -> AccountRunningImportantTasks in - var result: AccountRunningImportantTasks = [] - for value in values { - result.formUnion(value) - } - return result + |> deliverOn(Queue()) + |> map { values -> AccountRunningImportantTasks in + var result: AccountRunningImportantTasks = [] + for value in values { + result.formUnion(value) + } + return result } self.managedOperationsDisposable.add(importantBackgroundOperationsRunning.start(next: { [weak self] value in @@ -1170,32 +1173,38 @@ public class Account { self.managedOperationsDisposable.add(managedLocalizationUpdatesOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedPendingPeerNotificationSettings(postbox: self.postbox, network: self.network).start()) - let updatedPresence = self.shouldKeepOnlinePresence.get() - |> distinctUntilChanged - |> mapToSignal { [weak self] online -> Signal in - if let strongSelf = self { - let delayRequest: Signal = .complete() |> delay(60.0, queue: Queue.concurrentDefaultQueue()) - let pushStatusOnce = strongSelf.network.request(Api.functions.account.updateStatus(offline: online ? .boolFalse : .boolTrue)) - |> retryRequest - |> mapToSignal { _ -> Signal in return .complete() } - let pushStatusRepeatedly = (pushStatusOnce |> then(delayRequest)) |> restart - let peerId = strongSelf.peerId - let updatePresenceLocally = strongSelf.postbox.transaction { transaction -> Void in - let timestamp: Double - if online { - timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60.0 * 60.0 * 24.0 * 356.0 - } else { - timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - 1.0 - } - transaction.updatePeerPresences([peerId: TelegramUserPresence(status: .present(until: Int32(timestamp)))]) + /*let updatedPresence = self.shouldKeepOnlinePresence.get() + |> distinctUntilChanged + |> mapToSignal { [weak self] online -> Signal in + if let strongSelf = self { + let delayRequest: Signal = .complete() + |> delay(60.0, queue: Queue.concurrentDefaultQueue()) + let pushStatusOnce = strongSelf.network.request(Api.functions.account.updateStatus(offline: online ? .boolFalse : .boolTrue)) + |> retryRequest + |> mapToSignal { _ -> Signal in return .complete() } + + let pushStatusRepeatedly = (pushStatusOnce + |> then(delayRequest)) + |> restart + + let peerId = strongSelf.peerId + let updatePresenceLocally = strongSelf.postbox.transaction { transaction -> Void in + let timestamp: Double + if online { + timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60.0 * 60.0 * 24.0 * 356.0 + } else { + timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - 1.0 } - return combineLatest(pushStatusRepeatedly, updatePresenceLocally) - |> mapToSignal { _ -> Signal in return .complete() } - } else { - return .complete() + transaction.updatePeerPresences([peerId: TelegramUserPresence(status: .present(until: Int32(timestamp)))]) } + return combineLatest(pushStatusRepeatedly, updatePresenceLocally) + |> mapToSignal { _ -> Signal in return .complete() + } + } else { + return .complete() + } } - self.updatedPresenceDisposable.set(updatedPresence.start()) + self.updatedPresenceDisposable.set(updatedPresence.start())*/ } deinit { @@ -1204,7 +1213,6 @@ public class Account { self.notificationTokenDisposable.dispose() self.voipTokenDisposable.dispose() self.managedServiceViewsDisposable.dispose() - self.updatedPresenceDisposable.dispose() self.managedOperationsDisposable.dispose() } diff --git a/TelegramCore/AccountIntermediateState.swift b/TelegramCore/AccountIntermediateState.swift index ffc0f077fc..4a14f49e78 100644 --- a/TelegramCore/AccountIntermediateState.swift +++ b/TelegramCore/AccountIntermediateState.swift @@ -157,7 +157,7 @@ struct AccountMutableState { self.preCachedResources.append(contentsOf: other.preCachedResources) for (peerId, namespaces) in other.namespacesWithHolesFromPreviousState { if self.namespacesWithHolesFromPreviousState[peerId] == nil { - self.self.namespacesWithHolesFromPreviousState[peerId] = Set() + self.namespacesWithHolesFromPreviousState[peerId] = Set() } for namespace in namespaces { self.namespacesWithHolesFromPreviousState[peerId]!.insert(namespace) diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index 563477587e..11b5893907 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -1593,10 +1593,7 @@ private func resetChannels(_ account: Account, peers: [Peer], state: AccountMuta private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableState) -> Signal<(AccountMutableState, Bool, Int32?), NoError> { if let inputChannel = apiInputChannel(peer) { - var limit: Int32 = 20 - #if (arch(i386) || arch(x86_64)) && os(iOS) - limit = 3 - #endif + let limit: Int32 = 20 let pollPts: Int32 if let channelState = state.chatStates[peer.id] as? ChannelState { pollPts = channelState.pts diff --git a/TelegramCore/ManagedAccountPresence.swift b/TelegramCore/ManagedAccountPresence.swift new file mode 100644 index 0000000000..3ccc39629f --- /dev/null +++ b/TelegramCore/ManagedAccountPresence.swift @@ -0,0 +1,98 @@ +import Foundation +#if os(macOS) +import PostboxMac +import SwiftSignalKitMac +import MtProtoKitMac +#else +import Postbox +import SwiftSignalKit +import MtProtoKitDynamic +#endif + +#if os(macOS) +private typealias SignalKitTimer = SwiftSignalKitMac.Timer +#else +private typealias SignalKitTimer = SwiftSignalKit.Timer +#endif + +private final class AccountPresenceManagerImpl { + private let queue: Queue + private let network: Network + let isPerformingUpdate = ValuePromise(false, ignoreRepeated: true) + + private var shouldKeepOnlinePresenceDisposable: Disposable? + private let currentRequestDisposable = MetaDisposable() + private var onlineTimer: SignalKitTimer? + + init(queue: Queue, shouldKeepOnlinePresence: Signal, network: Network) { + self.queue = queue + self.network = network + + self.shouldKeepOnlinePresenceDisposable = (shouldKeepOnlinePresence + |> distinctUntilChanged + |> deliverOn(self.queue)).start(next: { [weak self] value in + self?.updatePresence(value) + }) + } + + deinit { + assert(self.queue.isCurrent()) + self.shouldKeepOnlinePresenceDisposable?.dispose() + self.currentRequestDisposable.dispose() + self.onlineTimer?.invalidate() + } + + private func updatePresence(_ isOnline: Bool) { + let request: Signal + if isOnline { + let timer = SignalKitTimer(timeout: 30.0, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.updatePresence(true) + }, queue: self.queue) + self.onlineTimer = timer + timer.start() + request = self.network.request(Api.functions.account.updateStatus(offline: .boolFalse)) + } else { + self.onlineTimer?.invalidate() + self.onlineTimer = nil + request = self.network.request(Api.functions.account.updateStatus(offline: .boolTrue)) + } + self.isPerformingUpdate.set(true) + self.currentRequestDisposable.set((request + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> deliverOn(self.queue)).start(completed: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.isPerformingUpdate.set(false) + })) + } +} + +final class AccountPresenceManager { + private let queue = Queue() + private let impl: QueueLocalObject + + init(shouldKeepOnlinePresence: Signal, network: Network) { + let queue = self.queue + self.impl = QueueLocalObject(queue: self.queue, generate: { + return AccountPresenceManagerImpl(queue: queue, shouldKeepOnlinePresence: shouldKeepOnlinePresence, network: network) + }) + } + + func isPerformingUpdate() -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.isPerformingUpdate.get().start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } +}