import Foundation import Postbox import SwiftSignalKit import TelegramApi import MtProtoKit import SyncCore private typealias SignalKitTimer = SwiftSignalKit.Timer private final class ManagedAutoremoveMessageOperationsHelper { var entry: (TimestampBasedMessageAttributesEntry, MetaDisposable)? func update(_ head: TimestampBasedMessageAttributesEntry?) -> (disposeOperations: [Disposable], beginOperations: [(TimestampBasedMessageAttributesEntry, MetaDisposable)]) { var disposeOperations: [Disposable] = [] var beginOperations: [(TimestampBasedMessageAttributesEntry, MetaDisposable)] = [] if self.entry?.0.index != head?.index { if let (_, disposable) = self.entry { self.entry = nil disposeOperations.append(disposable) } if let head = head { let disposable = MetaDisposable() self.entry = (head, disposable) beginOperations.append((head, disposable)) } } return (disposeOperations, beginOperations) } func reset() -> [Disposable] { if let entry = entry { return [entry.1] } else { return [] } } } func managedAutoremoveMessageOperations(network: Network, postbox: Postbox) -> Signal { return Signal { _ in let helper = Atomic(value: ManagedAutoremoveMessageOperationsHelper()) let timeOffsetOnce = Signal { subscriber in subscriber.putNext(network.globalTimeDifference) return EmptyDisposable } let timeOffset = ( timeOffsetOnce |> then( Signal.complete() |> delay(1.0, queue: .mainQueue()) ) ) |> restart |> map { value -> Double in round(value) } |> distinctUntilChanged let disposable = combineLatest(timeOffset, postbox.timestampBasedMessageAttributesView(tag: 0)).start(next: { timeOffset, view in let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(TimestampBasedMessageAttributesEntry, MetaDisposable)]) in return helper.update(view.head) } for disposable in disposeOperations { disposable.dispose() } for (entry, disposable) in beginOperations { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + timeOffset let signal = Signal.complete() |> suspendAwareDelay(max(0.0, Double(entry.timestamp) - timestamp), queue: Queue.concurrentDefaultQueue()) |> then(postbox.transaction { transaction -> Void in if let message = transaction.getMessage(entry.messageId) { var action: AutoremoveTimeoutMessageAttribute.Action = .remove for attribute in message.attributes { if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { action = attribute.action } } if message.id.peerId.namespace == Namespaces.Peer.SecretChat || action == .remove { deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [entry.messageId]) } else { transaction.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, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) } var updatedMedia = currentMessage.media for i in 0 ..< updatedMedia.count { if let _ = updatedMedia[i] as? TelegramMediaImage { updatedMedia[i] = TelegramMediaExpiredContent(data: .image) } else if let _ = updatedMedia[i] as? TelegramMediaFile { updatedMedia[i] = TelegramMediaExpiredContent(data: .file) } } var updatedAttributes = currentMessage.attributes for i in 0 ..< updatedAttributes.count { if let _ = updatedAttributes[i] as? AutoremoveTimeoutMessageAttribute { updatedAttributes.remove(at: i) break } } return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: updatedMedia)) }) } } }) disposable.set(signal.start()) } }) return ActionDisposable { disposable.dispose() let disposables = helper.with { helper -> [Disposable] in return helper.reset() } for disposable in disposables { disposable.dispose() } } } }