diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 63080d5951..b537c02847 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -245,6 +245,10 @@ D0561DE41E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */; }; D0561DEA1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */; }; D0561DEB1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */; }; + D0575AF11E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */; }; + D0575AF21E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */; }; + D0575AF41E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */; }; + D0575AF51E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */; }; D058E0D11E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */; }; D058E0D21E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */; }; D05A32E11E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05A32E01E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift */; }; @@ -635,6 +639,8 @@ D05452061E7B5093006EEF19 /* LoadedStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedStickerPack.swift; sourceTree = ""; }; D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeerInfo.swift; sourceTree = ""; }; D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdmins.swift; sourceTree = ""; }; + D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedGifsOperation.swift; sourceTree = ""; }; + D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeSavedGifsOperations.swift; sourceTree = ""; }; D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandaloneSendMessage.swift; sourceTree = ""; }; D05A32E01E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatedAccountPrivacySettings.swift; sourceTree = ""; }; D05A32E31E6F0B2E002760B4 /* RecentAccountSessions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentAccountSessions.swift; sourceTree = ""; }; @@ -1029,6 +1035,8 @@ D0E23DD91E806F7700B9B6D2 /* ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift */, D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */, D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */, + D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */, + D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */, D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */, D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */, ); @@ -1619,6 +1627,7 @@ D03B0CD31D62244300955575 /* Namespaces.swift in Sources */, D01D6BF91E42A713006151C6 /* SearchStickers.swift in Sources */, D08F4A691E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */, + D0575AF11E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift in Sources */, D0FA8BB91E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift in Sources */, D0561DE31E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */, D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */, @@ -1682,6 +1691,7 @@ D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */, D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */, D033FEB31E61F3C000644997 /* ReportPeer.swift in Sources */, + D0575AF41E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */, D021E0E21DB5401A00C6B04F /* StickerManagement.swift in Sources */, D0BC38701E40853E0044D6FE /* UpdatePeers.swift in Sources */, D0F3A8A81E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */, @@ -1749,6 +1759,7 @@ D0458C891E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift in Sources */, D050F26C1E4A5B6D00988324 /* UpdatePeers.swift in Sources */, D050F26D1E4A5B6D00988324 /* CreateGroup.swift in Sources */, + D0575AF51E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */, D00D34461E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */, D00D34401E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift in Sources */, D050F26E1E4A5B6D00988324 /* RemovePeerChat.swift in Sources */, @@ -1929,6 +1940,7 @@ D0448CA61E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */, D001F3F11E128A1C007A8C60 /* SynchronizePeerReadState.swift in Sources */, D050F2641E4A5AEB00988324 /* ManagedSynchronizePinnedChatsOperations.swift in Sources */, + D0575AF21E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift in Sources */, D0528E661E65C82400E2FEF5 /* UpdateContactName.swift in Sources */, D0F7B1E81E045C87007EB8A5 /* PeerParticipants.swift in Sources */, D0C48F3A1E8138DF0075317D /* ArchivedStickerPacksInfo.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 27b24d4823..379eb05dab 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -218,6 +218,7 @@ private var declaredEncodables: Void = { declareEncodable(SynchronizeMarkFeaturedStickerPacksAsSeenOperation.self, f: { SynchronizeMarkFeaturedStickerPacksAsSeenOperation(decoder: $0) }) declareEncodable(ArchivedStickerPacksInfo.self, f: { ArchivedStickerPacksInfo(decoder: $0) }) declareEncodable(SynchronizeChatInputStateOperation.self, f: { SynchronizeChatInputStateOperation(decoder: $0) }) + declareEncodable(SynchronizeSavedGifsOperation.self, f: { SynchronizeSavedGifsOperation(decoder: $0) }) return }() @@ -570,7 +571,7 @@ public class Account { self.managedOperationsDisposable.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager, namespace: .masks).start()) self.managedOperationsDisposable.add(managedSynchronizeMarkFeaturedStickerPacksAsSeenOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedRecentStickers(postbox: self.postbox, network: self.network).start()) - self.managedOperationsDisposable.add(managedRecentGifs(postbox: self.postbox, network: self.network).start()) + self.managedOperationsDisposable.add(managedSynchronizeSavedGifsOperations(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()) diff --git a/TelegramCore/ApplyUpdateMessage.swift b/TelegramCore/ApplyUpdateMessage.swift index 514efe1396..633323d359 100644 --- a/TelegramCore/ApplyUpdateMessage.swift +++ b/TelegramCore/ApplyUpdateMessage.swift @@ -50,6 +50,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes } var sentStickers: [TelegramMediaFile] = [] + var sentGifs: [TelegramMediaFile] = [] modifier.updateMessage(message.id, update: { currentMessage in let updatedId: MessageId @@ -101,9 +102,15 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox) } - for media in media { - if let file = media as? TelegramMediaFile, file.isSticker { - sentStickers.append(file) + if storeForwardInfo == nil { + for media in media { + if let file = media as? TelegramMediaFile { + if file.isSticker { + sentStickers.append(file) + } else if file.isVideo && file.isAnimated { + sentGifs.append(file) + } + } } } @@ -123,6 +130,9 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes for file in sentStickers { modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) } + for file in sentGifs { + modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 200) + } stateManager.addUpdates(result) } } diff --git a/TelegramCore/Holes.swift b/TelegramCore/Holes.swift index ae11f3e677..f8ecc9adde 100644 --- a/TelegramCore/Holes.swift +++ b/TelegramCore/Holes.swift @@ -18,8 +18,8 @@ private func messageFilterForTagMask(_ tagMask: MessageTags) -> Api.MessagesFilt return Api.MessagesFilter.inputMessagesFilterMusic } else if tagMask == .WebPage { return Api.MessagesFilter.inputMessagesFilterUrl - } else if tagMask == .Voice { - return Api.MessagesFilter.inputMessagesFilterVoice + } else if tagMask == .VoiceOrInstantVideo { + return Api.MessagesFilter.inputMessagesFilterRoundVoice } else { return nil } diff --git a/TelegramCore/ManagedSynchronizeSavedGifsOperations.swift b/TelegramCore/ManagedSynchronizeSavedGifsOperations.swift new file mode 100644 index 0000000000..51df36be27 --- /dev/null +++ b/TelegramCore/ManagedSynchronizeSavedGifsOperations.swift @@ -0,0 +1,139 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private final class ManagedSynchronizeSavedGifsOperationsHelper { + 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 SynchronizeSavedGifsOperation { + result = entry.mergedEntry! + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } else { + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } + }) + + return f(modifier, result) + } |> switchToLatest +} + +func managedSynchronizeSavedGifsOperations(postbox: Postbox, network: Network) -> Signal { + return Signal { _ in + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeSavedGifs + + let helper = Atomic(value: ManagedSynchronizeSavedGifsOperationsHelper()) + + 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? SynchronizeSavedGifsOperation { + return synchronizeSavedGifs(modifier: modifier, postbox: postbox, network: network, 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 synchronizeSavedGifs(modifier: Modifier, postbox: Postbox, network: Network, operation: SynchronizeSavedGifsOperation) -> Signal { + switch operation.content { + case let .add(id, accessHash): + return network.request(Api.functions.messages.saveGif(id: .inputDocument(id: id, accessHash: accessHash), unsave: .boolFalse)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + case let .remove(id, accessHash): + return network.request(Api.functions.messages.saveGif(id: .inputDocument(id: id, accessHash: accessHash), unsave: .boolTrue)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + case .sync: + return managedRecentGifs(postbox: postbox, network: network) + } +} diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index af48eb69ac..c51c6f0999 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -60,10 +60,10 @@ public extension MessageTags { static let File = MessageTags(rawValue: 1 << 1) static let Music = MessageTags(rawValue: 1 << 2) static let WebPage = MessageTags(rawValue: 1 << 3) - static let Voice = MessageTags(rawValue: 1 << 4) + static let VoiceOrInstantVideo = MessageTags(rawValue: 1 << 4) } -let allMessageTags: MessageTags = [.PhotoOrVideo, .File, .Music, .WebPage, .Voice] +let allMessageTags: MessageTags = [.PhotoOrVideo, .File, .Music, .WebPage, .VoiceOrInstantVideo] let peerIdNamespacesWithInitialCloudMessageHoles = [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel] struct OperationLogTags { @@ -79,6 +79,7 @@ struct OperationLogTags { static let SynchronizeInstalledMasks = PeerOperationLogTag(value: 9) static let SynchronizeMarkFeaturedStickerPacksAsSeen = PeerOperationLogTag(value: 10) static let SynchronizeChatInputStates = PeerOperationLogTag(value: 11) + static let SynchronizeSavedGifs = PeerOperationLogTag(value: 12) } private enum PreferencesKeyValues: Int32 { diff --git a/TelegramCore/RecentlySearchedPeerIds.swift b/TelegramCore/RecentlySearchedPeerIds.swift index e27858c9c3..f457cb8bfb 100644 --- a/TelegramCore/RecentlySearchedPeerIds.swift +++ b/TelegramCore/RecentlySearchedPeerIds.swift @@ -13,6 +13,12 @@ public func addRecentlySearchedPeer(postbox: Postbox, peerId: PeerId) -> Signal< } } +public func removeRecentlySearchedPeer(postbox: Postbox, peerId: PeerId) -> Signal { + return postbox.modify { modifier -> Void in + modifier.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, itemId: RecentPeerItemId(peerId).rawValue) + } +} + public func recentlySearchedPeers(postbox: Postbox) -> Signal<[Peer], NoError> { return postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.RecentlySearchedPeerIds)]) |> take(1) diff --git a/TelegramCore/StickerManagement.swift b/TelegramCore/StickerManagement.swift index 9a470e154d..fdbc567c6b 100644 --- a/TelegramCore/StickerManagement.swift +++ b/TelegramCore/StickerManagement.swift @@ -25,6 +25,7 @@ func manageStickerPacks(network: Network, postbox: Postbox) -> Signal Void in addSynchronizeInstalledStickerPacksOperation(modifier: modifier, namespace: .stickers) addSynchronizeInstalledStickerPacksOperation(modifier: modifier, namespace: .masks) + addSynchronizeSavedGifsOperation(modifier: modifier, operation: .sync) } |> then(.complete() |> delay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } diff --git a/TelegramCore/StoreMessage_Telegram.swift b/TelegramCore/StoreMessage_Telegram.swift index 0f4c86bfba..08d3b6233a 100644 --- a/TelegramCore/StoreMessage_Telegram.swift +++ b/TelegramCore/StoreMessage_Telegram.swift @@ -9,17 +9,33 @@ public func tagsForStoreMessage(media: [Media], textEntities: [MessageTextEntity var tags = MessageTags() for attachment in media { if let _ = attachment as? TelegramMediaImage { - let _ = tags.insert(.PhotoOrVideo) + tags.insert(.PhotoOrVideo) } else if let file = attachment as? TelegramMediaFile { - if file.isSticker || file.isAnimated { - } else if file.isVideo { - let _ = tags.insert(.PhotoOrVideo) - } else if file.isVoice { - let _ = tags.insert(.Voice) - } else if file.isMusic { - let _ = tags.insert(.Music) + var refinedTag: MessageTags? + inner: for attribute in file.attributes { + switch attribute { + case let .Video(_, _, flags): + if flags.contains(.instantRoundVideo) { + refinedTag = .VoiceOrInstantVideo + } else { + refinedTag = .PhotoOrVideo + } + break inner + case let .Audio(isVoice, _, _, _, _): + if isVoice { + refinedTag = .VoiceOrInstantVideo + } else { + refinedTag = .Music + } + break inner + default: + break + } + } + if let refinedTag = refinedTag { + tags.insert(refinedTag) } else { - let _ = tags.insert(.File) + tags.insert(.File) } } else if let webpage = attachment as? TelegramMediaWebpage, case .Loaded = webpage.content { tags.insert(.WebPage) diff --git a/TelegramCore/SynchronizeSavedGifsOperation.swift b/TelegramCore/SynchronizeSavedGifsOperation.swift new file mode 100644 index 0000000000..8b7ff454a2 --- /dev/null +++ b/TelegramCore/SynchronizeSavedGifsOperation.swift @@ -0,0 +1,105 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac +#else + import Postbox + import SwiftSignalKit +#endif + +private enum SynchronizeSavedGifsOperationContentType: Int32 { + case add + case remove + case sync +} + +enum SynchronizeSavedGifsOperationContent: Coding { + case add(id: Int64, accessHash: Int64) + case remove(id: Int64, accessHash: Int64) + case sync + + init(decoder: Decoder) { + switch decoder.decodeInt32ForKey("r") as Int32 { + case SynchronizeSavedGifsOperationContentType.add.rawValue: + self = .add(id: decoder.decodeInt64ForKey("i"), accessHash: decoder.decodeInt64ForKey("h")) + case SynchronizeSavedGifsOperationContentType.remove.rawValue: + self = .remove(id: decoder.decodeInt64ForKey("i"), accessHash: decoder.decodeInt64ForKey("h")) + case SynchronizeSavedGifsOperationContentType.sync.rawValue: + self = .sync + default: + assertionFailure() + self = .sync + } + } + + func encode(_ encoder: Encoder) { + switch self { + case let .add(id, accessHash): + encoder.encodeInt32(SynchronizeSavedGifsOperationContentType.add.rawValue, forKey: "r") + encoder.encodeInt64(id, forKey: "i") + encoder.encodeInt64(accessHash, forKey: "h") + case let .remove(id, accessHash): + encoder.encodeInt32(SynchronizeSavedGifsOperationContentType.remove.rawValue, forKey: "r") + encoder.encodeInt64(id, forKey: "i") + encoder.encodeInt64(accessHash, forKey: "h") + case .sync: + encoder.encodeInt32(SynchronizeSavedGifsOperationContentType.sync.rawValue, forKey: "r") + } + } +} + +final class SynchronizeSavedGifsOperation: Coding { + let content: SynchronizeSavedGifsOperationContent + + init(content: SynchronizeSavedGifsOperationContent) { + self.content = content + } + + init(decoder: Decoder) { + self.content = decoder.decodeObjectForKey("c", decoder: { SynchronizeSavedGifsOperationContent(decoder: $0) }) as! SynchronizeSavedGifsOperationContent + } + + func encode(_ encoder: Encoder) { + encoder.encodeObject(self.content, forKey: "c") + } +} + +func addSynchronizeSavedGifsOperation(modifier: Modifier, operation: SynchronizeSavedGifsOperationContent) { + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeSavedGifs + let peerId = PeerId(namespace: 0, id: 0) + + var topOperation: (SynchronizeSavedGifsOperation, Int32)? + modifier.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in + if let operation = entry.contents as? SynchronizeSavedGifsOperation { + topOperation = (operation, entry.tagLocalIndex) + } + return false + }) + + if let (topOperation, topLocalIndex) = topOperation, case .sync = topOperation.content { + let _ = modifier.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: topLocalIndex) + } + + modifier.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeSavedGifsOperation(content: operation)) + modifier.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeSavedGifsOperation(content: .sync)) +} + +public func addSavedGif(postbox: Postbox, file: TelegramMediaFile) -> Signal { + return postbox.modify { modifier -> Void in + if let resource = file.resource as? CloudDocumentMediaResource { + modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 200) + addSynchronizeSavedGifsOperation(modifier: modifier, operation: .add(id: resource.fileId, accessHash: resource.accessHash)) + } + } +} + +public func removeSavedGif(postbox: Postbox, mediaId: MediaId) -> Signal { + return postbox.modify { modifier -> Void in + if let entry = modifier.getOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, itemId: RecentMediaItemId(mediaId).rawValue), let item = entry.contents as? RecentMediaItem { + if let file = item.media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { + modifier.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, itemId: entry.id) + addSynchronizeSavedGifsOperation(modifier: modifier, operation: .remove(id: resource.fileId, accessHash: resource.accessHash)) + } + } + } +}