diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 1d7d709ce2..bd2ace692d 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -53,6 +53,16 @@ D00C7CE11E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CDF1E3785700080C3D5 /* MarkMessageContentAsConsumedInteractively.swift */; }; D00C7CEB1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */; }; D00C7CEC1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */; }; + D00D34391E6EC9520057B307 /* TeleramMediaUnsupported.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D34381E6EC9520057B307 /* TeleramMediaUnsupported.swift */; }; + D00D343A1E6EC9520057B307 /* TeleramMediaUnsupported.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D34381E6EC9520057B307 /* TeleramMediaUnsupported.swift */; }; + D00D343C1E6EC9770057B307 /* TelegramMediaGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D343B1E6EC9770057B307 /* TelegramMediaGame.swift */; }; + D00D343D1E6EC9770057B307 /* TelegramMediaGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D343B1E6EC9770057B307 /* TelegramMediaGame.swift */; }; + D00D343F1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D343E1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift */; }; + D00D34401E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D343E1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift */; }; + D00D34421E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D34411E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift */; }; + D00D34431E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D34411E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift */; }; + D00D34451E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D34441E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift */; }; + D00D34461E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D34441E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift */; }; D00D97C71E32901700E5C2B6 /* PeerInputActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D97C61E32901700E5C2B6 /* PeerInputActivity.swift */; }; D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D97C61E32901700E5C2B6 /* PeerInputActivity.swift */; }; D00D97CA1E32917C00E5C2B6 /* PeerInputActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D97C91E32917C00E5C2B6 /* PeerInputActivityManager.swift */; }; @@ -457,6 +467,11 @@ D00C7CCE1E3628180080C3D5 /* UpdateCachedChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateCachedChannelParticipants.swift; sourceTree = ""; }; D00C7CDF1E3785700080C3D5 /* MarkMessageContentAsConsumedInteractively.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkMessageContentAsConsumedInteractively.swift; sourceTree = ""; }; D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetSecretChatMessageAutoremoveTimeoutInteractively.swift; sourceTree = ""; }; + D00D34381E6EC9520057B307 /* TeleramMediaUnsupported.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeleramMediaUnsupported.swift; sourceTree = ""; }; + D00D343B1E6EC9770057B307 /* TelegramMediaGame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaGame.swift; sourceTree = ""; }; + D00D343E1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumableContentMessageAttribute.swift; sourceTree = ""; }; + D00D34411E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeConsumeMessageContentsOperations.swift; sourceTree = ""; }; + D00D34441E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeConsumeMessageContentsOperation.swift; sourceTree = ""; }; D00D97C61E32901700E5C2B6 /* PeerInputActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInputActivity.swift; sourceTree = ""; }; D00D97C91E32917C00E5C2B6 /* PeerInputActivityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInputActivityManager.swift; sourceTree = ""; }; D00DBBD61E64E41100DB5485 /* CreateSecretChat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateSecretChat.swift; sourceTree = ""; }; @@ -866,6 +881,7 @@ D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */, D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */, D0458C871E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift */, + D00D343E1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift */, ); name = Attributes; sourceTree = ""; @@ -873,12 +889,14 @@ D03B0CDD1D62247D00955575 /* Media */ = { isa = PBXGroup; children = ( + D00D34381E6EC9520057B307 /* TeleramMediaUnsupported.swift */, D03B0CEC1D62250800955575 /* TelegramMediaAction.swift */, D03B0CED1D62250800955575 /* TelegramMediaContact.swift */, D03B0CEE1D62250800955575 /* TelegramMediaFile.swift */, D03B0CEF1D62250800955575 /* TelegramMediaImage.swift */, D03B0CF11D62250800955575 /* TelegramMediaMap.swift */, D03B0CF31D62250800955575 /* TelegramMediaWebpage.swift */, + D00D343B1E6EC9770057B307 /* TelegramMediaGame.swift */, D07827CA1E02F5B200071108 /* RichText.swift */, D07827C81E02F59C00071108 /* InstantPage.swift */, ); @@ -911,6 +929,8 @@ D0BC38781E40BAF20044D6FE /* SynchronizePinnedChatsOperation.swift */, D0BC38761E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift */, D00DBBD91E64E67E00DB5485 /* UpdateSecretChat.swift */, + D00D34441E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift */, + D00D34411E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift */, ); name = State; sourceTree = ""; @@ -1357,12 +1377,15 @@ D03229F41E6B39700000AF9C /* ImportAccount.swift in Sources */, D021E0DF1DB539FC00C6B04F /* StickerPack.swift in Sources */, D03B0D091D62255C00955575 /* EnqueueMessage.swift in Sources */, + D00D343C1E6EC9770057B307 /* TelegramMediaGame.swift in Sources */, D033FEB01E61EB0200644997 /* PeerReportStatus.swift in Sources */, D050F2511E4A59C200988324 /* JoinLink.swift in Sources */, D07827C91E02F59C00071108 /* InstantPage.swift in Sources */, D0458C881E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift in Sources */, D07827CB1E02F5B200071108 /* RichText.swift in Sources */, D0613FD71E606B3B00202CDB /* ConvertGroupToSupergroup.swift in Sources */, + D00D34451E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */, + D00D343F1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift in Sources */, D03B0CE01D62249100955575 /* StoreMessage_Telegram.swift in Sources */, D08774FE1E3E3A3500A97350 /* GlobalNotificationSettings.swift in Sources */, D00C7CCF1E3628180080C3D5 /* UpdateCachedChannelParticipants.swift in Sources */, @@ -1420,6 +1443,7 @@ D00C7CE01E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */, D0B843811DA6EDAE005F29E1 /* CachedUserData.swift in Sources */, D049EAD51E43D98500A2CD3A /* RecentMediaItem.swift in Sources */, + D00D34421E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */, D03B0D0A1D62255C00955575 /* Holes.swift in Sources */, D0B843CB1DA7FF30005F29E1 /* NBPhoneNumberUtil.m in Sources */, D03B0D5E1D631A6900955575 /* Network.swift in Sources */, @@ -1490,6 +1514,7 @@ D0613FCA1E60440600202CDB /* InvitationLinks.swift in Sources */, D03B0D721D631ABA00955575 /* SearchMessages.swift in Sources */, D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */, + D00D34391E6EC9520057B307 /* TeleramMediaUnsupported.swift in Sources */, D00D97CA1E32917C00E5C2B6 /* PeerInputActivityManager.swift in Sources */, C2FD33E11E680E9E008D13D4 /* RequestUserPhotos.swift in Sources */, D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */, @@ -1563,12 +1588,15 @@ D03229F51E6B39700000AF9C /* ImportAccount.swift in Sources */, C239BE981E62F0D200C2C453 /* LoadMessagesIfNecessary.swift in Sources */, C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */, + D00D343D1E6EC9770057B307 /* TelegramMediaGame.swift in Sources */, D050F26A1E4A5B6D00988324 /* ManagedGlobalNotificationSettings.swift in Sources */, D050F26B1E4A5B6D00988324 /* ApplyMaxReadIndexInteractively.swift in Sources */, D033FEB11E61EB0200644997 /* PeerReportStatus.swift in Sources */, D0458C891E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift in Sources */, D050F26C1E4A5B6D00988324 /* UpdatePeers.swift in Sources */, D050F26D1E4A5B6D00988324 /* CreateGroup.swift in Sources */, + D00D34461E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */, + D00D34401E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift in Sources */, D050F26E1E4A5B6D00988324 /* RemovePeerChat.swift in Sources */, D0613FD81E606B3B00202CDB /* ConvertGroupToSupergroup.swift in Sources */, D01D6BFA1E42A718006151C6 /* SearchStickers.swift in Sources */, @@ -1626,6 +1654,7 @@ D0F3CC791DDE2859008148FA /* SearchMessages.swift in Sources */, D0B8442B1DAB91E0005F29E1 /* NBMetadataCore.m in Sources */, D00C7CD01E3628180080C3D5 /* UpdateCachedChannelParticipants.swift in Sources */, + D00D34431E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */, D049EAE91E44B67100A2CD3A /* RecentPeerItem.swift in Sources */, D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */, D0B8442D1DAB91E0005F29E1 /* NBMetadataCoreTest.m in Sources */, @@ -1696,6 +1725,7 @@ D0561DEB1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, D0613FCB1E60440600202CDB /* InvitationLinks.swift in Sources */, D0B844471DAB91FD005F29E1 /* ManagedServiceViews.swift in Sources */, + D00D343A1E6EC9520057B307 /* TeleramMediaUnsupported.swift in Sources */, D03C53691DAD5CA9004C17B3 /* PeerAccessRestrictionInfo.swift in Sources */, C2FD33E21E680E9E008D13D4 /* RequestUserPhotos.swift in Sources */, D0B8440E1DAB91CD005F29E1 /* MessageUtils.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index abe422b160..7cfb1555d9 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -209,6 +209,7 @@ private var declaredEncodables: Void = { declareEncodable(LoggedOutAccountAttribute.self, f: { LoggedOutAccountAttribute(decoder: $0) }) declareEncodable(CloudChatClearHistoryOperation.self, f: { CloudChatClearHistoryOperation(decoder: $0) }) declareEncodable(OutgoingContentInfoMessageAttribute.self, f: { OutgoingContentInfoMessageAttribute(decoder: $0) }) + declareEncodable(ConsumableContentMessageAttribute.self, f: { ConsumableContentMessageAttribute(decoder: $0) }) return }() diff --git a/TelegramCore/ConsumableContentMessageAttribute.swift b/TelegramCore/ConsumableContentMessageAttribute.swift new file mode 100644 index 0000000000..29e21c6dcd --- /dev/null +++ b/TelegramCore/ConsumableContentMessageAttribute.swift @@ -0,0 +1,22 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public class ConsumableContentMessageAttribute: MessageAttribute { + public let consumed: Bool + + public init(consumed: Bool) { + self.consumed = consumed + } + + required public init(decoder: Decoder) { + self.consumed = (decoder.decodeInt32ForKey("c") as Int32) != 0 + } + + public func encode(_ encoder: Encoder) { + encoder.encodeInt32(self.consumed ? 1 : 0, forKey: "c") + } +} diff --git a/TelegramCore/ManagedCloudChatRemoveMessagesOperations.swift b/TelegramCore/ManagedCloudChatRemoveMessagesOperations.swift index b9b33a338d..060ea91b4e 100644 --- a/TelegramCore/ManagedCloudChatRemoveMessagesOperations.swift +++ b/TelegramCore/ManagedCloudChatRemoveMessagesOperations.swift @@ -139,7 +139,13 @@ private func removeMessages(postbox: Postbox, network: Network, stateManager: Ac |> `catch` { _ in return .single(nil) } - |> mapToSignal { _ in + |> mapToSignal { result in + if let result = result { + switch result { + case let .affectedMessages(pts, ptsCount): + stateManager.addUpdateGroups([.updateChannelPts(channelId: peer.id.id, pts: pts, ptsCount: ptsCount)]) + } + } return .complete() } } else { diff --git a/TelegramCore/ManagedSynchronizeConsumeMessageContentsOperations.swift b/TelegramCore/ManagedSynchronizeConsumeMessageContentsOperations.swift new file mode 100644 index 0000000000..7595dc5ce9 --- /dev/null +++ b/TelegramCore/ManagedSynchronizeConsumeMessageContentsOperations.swift @@ -0,0 +1,135 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private final class ManagedSynchronizeConsumeMessageContentsOperationHelper { + 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, tagLocalIndex: Int32, _ f: @escaping (Modifier, PeerMergedOperationLogEntry?) -> Signal) -> Signal { + return postbox.modify { modifier -> Signal in + var result: PeerMergedOperationLogEntry? + modifier.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SynchronizeConsumeMessageContents, tagLocalIndex: tagLocalIndex, { entry in + if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeConsumeMessageContentsOperation { + result = entry.mergedEntry! + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } else { + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } + }) + + return f(modifier, result) + } |> switchToLatest +} + +func managedSynchronizeConsumeMessageContentOperations(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal { + return Signal { _ in + let helper = Atomic(value: ManagedSynchronizeConsumeMessageContentsOperationHelper()) + + let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SynchronizeConsumeMessageContents, 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, tagLocalIndex: entry.tagLocalIndex, { modifier, entry -> Signal in + if let entry = entry { + if let operation = entry.contents as? SynchronizeConsumeMessageContentsOperation { + return synchronizeConsumeMessageContents(network: network, stateManager: stateManager, peerId: entry.peerId, operation: operation) + } else { + assertionFailure() + } + } + return .complete() + }) + |> then(postbox.modify { modifier -> Void in + let _ = modifier.operationLogRemoveEntry(peerId: entry.peerId, tag: OperationLogTags.SynchronizeConsumeMessageContents, 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 synchronizeConsumeMessageContents(network: Network, stateManager: AccountStateManager, peerId: PeerId, operation: SynchronizeConsumeMessageContentsOperation) -> Signal { + if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup { + return network.request(Api.functions.messages.readMessageContents(id: operation.messageIds.map { $0.id })) + |> map { Optional($0) } + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + if let result = result { + switch result { + case let .affectedMessages(pts, ptsCount): + stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)]) + } + } + return .complete() + } + } else { + return .complete() + } +} diff --git a/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift b/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift index 4c7778cbfd..689856c602 100644 --- a/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift +++ b/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift @@ -123,7 +123,6 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ $0.namespace != Namespaces.Peer.SecretChat } - //messages.peerDialogs#3371c354 dialogs:Vector messages:Vector chats:Vector users:Vector state:updates.State = messages.PeerDialogs; return network.request(Api.functions.messages.getPinnedDialogs()) |> retryRequest |> mapToSignal { dialogs -> Signal in @@ -217,6 +216,9 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in return updated }) + + modifier.setPinnedPeerIds(resultingPeerIds) + modifier.updatePeerPresences(peerPresences) modifier.updatePeerNotificationSettings(notificationSettings) @@ -251,26 +253,9 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ } |> mapToSignal { result -> Signal in return postbox.modify { modifier -> Void in - modifier.setPinnedPeerIds(resultingPeerIds) } } } } |> switchToLatest } - - - /*var inputPeers: [Api.InputPeer] = [] - for peerId in peerIds { - if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) { - inputPeers.append(inputPeer) - } - } - - return network.request(Api.functions.messages.reorderPinnedDialogs(flags: 1 << 0, order: inputPeers)) - |> `catch` { _ -> Signal in - return .single(Api.Bool.boolFalse) - } - |> mapToSignal { result -> Signal in - return .complete() - }*/ } diff --git a/TelegramCore/MarkMessageContentAsConsumedInteractively.swift b/TelegramCore/MarkMessageContentAsConsumedInteractively.swift index 9756e0e787..05563e6405 100644 --- a/TelegramCore/MarkMessageContentAsConsumedInteractively.swift +++ b/TelegramCore/MarkMessageContentAsConsumedInteractively.swift @@ -10,37 +10,41 @@ import Foundation public func markMessageContentAsConsumedInteractively(postbox: Postbox, network: Network, messageId: MessageId) -> Signal { return postbox.modify { modifier -> Void in if let message = modifier.getMessage(messageId), message.flags.contains(.Incoming) { + var updateMessage = false + var updatedAttributes = message.attributes + + for i in 0 ..< updatedAttributes.count { + if let attribute = updatedAttributes[i] as? ConsumableContentMessageAttribute { + if !attribute.consumed { + updatedAttributes[i] = ConsumableContentMessageAttribute(consumed: true) + updateMessage = true + + addSynchronizeConsumeMessageContentsOperation(modifier: modifier, messageIds: [message.id]) + } + break + } + } + if messageId.peerId.namespace == Namespaces.Peer.SecretChat { let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - for attribute in message.attributes { - if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { + for i in 0 ..< updatedAttributes.count { + if let attribute = updatedAttributes[i] as? AutoremoveTimeoutMessageAttribute { if attribute.countdownBeginTime == nil && message.containsSecretMedia { - modifier.updateMessage(message.id, update: { currentMessage in - var storeForwardInfo: StoreMessageForwardInfo? - if let forwardInfo = currentMessage.forwardInfo { - storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date) - } - let updatedAttributes = currentMessage.attributes.map({ currentAttribute -> MessageAttribute in - if let currentAttribute = currentAttribute as? AutoremoveTimeoutMessageAttribute { - return AutoremoveTimeoutMessageAttribute(timeout: currentAttribute.timeout, countdownBeginTime: timestamp) - } else { - return currentAttribute - } - }) - return StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media) - }) + updatedAttributes[i] = AutoremoveTimeoutMessageAttribute(timeout: attribute.timeout, countdownBeginTime: timestamp) + updateMessage = true + modifier.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: messageId) var layer: SecretChatLayer? - var state = modifier.getPeerChatState(message.id.peerId) as? SecretChatState + let state = modifier.getPeerChatState(message.id.peerId) as? SecretChatState if let state = state { switch state.embeddedState { - case .terminated, .handshake: - break - case .basicLayer: - layer = .layer8 - case let .sequenceBasedLayer(sequenceState): - layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer) + case .terminated, .handshake: + break + case .basicLayer: + layer = .layer8 + case let .sequenceBasedLayer(sequenceState): + layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer) } } @@ -55,6 +59,16 @@ public func markMessageContentAsConsumedInteractively(postbox: Postbox, network: } } } + + if updateMessage { + modifier.updateMessage(message.id, update: { currentMessage in + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date) + } + return StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media) + }) + } } } } diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index 2ac5b50fe6..b8dcf10ebc 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -24,6 +24,7 @@ public struct Namespaces { public static let LocalFile: Int32 = 8 public static let CloudSecretImage: Int32 = 9 public static let CloudSecretFile: Int32 = 10 + public static let CloudGame: Int32 = 11 } public struct Peer { @@ -71,6 +72,7 @@ struct OperationLogTags { static let SynchronizePinnedCloudChats = PeerOperationLogTag(value: 4) static let AutoremoveMessages = PeerOperationLogTag(value: 5) static let SynchronizePinnedChats = PeerOperationLogTag(value: 6) + static let SynchronizeConsumeMessageContents = PeerOperationLogTag(value: 7) } private enum PreferencesKeyValues: Int32 { diff --git a/TelegramCore/StoreMessage_Telegram.swift b/TelegramCore/StoreMessage_Telegram.swift index 88e71565c9..af5222d355 100644 --- a/TelegramCore/StoreMessage_Telegram.swift +++ b/TelegramCore/StoreMessage_Telegram.swift @@ -229,11 +229,11 @@ func textAndMediaFromApiMedia(_ media: Api.MessageMedia?) -> (String?, Media?) { return (nil, mediaWebpage) } case .messageMediaUnsupported: - break + return (nil, TelegramMediaUnsupported()) case .messageMediaEmpty: break - case .messageMediaGame: - break + case let .messageMediaGame(game): + return (nil, TelegramMediaGame(apiGame: game)) } } @@ -345,6 +345,14 @@ extension StoreMessage { } } + if let file = media as? TelegramMediaFile { + if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup { + if file.isVoice { + attributes.append(ConsumableContentMessageAttribute(consumed: (flags & (1 << 5)) != 0)) + } + } + } + if let viaBotId = viaBotId { attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId))) } diff --git a/TelegramCore/SynchronizeConsumeMessageContentsOperation.swift b/TelegramCore/SynchronizeConsumeMessageContentsOperation.swift new file mode 100644 index 0000000000..29fe2a5a76 --- /dev/null +++ b/TelegramCore/SynchronizeConsumeMessageContentsOperation.swift @@ -0,0 +1,39 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +final class SynchronizeConsumeMessageContentsOperation: Coding { + let messageIds: [MessageId] + + init(messageIds: [MessageId]) { + self.messageIds = messageIds + } + + init(decoder: Decoder) { + self.messageIds = MessageId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("i")!) + } + + func encode(_ encoder: Encoder) { + let buffer = WriteBuffer() + MessageId.encodeArrayToBuffer(self.messageIds, buffer: buffer) + encoder.encodeBytes(buffer, forKey: "i") + } +} + +func addSynchronizeConsumeMessageContentsOperation(modifier: Modifier, messageIds: [MessageId]) { + for (peerId, messageIds) in messagesIdsGroupedByPeerId(Set(messageIds)) { + var updateLocalIndex: Int32? + /*modifier.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SynchronizeConsumeMessageContents, { entry in + updateLocalIndex = entry.tagLocalIndex + return false + })*/ + let operationContents = SynchronizeConsumeMessageContentsOperation(messageIds: messageIds) + if let updateLocalIndex = updateLocalIndex { + let _ = modifier.operationLogRemoveEntry(peerId: peerId, tag: OperationLogTags.SynchronizeConsumeMessageContents, tagLocalIndex: updateLocalIndex) + } + modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SynchronizeConsumeMessageContents, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: operationContents) + } +} diff --git a/TelegramCore/TelegramMediaAction.swift b/TelegramCore/TelegramMediaAction.swift index c395b72881..707dc41bf8 100644 --- a/TelegramCore/TelegramMediaAction.swift +++ b/TelegramCore/TelegramMediaAction.swift @@ -5,6 +5,13 @@ import Foundation import Postbox #endif +public enum PhoneCallDiscardReason: Int32 { + case missed = 0 + case disconnect = 1 + case hangup = 2 + case busy = 3 +} + public enum TelegramMediaActionType: Coding, Equatable { case unknown case groupCreated(title: String) @@ -19,6 +26,8 @@ public enum TelegramMediaActionType: Coding, Equatable { case historyCleared case historyScreenshot case messageAutoremoveTimeoutUpdated(Int32) + case gameScore(gameId: Int64, score: Int32) + case phoneCall(callId: Int64, discardReason: PhoneCallDiscardReason?, duration: Int32?) public init(decoder: Decoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue") @@ -47,6 +56,14 @@ public enum TelegramMediaActionType: Coding, Equatable { self = .historyScreenshot case 12: self = .messageAutoremoveTimeoutUpdated(decoder.decodeInt32ForKey("t")) + case 13: + self = .gameScore(gameId: decoder.decodeInt64ForKey("i"), score: decoder.decodeInt32ForKey("s")) + case 14: + var discardReason: PhoneCallDiscardReason? + if let value = (decoder.decodeInt32ForKey("dr") as Int32?) { + discardReason = PhoneCallDiscardReason(rawValue: value) + } + self = .phoneCall(callId: decoder.decodeInt64ForKey("i"), discardReason: discardReason, duration: decoder.decodeInt32ForKey("d")) default: self = .unknown } @@ -96,6 +113,23 @@ public enum TelegramMediaActionType: Coding, Equatable { case let .messageAutoremoveTimeoutUpdated(timeout): encoder.encodeInt32(12, forKey: "_rawValue") encoder.encodeInt32(timeout, forKey: "t") + case let .gameScore(gameId, score): + encoder.encodeInt32(13, forKey: "_rawValue") + encoder.encodeInt64(gameId, forKey: "i") + encoder.encodeInt32(score, forKey: "s") + case let .phoneCall(callId, discardReason, duration): + encoder.encodeInt32(14, forKey: "_rawValue") + encoder.encodeInt64(callId, forKey: "i") + if let discardReason = discardReason { + encoder.encodeInt32(discardReason.rawValue, forKey: "dr") + } else { + encoder.encodeNil(forKey: "dr") + } + if let duration = duration { + encoder.encodeInt32(duration, forKey: "d") + } else { + encoder.encodeNil(forKey: "d") + } } } @@ -197,6 +231,18 @@ public func ==(lhs: TelegramMediaActionType, rhs: TelegramMediaActionType) -> Bo } else { return false } + case let .gameScore(gameId, score): + if case .gameScore(gameId, score) = rhs { + return true + } else { + return false + } + case let .phoneCall(lhsCallId, lhsDiscardReason, lhsDuration): + if case let .phoneCall(rhsCallId, rhsDiscardReason, rhsDuration) = rhs, lhsCallId == rhsCallId && lhsDiscardReason == rhsDiscardReason && lhsDuration == rhsDuration { + return true + } else { + return false + } } return false } @@ -255,7 +301,30 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .historyCleared) case .messageActionPinMessage: return TelegramMediaAction(action: .pinnedMessageUpdated) - default: + case let .messageActionGameScore(gameId, score): + return TelegramMediaAction(action: .gameScore(gameId: gameId, score: score)) + case let .messageActionPhoneCall(_, callId, reason, duration): + var discardReason: PhoneCallDiscardReason? + if let reason = reason { + discardReason = PhoneCallDiscardReason(apiReason: reason) + } + return TelegramMediaAction(action: .phoneCall(callId: callId, discardReason: discardReason, duration: duration)) + case .messageActionEmpty: return nil } } + +extension PhoneCallDiscardReason { + init(apiReason: Api.PhoneCallDiscardReason) { + switch apiReason { + case .phoneCallDiscardReasonBusy: + self = .busy + case .phoneCallDiscardReasonDisconnect: + self = .disconnect + case .phoneCallDiscardReasonHangup: + self = .hangup + case .phoneCallDiscardReasonMissed: + self = .missed + } + } +} diff --git a/TelegramCore/TelegramMediaGame.swift b/TelegramCore/TelegramMediaGame.swift new file mode 100644 index 0000000000..3e2cb05409 --- /dev/null +++ b/TelegramCore/TelegramMediaGame.swift @@ -0,0 +1,116 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public final class TelegramMediaGame: Media { + public let gameId: Int64 + public let accessHash: Int64 + public let name: String + public let title: String + public let description: String + public let image: TelegramMediaImage? + public let file: TelegramMediaFile? + + public var id: MediaId? { + return MediaId(namespace: Namespaces.Media.CloudGame, id: self.gameId) + } + public let peerIds: [PeerId] = [] + + init(gameId: Int64, accessHash: Int64, name: String, title: String, description: String, image: TelegramMediaImage?, file: TelegramMediaFile?) { + self.gameId = gameId + self.accessHash = accessHash + self.name = name + self.title = title + self.description = description + self.image = image + self.file = file + } + + public init(decoder: Decoder) { + self.gameId = decoder.decodeInt64ForKey("i") + self.accessHash = decoder.decodeInt64ForKey("h") + self.name = decoder.decodeStringForKey("n") + self.title = decoder.decodeStringForKey("t") + self.description = decoder.decodeStringForKey("d") + self.image = decoder.decodeObjectForKey("p") as? TelegramMediaImage + self.file = decoder.decodeObjectForKey("f") as? TelegramMediaFile + } + + public func encode(_ encoder: Encoder) { + encoder.encodeInt64(self.gameId, forKey: "i") + encoder.encodeInt64(self.accessHash, forKey: "h") + encoder.encodeString(self.name, forKey: "n") + encoder.encodeString(self.title, forKey: "t") + encoder.encodeString(self.description, forKey: "d") + if let image = self.image { + encoder.encodeObject(image, forKey: "p") + } else { + encoder.encodeNil(forKey: "p") + } + if let file = self.file { + encoder.encodeObject(file, forKey: "f") + } else { + encoder.encodeNil(forKey: "f") + } + } + + public func isEqual(_ other: Media) -> Bool { + guard let other = other as? TelegramMediaGame else { + return false + } + + if self.gameId != other.gameId { + return false + } + + if self.accessHash != other.accessHash { + return false + } + + if self.name != other.name { + return false + } + + if self.title != other.title { + return false + } + + if self.description != other.description { + return false + } + + if let lhsImage = self.image, let rhsImage = other.image { + if !lhsImage.isEqual(rhsImage) { + return false + } + } else if (self.image != nil) != (other.image != nil) { + return false + } + + if let lhsFile = self.file, let rhsFile = other.file { + if !lhsFile.isEqual(rhsFile) { + return false + } + } else if (self.file != nil) != (other.file != nil) { + return false + } + + return true + } +} + +extension TelegramMediaGame { + convenience init(apiGame: Api.Game) { + switch apiGame { + case let .game(_, id, accessHash, shortName, title, description, photo, document): + var file: TelegramMediaFile? + if let document = document { + file = telegramMediaFileFromApiDocument(document) + } + self.init(gameId: id, accessHash: accessHash, name: shortName, title: title, description: description, image: telegramMediaImageFromApiPhoto(photo), file: file) + } + } +} diff --git a/TelegramCore/TeleramMediaUnsupported.swift b/TelegramCore/TeleramMediaUnsupported.swift new file mode 100644 index 0000000000..9779379905 --- /dev/null +++ b/TelegramCore/TeleramMediaUnsupported.swift @@ -0,0 +1,27 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public final class TelegramMediaUnsupported: Media { + public let id: MediaId? = nil + public let peerIds: [PeerId] = [] + + init() { + } + + public init(decoder: Decoder) { + } + + public func encode(_ encoder: Encoder) { + } + + public func isEqual(_ other: Media) -> Bool { + if other is TelegramMediaUnsupported { + return true + } + return false + } +} diff --git a/TelegramCore/UpdateGroup.swift b/TelegramCore/UpdateGroup.swift index e4d9fb801b..48ec066f0c 100644 --- a/TelegramCore/UpdateGroup.swift +++ b/TelegramCore/UpdateGroup.swift @@ -1,12 +1,13 @@ import Foundation -enum UpdateGroup: CustomStringConvertible { +enum UpdateGroup { case withPts(updates: [Api.Update], users: [Api.User], chats: [Api.Chat]) case withQts(updates: [Api.Update], users: [Api.User], chats: [Api.Chat]) case withSeq(updates: [Api.Update], seqRange: (Int32, Int32), date: Int32, users: [Api.User], chats: [Api.Chat]) case withDate(updates: [Api.Update], date: Int32, users: [Api.User], chats: [Api.Chat]) case reset case updatePts(pts: Int32, ptsCount: Int32) + case updateChannelPts(channelId: Int32, pts: Int32, ptsCount: Int32) var updates: [Api.Update] { switch self { @@ -18,7 +19,7 @@ enum UpdateGroup: CustomStringConvertible { return updates case let .withSeq(updates, _, _, _, _): return updates - case .reset, .updatePts: + case .reset, .updatePts, .updateChannelPts: return [] } } @@ -33,7 +34,7 @@ enum UpdateGroup: CustomStringConvertible { return users case let .withSeq(_, _, _, users, _): return users - case .reset, .updatePts: + case .reset, .updatePts, .updateChannelPts: return [] } } @@ -48,71 +49,10 @@ enum UpdateGroup: CustomStringConvertible { return chats case let .withSeq(_, _, _, _, chats): return chats - case .reset, .updatePts: + case .reset, .updatePts, .updateChannelPts: return [] } } - - var description: String { - var string = "" - - switch self { - case let .withPts(updates, _, _): - string += "withPts(" - var first = true - for update in updates { - if first { - first = false - } else { - string += ", " - } - string += "\(update)" - } - string += ")" - case let .withQts(updates, _, _): - string += "withQts(" - var first = true - for update in updates { - if first { - first = false - } else { - string += ", " - } - string += "\(update)" - } - string += ")" - case let .withSeq(updates, seqRange, date, _, _): - string += "withSeq(seq \(seqRange.0)..\(seqRange.1), date \(date), " - var first = true - for update in updates { - if first { - first = false - } else { - string += ", " - } - string += "\(update)" - } - string += ")" - case let .withDate(updates, date, _, _): - string += "withDate(date \(date), " - var first = true - for update in updates { - if first { - first = false - } else { - string += ", " - } - string += "\(update)" - } - string += ")" - case .reset: - string += "reset" - case .updatePts: - string += "updatePts" - } - - return string - } } extension Api.Update { @@ -142,10 +82,10 @@ extension Api.Update { var qtsRange: (Int32, Int32)? { get { switch self { - case let .updateNewEncryptedMessage(_, qts): - return (qts, 1) - case _: - return nil + case let .updateNewEncryptedMessage(_, qts): + return (qts, 1) + case _: + return nil } } }