From c2768b07b36ceddfa1b6f27cf5c6312a5b19a6c9 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 27 Dec 2016 14:40:30 +0300 Subject: [PATCH] no message --- TelegramCore.xcodeproj/project.pbxproj | 76 ++-- TelegramCore/Account.swift | 35 +- TelegramCore/AccountIntermediateState.swift | 292 +++++++++++++ ...wift => AccountStateManagementUtils.swift} | 325 +++------------ TelegramCore/AccountStateManager.swift | 393 ++++++++++++++++++ TelegramCore/AccountViewTracker.swift | 12 +- TelegramCore/ApplyUpdateMessage.swift | 2 +- TelegramCore/ManagedServiceViews.swift | 2 +- .../ManagedSynchronizePeerReadStates.swift | 2 +- TelegramCore/Network.swift | 22 +- TelegramCore/PendingMessageManager.swift | 8 +- TelegramCore/RequestStartBot.swift | 36 ++ TelegramCore/ResolvePeerByName.swift | 14 +- TelegramCore/SearchMessages.swift | 19 +- TelegramCore/SendUnsentMessage.swift | 2 +- TelegramCore/SynchronizePeerReadState.swift | 21 +- .../TextEntitiesMessageAttribute.swift | 4 +- 17 files changed, 930 insertions(+), 335 deletions(-) create mode 100644 TelegramCore/AccountIntermediateState.swift rename TelegramCore/{StateManagement.swift => AccountStateManagementUtils.swift} (83%) create mode 100644 TelegramCore/AccountStateManager.swift create mode 100644 TelegramCore/RequestStartBot.swift diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 17f58f100d..ac5c91c68d 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -7,7 +7,26 @@ objects = { /* Begin PBXBuildFile section */ + D001F3E81E128A1C007A8C60 /* ChannelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CFF1D62255C00955575 /* ChannelState.swift */; }; + D001F3E91E128A1C007A8C60 /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; }; + D001F3EA1E128A1C007A8C60 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; }; + D001F3EB1E128A1C007A8C60 /* EnqueueMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D001D62255C00955575 /* EnqueueMessage.swift */; }; + D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D011D62255C00955575 /* Holes.swift */; }; + D001F3ED1E128A1C007A8C60 /* SendUnsentMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D021D62255C00955575 /* SendUnsentMessage.swift */; }; + D001F3EE1E128A1C007A8C60 /* AccountStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495D1E118F790057C89A /* AccountStateManager.swift */; }; + D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495F1E118FC30057C89A /* AccountIntermediateState.swift */; }; + D001F3F01E128A1C007A8C60 /* AccountStateManagementUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D031D62255C00955575 /* AccountStateManagementUtils.swift */; }; + D001F3F11E128A1C007A8C60 /* SynchronizePeerReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D041D62255C00955575 /* SynchronizePeerReadState.swift */; }; + D001F3F21E128A1C007A8C60 /* UpdateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D051D62255C00955575 /* UpdateGroup.swift */; }; + D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D061D62255C00955575 /* UpdateMessageService.swift */; }; + D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D071D62255C00955575 /* UpdatesApiUtils.swift */; }; + D001F3F51E128A1C007A8C60 /* PendingMessageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09BB6B31DB02C2B00A905C0 /* PendingMessageManager.swift */; }; + 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 */; }; + D01749591E1092BC0057C89A /* RequestStartBot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01749581E1092BC0057C89A /* RequestStartBot.swift */; }; + D017495E1E118F790057C89A /* AccountStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495D1E118F790057C89A /* AccountStateManager.swift */; }; + D01749601E118FC30057C89A /* AccountIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495F1E118FC30057C89A /* AccountIntermediateState.swift */; }; D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; }; D01AC91D1DD5DA5E00E8160F /* RequestMessageActionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */; }; D01AC9211DD5E7E500E8160F /* RequestEditMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */; }; @@ -41,7 +60,7 @@ D03B0D091D62255C00955575 /* EnqueueMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D001D62255C00955575 /* EnqueueMessage.swift */; }; D03B0D0A1D62255C00955575 /* Holes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D011D62255C00955575 /* Holes.swift */; }; D03B0D0B1D62255C00955575 /* SendUnsentMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D021D62255C00955575 /* SendUnsentMessage.swift */; }; - D03B0D0C1D62255C00955575 /* StateManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D031D62255C00955575 /* StateManagement.swift */; }; + D03B0D0C1D62255C00955575 /* AccountStateManagementUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D031D62255C00955575 /* AccountStateManagementUtils.swift */; }; D03B0D0D1D62255C00955575 /* SynchronizePeerReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D041D62255C00955575 /* SynchronizePeerReadState.swift */; }; D03B0D0E1D62255C00955575 /* UpdateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D051D62255C00955575 /* UpdateGroup.swift */; }; D03B0D0F1D62255C00955575 /* UpdateMessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D061D62255C00955575 /* UpdateMessageService.swift */; }; @@ -91,8 +110,6 @@ D073CE6E1DCBCF17007511FD /* ForwardSourceInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */; }; D073CE6F1DCBCF17007511FD /* OutgoingMessageInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */; }; D073CEA11DCBF3D3007511FD /* StickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DE1DB539FC00C6B04F /* StickerPack.swift */; }; - D073CEA21DCBF3E1007511FD /* PendingMessageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09BB6B31DB02C2B00A905C0 /* PendingMessageManager.swift */; }; - D073CEA31DCBF3E1007511FD /* PendingMessageUploadedContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09BB6B51DB0428000A905C0 /* PendingMessageUploadedContent.swift */; }; D073CEA41DCBF3EA007511FD /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03C53761DAFF20F004C17B3 /* MultipartUpload.swift */; }; D073CEA51DCBF3F5007511FD /* StickerManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0E11DB5401A00C6B04F /* StickerManagement.swift */; }; D07827BB1E00451F00071108 /* SearchPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827BA1E00451F00071108 /* SearchPeers.swift */; }; @@ -191,16 +208,6 @@ D0B844341DAB91E0005F29E1 /* NBPhoneNumberDefines.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B843AD1DA7FF30005F29E1 /* NBPhoneNumberDefines.m */; }; D0B844351DAB91E0005F29E1 /* NBPhoneNumberDesc.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B843AF1DA7FF30005F29E1 /* NBPhoneNumberDesc.m */; }; D0B844361DAB91E0005F29E1 /* NBPhoneNumberUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D0B843B11DA7FF30005F29E1 /* NBPhoneNumberUtil.m */; }; - D0B844381DAB91EF005F29E1 /* ChannelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CFF1D62255C00955575 /* ChannelState.swift */; }; - D0B844391DAB91EF005F29E1 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; }; - D0B8443A1DAB91EF005F29E1 /* EnqueueMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D001D62255C00955575 /* EnqueueMessage.swift */; }; - D0B8443B1DAB91EF005F29E1 /* Holes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D011D62255C00955575 /* Holes.swift */; }; - D0B8443C1DAB91EF005F29E1 /* SendUnsentMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D021D62255C00955575 /* SendUnsentMessage.swift */; }; - D0B8443D1DAB91EF005F29E1 /* StateManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D031D62255C00955575 /* StateManagement.swift */; }; - D0B8443E1DAB91EF005F29E1 /* SynchronizePeerReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D041D62255C00955575 /* SynchronizePeerReadState.swift */; }; - D0B8443F1DAB91EF005F29E1 /* UpdateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D051D62255C00955575 /* UpdateGroup.swift */; }; - D0B844401DAB91EF005F29E1 /* UpdateMessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D061D62255C00955575 /* UpdateMessageService.swift */; }; - D0B844411DAB91EF005F29E1 /* UpdatesApiUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D071D62255C00955575 /* UpdatesApiUtils.swift */; }; D0B844431DAB91FD005F29E1 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D611D631A8B00955575 /* Account.swift */; }; D0B844441DAB91FD005F29E1 /* AccountSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D621D631A8B00955575 /* AccountSettings.swift */; }; D0B844451DAB91FD005F29E1 /* AccountViewTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D631D631A8B00955575 /* AccountViewTracker.swift */; }; @@ -256,6 +263,9 @@ /* Begin PBXFileReference section */ D003702A1DA42586004308D3 /* PhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = ""; }; + D01749581E1092BC0057C89A /* RequestStartBot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestStartBot.swift; sourceTree = ""; }; + D017495D1E118F790057C89A /* AccountStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStateManager.swift; sourceTree = ""; }; + D017495F1E118FC30057C89A /* AccountIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountIntermediateState.swift; sourceTree = ""; }; D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = ""; }; D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestMessageActionCallback.swift; sourceTree = ""; }; D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestEditMessage.swift; sourceTree = ""; }; @@ -289,7 +299,7 @@ D03B0D001D62255C00955575 /* EnqueueMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnqueueMessage.swift; sourceTree = ""; }; D03B0D011D62255C00955575 /* Holes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Holes.swift; sourceTree = ""; }; D03B0D021D62255C00955575 /* SendUnsentMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendUnsentMessage.swift; sourceTree = ""; }; - D03B0D031D62255C00955575 /* StateManagement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateManagement.swift; sourceTree = ""; }; + D03B0D031D62255C00955575 /* AccountStateManagementUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStateManagementUtils.swift; sourceTree = ""; }; D03B0D041D62255C00955575 /* SynchronizePeerReadState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizePeerReadState.swift; sourceTree = ""; }; D03B0D051D62255C00955575 /* UpdateGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateGroup.swift; sourceTree = ""; }; D03B0D061D62255C00955575 /* UpdateMessageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateMessageService.swift; sourceTree = ""; }; @@ -594,7 +604,9 @@ D03B0D001D62255C00955575 /* EnqueueMessage.swift */, D03B0D011D62255C00955575 /* Holes.swift */, D03B0D021D62255C00955575 /* SendUnsentMessage.swift */, - D03B0D031D62255C00955575 /* StateManagement.swift */, + D017495D1E118F790057C89A /* AccountStateManager.swift */, + D017495F1E118FC30057C89A /* AccountIntermediateState.swift */, + D03B0D031D62255C00955575 /* AccountStateManagementUtils.swift */, D03B0D041D62255C00955575 /* SynchronizePeerReadState.swift */, D03B0D051D62255C00955575 /* UpdateGroup.swift */, D03B0D061D62255C00955575 /* UpdateMessageService.swift */, @@ -672,6 +684,7 @@ D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */, D0DC354F1DE36900000195EB /* ChatContextResult.swift */, D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */, + D01749581E1092BC0057C89A /* RequestStartBot.swift */, ); name = Messages; sourceTree = ""; @@ -1029,12 +1042,14 @@ D03B0D0F1D62255C00955575 /* UpdateMessageService.swift in Sources */, D03B0CF61D62250800955575 /* TelegramMediaFile.swift in Sources */, D03B0CE81D6224AD00955575 /* ViewCountMessageAttribute.swift in Sources */, - D03B0D0C1D62255C00955575 /* StateManagement.swift in Sources */, + D03B0D0C1D62255C00955575 /* AccountStateManagementUtils.swift in Sources */, D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */, D03B0D721D631ABA00955575 /* SearchMessages.swift in Sources */, D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */, D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */, D03B0D5C1D631A6900955575 /* Download.swift in Sources */, + D01749591E1092BC0057C89A /* RequestStartBot.swift in Sources */, + D017495E1E118F790057C89A /* AccountStateManager.swift in Sources */, D0B843C71DA7FF30005F29E1 /* NBPhoneNumberDefines.m in Sources */, D03B0D5D1D631A6900955575 /* MultipartFetch.swift in Sources */, D0AB0B961D662F0B002C78E7 /* ManagedChatListHoles.swift in Sources */, @@ -1051,6 +1066,7 @@ D03B0CC11D62235000955575 /* StringFormat.swift in Sources */, D0B843C31DA7FF30005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */, D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */, + D01749601E118FC30057C89A /* AccountIntermediateState.swift in Sources */, D03B0D651D631A8B00955575 /* Account.swift in Sources */, D0AB0B941D662ECE002C78E7 /* ManagedMessageHistoryHoles.swift in Sources */, D03B0CF41D62250800955575 /* TelegramMediaAction.swift in Sources */, @@ -1075,7 +1091,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D0B8443D1DAB91EF005F29E1 /* StateManagement.swift in Sources */, D0F7B1EA1E045C87007EB8A5 /* ChangePeerNotificationSettings.swift in Sources */, D0B418A71D7E0592004562A4 /* Fetch.swift in Sources */, D0B418B81D7E05A6004562A4 /* ContactManagement.swift in Sources */, @@ -1083,38 +1098,42 @@ D0B844311DAB91E0005F29E1 /* NBPhoneMetaData.m in Sources */, D0B418AC1D7E0597004562A4 /* Network.swift in Sources */, D0B844141DAB91CD005F29E1 /* PhoneNumbers.swift in Sources */, - D0B844391DAB91EF005F29E1 /* TelegramPeerNotificationSettings.swift in Sources */, D0B844491DAB91FD005F29E1 /* ManagedChatListHoles.swift in Sources */, D03C53711DAD5CA9004C17B3 /* CachedGroupParticipants.swift in Sources */, D03C53671DAD5CA9004C17B3 /* ApiUtils.swift in Sources */, + D001F3F21E128A1C007A8C60 /* UpdateGroup.swift in Sources */, D0F7B1EB1E045C87007EB8A5 /* ResolvePeerByName.swift in Sources */, + D001F3EE1E128A1C007A8C60 /* AccountStateManager.swift in Sources */, D0B844351DAB91E0005F29E1 /* NBPhoneNumberDesc.m in Sources */, D0B8442F1DAB91E0005F29E1 /* NBMetadataHelper.m in Sources */, D0B8444C1DAB91FD005F29E1 /* UpdateCachedPeerData.swift in Sources */, D0B418A81D7E0597004562A4 /* Api.swift in Sources */, D03C536C1DAD5CA9004C17B3 /* TelegramChannel.swift in Sources */, D0B418951D7E0580004562A4 /* TelegramMediaContact.swift in Sources */, - D0B8443B1DAB91EF005F29E1 /* Holes.swift in Sources */, D0F3CC7A1DDE2859008148FA /* RequestMessageActionCallback.swift in Sources */, D073CEA11DCBF3D3007511FD /* StickerPack.swift in Sources */, D0E35A141DE4C69C00BC6096 /* FetchHttpResource.swift in Sources */, - D0B8443E1DAB91EF005F29E1 /* SynchronizePeerReadState.swift in Sources */, D0B8440D1DAB91CD005F29E1 /* ImageRepresentationsUtils.swift in Sources */, D03C536A1DAD5CA9004C17B3 /* TelegramUser.swift in Sources */, + D001F3EA1E128A1C007A8C60 /* TelegramPeerNotificationSettings.swift in Sources */, + D001F3EB1E128A1C007A8C60 /* EnqueueMessage.swift in Sources */, D0B844481DAB91FD005F29E1 /* ManagedMessageHistoryHoles.swift in Sources */, D0F3CC7B1DDE2859008148FA /* RequestEditMessage.swift in Sources */, D0B844441DAB91FD005F29E1 /* AccountSettings.swift in Sources */, + D001F3F01E128A1C007A8C60 /* AccountStateManagementUtils.swift in Sources */, D0F3CC791DDE2859008148FA /* SearchMessages.swift in Sources */, D0B8442B1DAB91E0005F29E1 /* NBMetadataCore.m in Sources */, + D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */, D0B8442D1DAB91E0005F29E1 /* NBMetadataCoreTest.m in Sources */, D0B844131DAB91CD005F29E1 /* StringFormat.swift in Sources */, - D0B844401DAB91EF005F29E1 /* UpdateMessageService.swift in Sources */, D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */, D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */, + D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */, D0B4189B1D7E0580004562A4 /* TelegramMediaWebpage.swift in Sources */, D0B844341DAB91E0005F29E1 /* NBPhoneNumberDefines.m in Sources */, D0B8442A1DAB91E0005F29E1 /* NBAsYouTypeFormatter.m in Sources */, D073CE6E1DCBCF17007511FD /* ForwardSourceInfoAttribute.swift in Sources */, + D001F3E81E128A1C007A8C60 /* ChannelState.swift in Sources */, D0B844451DAB91FD005F29E1 /* AccountViewTracker.swift in Sources */, D0B418A61D7E0592004562A4 /* CloudFileMediaResource.swift in Sources */, D073CEA51DCBF3F5007511FD /* StickerManagement.swift in Sources */, @@ -1122,12 +1141,12 @@ D0E35A151DE4C6A200BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */, D0B418A91D7E0597004562A4 /* Buffer.swift in Sources */, D0B8442E1DAB91E0005F29E1 /* NBMetadataCoreTestMapper.m in Sources */, - D0B8443F1DAB91EF005F29E1 /* UpdateGroup.swift in Sources */, D03C53731DAD5CA9004C17B3 /* CachedGroupData.swift in Sources */, D0F7AB2D1DCE889D009AD9A1 /* EditedMessageAttribute.swift in Sources */, D0B844121DAB91CD005F29E1 /* Log.swift in Sources */, D03C53721DAD5CA9004C17B3 /* CachedUserData.swift in Sources */, D073CE6B1DCBCF17007511FD /* ReplyMessageAttribute.swift in Sources */, + D001F3E91E128A1C007A8C60 /* SecretChatState.swift in Sources */, D0B8444B1DAB91FD005F29E1 /* ManagedSynchronizePeerReadStates.swift in Sources */, D073CE6C1DCBCF17007511FD /* TextEntitiesMessageAttribute.swift in Sources */, D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */, @@ -1137,14 +1156,15 @@ D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */, D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */, D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */, + D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */, D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */, D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */, - D0B8443C1DAB91EF005F29E1 /* SendUnsentMessage.swift in Sources */, D0B844321DAB91E0005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */, D073CEA41DCBF3EA007511FD /* MultipartUpload.swift in Sources */, D03C53701DAD5CA9004C17B3 /* ExportedInvitation.swift in Sources */, D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */, D0B418AA1D7E0597004562A4 /* Download.swift in Sources */, + D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */, D0B4188E1D7E0578004562A4 /* StoreMessage_Telegram.swift in Sources */, D0B844461DAB91FD005F29E1 /* RecentPeers.swift in Sources */, D03C53681DAD5CA9004C17B3 /* PeerUtils.swift in Sources */, @@ -1155,25 +1175,25 @@ D0B418BA1D7E05BB004562A4 /* NetworkLogging.m in Sources */, D03C536B1DAD5CA9004C17B3 /* TelegramGroup.swift in Sources */, D0B418941D7E0580004562A4 /* TelegramMediaAction.swift in Sources */, - D0B844411DAB91EF005F29E1 /* UpdatesApiUtils.swift in Sources */, - D0B844381DAB91EF005F29E1 /* ChannelState.swift in Sources */, D0B8442C1DAB91E0005F29E1 /* NBMetadataCoreMapper.m in Sources */, - D0B8443A1DAB91EF005F29E1 /* EnqueueMessage.swift in Sources */, + D001F3ED1E128A1C007A8C60 /* SendUnsentMessage.swift in Sources */, D073CE6A1DCBCF17007511FD /* ViewCountMessageAttribute.swift in Sources */, D0B418AB1D7E0597004562A4 /* MultipartFetch.swift in Sources */, D0B418B71D7E05A6004562A4 /* Phonebook.swift in Sources */, D03C53741DAD5CA9004C17B3 /* CachedChannelData.swift in Sources */, - D073CEA31DCBF3E1007511FD /* PendingMessageUploadedContent.swift in Sources */, D0B418861D7E056D004562A4 /* Namespaces.swift in Sources */, D0F7B1E41E045C7B007EB8A5 /* InstantPage.swift in Sources */, D0B418AD1D7E0597004562A4 /* Serialization.swift in Sources */, D03C536F1DAD5CA9004C17B3 /* BotInfo.swift in Sources */, D0B844101DAB91CD005F29E1 /* MergeLists.swift in Sources */, + D001F3F11E128A1C007A8C60 /* SynchronizePeerReadState.swift in Sources */, D0F7B1E81E045C87007EB8A5 /* PeerParticipants.swift in Sources */, D0B844331DAB91E0005F29E1 /* NBPhoneNumber.m in Sources */, + D001F3F51E128A1C007A8C60 /* PendingMessageManager.swift in Sources */, + D001F3F61E128A1C007A8C60 /* PendingMessageUploadedContent.swift in Sources */, D0F7B1E71E045C87007EB8A5 /* JoinChannel.swift in Sources */, D0B844301DAB91E0005F29E1 /* NBNumberFormat.m in Sources */, - D073CEA21DCBF3E1007511FD /* PendingMessageManager.swift in Sources */, + D001F3F71E128A1C007A8C60 /* ApplyUpdateMessage.swift in Sources */, D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */, D0B844361DAB91E0005F29E1 /* NBPhoneNumberUtil.m in Sources */, D073CE6F1DCBCF17007511FD /* OutgoingMessageInfoAttribute.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 80be92e4c4..68dd13d801 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -353,6 +353,13 @@ public enum AccountServiceTaskMasterMode { case never } +public enum AccountNetworkState { + case waitingForNetwork + case connecting + case updating + case online +} + public class Account { public let id: AccountId public let basePath: String @@ -360,7 +367,7 @@ public class Account { public let network: Network public let peerId: PeerId - public private(set) var stateManager: StateManager! + public private(set) var stateManager: AccountStateManager! public private(set) var viewTracker: AccountViewTracker! public private(set) var pendingMessageManager: PendingMessageManager! fileprivate let managedContactsDisposable = MetaDisposable() @@ -384,6 +391,11 @@ public class Account { public let shouldBeServiceTaskMaster = Promise() public let shouldKeepOnlinePresence = Promise() + private let networkStateValue = Promise(.connecting) + public var networkState: Signal { + return self.networkStateValue.get() + } + public init(id: AccountId, basePath: String, postbox: Postbox, network: Network, peerId: PeerId) { self.id = id self.basePath = basePath @@ -391,10 +403,29 @@ public class Account { self.network = network self.peerId = peerId - self.stateManager = StateManager(account: self) + self.stateManager = AccountStateManager(account: self) self.viewTracker = AccountViewTracker(account: self) self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, stateManager: self.stateManager) + let networkStateSignal = combineLatest(self.stateManager.isUpdating, network.connectionStatus) + |> map { isUpdating, connectionStatus -> AccountNetworkState in + switch connectionStatus { + case .WaitingForNetwork: + return .waitingForNetwork + case .Connecting: + return .connecting + case .Updating: + return .updating + case .Online: + if isUpdating { + return .updating + } else { + return .online + } + } + } + self.networkStateValue.set(networkStateSignal |> distinctUntilChanged) + let appliedNotificationToken = self.notificationToken.get() |> distinctUntilChanged |> mapToSignal { token -> Signal in diff --git a/TelegramCore/AccountIntermediateState.swift b/TelegramCore/AccountIntermediateState.swift new file mode 100644 index 0000000000..218d9716ef --- /dev/null +++ b/TelegramCore/AccountIntermediateState.swift @@ -0,0 +1,292 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +final class AccountInitialState { + let state: AuthorizedAccountState.State + let peerIds: Set + let messageIds: Set + let channelStates: [PeerId: ChannelState] + let peerNotificationSettings: [PeerId: PeerNotificationSettings] + let peerIdsWithNewMessages: Set + let locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]] + + init(state: AuthorizedAccountState.State, peerIds: Set, messageIds: Set, peerIdsWithNewMessages: Set, channelStates: [PeerId: ChannelState], peerNotificationSettings: [PeerId: PeerNotificationSettings], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) { + self.state = state + self.peerIds = peerIds + self.messageIds = messageIds + self.channelStates = channelStates + self.peerIdsWithNewMessages = peerIdsWithNewMessages + self.peerNotificationSettings = peerNotificationSettings + self.locallyGeneratedMessageTimestamps = locallyGeneratedMessageTimestamps + } +} + +enum AccountStateMutationOperation { + case AddMessages([StoreMessage], AddMessagesLocation) + case DeleteMessagesWithGlobalIds([Int32]) + case DeleteMessages([MessageId]) + case EditMessage(MessageId, StoreMessage) + case UpdateMedia(MediaId, Media?) + case ReadInbox(MessageId) + case ReadOutbox(MessageId) + case ResetReadState(PeerId, MessageId.Namespace, MessageId.Id, MessageId.Id, MessageId.Id, Int32) + case UpdateState(AuthorizedAccountState.State) + case UpdateChannelState(PeerId, ChannelState) + case UpdatePeerNotificationSettings(PeerId, PeerNotificationSettings) + case AddHole(MessageId) + case MergeApiChats([Api.Chat]) + case UpdatePeer(PeerId, (Peer) -> Peer) + case MergeApiUsers([Api.User]) + case MergePeerPresences([PeerId: PeerPresence]) +} + +struct AccountMutableState { + let initialState: AccountInitialState + let branchOperationIndex: Int + + var operations: [AccountStateMutationOperation] = [] + + var state: AuthorizedAccountState.State + var peers: [PeerId: Peer] + var channelStates: [PeerId: ChannelState] + var peerNotificationSettings: [PeerId: PeerNotificationSettings] + var storedMessages: Set + var readInboxMaxIds: [PeerId: MessageId] + + var storedMessagesByPeerIdAndTimestamp: [PeerId: Set] + + var insertedPeers: [PeerId: Peer] = [:] + + var preCachedResources: [(MediaResource, Data)] = [] + + init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialStoredMessages: Set, initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set]) { + self.initialState = initialState + self.state = initialState.state + self.peers = initialPeers + self.storedMessages = initialStoredMessages + self.readInboxMaxIds = initialReadInboxMaxIds + self.channelStates = initialState.channelStates + self.peerNotificationSettings = initialState.peerNotificationSettings + self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp + self.branchOperationIndex = 0 + } + + init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], channelStates: [PeerId: ChannelState], peerNotificationSettings: [PeerId: PeerNotificationSettings], storedMessages: Set, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set], branchOperationIndex: Int) { + self.initialState = initialState + self.operations = operations + self.state = state + self.peers = peers + self.channelStates = channelStates + self.storedMessages = storedMessages + self.peerNotificationSettings = peerNotificationSettings + self.readInboxMaxIds = readInboxMaxIds + self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp + self.branchOperationIndex = branchOperationIndex + } + + func branch() -> AccountMutableState { + return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, channelStates: self.channelStates, peerNotificationSettings: self.peerNotificationSettings, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, branchOperationIndex: self.operations.count) + } + + mutating func merge(_ other: AccountMutableState) { + for i in other.branchOperationIndex ..< other.operations.count { + self.addOperation(other.operations[i]) + } + for (_, peer) in other.insertedPeers { + self.peers[peer.id] = peer + } + self.preCachedResources.append(contentsOf: other.preCachedResources) + } + + mutating func addPreCachedResource(_ resource: MediaResource, data: Data) { + self.preCachedResources.append((resource, data)) + } + + mutating func addMessages(_ messages: [StoreMessage], location: AddMessagesLocation) { + self.addOperation(.AddMessages(messages, location)) + } + + mutating func deleteMessagesWithGlobalIds(_ globalIds: [Int32]) { + self.addOperation(.DeleteMessagesWithGlobalIds(globalIds)) + } + + mutating func deleteMessages(_ messageIds: [MessageId]) { + self.addOperation(.DeleteMessages(messageIds)) + } + + mutating func editMessage(_ id: MessageId, message: StoreMessage) { + self.addOperation(.EditMessage(id, message)) + } + + mutating func updateMedia(_ id: MediaId, media: Media?) { + self.addOperation(.UpdateMedia(id, media)) + } + + mutating func readInbox(_ messageId: MessageId) { + self.addOperation(.ReadInbox(messageId)) + } + + mutating func readOutbox(_ messageId: MessageId) { + self.addOperation(.ReadOutbox(messageId)) + } + + mutating func resetReadState(_ peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32) { + self.addOperation(.ResetReadState(peerId, namespace, maxIncomingReadId, maxOutgoingReadId, maxKnownId, count)) + } + + mutating func updateState(_ state: AuthorizedAccountState.State) { + self.addOperation(.UpdateState(state)) + } + + mutating func updateChannelState(_ peerId: PeerId, state: ChannelState) { + self.addOperation(.UpdateChannelState(peerId, state)) + } + + mutating func updatePeerNotificationSettings(_ peerId: PeerId, notificationSettings: PeerNotificationSettings) { + self.addOperation(.UpdatePeerNotificationSettings(peerId, notificationSettings)) + } + + mutating func addHole(_ messageId: MessageId) { + self.addOperation(.AddHole(messageId)) + } + + mutating func mergeChats(_ chats: [Api.Chat]) { + self.addOperation(.MergeApiChats(chats)) + } + + mutating func updatePeer(_ id: PeerId, _ f: @escaping (Peer) -> Peer) { + self.addOperation(.UpdatePeer(id, f)) + } + + mutating func mergeUsers(_ users: [Api.User]) { + self.addOperation(.MergeApiUsers(users)) + + var presences: [PeerId: PeerPresence] = [:] + for user in users { + switch user { + case let .user(_, id, _, _, _, _, _, _, status, _, _, _): + if let status = status { + presences[PeerId(namespace: Namespaces.Peer.CloudUser, id: id)] = TelegramUserPresence(apiStatus: status) + } + break + case .userEmpty: + break + } + } + if !presences.isEmpty { + self.addOperation(.MergePeerPresences(presences)) + } + } + + mutating func mergePeerPresences(_ presences: [PeerId: PeerPresence]) { + self.addOperation(.MergePeerPresences(presences)) + } + + mutating func addOperation(_ operation: AccountStateMutationOperation) { + switch operation { + case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .MergePeerPresences: + break + case let .AddMessages(messages, _): + for message in messages { + if case let .Id(id) = message.id { + self.storedMessages.insert(id) + } + } + case let .UpdateState(state): + self.state = state + case let .UpdateChannelState(peerId, channelState): + self.channelStates[peerId] = channelState + case let .UpdatePeerNotificationSettings(peerId, notificationSettings): + self.peerNotificationSettings[peerId] = notificationSettings + case let .MergeApiChats(chats): + for chat in chats { + if let groupOrChannel = mergeGroupOrChannel(lhs: peers[chat.peerId], rhs: chat) { + peers[groupOrChannel.id] = groupOrChannel + insertedPeers[groupOrChannel.id] = groupOrChannel + } + } + case let .MergeApiUsers(users): + for apiUser in users { + if let user = TelegramUser.merge(peers[apiUser.peerId] as? TelegramUser, rhs: apiUser) { + peers[user.id] = user + insertedPeers[user.id] = user + } + } + case let .UpdatePeer(id, f): + if let peer = self.peers[id] { + let updatedPeer = f(peer) + peers[id] = updatedPeer + insertedPeers[id] = updatedPeer + } + case let .ReadInbox(messageId): + let current = self.readInboxMaxIds[messageId.peerId] + if current == nil || current! < messageId { + self.readInboxMaxIds[messageId.peerId] = messageId + } + //namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32 + case let .ResetReadState(peerId, namespace, maxIncomingReadId, _, _, _): + let current = self.readInboxMaxIds[peerId] + if namespace == Namespaces.Message.Cloud { + if current == nil || current!.id < maxIncomingReadId { + self.readInboxMaxIds[peerId] = MessageId(peerId: peerId, namespace: namespace, id: maxIncomingReadId) + } + } + } + + self.operations.append(operation) + } +} + +struct AccountFinalState { + let state: AccountMutableState + let shouldPoll: Bool + let incomplete: Bool +} + +struct AccountFinalStateEvents { + let addedIncomingMessageIds: [MessageId] + + var isEmpty: Bool { + return self.addedIncomingMessageIds.isEmpty + } + + init() { + self.addedIncomingMessageIds = [] + } + + init(addedIncomingMessageIds: [MessageId]) { + self.addedIncomingMessageIds = addedIncomingMessageIds + } + + init(state: AccountMutableState) { + var addedIncomingMessageIds: [MessageId] = [] + for operation in state.operations { + switch operation { + case let .AddMessages(messages, location): + if case .UpperHistoryBlock = location { + for message in messages { + if case let .Id(id) = message.id, message.flags.contains(.Incoming) { + addedIncomingMessageIds.append(id) + } + } + } + default: + break + } + } + self.addedIncomingMessageIds = addedIncomingMessageIds + } + + + func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { + return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds) + } +} diff --git a/TelegramCore/StateManagement.swift b/TelegramCore/AccountStateManagementUtils.swift similarity index 83% rename from TelegramCore/StateManagement.swift rename to TelegramCore/AccountStateManagementUtils.swift index 2aae4245bf..248f333099 100644 --- a/TelegramCore/StateManagement.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -15,232 +15,6 @@ private enum Event { case Completion } -private final class InitialState { - let state: AuthorizedAccountState.State - let peerIds: Set - let messageIds: Set - let channelStates: [PeerId: ChannelState] - let peerNotificationSettings: [PeerId: PeerNotificationSettings] - let peerIdsWithNewMessages: Set - let locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]] - - init(state: AuthorizedAccountState.State, peerIds: Set, messageIds: Set, peerIdsWithNewMessages: Set, channelStates: [PeerId: ChannelState], peerNotificationSettings: [PeerId: PeerNotificationSettings], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) { - self.state = state - self.peerIds = peerIds - self.messageIds = messageIds - self.channelStates = channelStates - self.peerIdsWithNewMessages = peerIdsWithNewMessages - self.peerNotificationSettings = peerNotificationSettings - self.locallyGeneratedMessageTimestamps = locallyGeneratedMessageTimestamps - } -} - -private enum MutationOperation { - case AddMessages([StoreMessage], AddMessagesLocation) - case DeleteMessagesWithGlobalIds([Int32]) - case DeleteMessages([MessageId]) - case EditMessage(MessageId, StoreMessage) - case UpdateMedia(MediaId, Media?) - case ReadInbox(MessageId) - case ReadOutbox(MessageId) - case ResetReadState(PeerId, MessageId.Namespace, MessageId.Id, MessageId.Id, MessageId.Id, Int32) - case UpdateState(AuthorizedAccountState.State) - case UpdateChannelState(PeerId, ChannelState) - case UpdatePeerNotificationSettings(PeerId, PeerNotificationSettings) - case AddHole(MessageId) - case MergeApiChats([Api.Chat]) - case UpdatePeer(PeerId, (Peer) -> Peer) - case MergeApiUsers([Api.User]) - case MergePeerPresences([PeerId: PeerPresence]) -} - -private struct MutableState { - let initialState: InitialState - let branchOperationIndex: Int - - fileprivate var operations: [MutationOperation] = [] - - fileprivate var state: AuthorizedAccountState.State - fileprivate var peers: [PeerId: Peer] - fileprivate var channelStates: [PeerId: ChannelState] - fileprivate var peerNotificationSettings: [PeerId: PeerNotificationSettings] - fileprivate var storedMessages: Set - - fileprivate var storedMessagesByPeerIdAndTimestamp: [PeerId: Set] - - fileprivate var insertedPeers: [PeerId: Peer] = [:] - - fileprivate var preCachedResources: [(MediaResource, Data)] = [] - - init(initialState: InitialState, initialPeers: [PeerId: Peer], initialStoredMessages: Set, storedMessagesByPeerIdAndTimestamp: [PeerId: Set]) { - self.initialState = initialState - self.state = initialState.state - self.peers = initialPeers - self.storedMessages = initialStoredMessages - self.channelStates = initialState.channelStates - self.peerNotificationSettings = initialState.peerNotificationSettings - self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp - self.branchOperationIndex = 0 - } - - init(initialState: InitialState, operations: [MutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], channelStates: [PeerId: ChannelState], peerNotificationSettings: [PeerId: PeerNotificationSettings], storedMessages: Set, storedMessagesByPeerIdAndTimestamp: [PeerId: Set], branchOperationIndex: Int) { - self.initialState = initialState - self.operations = operations - self.state = state - self.peers = peers - self.channelStates = channelStates - self.storedMessages = storedMessages - self.peerNotificationSettings = peerNotificationSettings - self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp - self.branchOperationIndex = branchOperationIndex - } - - func branch() -> MutableState { - return MutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, channelStates: self.channelStates, peerNotificationSettings: self.peerNotificationSettings, storedMessages: self.storedMessages, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, branchOperationIndex: self.operations.count) - } - - mutating func merge(_ other: MutableState) { - for i in other.branchOperationIndex ..< other.operations.count { - self.addOperation(other.operations[i]) - } - for (_, peer) in other.insertedPeers { - self.peers[peer.id] = peer - } - self.preCachedResources.append(contentsOf: other.preCachedResources) - } - - mutating func addPreCachedResource(_ resource: MediaResource, data: Data) { - self.preCachedResources.append((resource, data)) - } - - mutating func addMessages(_ messages: [StoreMessage], location: AddMessagesLocation) { - self.addOperation(.AddMessages(messages, location)) - } - - mutating func deleteMessagesWithGlobalIds(_ globalIds: [Int32]) { - self.addOperation(.DeleteMessagesWithGlobalIds(globalIds)) - } - - mutating func deleteMessages(_ messageIds: [MessageId]) { - self.addOperation(.DeleteMessages(messageIds)) - } - - mutating func editMessage(_ id: MessageId, message: StoreMessage) { - self.addOperation(.EditMessage(id, message)) - } - - mutating func updateMedia(_ id: MediaId, media: Media?) { - self.addOperation(.UpdateMedia(id, media)) - } - - mutating func readInbox(_ messageId: MessageId) { - self.addOperation(.ReadInbox(messageId)) - } - - mutating func readOutbox(_ messageId: MessageId) { - self.addOperation(.ReadOutbox(messageId)) - } - - mutating func resetReadState(_ peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32) { - self.addOperation(.ResetReadState(peerId, namespace, maxIncomingReadId, maxOutgoingReadId, maxKnownId, count)) - } - - mutating func updateState(_ state: AuthorizedAccountState.State) { - self.addOperation(.UpdateState(state)) - } - - mutating func updateChannelState(_ peerId: PeerId, state: ChannelState) { - self.addOperation(.UpdateChannelState(peerId, state)) - } - - mutating func updatePeerNotificationSettings(_ peerId: PeerId, notificationSettings: PeerNotificationSettings) { - self.addOperation(.UpdatePeerNotificationSettings(peerId, notificationSettings)) - } - - mutating func addHole(_ messageId: MessageId) { - self.addOperation(.AddHole(messageId)) - } - - mutating func mergeChats(_ chats: [Api.Chat]) { - self.addOperation(.MergeApiChats(chats)) - } - - mutating func updatePeer(_ id: PeerId, _ f: @escaping (Peer) -> Peer) { - self.addOperation(.UpdatePeer(id, f)) - } - - mutating func mergeUsers(_ users: [Api.User]) { - self.addOperation(.MergeApiUsers(users)) - - var presences: [PeerId: PeerPresence] = [:] - for user in users { - switch user { - case let .user(_, id, _, _, _, _, _, _, status, _, _, _): - if let status = status { - presences[PeerId(namespace: Namespaces.Peer.CloudUser, id: id)] = TelegramUserPresence(apiStatus: status) - } - break - case .userEmpty: - break - } - } - if !presences.isEmpty { - self.addOperation(.MergePeerPresences(presences)) - } - } - - mutating func mergePeerPresences(_ presences: [PeerId: PeerPresence]) { - self.addOperation(.MergePeerPresences(presences)) - } - - mutating func addOperation(_ operation: MutationOperation) { - switch operation { - case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadInbox, .ReadOutbox, .ResetReadState, .MergePeerPresences: - break - case let .AddMessages(messages, _): - for message in messages { - if case let .Id(id) = message.id { - self.storedMessages.insert(id) - } - } - case let .UpdateState(state): - self.state = state - case let .UpdateChannelState(peerId, channelState): - self.channelStates[peerId] = channelState - case let .UpdatePeerNotificationSettings(peerId, notificationSettings): - self.peerNotificationSettings[peerId] = notificationSettings - case let .MergeApiChats(chats): - for chat in chats { - if let groupOrChannel = mergeGroupOrChannel(lhs: peers[chat.peerId], rhs: chat) { - peers[groupOrChannel.id] = groupOrChannel - insertedPeers[groupOrChannel.id] = groupOrChannel - } - } - case let .MergeApiUsers(users): - for apiUser in users { - if let user = TelegramUser.merge(peers[apiUser.peerId] as? TelegramUser, rhs: apiUser) { - peers[user.id] = user - insertedPeers[user.id] = user - } - } - case let .UpdatePeer(id, f): - if let peer = self.peers[id] { - let updatedPeer = f(peer) - peers[id] = updatedPeer - insertedPeers[id] = updatedPeer - } - } - - self.operations.append(operation) - } -} - -private struct FinalState { - let state: MutableState - let shouldPoll: Bool - let incomplete: Bool -} - private func peerIdsFromUpdateGroups(_ groups: [UpdateGroup]) -> Set { var peerIds = Set() @@ -279,6 +53,13 @@ private func peersWithNewMessagesFromUpdateGroups(_ groups: [UpdateGroup]) -> Se if let messageId = update.messageId { peerIds.insert(messageId.peerId) } + switch update { + case let .updateChannelTooLong(_, channelId, _): + let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + peerIds.insert(peerId) + default: + break + } } } @@ -403,6 +184,13 @@ private func peersWithNewMessagesFromDifference(_ difference: Api.updates.Differ if let messageId = update.messageId { peerIds.insert(messageId.peerId) } + switch update { + case let .updateChannelTooLong(_, channelId, _): + let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + peerIds.insert(peerId) + default: + break + } } case .differenceEmpty: break @@ -417,6 +205,13 @@ private func peersWithNewMessagesFromDifference(_ difference: Api.updates.Differ if let messageId = update.messageId { peerIds.insert(messageId.peerId) } + switch update { + case let .updateChannelTooLong(_, channelId, _): + let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + peerIds.insert(peerId) + default: + break + } } case .differenceTooLong: break @@ -461,7 +256,7 @@ private func locallyGeneratedMessageTimestampsFromDifference(_ difference: Api.u return messageTimestamps } -private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set, associatedMessageIds: Set, peerIdsWithNewMessages: Set, locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) -> MutableState { +private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set, associatedMessageIds: Set, peerIdsWithNewMessages: Set, locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) -> AccountMutableState { var peers: [PeerId: Peer] = [:] var channelStates: [PeerId: ChannelState] = [:] @@ -494,17 +289,27 @@ private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set, } var peerNotificationSettings: [PeerId: PeerNotificationSettings] = [:] + var readInboxMaxIds: [PeerId: MessageId] = [:] + for peerId in peerIdsWithNewMessages { if let notificationSettings = modifier.getPeerNotificationSettings(peerId) { peerNotificationSettings[peerId] = notificationSettings } + if let readStates = modifier.getPeerReadStates(peerId) { + for (namespace, state) in readStates { + if namespace == Namespaces.Message.Cloud { + readInboxMaxIds[peerId] = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: state.maxIncomingReadId) + break + } + } + } } - return MutableState(initialState: InitialState(state: (modifier.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, messageIds: associatedMessageIds, peerIdsWithNewMessages: peerIdsWithNewMessages, channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps), initialPeers: peers, initialStoredMessages: storedMessages, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp) + return AccountMutableState(initialState: AccountInitialState(state: (modifier.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, messageIds: associatedMessageIds, peerIdsWithNewMessages: peerIdsWithNewMessages, channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps), initialPeers: peers, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp) } -private func initialStateWithUpdateGroups(_ account: Account, groups: [UpdateGroup]) -> Signal { - return account.postbox.modify { modifier -> MutableState in +func initialStateWithUpdateGroups(_ account: Account, groups: [UpdateGroup]) -> Signal { + return account.postbox.modify { modifier -> AccountMutableState in let peerIds = peerIdsFromUpdateGroups(groups) let associatedMessageIds = associatedMessageIdsFromUpdateGroups(groups) let peerIdsWithNewMessages = peersWithNewMessagesFromUpdateGroups(groups) @@ -513,8 +318,8 @@ private func initialStateWithUpdateGroups(_ account: Account, groups: [UpdateGro } } -private func initialStateWithDifference(_ account: Account, difference: Api.updates.Difference) -> Signal { - return account.postbox.modify { modifier -> MutableState in +func initialStateWithDifference(_ account: Account, difference: Api.updates.Difference) -> Signal { + return account.postbox.modify { modifier -> AccountMutableState in let peerIds = peerIdsFromDifference(difference) let associatedMessageIds = associatedMessageIdsFromDifference(difference) let peerIdsWithNewMessages = peersWithNewMessagesFromDifference(difference) @@ -522,7 +327,7 @@ private func initialStateWithDifference(_ account: Account, difference: Api.upda } } -private func finalStateWithUpdateGroups(_ account: Account, state: MutableState, groups: [UpdateGroup]) -> Signal { +func finalStateWithUpdateGroups(_ account: Account, state: AccountMutableState, groups: [UpdateGroup]) -> Signal { var updatedState = state var hadReset = false @@ -640,7 +445,7 @@ private func finalStateWithUpdateGroups(_ account: Account, state: MutableState, return finalStateWithUpdates(account: account, state: updatedState, updates: collectedUpdates, shouldPoll: hadReset, missingUpdates: !ptsUpdatesAfterHole.isEmpty || !qtsUpdatesAfterHole.isEmpty || !seqGroupsAfterHole.isEmpty) } -private func finalStateWithDifference(account: Account, state: MutableState, difference: Api.updates.Difference) -> Signal { +func finalStateWithDifference(account: Account, state: AccountMutableState, difference: Api.updates.Difference) -> Signal { var updatedState = state var messages: [Api.Message] = [] @@ -764,7 +569,7 @@ private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] { return result } -private func finalStateWithUpdates(account: Account, state: MutableState, updates: [Api.Update], shouldPoll: Bool, missingUpdates: Bool) -> Signal { +private func finalStateWithUpdates(account: Account, state: AccountMutableState, updates: [Api.Update], shouldPoll: Bool, missingUpdates: Bool) -> Signal { var updatedState = state var channelsToPoll = Set() @@ -979,7 +784,7 @@ private func finalStateWithUpdates(account: Account, state: MutableState, update } } - var pollChannelSignals: [Signal] = [] + var pollChannelSignals: [Signal] = [] for peerId in channelsToPoll { if let peer = updatedState.peers[peerId] { pollChannelSignals.append(pollChannel(account, peer: peer, state: updatedState.branch())) @@ -988,16 +793,16 @@ private func finalStateWithUpdates(account: Account, state: MutableState, update } } - return combineLatest(pollChannelSignals) |> mapToSignal { states -> Signal in + return combineLatest(pollChannelSignals) |> mapToSignal { states -> Signal in var finalState = updatedState for state in states { finalState.merge(state) } return resolveAssociatedMessages(account: account, state: finalState) - |> mapToSignal { resultingState -> Signal in + |> mapToSignal { resultingState -> Signal in return resolveMissingPeerNotificationSettings(account: account, state: resultingState) - |> map { resultingState -> FinalState in - return FinalState(state: resultingState, shouldPoll: shouldPoll || missingUpdates, incomplete: missingUpdates) + |> map { resultingState -> AccountFinalState in + return AccountFinalState(state: resultingState, shouldPoll: shouldPoll, incomplete: missingUpdates) } } } @@ -1018,7 +823,7 @@ private func messagesIdsGroupedByPeerId(_ ids: Set) -> [PeerId: [Mess return dict } -private func resolveAssociatedMessages(account: Account, state: MutableState) -> Signal { +private func resolveAssociatedMessages(account: Account, state: AccountMutableState) -> Signal { let missingMessageIds = state.initialState.messageIds.subtracting(state.storedMessages) if missingMessageIds.isEmpty { return .single(state) @@ -1084,7 +889,7 @@ private func resolveAssociatedMessages(account: Account, state: MutableState) -> } } -private func resolveMissingPeerNotificationSettings(account: Account, state: MutableState) -> Signal { +private func resolveMissingPeerNotificationSettings(account: Account, state: AccountMutableState) -> Signal { var missingPeers: [PeerId: Api.InputPeer] = [:] for peerId in state.initialState.peerIdsWithNewMessages { @@ -1113,7 +918,7 @@ private func resolveMissingPeerNotificationSettings(account: Account, state: Mut signals.append(fetchSettings) } return combineLatest(signals) - |> map { peersAndSettings -> MutableState in + |> map { peersAndSettings -> AccountMutableState in var updatedState = state for pair in peersAndSettings { if let (peerId, settings) = pair { @@ -1127,11 +932,11 @@ private func resolveMissingPeerNotificationSettings(account: Account, state: Mut return .single(state) } -private func pollChannel(_ account: Account, peer: Peer, state: MutableState) -> Signal { +private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableState) -> Signal { if let inputChannel = apiInputChannel(peer) { return account.network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: state.channelStates[peer.id]?.pts ?? 1, limit: 20)) |> retryRequest - |> map { difference -> MutableState in + |> map { difference -> AccountMutableState in var updatedState = state switch difference { case let .channelDifference(_, pts, _, newMessages, otherUpdates, chats, users): @@ -1215,7 +1020,7 @@ private func pollChannel(_ account: Account, peer: Peer, state: MutableState) -> } } -private func verifyTransaction(_ modifier: Modifier, finalState: MutableState) -> Bool { +private func verifyTransaction(_ modifier: Modifier, finalState: AccountMutableState) -> Bool { var hadUpdateState = false var channelsWithUpdatedStates = Set() @@ -1300,8 +1105,8 @@ private final class OptimizeAddMessagesState { } } -private func optimizedOperations(_ operations: [MutationOperation]) -> [MutationOperation] { - var result: [MutationOperation] = [] +private func optimizedOperations(_ operations: [AccountStateMutationOperation]) -> [AccountStateMutationOperation] { + var result: [AccountStateMutationOperation] = [] var updatedState: AuthorizedAccountState.State? var updatedChannelStates: [PeerId: ChannelState] = [:] @@ -1345,7 +1150,7 @@ private func optimizedOperations(_ operations: [MutationOperation]) -> [Mutation return result } -private func replayFinalState(_ modifier: Modifier, finalState: MutableState) -> Bool { +func replayFinalState(_ modifier: Modifier, finalState: AccountMutableState) -> Bool { let verified = verifyTransaction(modifier, finalState: finalState) if !verified { return false @@ -1476,28 +1281,14 @@ private func pollDifference(_ account: Account) -> Signal { return signal } -#if os(macOS) + + +/*#if os(macOS) private typealias SignalKitTimer = SwiftSignalKitMac.Timer #else private typealias SignalKitTimer = SwiftSignalKit.Timer #endif -private enum StateManagerState { - case none - case pollingDifference -} - -private final class StateManagerInternal { - private let queue = Queue() - private let account: Account - - init(account: Account) { - self.account = account - } - - -} - public final class StateManager { private let stateQueue = Queue() @@ -1674,4 +1465,4 @@ public final class StateManager { } } } -} +}*/ diff --git a/TelegramCore/AccountStateManager.swift b/TelegramCore/AccountStateManager.swift new file mode 100644 index 0000000000..c595f15afc --- /dev/null +++ b/TelegramCore/AccountStateManager.swift @@ -0,0 +1,393 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private enum AccountStateManagerOperation { + case pollDifference(AccountFinalStateEvents) + case collectUpdateGroups([UpdateGroup], Double) + case processUpdateGroups([UpdateGroup]) + case custom(Signal) +} + +#if os(macOS) + private typealias SignalKitTimer = SwiftSignalKitMac.Timer +#else + private typealias SignalKitTimer = SwiftSignalKit.Timer +#endif + +private enum CustomOperationEvent { + case Next(T) + case Error(E) + case Completion +} + +public final class AccountStateManager { + private let queue = Queue() + private let account: Account + + private var updateService: UpdateMessageService? + private let updateServiceDisposable = MetaDisposable() + + private var operations: [AccountStateManagerOperation] = [] + private let operationDisposable = MetaDisposable() + private var operationTimer: SignalKitTimer? + + private let isUpdatingValue = ValuePromise(false) + private var currentIsUpdatingValue = false { + didSet { + if self.currentIsUpdatingValue != oldValue { + self.isUpdatingValue.set(self.currentIsUpdatingValue) + } + } + } + public var isUpdating: Signal { + return self.isUpdatingValue.get() + } + + private let notificationMessagesPipe = ValuePipe<[Message]>() + public var notificationMessages: Signal<[Message], NoError> { + return self.notificationMessagesPipe.signal() + } + + init(account: Account) { + self.account = account + } + + deinit { + self.updateServiceDisposable.dispose() + self.operationDisposable.dispose() + } + + public func reset() { + self.queue.async { + if self.updateService == nil { + self.updateService = UpdateMessageService(peerId: self.account.peerId) + self.updateServiceDisposable.set(self.updateService!.pipe.signal().start(next: { [weak self] groups in + if let strongSelf = self { + strongSelf.addUpdateGroups(groups) + } + })) + self.account.network.mtProto.add(self.updateService) + } + self.operationDisposable.set(nil) + self.operations.removeAll() + self.addOperation(.pollDifference(AccountFinalStateEvents())) + } + } + + func addUpdates(_ updates: Api.Updates) { + self.queue.async { + self.updateService?.addUpdates(updates) + } + } + + func addUpdateGroups(_ groups: [UpdateGroup]) { + self.queue.async { + if let last = self.operations.last { + switch last { + case .pollDifference, .processUpdateGroups, .custom: + self.operations.append(.collectUpdateGroups(groups, 0.0)) + case let .collectUpdateGroups(currentGroups, timestamp): + if timestamp.isEqual(to: 0.0) { + self.operations[self.operations.count - 1] = .collectUpdateGroups(currentGroups + groups, timestamp) + } else { + self.operations[self.operations.count - 1] = .processUpdateGroups(currentGroups + groups) + self.startFirstOperation() + } + } + } else { + self.operations.append(.collectUpdateGroups(groups, 0.0)) + self.startFirstOperation() + } + } + } + + func addCustomOperation(_ f: Signal) -> Signal { + let pipe = ValuePipe>() + return Signal { subscriber in + let disposable = pipe.signal().start(next: { event in + switch event { + case let .Next(next): + subscriber.putNext(next) + case let .Error(error): + subscriber.putError(error) + case .Completion: + subscriber.putCompletion() + } + }) + + let signal = Signal { subscriber in + return f.start(next: { next in + pipe.putNext(.Next(next)) + }, error: { error in + pipe.putNext(.Error(error)) + subscriber.putCompletion() + }, completed: { + pipe.putNext(.Completion) + subscriber.putCompletion() + }) + } + + self.addOperation(.custom(signal)) + + return disposable + } |> runOn(self.queue) + } + + private func addOperation(_ operation: AccountStateManagerOperation) { + self.queue.async { + let begin = self.operations.isEmpty + self.operations.append(operation) + if begin { + self.startFirstOperation() + } + } + } + + private func startFirstOperation() { + guard let operation = self.operations.first else { + return + } + switch operation { + case let .pollDifference(currentEvents): + self.currentIsUpdatingValue = true + let account = self.account + let queue = self.queue + let signal = account.postbox.state() + |> filter { state in + if let _ = state as? AuthorizedAccountState { + return true + } else { + return false + } + } + |> take(1) + |> mapToSignal { state -> Signal<(Api.updates.Difference?, AccountFinalState?), NoError> in + if let authorizedState = (state as! AuthorizedAccountState).state { + let request = account.network.request(Api.functions.updates.getDifference(flags: 0, pts: authorizedState.pts, ptsTotalLimit: nil, date: authorizedState.date, qts: authorizedState.qts)) + |> retryRequest + return request |> mapToSignal { difference -> Signal<(Api.updates.Difference?, AccountFinalState?), NoError> in + return initialStateWithDifference(account, difference: difference) + |> mapToSignal { state -> Signal<(Api.updates.Difference?, AccountFinalState?), NoError> in + if state.initialState.state != authorizedState { + trace("State", what: "pollDifference initial state \(authorizedState) != current state \(state.initialState.state)") + return .single((nil, nil)) + } else { + return finalStateWithDifference(account: account, state: state, difference: difference) + |> mapToSignal { finalState -> Signal<(Api.updates.Difference?, AccountFinalState?), NoError> in + if !finalState.state.preCachedResources.isEmpty { + for (resource, data) in finalState.state.preCachedResources { + account.postbox.mediaBox.storeResourceData(resource.id, data: data) + } + } + return account.postbox.modify { modifier -> (Api.updates.Difference?, AccountFinalState?) in + if replayFinalState(modifier, finalState: finalState.state) { + return (difference, finalState) + } else { + return (nil, nil) + } + } + } + } + } + } + } else { + let appliedState = account.network.request(Api.functions.updates.getState()) + |> retryRequest + |> mapToSignal { state in + return account.postbox.modify { modifier -> (Api.updates.Difference?, AccountFinalState?) in + if let currentState = modifier.getState() as? AuthorizedAccountState { + switch state { + case let .state(pts, qts, date, seq, _): + modifier.setState(currentState.changedState(AuthorizedAccountState.State(pts: pts, qts: qts, date: date, seq: seq))) + } + } + return (nil, nil) + } + } + return appliedState + } + } + signal.start(next: { [weak self] difference, finalState in + if let strongSelf = self { + if case let .pollDifference = strongSelf.operations.removeFirst() { + let events: AccountFinalStateEvents + if let finalState = finalState { + events = currentEvents.union(with: AccountFinalStateEvents(state: finalState.state)) + } else { + events = currentEvents + } + if let difference = difference { + switch difference { + case .differenceSlice: + strongSelf.operations.insert(.pollDifference(events), at: 0) + default: + if !events.isEmpty { + strongSelf.addEvents(events) + } + strongSelf.currentIsUpdatingValue = false + } + } else { + if !events.isEmpty { + strongSelf.addEvents(events) + } + strongSelf.operations.removeAll() + strongSelf.operations.append(.pollDifference(AccountFinalStateEvents())) + } + strongSelf.startFirstOperation() + } else { + assertionFailure() + } + } + }, error: { _ in + assertionFailure() + trace("AccountStateManager", what: "processUpdateGroups signal completed with error") + }) + case let .collectUpdateGroups(groups, timeout): + self.operationTimer?.invalidate() + let operationTimer = SignalKitTimer(timeout: timeout, repeat: false, completion: { [weak self] in + if let strongSelf = self { + if case let .collectUpdateGroups(groups, _) = strongSelf.operations[0] { + if timeout.isEqual(to: 0.0) { + strongSelf.operations[0] = .processUpdateGroups(groups) + } else { + trace("AccountStateManager", what: "timeout while waiting for updates") + strongSelf.operations.removeAll() + strongSelf.operations.append(.pollDifference(AccountFinalStateEvents())) + } + strongSelf.startFirstOperation() + } else { + assertionFailure() + } + } + }, queue: self.queue) + self.operationTimer = operationTimer + operationTimer.start() + case let .processUpdateGroups(groups): + let account = self.account + let queue = self.queue + let signal = initialStateWithUpdateGroups(account, groups: groups) + |> mapToSignal { [weak self] state -> Signal<(Bool, AccountFinalState), NoError> in + return finalStateWithUpdateGroups(account, state: state, groups: groups) + |> mapToSignal { finalState in + if !finalState.state.preCachedResources.isEmpty { + for (resource, data) in finalState.state.preCachedResources { + account.postbox.mediaBox.storeResourceData(resource.id, data: data) + } + } + + return account.postbox.modify { modifier -> Bool in + return replayFinalState(modifier, finalState: finalState.state) + } + |> map({ ($0, finalState) }) + |> deliverOn(queue) + } + } + signal.start(next: { [weak self] result, finalState in + if let strongSelf = self { + if case let .processUpdateGroups(groups) = strongSelf.operations.removeFirst() { + if result && !finalState.shouldPoll { + let events = AccountFinalStateEvents(state: finalState.state) + if !events.isEmpty { + strongSelf.addEvents(events) + } + if finalState.incomplete { + strongSelf.operations.insert(.collectUpdateGroups(groups, 2.0), at: 0) + } + } else { + strongSelf.operations.removeAll() + strongSelf.operations.append(.pollDifference(AccountFinalStateEvents())) + } + strongSelf.startFirstOperation() + } else { + assertionFailure() + } + } + }, error: { _ in + assertionFailure() + trace("AccountStateManager", what: "processUpdateGroups signal completed with error") + }) + case let .custom(signal): + let completed: () -> Void = { [weak self] in + if let strongSelf = self { + if case .custom = strongSelf.operations.removeFirst() { + strongSelf.startFirstOperation() + } else { + assertionFailure() + } + } + } + signal.start(error: { _ in + completed() + }, completed: { + completed() + }) + } + } + + private func addEvents(_ events: AccountFinalStateEvents) { + if !events.addedIncomingMessageIds.isEmpty { + (self.account.postbox.modify { modifier -> [Message] in + let timestamp = Int32(self.account.network.context.globalTime()) + var messages: [Message] = [] + for id in events.addedIncomingMessageIds { + var notify = true + + if let notificationSettings = modifier.getPeerNotificationSettings(id.peerId) as? TelegramPeerNotificationSettings { + switch notificationSettings.muteState { + case let .muted(until): + if until >= timestamp { + notify = false + } + case .unmuted: + break + } + } else { + trace("AccountStateManager", what: "notification settings for \(id.peerId) are undefined") + } + + var foundReadState = false + if let readStates = modifier.getPeerReadStates(id.peerId) { + for (namespace, readState) in readStates { + if namespace == id.namespace { + if id.id <= readState.maxIncomingReadId { + notify = false + } + foundReadState = true + break + } + } + } + + if !foundReadState { + trace("AccountStateManager", what: "read state for \(id.peerId) is undefined") + } + + if notify { + if let message = modifier.getMessage(id) { + messages.append(message) + } else { + trace("AccountStateManager", what: "notification message doesn't exist") + } + } + } + return messages + }).start(next: { [weak self] messages in + if let strongSelf = self { + for message in messages { + print("notify: \(message.peers[message.id.peerId]?.displayTitle): \(message.text)") + } + + strongSelf.notificationMessagesPipe.putNext(messages) + } + }) + } + } +} diff --git a/TelegramCore/AccountViewTracker.swift b/TelegramCore/AccountViewTracker.swift index aac6861a8a..95d036045e 100644 --- a/TelegramCore/AccountViewTracker.swift +++ b/TelegramCore/AccountViewTracker.swift @@ -261,27 +261,27 @@ public final class AccountViewTracker { }) } - public func aroundUnreadMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundUnreadMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, tagMask: MessageTags? = nil, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundUnreadMessageHistoryViewForPeerId(peerId, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask) + let signal = account.postbox.aroundUnreadMessageHistoryViewForPeerId(peerId, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, additionalData: additionalData) return wrappedMessageHistorySignal(signal) } else { return .never() } } - public func aroundIdMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, messageId: MessageId, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundIdMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask) + let signal = account.postbox.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, additionalData: additionalData) return wrappedMessageHistorySignal(signal) } else { return .never() } } - public func aroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageIndex, count: Int, anchorIndex: MessageIndex, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageIndex, count: Int, anchorIndex: MessageIndex, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundMessageHistoryViewForPeerId(peerId, index: index, count: count, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask) + let signal = account.postbox.aroundMessageHistoryViewForPeerId(peerId, index: index, count: count, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, additionalData: additionalData) return wrappedMessageHistorySignal(signal) } else { return .never() diff --git a/TelegramCore/ApplyUpdateMessage.swift b/TelegramCore/ApplyUpdateMessage.swift index 3a1971635a..bfd89fcecb 100644 --- a/TelegramCore/ApplyUpdateMessage.swift +++ b/TelegramCore/ApplyUpdateMessage.swift @@ -23,7 +23,7 @@ private func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox) } } -func applyUpdateMessage(postbox: Postbox, stateManager: StateManager, message: Message, result: Api.Updates) -> Signal { +func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates) -> Signal { let messageId = result.rawMessageIds.first let apiMessage = result.messages.first diff --git a/TelegramCore/ManagedServiceViews.swift b/TelegramCore/ManagedServiceViews.swift index 89c122e414..7fa10e2ff1 100644 --- a/TelegramCore/ManagedServiceViews.swift +++ b/TelegramCore/ManagedServiceViews.swift @@ -7,7 +7,7 @@ import Foundation import SwiftSignalKit #endif -func managedServiceViews(network: Network, postbox: Postbox, stateManager: StateManager, pendingMessageManager: PendingMessageManager) -> Signal { +func managedServiceViews(network: Network, postbox: Postbox, stateManager: AccountStateManager, pendingMessageManager: PendingMessageManager) -> Signal { return Signal { _ in let disposable = DisposableSet() disposable.add(managedMessageHistoryHoles(network: network, postbox: postbox).start()) diff --git a/TelegramCore/ManagedSynchronizePeerReadStates.swift b/TelegramCore/ManagedSynchronizePeerReadStates.swift index 2600797017..8c16fe9bab 100644 --- a/TelegramCore/ManagedSynchronizePeerReadStates.swift +++ b/TelegramCore/ManagedSynchronizePeerReadStates.swift @@ -39,7 +39,7 @@ private final class ManagedSynchronizePeerReadStatesState { } } -func managedSynchronizePeerReadStates(network: Network, postbox: Postbox, stateManager: StateManager) -> Signal { +func managedSynchronizePeerReadStates(network: Network, postbox: Postbox, stateManager: AccountStateManager) -> Signal { return Signal { _ in let state = Atomic(value: ManagedSynchronizePeerReadStatesState()) diff --git a/TelegramCore/Network.swift b/TelegramCore/Network.swift index 05bed7b5e9..b346fd5fc3 100644 --- a/TelegramCore/Network.swift +++ b/TelegramCore/Network.swift @@ -133,14 +133,22 @@ func initializedNetwork(datacenterId: Int, keychain: Keychain, networkUsageInfoP let requestService = MTRequestMessageService(context: context)! let connectionStatusDelegate = MTProtoConnectionStatusDelegate() connectionStatusDelegate.action = { [weak connectionStatus] flags in - if !flags.contains(.NetworkAvailable) { - connectionStatus?.set(single(ConnectionStatus.WaitingForNetwork, NoError.self)) - } else if !flags.contains(.Connected) { - connectionStatus?.set(single(ConnectionStatus.Connecting, NoError.self)) - } else if !flags.intersection([.UpdatingConnectionContext, .PerformingServiceTasks]).isEmpty { - connectionStatus?.set(single(ConnectionStatus.Updating, NoError.self)) + if flags.contains(.Connected) { + if !flags.intersection([.UpdatingConnectionContext, .PerformingServiceTasks]).isEmpty { + connectionStatus?.set(single(ConnectionStatus.Updating, NoError.self)) + } else { + connectionStatus?.set(single(ConnectionStatus.Online, NoError.self)) + } } else { - connectionStatus?.set(single(ConnectionStatus.Online, NoError.self)) + if !flags.contains(.NetworkAvailable) { + connectionStatus?.set(single(ConnectionStatus.WaitingForNetwork, NoError.self)) + } else if !flags.contains(.Connected) { + connectionStatus?.set(single(ConnectionStatus.Connecting, NoError.self)) + } else if !flags.intersection([.UpdatingConnectionContext, .PerformingServiceTasks]).isEmpty { + connectionStatus?.set(single(ConnectionStatus.Updating, NoError.self)) + } else { + connectionStatus?.set(single(ConnectionStatus.Online, NoError.self)) + } } } mtProto.delegate = connectionStatusDelegate diff --git a/TelegramCore/PendingMessageManager.swift b/TelegramCore/PendingMessageManager.swift index 385b5a9cc3..ef432ea83f 100644 --- a/TelegramCore/PendingMessageManager.swift +++ b/TelegramCore/PendingMessageManager.swift @@ -47,7 +47,7 @@ private final class PendingMessageRequestDependencyTag: NetworkRequestDependency public final class PendingMessageManager { private let network: Network private let postbox: Postbox - private let stateManager: StateManager + private let stateManager: AccountStateManager private let queue = Queue() @@ -57,7 +57,7 @@ public final class PendingMessageManager { private var peerSummaryContexts: [PeerId: PeerPendingMessagesSummaryContext] = [:] - init(network: Network, postbox: Postbox, stateManager: StateManager) { + init(network: Network, postbox: Postbox, stateManager: AccountStateManager) { self.network = network self.postbox = postbox self.stateManager = stateManager @@ -219,7 +219,7 @@ public final class PendingMessageManager { })) } - private func sendMessageContent(network: Network, postbox: Postbox, stateManager: StateManager, message: Message, content: PendingMessageUploadedContent) -> Signal { + private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, message: Message, content: PendingMessageUploadedContent) -> Signal { return postbox.modify { [weak self] modifier -> Signal in if let peer = modifier.getPeer(message.id.peerId), let inputPeer = apiInputPeer(peer) { var uniqueId: Int64 = 0 @@ -301,7 +301,7 @@ public final class PendingMessageManager { } |> switchToLatest } - private func applySentMessage(postbox: Postbox, stateManager: StateManager, message: Message, result: Api.Updates) -> Signal { + private func applySentMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates) -> Signal { let messageId = result.rawMessageIds.first let apiMessage = result.messages.first diff --git a/TelegramCore/RequestStartBot.swift b/TelegramCore/RequestStartBot.swift new file mode 100644 index 0000000000..8ea56a3bde --- /dev/null +++ b/TelegramCore/RequestStartBot.swift @@ -0,0 +1,36 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public func requestStartBot(account: Account, botPeerId: PeerId, payload: String?) -> Signal { + if let payload = payload, !payload.isEmpty { + return account.postbox.loadedPeerWithId(botPeerId) + |> mapToSignal { botPeer -> Signal in + if let inputUser = apiInputUser(botPeer) { + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let r = account.network.request(Api.functions.messages.startBot(bot: inputUser, peer: .inputPeerEmpty, randomId: randomId, startParam: payload ?? "")) + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + return .complete() + } + |> `catch` { _ -> Signal in + return .complete() + } + return r + |> retryRequest + } else { + return .complete() + } + } + } else { + return enqueueMessages(account: account, peerId: botPeerId, messages: [.message(text: "/start", attributes: [], media: nil, replyToMessageId: nil)]) + } +} diff --git a/TelegramCore/ResolvePeerByName.swift b/TelegramCore/ResolvePeerByName.swift index 4362b19af6..f31f6558e4 100644 --- a/TelegramCore/ResolvePeerByName.swift +++ b/TelegramCore/ResolvePeerByName.swift @@ -46,12 +46,18 @@ final class CachedResolvedByNamePeer: Coding { private let resolvedByNamePeersCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 150, highWaterItemCount: 200) -public enum ResolvePeerByNameCachedPolicy { - case remoteIfEarlierThan(timestamp: Int32) - case remote +public enum ResolvePeerByNameOptionCached { + case none + case cached + case cachedIfLaterThan(timestamp: Int32) } -public func resolvePeerByName(account: Account, name: String, ageLimit: Int32 = 60 * 60) -> Signal { +public enum ResolvePeerByNameOptionRemote { + case updateIfEarlierThan(timestamp: Int32) + case update +} + +public func resolvePeerByName(account: Account, name: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal { var normalizedName = name if normalizedName.hasPrefix("@") { normalizedName = normalizedName.substring(from: name.index(after: name.startIndex)) diff --git a/TelegramCore/SearchMessages.swift b/TelegramCore/SearchMessages.swift index ba18b98df3..81d54f35e8 100644 --- a/TelegramCore/SearchMessages.swift +++ b/TelegramCore/SearchMessages.swift @@ -31,9 +31,22 @@ private func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer] return Message(stableId: 0, stableVersion: 0, id: id, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, forwardInfo: nil, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) } -public func searchMessages(account: Account, query: String) -> Signal<[Message], NoError> { - let searchResult = account.network.request(Api.functions.messages.searchGlobal(q: query, offsetDate: 0, offsetPeer: Api.InputPeer.inputPeerEmpty, offsetId: 0, limit: 64)) - |> retryRequest +public func searchMessages(account: Account, peerId: PeerId?, query: String) -> Signal<[Message], NoError> { + let searchResult: Signal + if let peerId = peerId { + searchResult = account.postbox.loadedPeerWithId(peerId) + |> mapToSignal { peer -> Signal in + if let inputPeer = apiInputPeer(peer) { + return account.network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: query, filter: .inputMessagesFilterEmpty, minDate: 0, maxDate: Int32.max - 1, offset: 0, maxId: Int32.max - 1, limit: 64)) + |> retryRequest + } else { + return .never() + } + } + } else { + searchResult = account.network.request(Api.functions.messages.searchGlobal(q: query, offsetDate: 0, offsetPeer: Api.InputPeer.inputPeerEmpty, offsetId: 0, limit: 64)) + |> retryRequest + } let processedSearchResult = searchResult |> mapToSignal { result -> Signal<[Message], NoError> in diff --git a/TelegramCore/SendUnsentMessage.swift b/TelegramCore/SendUnsentMessage.swift index ca30e84935..2cd0837b3e 100644 --- a/TelegramCore/SendUnsentMessage.swift +++ b/TelegramCore/SendUnsentMessage.swift @@ -39,7 +39,7 @@ private func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox) } } -func sendUnsentMessage(network: Network, postbox: Postbox, stateManager: StateManager, message: Message) -> Signal { +func sendUnsentMessage(network: Network, postbox: Postbox, stateManager: AccountStateManager, message: Message) -> Signal { return postbox.loadedPeerWithId(message.id.peerId) |> take(1) |> mapToSignal { peer -> Signal in diff --git a/TelegramCore/SynchronizePeerReadState.swift b/TelegramCore/SynchronizePeerReadState.swift index a4d77dcefe..c2a9a09790 100644 --- a/TelegramCore/SynchronizePeerReadState.swift +++ b/TelegramCore/SynchronizePeerReadState.swift @@ -149,7 +149,7 @@ private func localReadStateMarker(network: Network, postbox: Postbox, peerId: Pe } } -private func validatePeerReadState(network: Network, postbox: Postbox, stateManager: StateManager, peerId: PeerId) -> Signal { +private func validatePeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Signal { let readStateWithInitialState = localReadStateMarker(network: network, postbox: postbox, peerId: peerId) |> mapToSignal { marker -> Signal<(PeerReadState, PeerReadStateMarker, PeerReadStateMarker), VerifyReadStateError> in return dialogReadState(network: network, postbox: postbox, peerId: peerId) @@ -157,7 +157,7 @@ private func validatePeerReadState(network: Network, postbox: Postbox, stateMana } let maybeAppliedReadState = readStateWithInitialState |> mapToSignal { (readState, initialMarker, finalMarker) -> Signal in - return stateManager.injectedStateModification(postbox.modify { modifier -> VerifyReadStateError? in + return stateManager.addCustomOperation(postbox.modify { modifier -> VerifyReadStateError? in if initialMarker == finalMarker { modifier.resetIncomingReadStates([peerId: [Namespaces.Message.Cloud: readState]]) return nil @@ -186,7 +186,7 @@ private func validatePeerReadState(network: Network, postbox: Postbox, stateMana |> retry(0.1, maxDelay: 5.0, onQueue: Queue.concurrentDefaultQueue()) } -private func pushPeerReadState(network: Network, postbox: Postbox, peerId: PeerId, readState: PeerReadState) -> Signal { +private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId, readState: PeerReadState) -> Signal { return inputPeer(postbox: postbox, peerId: peerId) |> mapToSignal { inputPeer -> Signal in switch inputPeer { @@ -199,14 +199,19 @@ private func pushPeerReadState(network: Network, postbox: Postbox, peerId: PeerI default: return network.request(Api.functions.messages.readHistory(peer: inputPeer, maxId: readState.maxIncomingReadId)) |> retryRequest - |> mapToSignalPromotingError { _ -> Signal in + |> mapToSignalPromotingError { result -> Signal + in + switch result { + case let .affectedMessages(pts, ptsCount): + stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)]) + } return .single(readState) } } } } -private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: StateManager, peerId: PeerId) -> Signal { +private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Signal { let currentReadState = postbox.modify { modifier -> PeerReadState? in if let readStates = modifier.getPeerReadStates(peerId) { for (namespace, readState) in readStates { @@ -221,7 +226,7 @@ private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: let pushedState = currentReadState |> mapToSignalPromotingError { readState -> Signal in if let readState = readState { - return pushPeerReadState(network: network, postbox: postbox, peerId: peerId, readState: readState) + return pushPeerReadState(network: network, postbox: postbox, stateManager: stateManager, peerId: peerId, readState: readState) } else { return .complete() } @@ -229,7 +234,7 @@ private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: let verifiedState = pushedState |> mapToSignal { readState -> Signal in - return stateManager.injectedStateModification(postbox.modify { modifier -> VerifyReadStateError? in + return stateManager.addCustomOperation(postbox.modify { modifier -> VerifyReadStateError? in if let readStates = modifier.getPeerReadStates(peerId) { for (namespace, currentReadState) in readStates where namespace == Namespaces.Message.Cloud { if currentReadState == readState { @@ -264,7 +269,7 @@ private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: |> retry(0.1, maxDelay: 5.0, onQueue: Queue.concurrentDefaultQueue()) } -func synchronizePeerReadState(network: Network, postbox: Postbox, stateManager: StateManager, peerId: PeerId, push: Bool, validate: Bool) -> Signal { +func synchronizePeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId, push: Bool, validate: Bool) -> Signal { var signal: Signal = .complete() if push { signal = signal |> then(pushPeerReadState(network: network, postbox: postbox, stateManager: stateManager, peerId: peerId)) diff --git a/TelegramCore/TextEntitiesMessageAttribute.swift b/TelegramCore/TextEntitiesMessageAttribute.swift index d9414c9b20..bb560ba0c7 100644 --- a/TelegramCore/TextEntitiesMessageAttribute.swift +++ b/TelegramCore/TextEntitiesMessageAttribute.swift @@ -101,7 +101,7 @@ public struct MessageTextEntity: Coding, Equatable { public let range: Range public let type: MessageTextEntityType - init(range: Range, type: MessageTextEntityType) { + public init(range: Range, type: MessageTextEntityType) { self.range = range self.type = type } @@ -191,7 +191,7 @@ public class TextEntitiesMessageAttribute: MessageAttribute, Equatable { return result } - init(entities: [MessageTextEntity]) { + public init(entities: [MessageTextEntity]) { self.entities = entities }