From c55a138e0becf7adcd0690a985636a55d09faccd Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 23 Mar 2017 21:26:36 +0300 Subject: [PATCH] no message --- TelegramCore.xcodeproj/project.pbxproj | 30 ++++ TelegramCore/Account.swift | 34 ++++- TelegramCore/AccountIntermediateState.swift | 7 +- TelegramCore/AccountManager.swift | 12 +- .../AccountStateManagementUtils.swift | 21 ++- TelegramCore/AccountStateManager.swift | 13 +- TelegramCore/CloudFileMediaResource.swift | 8 +- TelegramCore/Fetch.swift | 33 +++-- TelegramCore/FetchHttpResource.swift | 3 +- TelegramCore/MD5.swift | 18 +++ ...dSynchronizeChatInputStateOperations.swift | 136 ++++++++++++++++++ TelegramCore/MultipartFetch.swift | 4 +- TelegramCore/MultipartUpload.swift | 107 +++++++++----- TelegramCore/Namespaces.swift | 1 + TelegramCore/PendingMessageManager.swift | 2 + .../SynchronizeChatInputStateOperation.swift | 51 +++++++ .../SynchronizeableChatInputState.swift | 60 ++++++++ .../UpdatePeerChatInterfaceState.swift | 24 ++++ 18 files changed, 487 insertions(+), 77 deletions(-) create mode 100644 TelegramCore/MD5.swift create mode 100644 TelegramCore/ManagedSynchronizeChatInputStateOperations.swift create mode 100644 TelegramCore/SynchronizeChatInputStateOperation.swift create mode 100644 TelegramCore/SynchronizeableChatInputState.swift create mode 100644 TelegramCore/UpdatePeerChatInterfaceState.swift diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 3f4ecff2ae..fbb97ce3b6 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -200,6 +200,8 @@ D049EAEC1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAEA1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift */; }; D049EAF51E44DF3300A2CD3A /* AccountState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAF41E44DF3300A2CD3A /* AccountState.swift */; }; D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAF41E44DF3300A2CD3A /* AccountState.swift */; }; + D04CAA5A1E83310D0047E51F /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04CAA591E83310D0047E51F /* MD5.swift */; }; + D04CAA5B1E83310D0047E51F /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04CAA591E83310D0047E51F /* MD5.swift */; }; D050F2101E48AB0600988324 /* InteractivePhoneFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F20F1E48AB0600988324 /* InteractivePhoneFormatter.swift */; }; D050F2111E48AB0600988324 /* InteractivePhoneFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F20F1E48AB0600988324 /* InteractivePhoneFormatter.swift */; }; D050F2511E4A59C200988324 /* JoinLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2501E4A59C200988324 /* JoinLink.swift */; }; @@ -429,6 +431,14 @@ D0E35A151DE4C6A200BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */; }; D0E6521F1E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */; }; D0E652201E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */; }; + D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */; }; + D0F3A8A01E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */; }; + D0F3A8A21E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */; }; + D0F3A8A31E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */; }; + D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A8A41E82C94C00B4C64C /* SynchronizeableChatInputState.swift */; }; + D0F3A8A61E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A8A41E82C94C00B4C64C /* SynchronizeableChatInputState.swift */; }; + D0F3A8A81E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A8A71E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift */; }; + D0F3A8A91E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A8A71E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift */; }; D0F3CC791DDE2859008148FA /* SearchMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D711D631ABA00955575 /* SearchMessages.swift */; }; D0F3CC7A1DDE2859008148FA /* RequestMessageActionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */; }; D0F3CC7B1DDE2859008148FA /* RequestEditMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */; }; @@ -604,6 +614,7 @@ D049EAE71E44B67100A2CD3A /* RecentPeerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentPeerItem.swift; sourceTree = ""; }; D049EAEA1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentlySearchedPeerIds.swift; sourceTree = ""; }; D049EAF41E44DF3300A2CD3A /* AccountState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountState.swift; sourceTree = ""; }; + D04CAA591E83310D0047E51F /* MD5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MD5.swift; sourceTree = ""; }; D050F20F1E48AB0600988324 /* InteractivePhoneFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractivePhoneFormatter.swift; sourceTree = ""; }; D050F2501E4A59C200988324 /* JoinLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinLink.swift; sourceTree = ""; }; D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedLocalInputActivities.swift; sourceTree = ""; }; @@ -723,6 +734,10 @@ D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageWithChatContextResult.swift; sourceTree = ""; }; D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = ""; }; D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAccountPeerName.swift; sourceTree = ""; }; + D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeChatInputStateOperation.swift; sourceTree = ""; }; + D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeChatInputStateOperations.swift; sourceTree = ""; }; + D0F3A8A41E82C94C00B4C64C /* SynchronizeableChatInputState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeableChatInputState.swift; sourceTree = ""; }; + D0F3A8A71E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeerChatInterfaceState.swift; sourceTree = ""; }; D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolvePeerByName.swift; sourceTree = ""; }; D0F53BE81E784A4800117362 /* ChangeAccountPhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeAccountPhoneNumber.swift; sourceTree = ""; }; D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditedMessageAttribute.swift; sourceTree = ""; }; @@ -874,6 +889,7 @@ D0448C9E1E27F5EB005A61A7 /* Random.swift */, D050F20F1E48AB0600988324 /* InteractivePhoneFormatter.swift */, D03229F31E6B39700000AF9C /* ImportAccount.swift */, + D04CAA591E83310D0047E51F /* MD5.swift */, ); name = Utils; sourceTree = ""; @@ -914,6 +930,7 @@ D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */, D00D97C61E32901700E5C2B6 /* PeerInputActivity.swift */, D033FEAF1E61EB0200644997 /* PeerReportStatus.swift */, + D0F3A8A41E82C94C00B4C64C /* SynchronizeableChatInputState.swift */, ); name = Peers; sourceTree = ""; @@ -994,6 +1011,8 @@ D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */, D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */, D0E23DD91E806F7700B9B6D2 /* ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift */, + D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */, + D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */, ); name = State; sourceTree = ""; @@ -1243,6 +1262,7 @@ C2FD33E31E687BF1008D13D4 /* PeerPhotoUpdater.swift */, C2FD33EA1E696C78008D13D4 /* GroupsInCommon.swift */, D0C48F3B1E8142EF0075317D /* LoadedPeerFromMessage.swift */, + D0F3A8A71E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift */, ); name = Peers; sourceTree = ""; @@ -1475,6 +1495,7 @@ D0B843B91DA7FF30005F29E1 /* NBMetadataCoreTest.m in Sources */, D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */, D03B0D0E1D62255C00955575 /* UpdateGroup.swift in Sources */, + D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */, D01AC9231DD5E9A200E8160F /* ApplyUpdateMessage.swift in Sources */, D03B0CF71D62250800955575 /* TelegramMediaImage.swift in Sources */, D0E23DDF1E8082A400B9B6D2 /* ArchivedStickerPacks.swift in Sources */, @@ -1511,6 +1532,7 @@ D0FA8B981E1E955C001E855B /* SecretChatOutgoingOperation.swift in Sources */, D0DF0C911D81A857008AEB01 /* ImageRepresentationsUtils.swift in Sources */, D0E6521F1E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */, + D0F3A8A21E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift in Sources */, D03B0D5A1D631A6900955575 /* Api.swift in Sources */, D049EAE81E44B67100A2CD3A /* RecentPeerItem.swift in Sources */, D02ABC7B1E30058F00CAE539 /* DeleteMessagesInteractively.swift in Sources */, @@ -1570,6 +1592,7 @@ D0FA8BB91E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift in Sources */, D0561DE31E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */, D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */, + D04CAA5A1E83310D0047E51F /* MD5.swift in Sources */, D05452071E7B5093006EEF19 /* LoadedStickerPack.swift in Sources */, D0F7AB2F1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */, D0B843971DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift in Sources */, @@ -1623,11 +1646,13 @@ D0AB0B961D662F0B002C78E7 /* ManagedChatListHoles.swift in Sources */, D05A32E41E6F0B2E002760B4 /* RecentAccountSessions.swift in Sources */, D03E5E0C1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */, + D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */, D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */, D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */, D033FEB31E61F3C000644997 /* ReportPeer.swift in Sources */, D021E0E21DB5401A00C6B04F /* StickerManagement.swift in Sources */, D0BC38701E40853E0044D6FE /* UpdatePeers.swift in Sources */, + D0F3A8A81E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */, D03B0CE21D62249B00955575 /* InlineBotMessageAttribute.swift in Sources */, D0AB0B9A1D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift in Sources */, C27982531E73077800262BFD /* SecretChatStateBridge.swift in Sources */, @@ -1711,6 +1736,7 @@ D0B844311DAB91E0005F29E1 /* NBPhoneMetaData.m in Sources */, C22EE61C1E67418000334C38 /* ToggleChannelSignatures.swift in Sources */, D0B418AC1D7E0597004562A4 /* Network.swift in Sources */, + D04CAA5B1E83310D0047E51F /* MD5.swift in Sources */, D0B844141DAB91CD005F29E1 /* PhoneNumbers.swift in Sources */, D0E305AB1E5BA02D00D7A3A2 /* ChannelBlacklist.swift in Sources */, D00D97CB1E32917C00E5C2B6 /* PeerInputActivityManager.swift in Sources */, @@ -1754,6 +1780,7 @@ D0F3CC791DDE2859008148FA /* SearchMessages.swift in Sources */, D0B8442B1DAB91E0005F29E1 /* NBMetadataCore.m in Sources */, D00C7CD01E3628180080C3D5 /* UpdateCachedChannelParticipants.swift in Sources */, + D0F3A8A61E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */, D00D34431E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */, D049EAE91E44B67100A2CD3A /* RecentPeerItem.swift in Sources */, D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */, @@ -1775,6 +1802,7 @@ C2366C8A1E4F40480097CCFF /* SupportPeerId.swift in Sources */, D0B844451DAB91FD005F29E1 /* AccountViewTracker.swift in Sources */, D0C48F3D1E8142EF0075317D /* LoadedPeerFromMessage.swift in Sources */, + D0F3A8A01E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */, D0BE383F1E7C5995000079AF /* MediaPool.swift in Sources */, D050F2601E4A5AD500988324 /* AutoremoveTimeoutMessageAttribute.swift in Sources */, D049EAD91E43DAD200A2CD3A /* ManagedRecentStickers.swift in Sources */, @@ -1832,6 +1860,7 @@ D0561DEB1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, D0613FCB1E60440600202CDB /* InvitationLinks.swift in Sources */, D0B844471DAB91FD005F29E1 /* ManagedServiceViews.swift in Sources */, + D0F3A8A91E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */, D00D343A1E6EC9520057B307 /* TeleramMediaUnsupported.swift in Sources */, D03C53691DAD5CA9004C17B3 /* PeerAccessRestrictionInfo.swift in Sources */, C2FD33E21E680E9E008D13D4 /* RequestUserPhotos.swift in Sources */, @@ -1858,6 +1887,7 @@ D0FA8BAE1E1FD6E2001E855B /* MemoryBufferExtensions.swift in Sources */, D0FA8BB41E201B02001E855B /* ProcessSecretChatIncomingEncryptedOperations.swift in Sources */, D0B844101DAB91CD005F29E1 /* MergeLists.swift in Sources */, + D0F3A8A31E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift in Sources */, D0448CA61E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */, D001F3F11E128A1C007A8C60 /* SynchronizePeerReadState.swift in Sources */, D050F2641E4A5AEB00988324 /* ManagedSynchronizePinnedChatsOperations.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 3f173e6872..b84cc28570 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -218,6 +218,7 @@ private var declaredEncodables: Void = { declareEncodable(FeaturedStickerPackItem.self, f: { FeaturedStickerPackItem(decoder: $0) }) declareEncodable(SynchronizeMarkFeaturedStickerPacksAsSeenOperation.self, f: { SynchronizeMarkFeaturedStickerPacksAsSeenOperation(decoder: $0) }) declareEncodable(ArchivedStickerPacksInfo.self, f: { ArchivedStickerPacksInfo(decoder: $0) }) + declareEncodable(SynchronizeChatInputStateOperation.self, f: { SynchronizeChatInputStateOperation(decoder: $0) }) return }() @@ -230,7 +231,7 @@ private func accountRecordIdPathName(_ id: AccountRecordId) -> String { return "account-\(UInt64(bitPattern: id.int64))" } -public func accountWithId(apiId: Int32, id: AccountRecordId, appGroupPath: String, testingEnvironment: Bool, shouldKeepAutoConnection: Bool = true) -> Signal, NoError> { +public func accountWithId(apiId: Int32, id: AccountRecordId, appGroupPath: String, testingEnvironment: Bool, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal, NoError> { return Signal<(String, Postbox, Coding?), NoError> { subscriber in let _ = declaredEncodables @@ -271,7 +272,7 @@ public func accountWithId(apiId: Int32, id: AccountRecordId, appGroupPath: Strin case let authorizedState as AuthorizedAccountState: return initializedNetwork(apiId: apiId, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: basePath), testingEnvironment: testingEnvironment) |> map { network -> Either in - return .right(value: Account(id: id, basePath: basePath, testingEnvironment: testingEnvironment, postbox: postbox, network: network, peerId: authorizedState.peerId)) + return .right(value: Account(id: id, basePath: basePath, testingEnvironment: testingEnvironment, postbox: postbox, network: network, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods)) } case _: assertionFailure("Unexpected accountState \(accountState)") @@ -353,6 +354,16 @@ public enum AccountNetworkState { case online } +public final class AccountAuxiliaryMethods { + public let updatePeerChatInputState: (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState? + public let fetchResource: (Account, MediaResource, Range) -> Signal? + + public init(updatePeerChatInputState: @escaping (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?, fetchResource: @escaping (Account, MediaResource, Range) -> Signal?) { + self.updatePeerChatInputState = updatePeerChatInputState + self.fetchResource = fetchResource + } +} + public class Account { public let id: AccountRecordId public let basePath: String @@ -361,6 +372,8 @@ public class Account { public let network: Network public let peerId: PeerId + public let auxiliaryMethods: AccountAuxiliaryMethods + private let serviceQueue = Queue() public private(set) var stateManager: AccountStateManager! @@ -401,7 +414,7 @@ public class Account { var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? - public init(id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, peerId: PeerId) { + public init(id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods) { self.id = id self.basePath = basePath self.testingEnvironment = testingEnvironment @@ -409,8 +422,10 @@ public class Account { self.network = network self.peerId = peerId + self.auxiliaryMethods = auxiliaryMethods + self.peerInputActivityManager = PeerInputActivityManager() - self.stateManager = AccountStateManager(account: self, peerInputActivityManager: self.peerInputActivityManager) + self.stateManager = AccountStateManager(account: self, peerInputActivityManager: self.peerInputActivityManager, auxiliaryMethods: auxiliaryMethods) self.localInputActivityManager = PeerInputActivityManager() self.viewTracker = AccountViewTracker(account: self) self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, stateManager: self.stateManager) @@ -550,7 +565,8 @@ public class Account { self.managedOperationsDisposable.add(managedRecentGifs(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedRecentlyUsedInlineBots(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.postbox, network: self.network).start()) - self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager : self.stateManager).start()) + self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) + self.managedOperationsDisposable.add(managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network).start()) let updatedPresence = self.shouldKeepOnlinePresence.get() @@ -634,7 +650,13 @@ public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil) { account.postbox.mediaBox.fetchResource = { [weak account] resource, range -> Signal in if let strongAccount = account { - return fetchResource(account: strongAccount, resource: resource, range: range) + if let result = fetchResource(account: strongAccount, resource: resource, range: range) { + return result + } else if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, range) { + return result + } else { + return .never() + } } else { return .never() } diff --git a/TelegramCore/AccountIntermediateState.swift b/TelegramCore/AccountIntermediateState.swift index 7ae2033a9f..1333784a61 100644 --- a/TelegramCore/AccountIntermediateState.swift +++ b/TelegramCore/AccountIntermediateState.swift @@ -68,6 +68,7 @@ enum AccountStateMutationOperation { case ReadGlobalMessageContents([Int32]) case UpdateMessageImpressionCount(MessageId, Int32) case UpdateInstalledStickerPacks(AccountStateUpdateStickerPacksOperation) + case UpdateChatInputState(PeerId, SynchronizeableChatInputState?) } struct AccountMutableState { @@ -248,9 +249,13 @@ struct AccountMutableState { self.addOperation(.UpdateInstalledStickerPacks(operation)) } + mutating func addUpdateChatInputState(peerId: PeerId, state: SynchronizeableChatInputState?) { + self.addOperation(.UpdateChatInputState(peerId, state)) + } + mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadGlobalMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks: + case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadGlobalMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateChatInputState: break case let .AddMessages(messages, _): for message in messages { diff --git a/TelegramCore/AccountManager.swift b/TelegramCore/AccountManager.swift index 810d567cb7..31d74d9709 100644 --- a/TelegramCore/AccountManager.swift +++ b/TelegramCore/AccountManager.swift @@ -14,7 +14,7 @@ private enum AccountKind { case unauthorized } -public func currentAccount(apiId: Int32, manager: AccountManager, appGroupPath: String, testingEnvironment: Bool) -> Signal?, NoError> { +public func currentAccount(apiId: Int32, manager: AccountManager, appGroupPath: String, testingEnvironment: Bool, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal?, NoError> { return manager.allocatedCurrentAccountId() |> distinctUntilChanged(isEqual: { lhs, rhs in return lhs == rhs @@ -23,7 +23,7 @@ public func currentAccount(apiId: Int32, manager: AccountManager, appGroupPath: if let id = id { let reload = ValuePromise(true, ignoreRepeated: false) return reload.get() |> mapToSignal { _ -> Signal?, NoError> in - return accountWithId(apiId: apiId, id: id, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment) + return accountWithId(apiId: apiId, id: id, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment, auxiliaryMethods: auxiliaryMethods) |> mapToSignal { account -> Signal?, NoError> in let postbox: Postbox let initialKind: AccountKind @@ -96,7 +96,7 @@ public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManage } } -public func managedCleanupAccounts(apiId: Int32, accountManager: AccountManager, appGroupPath: String) -> Signal { +public func managedCleanupAccounts(apiId: Int32, accountManager: AccountManager, appGroupPath: String, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal { return Signal { subscriber in let loggedOutAccounts = Atomic<[AccountRecordId: MetaDisposable]>(value: [:]) let disposable = accountManager.accountRecords().start(next: { view in @@ -139,7 +139,7 @@ public func managedCleanupAccounts(apiId: Int32, accountManager: AccountManager, disposable.dispose() } for (id, disposable) in beginList { - disposable.set(cleanupAccount(apiId: apiId, accountManager: accountManager, id: id, appGroupPath: appGroupPath).start()) + disposable.set(cleanupAccount(apiId: apiId, accountManager: accountManager, id: id, appGroupPath: appGroupPath, auxiliaryMethods: auxiliaryMethods).start()) } }) @@ -150,8 +150,8 @@ public func managedCleanupAccounts(apiId: Int32, accountManager: AccountManager, } -private func cleanupAccount(apiId: Int32, accountManager: AccountManager, id: AccountRecordId, appGroupPath: String) -> Signal { - return accountWithId(apiId: apiId, id: id, appGroupPath: appGroupPath, testingEnvironment: false) +private func cleanupAccount(apiId: Int32, accountManager: AccountManager, id: AccountRecordId, appGroupPath: String, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal { + return accountWithId(apiId: apiId, id: id, appGroupPath: appGroupPath, testingEnvironment: false, auxiliaryMethods: auxiliaryMethods) |> mapToSignal { account -> Signal in switch account { case .left: diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index daf175aa74..13bc2d3d47 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -982,6 +982,19 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, updatedState.addUpdateInstalledStickerPacks(.reorder(namespace, order)) case .updateStickerSets: updatedState.addUpdateInstalledStickerPacks(.sync) + case let .updateDraftMessage(peer, draft): + let inputState: SynchronizeableChatInputState? + switch draft { + case .draftMessageEmpty: + inputState = nil + case let .draftMessage(_, replyToMsgId, message, entities, date): + var replyToMessageId: MessageId? + if let replyToMsgId = replyToMsgId { + replyToMessageId = MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId) + } + inputState = SynchronizeableChatInputState(replyToMessageId: replyToMessageId, text: message, timestamp: date) + } + updatedState.addUpdateChatInputState(peerId: peer.peerId, state: inputState) default: break } @@ -1356,7 +1369,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ResetReadState, .UpdatePeerNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadGlobalMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks: + case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ResetReadState, .UpdatePeerNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadGlobalMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateChatInputState: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -1392,7 +1405,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) return result } -func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modifier, finalState: AccountFinalState) -> AccountReplayedFinalState? { +func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modifier, auxiliaryMethods: AccountAuxiliaryMethods, finalState: AccountFinalState) -> AccountReplayedFinalState? { let verified = verifyTransaction(modifier, finalState: finalState.state) if !verified { return nil @@ -1552,6 +1565,10 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif }) case let .UpdateInstalledStickerPacks(operation): stickerPackOperations.append(operation) + case let .UpdateChatInputState(peerId, inputState): + modifier.updatePeerChatInterfaceState(peerId, update: { current in + return auxiliaryMethods.updatePeerChatInputState(current, inputState) + }) } } diff --git a/TelegramCore/AccountStateManager.swift b/TelegramCore/AccountStateManager.swift index 78853b1daa..fcdf19f150 100644 --- a/TelegramCore/AccountStateManager.swift +++ b/TelegramCore/AccountStateManager.swift @@ -39,6 +39,7 @@ public final class AccountStateManager { private let queue = Queue() private let account: Account private let peerInputActivityManager: PeerInputActivityManager + private let auxiliaryMethods: AccountAuxiliaryMethods private var updateService: UpdateMessageService? private let updateServiceDisposable = MetaDisposable() @@ -81,9 +82,10 @@ public final class AccountStateManager { private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:] - init(account: Account, peerInputActivityManager: PeerInputActivityManager) { + init(account: Account, peerInputActivityManager: PeerInputActivityManager, auxiliaryMethods: AccountAuxiliaryMethods) { self.account = account self.peerInputActivityManager = peerInputActivityManager + self.auxiliaryMethods = auxiliaryMethods } deinit { @@ -233,6 +235,7 @@ public final class AccountStateManager { let account = self.account let mediaBox = account.postbox.mediaBox let accountPeerId = account.peerId + let auxiliaryMethods = self.auxiliaryMethods let signal = account.postbox.stateView() |> mapToSignal { view -> Signal in if let state = view.state as? AuthorizedAccountState { @@ -261,7 +264,7 @@ public final class AccountStateManager { } } return account.postbox.modify { modifier -> (Api.updates.Difference?, AccountReplayedFinalState?) in - if let replayedState = replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, modifier: modifier, finalState: finalState) { + if let replayedState = replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, modifier: modifier, auxiliaryMethods: auxiliaryMethods, finalState: finalState) { return (difference, replayedState) } else { return (nil, nil) @@ -345,6 +348,7 @@ public final class AccountStateManager { case let .processUpdateGroups(groups): self.operationTimer?.invalidate() let account = self.account + let auxiliaryMethods = self.auxiliaryMethods let accountPeerId = account.peerId let mediaBox = account.postbox.mediaBox let queue = self.queue @@ -359,7 +363,7 @@ public final class AccountStateManager { } return account.postbox.modify { modifier -> AccountReplayedFinalState? in - return replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, modifier: modifier, finalState: finalState) + return replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, modifier: modifier, auxiliaryMethods: auxiliaryMethods, finalState: finalState) } |> map({ ($0, finalState) }) |> deliverOn(queue) @@ -511,8 +515,9 @@ public final class AccountStateManager { let accountPeerId = self.account.peerId let mediaBox = self.account.postbox.mediaBox + let auxiliaryMethods = self.auxiliaryMethods let signal = self.account.postbox.modify { modifier -> AccountReplayedFinalState? in - return replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, modifier: modifier, finalState: finalState) + return replayFinalState(accountPeerId: accountPeerId, mediaBox: mediaBox, modifier: modifier, auxiliaryMethods: auxiliaryMethods, finalState: finalState) } |> map({ ($0, finalState) }) |> deliverOn(self.queue) diff --git a/TelegramCore/CloudFileMediaResource.swift b/TelegramCore/CloudFileMediaResource.swift index cbe6a9392c..99aa10203e 100644 --- a/TelegramCore/CloudFileMediaResource.swift +++ b/TelegramCore/CloudFileMediaResource.swift @@ -299,23 +299,27 @@ public struct LocalFileReferenceMediaResourceId: MediaResourceId { public class LocalFileReferenceMediaResource: TelegramMediaResource { let localFilePath: String let randomId: Int64 + let isUniquelyReferencedTemporaryFile: Bool let size: Int32? - public init(localFilePath: String, randomId: Int64, size: Int32? = nil) { + public init(localFilePath: String, randomId: Int64, isUniquelyReferencedTemporaryFile: Bool = false, size: Int32? = nil) { self.localFilePath = localFilePath self.randomId = randomId + self.isUniquelyReferencedTemporaryFile = isUniquelyReferencedTemporaryFile self.size = size } public required init(decoder: Decoder) { self.localFilePath = decoder.decodeStringForKey("p") self.randomId = decoder.decodeInt64ForKey("r") + self.isUniquelyReferencedTemporaryFile = (decoder.decodeInt32ForKey("t") as Int32) != 0 self.size = decoder.decodeInt32ForKey("s") } public func encode(_ encoder: Encoder) { encoder.encodeString(self.localFilePath, forKey: "p") encoder.encodeInt64(self.randomId, forKey: "r") + encoder.encodeInt32(self.isUniquelyReferencedTemporaryFile ? 1 : 0, forKey: "t") if let size = self.size { encoder.encodeInt32(size, forKey: "s") } else { @@ -329,7 +333,7 @@ public class LocalFileReferenceMediaResource: TelegramMediaResource { public func isEqual(to: TelegramMediaResource) -> Bool { if let to = to as? LocalFileReferenceMediaResource { - return self.localFilePath == to.localFilePath && self.randomId == to.randomId && self.size == size + return self.localFilePath == to.localFilePath && self.randomId == to.randomId && self.size == to.size && self.isUniquelyReferencedTemporaryFile == to.isUniquelyReferencedTemporaryFile } else { return false } diff --git a/TelegramCore/Fetch.swift b/TelegramCore/Fetch.swift index db1fd89d97..f7ea6628e5 100644 --- a/TelegramCore/Fetch.swift +++ b/TelegramCore/Fetch.swift @@ -36,7 +36,7 @@ private func fetchPhotoLibraryResource(localIdentifier: String) -> Signal Signal Signal { +private func fetchLocalFileResource(path: String, move: Bool) -> Signal { return Signal { subscriber in - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { - subscriber.putNext(MediaResourceDataFetchResult(data: data, complete: true)) + if move { + subscriber.putNext(.moveLocalFile(path: path)) subscriber.putCompletion() } else { - subscriber.putNext(MediaResourceDataFetchResult(data: Data(), complete: false)) + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + subscriber.putNext(.dataPart(data: data, range: 0 ..< data.count, complete: true)) + subscriber.putCompletion() + } else { + subscriber.putNext(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) + } } return EmptyDisposable } } -func fetchResource(account: Account, resource: MediaResource, range: Range) -> Signal { +func fetchResource(account: Account, resource: MediaResource, range: Range) -> Signal? { if let _ = resource as? EmptyMediaResource { return .never() } else if let secretFileResource = resource as? SecretFileMediaResource { - return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchSecretFileResource(account: account, resource: secretFileResource, range: range)) + return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchSecretFileResource(account: account, resource: secretFileResource, range: range)) } else if let cloudResource = resource as? TelegramCloudMediaResource { - return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, size: resource.size, range: range)) + return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, size: resource.size, range: range)) } else if let photoLibraryResource = resource as? PhotoLibraryMediaResource { #if os(iOS) - return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier)) + return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier)) #else - return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) + return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) #endif } else if let localFileResource = resource as? LocalFileReferenceMediaResource { if false { - //return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchLocalFileResource(path: localFileResource.localFilePath) |> delay(10.0, queue: Queue.concurrentDefaultQueue())) + //return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchLocalFileResource(path: localFileResource.localFilePath) |> delay(10.0, queue: Queue.concurrentDefaultQueue())) } else { - return fetchLocalFileResource(path: localFileResource.localFilePath) + return fetchLocalFileResource(path: localFileResource.localFilePath, move: localFileResource.isUniquelyReferencedTemporaryFile) } } else if let httpReference = resource as? HttpReferenceMediaResource { - return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) |> then(fetchHttpResource(url: httpReference.url)) + return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchHttpResource(url: httpReference.url)) } - return .single(MediaResourceDataFetchResult(data: Data(), complete: false)) + return nil } diff --git a/TelegramCore/FetchHttpResource.swift b/TelegramCore/FetchHttpResource.swift index 8f62a62246..721e818ed4 100644 --- a/TelegramCore/FetchHttpResource.swift +++ b/TelegramCore/FetchHttpResource.swift @@ -14,7 +14,8 @@ func fetchHttpResource(url: String) -> Signal Data { + var res = Data() + res.count = Int(CC_MD5_DIGEST_LENGTH) + res.withUnsafeMutableBytes { mutableBytes -> Void in + CC_MD5(self.memory, CC_LONG(self.length), mutableBytes) + } + return res + } +} diff --git a/TelegramCore/ManagedSynchronizeChatInputStateOperations.swift b/TelegramCore/ManagedSynchronizeChatInputStateOperations.swift new file mode 100644 index 0000000000..bbf4411676 --- /dev/null +++ b/TelegramCore/ManagedSynchronizeChatInputStateOperations.swift @@ -0,0 +1,136 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private final class ManagedSynchronizeChatInputStateOperationsHelper { + var operationDisposables: [Int32: Disposable] = [:] + + func update(_ entries: [PeerMergedOperationLogEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) { + var disposeOperations: [Disposable] = [] + var beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)] = [] + + var hasRunningOperationForPeerId = Set() + var validMergedIndices = Set() + for entry in entries { + if !hasRunningOperationForPeerId.contains(entry.peerId) { + hasRunningOperationForPeerId.insert(entry.peerId) + validMergedIndices.insert(entry.mergedIndex) + + if self.operationDisposables[entry.mergedIndex] == nil { + let disposable = MetaDisposable() + beginOperations.append((entry, disposable)) + self.operationDisposables[entry.mergedIndex] = disposable + } + } + } + + var removeMergedIndices: [Int32] = [] + for (mergedIndex, disposable) in self.operationDisposables { + if !validMergedIndices.contains(mergedIndex) { + removeMergedIndices.append(mergedIndex) + disposeOperations.append(disposable) + } + } + + for mergedIndex in removeMergedIndices { + self.operationDisposables.removeValue(forKey: mergedIndex) + } + + return (disposeOperations, beginOperations) + } + + func reset() -> [Disposable] { + let disposables = Array(self.operationDisposables.values) + self.operationDisposables.removeAll() + return disposables + } +} + +private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: @escaping (Modifier, PeerMergedOperationLogEntry?) -> Signal) -> Signal { + return postbox.modify { modifier -> Signal in + var result: PeerMergedOperationLogEntry? + modifier.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in + if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeChatInputStateOperation { + result = entry.mergedEntry! + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } else { + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } + }) + + return f(modifier, result) + } |> switchToLatest +} + +func managedSynchronizeChatInputStateOperations(postbox: Postbox, network: Network) -> Signal { + return Signal { _ in + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeChatInputStates + + let helper = Atomic(value: ManagedSynchronizeChatInputStateOperationsHelper()) + + let disposable = postbox.mergedOperationLogView(tag: tag, limit: 10).start(next: { view in + let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in + return helper.update(view.entries) + } + + for disposable in disposeOperations { + disposable.dispose() + } + + for (entry, disposable) in beginOperations { + let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { modifier, entry -> Signal in + if let entry = entry { + if let operation = entry.contents as? SynchronizeChatInputStateOperation { + return synchronizeChatInputState(modifier: modifier, postbox: postbox, network: network, peerId: entry.peerId, operation: operation) + } else { + assertionFailure() + } + } + return .complete() + }) + |> then(postbox.modify { modifier -> Void in + let _ = modifier.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex) + }) + + disposable.set(signal.start()) + } + }) + + return ActionDisposable { + let disposables = helper.with { helper -> [Disposable] in + return helper.reset() + } + for disposable in disposables { + disposable.dispose() + } + disposable.dispose() + } + } +} + +private func synchronizeChatInputState(modifier: Modifier, postbox: Postbox, network: Network, peerId: PeerId, operation: SynchronizeChatInputStateOperation) -> Signal { + let inputState = (modifier.getPeerChatInterfaceState(peerId) as? SynchronizeableChatInterfaceState)?.synchronizeableInputState + if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) { + var flags: Int32 = 0 + if inputState?.replyToMessageId != nil { + flags |= (1 << 0) + } + return network.request(Api.functions.messages.saveDraft(flags: flags, replyToMsgId: inputState?.replyToMessageId?.id, peer: inputPeer, message: inputState?.text ?? "", entities: nil)) + |> delay(2.0, queue: Queue.concurrentDefaultQueue()) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } else { + return .complete() + } +} diff --git a/TelegramCore/MultipartFetch.swift b/TelegramCore/MultipartFetch.swift index 35e3b6e5ef..f1aff31a65 100644 --- a/TelegramCore/MultipartFetch.swift +++ b/TelegramCore/MultipartFetch.swift @@ -200,9 +200,9 @@ func multipartFetch(account: Account, resource: TelegramCloudMediaResource, size let manager = MultipartFetchManager(size: size, range: range, encryptionKey: encryptionKey, decryptedSize: decryptedSize, fetchPart: { offset, size in return download.part(location: inputLocation, offset: offset, length: size) }, partReady: { data in - subscriber.putNext(MediaResourceDataFetchResult(data: data, complete: false)) + subscriber.putNext(.dataPart(data: data, range: 0 ..< data.count, complete: false)) }, completed: { - subscriber.putNext(MediaResourceDataFetchResult(data: Data(), complete: true)) + subscriber.putNext(.dataPart(data: Data(), range: 0 ..< 0, complete: true)) subscriber.putCompletion() }) diff --git a/TelegramCore/MultipartUpload.swift b/TelegramCore/MultipartUpload.swift index 9aeabd0041..8d024c499f 100644 --- a/TelegramCore/MultipartUpload.swift +++ b/TelegramCore/MultipartUpload.swift @@ -61,7 +61,6 @@ private func md5(_ data : Data) -> Data { private final class MultipartUploadState { let aesKey: Data var aesIv: Data - var md5Context = CC_MD5_CTX() var effectiveSize: Int = 0 init(encryptionKey: SecretFileEncryptionKey?) { @@ -72,7 +71,6 @@ private final class MultipartUploadState { self.aesKey = Data() self.aesIv = Data() } - CC_MD5_Init(&self.md5Context) } func transform(data: Data) -> Data { @@ -92,34 +90,17 @@ private final class MultipartUploadState { self.aesIv.withUnsafeMutableBytes { (iv: UnsafeMutablePointer) -> Void in MTAesEncryptBytesInplaceAndModifyIv(bytes, encryptedData.count, self.aesKey, iv) } - CC_MD5_Update(&self.md5Context, bytes, UInt32(encryptedData.count)) } effectiveSize += encryptedData.count return encryptedData } else { - data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in - CC_MD5_Update(&self.md5Context, bytes, UInt32(data.count)) - } effectiveSize += data.count return data } } - func finalize() -> (md5Digest: String, effectiveSize: Int) { - var res = Data() - res.count = Int(CC_MD5_DIGEST_LENGTH) - res.withUnsafeMutableBytes { mutableBytes -> Void in - CC_MD5_Final(mutableBytes, &self.md5Context) - } - let hashString = res.withUnsafeBytes { (bytes: UnsafePointer) -> String in - let hexString = NSMutableString() - for i in 0 ..< res.count { - let byteValue = UInt(bytes.advanced(by: i).pointee) - hexString.appendFormat("%02x", byteValue) - } - return hexString as String - } - return (hashString, self.effectiveSize) + func finalize() -> Int { + return self.effectiveSize } } @@ -152,9 +133,11 @@ private final class MultipartUploadManager { let dataDisposable = MetaDisposable() var resourceData: MediaResourceData? + var headerPartReady: Bool + let state: MultipartUploadState - init(data: Signal, encryptionKey: SecretFileEncryptionKey?, hintFileSize: Int?, uploadPart: @escaping (UploadPart) -> Signal, progress: @escaping (Float) -> Void, completed: @escaping (MultipartIntermediateResult) -> Void) { + init(resource: MediaResource, data: Signal, encryptionKey: SecretFileEncryptionKey?, hintFileSize: Int?, uploadPart: @escaping (UploadPart) -> Signal, progress: @escaping (Float) -> Void, completed: @escaping (MultipartIntermediateResult) -> Void) { self.dataSignal = data var fileId: Int64 = 0 @@ -168,12 +151,28 @@ private final class MultipartUploadManager { self.progress = progress self.completed = completed + self.headerPartReady = resource.headerSize == 0 + if let hintFileSize = hintFileSize, hintFileSize > 5 * 1024 * 1024 { self.defaultPartSize = 512 * 1024 self.bigTotalParts = (hintFileSize / self.defaultPartSize) + (hintFileSize % self.defaultPartSize == 0 ? 0 : 1) } else { - self.defaultPartSize = 32 * 1024 - self.bigTotalParts = nil + if self.headerPartReady { + self.defaultPartSize = 32 * 1024 + self.bigTotalParts = nil + } else { + self.defaultPartSize = 32 * 1024 + self.bigTotalParts = nil + //self.defaultPartSize = 128 * 1024 + //self.bigTotalParts = -1 + } + } + + if self.headerPartReady { + self.committedOffset = 0 + } else { + self.committedOffset = self.defaultPartSize + self.state.effectiveSize = self.defaultPartSize } } @@ -213,8 +212,29 @@ private final class MultipartUploadManager { } if let resourceData = self.resourceData, resourceData.complete, self.committedOffset >= resourceData.size { - let (md5Digest, effectiveSize) = self.state.finalize() - self.completed(MultipartIntermediateResult(id: self.fileId, partCount: Int32(effectiveSize / self.defaultPartSize + (effectiveSize % self.defaultPartSize == 0 ? 0 : 1)), md5Digest: md5Digest, size: Int32(resourceData.size), bigTotalParts: self.bigTotalParts)) + if self.headerPartReady { + let effectiveSize = self.state.finalize() + self.completed(MultipartIntermediateResult(id: self.fileId, partCount: Int32(effectiveSize / self.defaultPartSize + (effectiveSize % self.defaultPartSize == 0 ? 0 : 1)), md5Digest: "", size: Int32(resourceData.size), bigTotalParts: self.bigTotalParts)) + } else { + let partOffset = 0 + let partSize = min(resourceData.size - partOffset, self.defaultPartSize) + let partIndex = partOffset / self.defaultPartSize + let fileData = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.alwaysMapped]) + let partData = fileData!.subdata(in: partOffset ..< (partOffset + partSize)) + var currentBigTotalParts = self.bigTotalParts + if let _ = self.bigTotalParts { + currentBigTotalParts = (resourceData.size / self.defaultPartSize) + (resourceData.size % self.defaultPartSize == 0 ? 0 : 1) + } + let part = self.uploadPart(UploadPart(fileId: self.fileId, index: partIndex, data: partData, bigTotalParts: currentBigTotalParts)) + |> deliverOn(self.queue) + self.uploadingParts[0] = (partSize, part.start(completed: { [weak self] in + if let strongSelf = self { + let _ = strongSelf.uploadingParts.removeValue(forKey: 0) + strongSelf.headerPartReady = true + strongSelf.checkState() + } + })) + } } else { while uploadingParts.count < self.parallelParts { var nextOffset = self.committedOffset @@ -225,21 +245,30 @@ private final class MultipartUploadManager { nextOffset = max(nextOffset, offset + partSize) } - if let resourceData = self.resourceData, nextOffset < resourceData.size, (resourceData.complete || nextOffset % 1024 == 0) { + if let resourceData = self.resourceData { let partOffset = nextOffset let partSize = min(resourceData.size - partOffset, self.defaultPartSize) - let partIndex = partOffset / self.defaultPartSize - let fileData = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.alwaysMapped]) - let partData = self.state.transform(data: fileData!.subdata(in: partOffset ..< (partOffset + partSize))) - let part = self.uploadPart(UploadPart(fileId: self.fileId, index: partIndex, data: partData, bigTotalParts: self.bigTotalParts)) - |> deliverOn(self.queue) - self.uploadingParts[nextOffset] = (partSize, part.start(completed: { [weak self] in - if let strongSelf = self { - let _ = strongSelf.uploadingParts.removeValue(forKey: nextOffset) - strongSelf.uploadedParts[partOffset] = partSize - strongSelf.checkState() + + if nextOffset < resourceData.size && partSize > 0 && (resourceData.complete || partSize == self.defaultPartSize) { + let partIndex = partOffset / self.defaultPartSize + let fileData = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.alwaysMapped]) + let partData = self.state.transform(data: fileData!.subdata(in: partOffset ..< (partOffset + partSize))) + var currentBigTotalParts = self.bigTotalParts + if let _ = self.bigTotalParts, resourceData.complete && partOffset + partSize == resourceData.size { + currentBigTotalParts = (resourceData.size / self.defaultPartSize) + (resourceData.size % self.defaultPartSize == 0 ? 0 : 1) } - })) + let part = self.uploadPart(UploadPart(fileId: self.fileId, index: partIndex, data: partData, bigTotalParts: currentBigTotalParts)) + |> deliverOn(self.queue) + self.uploadingParts[nextOffset] = (partSize, part.start(completed: { [weak self] in + if let strongSelf = self { + let _ = strongSelf.uploadingParts.removeValue(forKey: nextOffset) + strongSelf.uploadedParts[partOffset] = partSize + strongSelf.checkState() + } + })) + } else { + break + } } else { break } @@ -275,7 +304,7 @@ func multipartUpload(network: Network, postbox: Postbox, resource: MediaResource let resourceData = postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: true)) - let manager = MultipartUploadManager(data: resourceData, encryptionKey: encryptionKey, hintFileSize: hintFileSize, uploadPart: { part in + let manager = MultipartUploadManager(resource: resource, data: resourceData, encryptionKey: encryptionKey, hintFileSize: hintFileSize, uploadPart: { part in return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, bigTotalParts: part.bigTotalParts) }, progress: { progress in subscriber.putNext(.progress(progress)) diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index 891f258471..af48eb69ac 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -78,6 +78,7 @@ struct OperationLogTags { static let SynchronizeInstalledStickerPacks = PeerOperationLogTag(value: 8) static let SynchronizeInstalledMasks = PeerOperationLogTag(value: 9) static let SynchronizeMarkFeaturedStickerPacksAsSeen = PeerOperationLogTag(value: 10) + static let SynchronizeChatInputStates = PeerOperationLogTag(value: 11) } private enum PreferencesKeyValues: Int32 { diff --git a/TelegramCore/PendingMessageManager.swift b/TelegramCore/PendingMessageManager.swift index c37e032c07..c17e5774a4 100644 --- a/TelegramCore/PendingMessageManager.swift +++ b/TelegramCore/PendingMessageManager.swift @@ -474,6 +474,8 @@ public final class PendingMessageManager { var flags: Int32 = 0 + flags |= (1 << 7) + for attribute in message.attributes { if let replyAttribute = attribute as? ReplyMessageAttribute { replyMessageId = replyAttribute.messageId.id diff --git a/TelegramCore/SynchronizeChatInputStateOperation.swift b/TelegramCore/SynchronizeChatInputStateOperation.swift new file mode 100644 index 0000000000..70e08e1376 --- /dev/null +++ b/TelegramCore/SynchronizeChatInputStateOperation.swift @@ -0,0 +1,51 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +final class SynchronizeChatInputStateOperation: Coding { + let previousState: SynchronizeableChatInputState? + + init(previousState: SynchronizeableChatInputState?) { + self.previousState = previousState + } + + init(decoder: Decoder) { + self.previousState = decoder.decodeObjectForKey("p", decoder: { SynchronizeableChatInputState(decoder: $0) }) as? SynchronizeableChatInputState + } + + func encode(_ encoder: Encoder) { + if let previousState = self.previousState { + encoder.encodeObject(previousState, forKey: "p") + } else { + encoder.encodeNil(forKey: "p") + } + } +} + +func addSynchronizeChatInputStateOperation(modifier: Modifier, peerId: PeerId) { + var updateLocalIndex: Int32? + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeChatInputStates + + var previousOperation: SynchronizeChatInputStateOperation? + modifier.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in + updateLocalIndex = entry.tagLocalIndex + if let operation = entry.contents as? SynchronizeChatInputStateOperation { + previousOperation = operation + } + return false + }) + var previousState: SynchronizeableChatInputState? + if let previousOperation = previousOperation { + previousState = previousOperation.previousState + } else if let peerChatInterfaceState = modifier.getPeerChatInterfaceState(peerId) as? SynchronizeableChatInterfaceState { + previousState = peerChatInterfaceState.synchronizeableInputState + } + let operationContents = SynchronizeChatInputStateOperation(previousState: previousState) + if let updateLocalIndex = updateLocalIndex { + let _ = modifier.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: updateLocalIndex) + } + modifier.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: operationContents) +} diff --git a/TelegramCore/SynchronizeableChatInputState.swift b/TelegramCore/SynchronizeableChatInputState.swift new file mode 100644 index 0000000000..ca72e8ee0d --- /dev/null +++ b/TelegramCore/SynchronizeableChatInputState.swift @@ -0,0 +1,60 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public struct SynchronizeableChatInputState: Coding, Equatable { + public let replyToMessageId: MessageId? + public let text: String + public let timestamp: Int32 + + public init(replyToMessageId: MessageId?, text: String, timestamp: Int32) { + self.replyToMessageId = replyToMessageId + self.text = text + self.timestamp = timestamp + } + + public init(decoder: Decoder) { + self.text = decoder.decodeStringForKey("t") + self.timestamp = decoder.decodeInt32ForKey("s") + if let messageIdPeerId = (decoder.decodeInt64ForKey("m.p") as Int64?), let messageIdNamespace = (decoder.decodeInt32ForKey("m.n") as Int32?), let messageIdId = (decoder.decodeInt32ForKey("m.i") as Int32?) { + self.replyToMessageId = MessageId(peerId: PeerId(messageIdPeerId), namespace: messageIdNamespace, id: messageIdId) + } else { + self.replyToMessageId = nil + } + } + + public func encode(_ encoder: Encoder) { + encoder.encodeString(self.text, forKey: "t") + encoder.encodeInt32(self.timestamp, forKey: "s") + if let replyToMessageId = self.replyToMessageId { + encoder.encodeInt64(replyToMessageId.peerId.toInt64(), forKey: "m.p") + encoder.encodeInt32(replyToMessageId.namespace, forKey: "m.n") + encoder.encodeInt32(replyToMessageId.id, forKey: "m.i") + } else { + encoder.encodeNil(forKey: "m.p") + encoder.encodeNil(forKey: "m.n") + encoder.encodeNil(forKey: "m.i") + } + } + + public static func ==(lhs: SynchronizeableChatInputState, rhs: SynchronizeableChatInputState) -> Bool { + if lhs.replyToMessageId != rhs.replyToMessageId { + return false + } + if lhs.text != rhs.text { + return true + } + if lhs.timestamp != rhs.timestamp { + return false + } + return true + } +} + +public protocol SynchronizeableChatInterfaceState: PeerChatInterfaceState { + var synchronizeableInputState: SynchronizeableChatInputState? { get } + func withUpdatedSynchronizeableInputState(_ state: SynchronizeableChatInputState?) -> SynchronizeableChatInterfaceState +} diff --git a/TelegramCore/UpdatePeerChatInterfaceState.swift b/TelegramCore/UpdatePeerChatInterfaceState.swift new file mode 100644 index 0000000000..7e2529b128 --- /dev/null +++ b/TelegramCore/UpdatePeerChatInterfaceState.swift @@ -0,0 +1,24 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac +#else + import Postbox + import SwiftSignalKit +#endif + +public func updatePeerChatInterfaceState(account: Account, peerId: PeerId, state: SynchronizeableChatInterfaceState) -> Signal { + return account.postbox.modify { modifier -> Void in + let currentInputState = (modifier.getPeerChatInterfaceState(peerId) as? SynchronizeableChatInterfaceState)?.synchronizeableInputState + let updatedInputState = state.synchronizeableInputState + + if currentInputState != updatedInputState { + if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup { + addSynchronizeChatInputStateOperation(modifier: modifier, peerId: peerId) + } + } + modifier.updatePeerChatInterfaceState(peerId, update: { _ in + return state + }) + } +}