import Foundation #if os(macOS) import PostboxMac import SwiftSignalKitMac import MtProtoKitMac #else import Postbox import SwiftSignalKit import MtProtoKitDynamic #endif private func messageFilterForTagMask(_ tagMask: MessageTags) -> Api.MessagesFilter? { if tagMask == .PhotoOrVideo { return Api.MessagesFilter.inputMessagesFilterPhotoVideo } else if tagMask == .File { return Api.MessagesFilter.inputMessagesFilterDocument } else if tagMask == .Music { return Api.MessagesFilter.inputMessagesFilterMusic } else if tagMask == .WebPage { return Api.MessagesFilter.inputMessagesFilterUrl } else if tagMask == .VoiceOrInstantVideo { return Api.MessagesFilter.inputMessagesFilterRoundVoice } else { return nil } } func fetchMessageHistoryHole(network: Network, postbox: Postbox, hole: MessageHistoryHole, direction: MessageHistoryViewRelativeHoleDirection, tagMask: MessageTags?) -> Signal { return postbox.loadedPeerWithId(hole.maxIndex.id.peerId) |> take(1) //|> delay(4.0, queue: Queue.concurrentDefaultQueue()) |> mapToSignal { peer in if let inputPeer = apiInputPeer(peer) { let limit = 100 let request: Signal if let tagMask = tagMask, let filter = messageFilterForTagMask(tagMask) { switch direction { case .UpperToLower: break case .LowerToUpper: assertionFailure(".LowerToUpper not supported") case .AroundIndex: assertionFailure(".AroundIndex not supported") } //request = network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", filter: filter, minDate: 0, maxDate: hole.maxIndex.timestamp, offset: 0, maxId: hole.maxIndex.id.id + 1, limit: Int32(limit))) request = network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", filter: filter, minDate: 0, maxDate: hole.maxIndex.timestamp, offset: 0, maxId: Int32.max, limit: Int32(limit))) } else { let offsetId: Int32 let addOffset: Int32 let selectedLimit = limit let maxId: Int32 let minId: Int32 switch direction { case .UpperToLower: offsetId = hole.maxIndex.id.id == Int32.max ? hole.maxIndex.id.id : (hole.maxIndex.id.id + 1) addOffset = 0 maxId = hole.maxIndex.id.id == Int32.max ? hole.maxIndex.id.id : (hole.maxIndex.id.id + 1) minId = 1 case .LowerToUpper: offsetId = hole.min <= 1 ? 1 : (hole.min - 1) addOffset = Int32(-limit) maxId = Int32.max minId = hole.min - 1 case let .AroundIndex(index): offsetId = index.id.id addOffset = Int32(-limit / 2) maxId = Int32.max minId = 1 } //request = network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: hole.maxIndex.timestamp, addOffset: addOffset, limit: Int32(selectedLimit), maxId: hole.maxIndex.id.id == Int32.max ? hole.maxIndex.id.id : (hole.maxIndex.id.id + 1), minId: hole.min - 1)) request = network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: hole.maxIndex.timestamp, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) } return request |> retryRequest |> mapToSignal { result in let messages: [Api.Message] let chats: [Api.Chat] let users: [Api.User] switch result { case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers): messages = apiMessages chats = apiChats users = apiUsers case let .messagesSlice(_, messages: apiMessages, chats: apiChats, users: apiUsers): messages = apiMessages chats = apiChats users = apiUsers case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers): messages = apiMessages chats = apiChats users = apiUsers } return postbox.modify { modifier in var storeMessages: [StoreMessage] = [] for message in messages { if let storeMessage = StoreMessage(apiMessage: message) { storeMessages.append(storeMessage) } } let fillDirection: HoleFillDirection switch direction { case .UpperToLower: fillDirection = .UpperToLower case .LowerToUpper: fillDirection = .LowerToUpper case let .AroundIndex(index): fillDirection = .AroundIndex(index, lowerComplete: false, upperComplete: false) } modifier.fillMultipleHoles(hole, fillType: HoleFill(complete: messages.count == 0, direction: fillDirection), tagMask: tagMask, messages: storeMessages) var peers: [Peer] = [] var peerPresences: [PeerId: PeerPresence] = [:] for chat in chats { if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { peers.append(groupOrChannel) } } for user in users { let telegramUser = TelegramUser(user: user) peers.append(telegramUser) if let presence = TelegramUserPresence(apiUser: user) { peerPresences[telegramUser.id] = presence } } updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in return updated }) modifier.updatePeerPresences(peerPresences) return } } } else { return fail(Void.self, NoError()) } } } func fetchChatListHole(network: Network, postbox: Postbox, hole: ChatListHole) -> Signal { let offset: Signal<(Int32, Int32, Api.InputPeer), NoError> if hole.index.id.peerId.namespace == Namespaces.Peer.Empty { offset = single((0, 0, Api.InputPeer.inputPeerEmpty), NoError.self) } else { offset = postbox.loadedPeerWithId(hole.index.id.peerId) |> take(1) |> map { peer in return (hole.index.timestamp, hole.index.id.id + 1, apiInputPeer(peer) ?? .inputPeerEmpty) } } return offset |> mapToSignal { (timestamp, id, peer) in let pinnedChats: Signal if case .inputPeerEmpty = peer, timestamp == 0 { pinnedChats = network.request(Api.functions.messages.getPinnedDialogs()) |> retryRequest |> map { Optional($0) } } else { pinnedChats = .single(nil) } return combineLatest(network.request(Api.functions.messages.getDialogs(flags: 0, offsetDate: timestamp, offsetId: id, offsetPeer: peer, limit: 100)) |> retryRequest, pinnedChats) |> mapToSignal { result, pinnedChats -> Signal in var dialogsChats: [Api.Chat] = [] var dialogsUsers: [Api.User] = [] var replacementHole: ChatListHole? var storeMessages: [StoreMessage] = [] var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] var chatStates: [PeerId: PeerChatState] = [:] var notificationSettings: [PeerId: PeerNotificationSettings] = [:] switch result { case let .dialogs(dialogs, messages, chats, users): dialogsChats.append(contentsOf: chats) dialogsUsers.append(contentsOf: users) for dialog in dialogs { let apiPeer: Api.Peer let apiReadInboxMaxId: Int32 let apiReadOutboxMaxId: Int32 let apiTopMessage: Int32 let apiUnreadCount: Int32 var apiChannelPts: Int32? let apiNotificationSettings: Api.PeerNotifySettings switch dialog { case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, peerNotificationSettings, pts, _): apiPeer = peer apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId apiReadOutboxMaxId = readOutboxMaxId apiUnreadCount = unreadCount apiNotificationSettings = peerNotificationSettings apiChannelPts = pts } let peerId: PeerId switch apiPeer { case let .peerUser(userId): peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) case let .peerChat(chatId): peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) case let .peerChannel(channelId): peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) } if readStates[peerId] == nil { readStates[peerId] = [:] } readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) if let apiChannelPts = apiChannelPts { chatStates[peerId] = ChannelState(pts: apiChannelPts) } notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) } for message in messages { if let storeMessage = StoreMessage(apiMessage: message) { storeMessages.append(storeMessage) } } case let .dialogsSlice(_, dialogs, messages, chats, users): for message in messages { if let storeMessage = StoreMessage(apiMessage: message) { storeMessages.append(storeMessage) } } dialogsChats.append(contentsOf: chats) dialogsUsers.append(contentsOf: users) for dialog in dialogs { let apiPeer: Api.Peer let apiTopMessage: Int32 let apiReadInboxMaxId: Int32 let apiReadOutboxMaxId: Int32 let apiUnreadCount: Int32 let apiNotificationSettings: Api.PeerNotifySettings let isPinned: Bool switch dialog { case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, peerNotificationSettings, _, _): isPinned = (flags & (1 << 2)) != 0 apiPeer = peer apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId apiReadOutboxMaxId = readOutboxMaxId apiUnreadCount = unreadCount apiNotificationSettings = peerNotificationSettings } let peerId: PeerId switch apiPeer { case let .peerUser(userId): peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) case let .peerChat(chatId): peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) case let .peerChannel(channelId): peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) } if readStates[peerId] == nil { readStates[peerId] = [:] } readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) let topMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: apiTopMessage) var timestamp: Int32? for message in storeMessages { if case let .Id(id) = message.id, id == topMessageId { timestamp = message.timestamp } } if let timestamp = timestamp { let index = MessageIndex(id: MessageId(peerId: topMessageId.peerId, namespace: topMessageId.namespace, id: topMessageId.id - 1), timestamp: timestamp) if !isPinned && (replacementHole == nil || replacementHole!.index > index) { replacementHole = ChatListHole(index: index) } } } } var replacePinnedPeerIds: [PeerId]? if let pinnedChats = pinnedChats { switch pinnedChats { case let .peerDialogs(apiDialogs, apiMessages, apiChats, apiUsers, _): dialogsChats.append(contentsOf: apiChats) dialogsUsers.append(contentsOf: apiUsers) var peerIds: [PeerId] = [] for dialog in apiDialogs { let apiPeer: Api.Peer let apiReadInboxMaxId: Int32 let apiReadOutboxMaxId: Int32 let apiTopMessage: Int32 let apiUnreadCount: Int32 var apiChannelPts: Int32? let apiNotificationSettings: Api.PeerNotifySettings switch dialog { case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, peerNotificationSettings, pts, _): apiPeer = peer apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId apiReadOutboxMaxId = readOutboxMaxId apiUnreadCount = unreadCount apiNotificationSettings = peerNotificationSettings apiChannelPts = pts } let peerId: PeerId switch apiPeer { case let .peerUser(userId): peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) case let .peerChat(chatId): peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) case let .peerChannel(channelId): peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) } peerIds.append(peerId) if readStates[peerId] == nil { readStates[peerId] = [:] } readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) if let apiChannelPts = apiChannelPts { chatStates[peerId] = ChannelState(pts: apiChannelPts) } notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) } replacePinnedPeerIds = peerIds for message in apiMessages { if let storeMessage = StoreMessage(apiMessage: message) { storeMessages.append(storeMessage) } } } } var peers: [Peer] = [] var peerPresences: [PeerId: PeerPresence] = [:] for chat in dialogsChats { if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { peers.append(groupOrChannel) } } for user in dialogsUsers { let telegramUser = TelegramUser(user: user) peers.append(telegramUser) if let presence = TelegramUserPresence(apiUser: user) { peerPresences[telegramUser.id] = presence } } return postbox.modify { modifier in updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in return updated }) modifier.updatePeerPresences(peerPresences) modifier.updatePeerNotificationSettings(notificationSettings) var allPeersWithMessages = Set() for message in storeMessages { if !allPeersWithMessages.contains(message.id.peerId) { allPeersWithMessages.insert(message.id.peerId) } } let _ = modifier.addMessages(storeMessages, location: .UpperHistoryBlock) modifier.replaceChatListHole(hole.index, hole: replacementHole) modifier.resetIncomingReadStates(readStates) for (peerId, chatState) in chatStates { modifier.setPeerChatState(peerId, state: chatState) } if let replacePinnedPeerIds = replacePinnedPeerIds { modifier.setPinnedPeerIds(replacePinnedPeerIds) } } } } } func fetchCallListHole(network: Network, postbox: Postbox, holeIndex: MessageIndex, limit: Int32 = 100) -> Signal { let offset: Signal<(Int32, Int32, Api.InputPeer), NoError> if holeIndex.id.peerId.namespace == Namespaces.Peer.Empty { offset = single((0, 0, Api.InputPeer.inputPeerEmpty), NoError.self) } else { offset = postbox.loadedPeerWithId(holeIndex.id.peerId) |> take(1) |> map { peer in return (holeIndex.timestamp, holeIndex.id.id + 1, apiInputPeer(peer) ?? .inputPeerEmpty) } } return offset |> mapToSignal { (timestamp, id, peer) -> Signal in let searchResult = network.request(Api.functions.messages.search(flags: 0, peer: peer, q: "", filter: .inputMessagesFilterPhoneCalls(flags: 0), minDate: 0, maxDate: holeIndex.timestamp, offset: 0, maxId: holeIndex.id.id, limit: limit)) |> retryRequest |> mapToSignal { result -> Signal in let messages: [Api.Message] let chats: [Api.Chat] let users: [Api.User] switch result { case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers): messages = apiMessages chats = apiChats users = apiUsers case let .messagesSlice(_, messages: apiMessages, chats: apiChats, users: apiUsers): messages = apiMessages chats = apiChats users = apiUsers case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers): messages = apiMessages chats = apiChats users = apiUsers } return postbox.modify { modifier -> Void in var storeMessages: [StoreMessage] = [] var topIndex: MessageIndex? for message in messages { if let storeMessage = StoreMessage(apiMessage: message) { storeMessages.append(storeMessage) if let index = storeMessage.index, topIndex == nil || index < topIndex! { topIndex = index } } } var updatedIndex: MessageIndex? if let topIndex = topIndex { updatedIndex = topIndex.predecessor() } modifier.replaceGlobalMessageTagsHole(globalTags: [.Calls, .MissedCalls], index: holeIndex, with: updatedIndex, messages: storeMessages) var peers: [Peer] = [] var peerPresences: [PeerId: PeerPresence] = [:] for chat in chats { if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { peers.append(groupOrChannel) } } for user in users { let telegramUser = TelegramUser(user: user) peers.append(telegramUser) if let presence = TelegramUserPresence(apiUser: user) { peerPresences[telegramUser.id] = presence } } updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in return updated }) modifier.updatePeerPresences(peerPresences) } } return searchResult } }