mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-04-12 16:01:33 +00:00
Fixes
fix localeWithStrings globally (#30)
Fix badge on zoomed devices. closes #9
Hide channel bottom panel closes #27
Another attempt to fix badge on some Zoomed devices
Force System Share sheet tg://sg/debug
fixes for device badge
New Crowdin updates (#34)
* New translations sglocalizable.strings (Chinese Traditional)
* New translations sglocalizable.strings (Chinese Simplified)
* New translations sglocalizable.strings (Chinese Traditional)
Fix input panel hidden on selection (#31)
* added if check for selectionState != nil
* same order of subnodes
Revert "Fix input panel hidden on selection (#31)"
This reverts commit e8a8bb1496.
Fix input panel for channels Closes #37
Quickly share links with system's share menu
force tabbar when editing
increase height for correct animation
New translations sglocalizable.strings (Ukrainian) (#38)
Hide Post Story button
Fix 10.15.1
Fix archive option for long-tap
Enable in-app Safari
Disable some unsupported purchases
disableDeleteChatSwipeOption + refactor restart alert
Hide bot in suggestions list
Fix merge v11.0
Fix exceptions for safari webview controller
New Crowdin updates (#47)
* New translations sglocalizable.strings (Romanian)
* New translations sglocalizable.strings (French)
* New translations sglocalizable.strings (Spanish)
* New translations sglocalizable.strings (Afrikaans)
* New translations sglocalizable.strings (Arabic)
* New translations sglocalizable.strings (Catalan)
* New translations sglocalizable.strings (Czech)
* New translations sglocalizable.strings (Danish)
* New translations sglocalizable.strings (German)
* New translations sglocalizable.strings (Greek)
* New translations sglocalizable.strings (Finnish)
* New translations sglocalizable.strings (Hebrew)
* New translations sglocalizable.strings (Hungarian)
* New translations sglocalizable.strings (Italian)
* New translations sglocalizable.strings (Japanese)
* New translations sglocalizable.strings (Korean)
* New translations sglocalizable.strings (Dutch)
* New translations sglocalizable.strings (Norwegian)
* New translations sglocalizable.strings (Polish)
* New translations sglocalizable.strings (Portuguese)
* New translations sglocalizable.strings (Serbian (Cyrillic))
* New translations sglocalizable.strings (Swedish)
* New translations sglocalizable.strings (Turkish)
* New translations sglocalizable.strings (Vietnamese)
* New translations sglocalizable.strings (Indonesian)
* New translations sglocalizable.strings (Hindi)
* New translations sglocalizable.strings (Uzbek)
New Crowdin updates (#49)
* New translations sglocalizable.strings (Arabic)
* New translations sglocalizable.strings (Arabic)
New translations sglocalizable.strings (Russian) (#51)
Call confirmation
WIP Settings search
Settings Search
Localize placeholder
Update AccountUtils.swift
mark mutual contact
Align back context action to left
New Crowdin updates (#54)
* New translations sglocalizable.strings (Chinese Simplified)
* New translations sglocalizable.strings (Chinese Traditional)
* New translations sglocalizable.strings (Ukrainian)
Independent Playground app for simulator
New translations sglocalizable.strings (Ukrainian) (#55)
Playground UIKit base and controllers
Inject SwiftUI view with overflow to AsyncDisplayKit
Launch Playgound project on simulator
Create .swiftformat
Move Playground to example
Update .swiftformat
Init SwiftUIViewController
wip
New translations sglocalizable.strings (Chinese Traditional) (#57)
Xcode 16 fixes
Fix
New translations sglocalizable.strings (Italian) (#59)
New translations sglocalizable.strings (Chinese Simplified) (#63)
Force disable CallKit integration due to missing NSE Entitlement
Fix merge
Fix whole chat translator
Sweetpad config
Bump version
11.3.1 fixes
Mutual contact placement fix
Disable Video PIP swipe
Update versions.json
Fix PIP crash
5020 lines
256 KiB
Swift
5020 lines
256 KiB
Swift
import Foundation
|
|
|
|
import SwiftSignalKit
|
|
|
|
public protocol PostboxTypes {
|
|
associatedtype Media
|
|
}
|
|
|
|
public protocol PeerChatState: PostboxCoding {
|
|
func equals(_ other: PeerChatState) -> Bool
|
|
}
|
|
|
|
public enum PostboxUpdateMessage {
|
|
case update(StoreMessage)
|
|
case skip
|
|
}
|
|
|
|
public protocol StoreOrUpdateMessageAction: AnyObject {
|
|
func addOrUpdate(messages: [StoreMessage], transaction: Transaction)
|
|
}
|
|
|
|
public final class Transaction {
|
|
private let queue: Queue
|
|
private weak var postbox: PostboxImpl?
|
|
var disposed = false
|
|
|
|
fileprivate init(queue: Queue, postbox: PostboxImpl) {
|
|
assert(queue.isCurrent())
|
|
|
|
self.queue = queue
|
|
self.postbox = postbox
|
|
}
|
|
|
|
public func keychainEntryForKey(_ key: String) -> Data? {
|
|
assert(self.queue.isCurrent())
|
|
|
|
assert(!self.disposed)
|
|
return self.postbox?.keychainTable.get(key)
|
|
}
|
|
|
|
public func setKeychainEntry(_ value: Data, forKey key: String) {
|
|
assert(!self.disposed)
|
|
self.postbox?.keychainTable.set(key, value: value)
|
|
}
|
|
|
|
public func addMessages(_ messages: [StoreMessage], location: AddMessagesLocation) -> [Int64: MessageId] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.addMessages(transaction: self, messages: messages, location: location)
|
|
} else {
|
|
return [:]
|
|
}
|
|
}
|
|
|
|
public func messageExists(id: MessageId) -> Bool {
|
|
if let postbox = self.postbox {
|
|
return postbox.messageHistoryIndexTable.exists(id)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
public func countIncomingMessage(id: MessageId) {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
postbox.countIncomingMessage(id: id)
|
|
}
|
|
}
|
|
|
|
public func addHole(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleOperationSpace, range: ClosedRange<MessageId.Id>) {
|
|
assert(!self.disposed)
|
|
self.postbox?.addHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range)
|
|
}
|
|
|
|
public func removeHole(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleOperationSpace, range: ClosedRange<MessageId.Id>) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range)
|
|
}
|
|
|
|
public func getHole(containing id: MessageId) -> [MessageHistoryHoleSpace: ClosedRange<MessageId.Id>] {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageHistoryHoleIndexTable.containing(id: id) ?? [:]
|
|
}
|
|
|
|
public func getHoles(peerId: PeerId, namespace: MessageId.Namespace) -> IndexSet {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... (Int32.max - 1)) ?? IndexSet()
|
|
}
|
|
|
|
public func getThreadIndexHoles(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace) -> IndexSet {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryThreadHoleIndexTable.closest(peerId: peerId, threadId: threadId, namespace: namespace, space: .everywhere, range: 1 ... (Int32.max - 1))
|
|
}
|
|
|
|
public func getThreadIndexHole(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, containing id: MessageId.Id) -> [MessageHistoryHoleSpace: ClosedRange<MessageId.Id>] {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryThreadHoleIndexTable.containing(threadId: threadId, id: MessageId(peerId: peerId, namespace: namespace, id: id))
|
|
}
|
|
|
|
public func getThreadMessageCount(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, fromIdExclusive: Int32?, toIndex: MessageIndex) -> Int? {
|
|
assert(!self.disposed)
|
|
let fromIndex: MessageIndex?
|
|
if let fromIdExclusive = fromIdExclusive {
|
|
if let message = self.postbox?.getMessage(MessageId(peerId: peerId, namespace: namespace, id: fromIdExclusive)) {
|
|
fromIndex = message.index.peerLocalSuccessor()
|
|
} else {
|
|
fromIndex = self.postbox!.messageHistoryIndexTable.closestIndex(id: MessageId(peerId: peerId, namespace: namespace, id: fromIdExclusive))?.peerLocalSuccessor()
|
|
}
|
|
} else {
|
|
fromIndex = nil
|
|
}
|
|
|
|
if let fromIndex = fromIndex {
|
|
return self.postbox!.messageHistoryThreadsTable.getMessageCountInRange(threadId: threadId, peerId: peerId, namespace: namespace, lowerBound: fromIndex, upperBound: toIndex)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func doesChatListGroupContainHoles(groupId: PeerGroupId) -> Bool {
|
|
assert(!self.disposed)
|
|
return self.postbox?.chatListTable.doesGroupContainHoles(groupId: groupId) ?? false
|
|
}
|
|
|
|
public func recalculateChatListGroupStats(groupId: PeerGroupId) {
|
|
assert(!self.disposed)
|
|
self.postbox?.recalculateChatListGroupStats(groupId: groupId)
|
|
}
|
|
|
|
public func replaceChatListHole(groupId: PeerGroupId, index: MessageIndex, hole: ChatListHole?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceChatListHole(groupId: groupId, index: index, hole: hole)
|
|
}
|
|
|
|
public func allChatListHoles(groupId: PeerGroupId) -> [ChatListHole] {
|
|
assert(!self.disposed)
|
|
return self.postbox?.chatListTable.getHoles(groupId: groupId) ?? []
|
|
}
|
|
|
|
public func addChatListHole(groupId: PeerGroupId, hole: ChatListHole) {
|
|
assert(!self.disposed)
|
|
self.postbox?.addChatListHole(groupId: groupId, hole: hole)
|
|
}
|
|
|
|
public func deleteMessages(_ messageIds: [MessageId], forEachMedia: ((Media) -> Void)?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.deleteMessages(messageIds, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: ((Media) -> Void)?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.deleteMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
public func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace? = nil, reversed: Bool = false, _ f: (Message) -> Bool) {
|
|
self.postbox?.withAllMessages(peerId: peerId, namespace: namespace, reversed: reversed, f)
|
|
}
|
|
|
|
public func clearHistory(_ peerId: PeerId, threadId: Int64?, minTimestamp: Int32?, maxTimestamp: Int32?, namespaces: MessageIdNamespaces, forEachMedia: ((Media) -> Void)?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.clearHistory(peerId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, namespaces: namespaces, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: ((Media) -> Void)?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
public func removeAllMessagesWithGlobalTag(tag: GlobalMessageTags) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeAllMessagesWithGlobalTag(tag: tag)
|
|
}
|
|
|
|
public func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: ((Media) -> Void)?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeAllMessagesWithForwardAuthor(peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
public func messageIdsForGlobalIds(_ ids: [Int32]) -> [MessageId] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.messageIdsForGlobalIds(ids)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
public func failedMessageIds(for peerId: PeerId) -> [MessageId] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.failedMessageIds(for: peerId)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func deleteMessagesWithGlobalIds(_ ids: [Int32], forEachMedia: ((Media) -> Void)?) {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
let messageIds = postbox.messageIdsForGlobalIds(ids)
|
|
postbox.deleteMessages(messageIds, forEachMedia: forEachMedia)
|
|
}
|
|
}
|
|
|
|
public func messageIdForGloballyUniqueMessageId(peerId: PeerId, id: Int64) -> MessageId? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageIdForGloballyUniqueMessageId(peerId: peerId, id: id)
|
|
}
|
|
|
|
public func resetIncomingReadStates(_ states: [PeerId: [MessageId.Namespace: PeerReadState]]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.resetIncomingReadStates(states)
|
|
}
|
|
|
|
public func setNeedsIncomingReadStateSynchronization(_ peerId: PeerId) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setNeedsIncomingReadStateSynchronization(peerId)
|
|
}
|
|
|
|
public func confirmSynchronizedIncomingReadState(_ peerId: PeerId) {
|
|
assert(!self.disposed)
|
|
self.postbox?.confirmSynchronizedIncomingReadState(peerId)
|
|
}
|
|
|
|
public func applyIncomingReadMaxId(_ messageId: MessageId) {
|
|
assert(!self.disposed)
|
|
self.postbox?.applyIncomingReadMaxId(messageId)
|
|
}
|
|
|
|
public func applyOutgoingReadMaxId(_ messageId: MessageId) {
|
|
assert(!self.disposed)
|
|
self.postbox?.applyOutgoingReadMaxId(messageId)
|
|
}
|
|
|
|
public func applyInteractiveReadMaxIndex(_ messageIndex: MessageIndex) -> [MessageId] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.applyInteractiveReadMaxIndex(messageIndex: messageIndex)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func applyMarkUnread(peerId: PeerId, namespace: MessageId.Namespace, value: Bool, interactive: Bool) {
|
|
assert(!self.disposed)
|
|
self.postbox?.applyMarkUnread(peerId: peerId, namespace: namespace, value: value, interactive: interactive)
|
|
}
|
|
|
|
public func applyOutgoingReadMaxIndex(_ messageIndex: MessageIndex) -> [MessageId] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.applyOutgoingReadMaxIndex(messageIndex)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func resetPeerGroupSummary(groupId: PeerGroupId, namespace: MessageId.Namespace, summary: PeerGroupUnreadCountersSummary) {
|
|
assert(!self.disposed)
|
|
self.postbox?.resetPeerGroupSummary(groupId: groupId, namespace: namespace, summary: summary)
|
|
}
|
|
|
|
public func setNeedsPeerGroupMessageStatsSynchronization(groupId: PeerGroupId, namespace: MessageId.Namespace) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setNeedsPeerGroupMessageStatsSynchronization(groupId: groupId, namespace: namespace)
|
|
}
|
|
|
|
public func confirmSynchronizedPeerGroupMessageStats(groupId: PeerGroupId, namespace: MessageId.Namespace) {
|
|
assert(!self.disposed)
|
|
self.postbox?.confirmSynchronizedPeerGroupMessageStats(groupId: groupId, namespace: namespace)
|
|
}
|
|
|
|
public func getState() -> PostboxCoding? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getState()
|
|
}
|
|
|
|
public func setState(_ state: PostboxCoding) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setState(state)
|
|
}
|
|
|
|
public func getPeerChatState(_ id: PeerId) -> PeerChatState? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.peerChatStateTable.get(id)?.getLegacy() as? PeerChatState
|
|
}
|
|
|
|
public func setPeerChatState(_ id: PeerId, state: PeerChatState) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setPeerChatState(id, state: state)
|
|
}
|
|
|
|
public func getGlobalNotificationSettings() -> PostboxGlobalNotificationSettings {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.getGlobalNotificationSettings(transaction: self)
|
|
} else {
|
|
preconditionFailure()
|
|
}
|
|
}
|
|
|
|
public func getPeerChatInterfaceState(_ id: PeerId) -> StoredPeerChatInterfaceState? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.peerChatInterfaceStateTable.get(id)
|
|
}
|
|
|
|
public func getPeerChatThreadInterfaceState(_ id: PeerId, threadId: Int64) -> StoredPeerChatInterfaceState? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: id, threadId: threadId))
|
|
}
|
|
|
|
public func setPeerChatInterfaceState(_ id: PeerId, state: StoredPeerChatInterfaceState?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setPeerChatInterfaceState(id, state: state)
|
|
}
|
|
|
|
public func setPeerChatThreadInterfaceState(_ id: PeerId, threadId: Int64, state: StoredPeerChatInterfaceState?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setPeerChatThreadInterfaceState(id, threadId: threadId, state: state)
|
|
}
|
|
|
|
public func getPeer(_ id: PeerId) -> Peer? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.peerTable.get(id)
|
|
}
|
|
|
|
public func getPeerReadStates(_ id: PeerId) -> [(MessageId.Namespace, PeerReadState)]? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.readStateTable.getCombinedState(id)?.states
|
|
}
|
|
|
|
public func getCombinedPeerReadState(_ id: PeerId) -> CombinedPeerReadState? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.readStateTable.getCombinedState(id)
|
|
}
|
|
|
|
public func getPeerNotificationSettings(id: PeerId) -> PeerNotificationSettings? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.peerNotificationSettingsTable.getEffective(id)
|
|
}
|
|
|
|
public func getAllPeerNotificationSettings() -> [PeerId : PeerNotificationSettings]? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.peerNotificationSettingsTable.getAll()
|
|
}
|
|
|
|
public func getPendingPeerNotificationSettings(_ id: PeerId) -> PeerNotificationSettings? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.peerNotificationSettingsTable.getPending(id)
|
|
}
|
|
|
|
public func updatePeersInternal(_ peers: [Peer], update: (Peer?, Peer) -> Peer?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updatePeers(peers, update: update)
|
|
}
|
|
|
|
public func getPeerChatListInclusion(_ id: PeerId) -> PeerChatListInclusion {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.getPeerChatListInclusion(id)
|
|
}
|
|
return .notIncluded
|
|
}
|
|
|
|
public func getAssociatedPeerIds(_ id: PeerId) -> Set<PeerId> {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.reverseAssociatedPeerTable.get(peerId: id)
|
|
}
|
|
return []
|
|
}
|
|
|
|
public func getTopPeerMessageId(peerId: PeerId, namespace: MessageId.Namespace) -> MessageId? {
|
|
assert(!self.disposed)
|
|
return self.getTopPeerMessageIndex(peerId: peerId, namespace: namespace)?.id
|
|
}
|
|
|
|
public func getTopPeerMessageIndex(peerId: PeerId, namespace: MessageId.Namespace) -> MessageIndex? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getTopPeerMessageIndex(peerId: peerId, namespace: namespace)
|
|
}
|
|
|
|
public func getTopPeerMessageIndex(peerId: PeerId) -> MessageIndex? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getTopPeerMessageIndex(peerId: peerId)
|
|
}
|
|
|
|
public func getPeerChatListIndex(_ peerId: PeerId) -> (PeerGroupId, ChatListIndex)? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.chatListTable.getPeerChatListIndex(peerId: peerId)
|
|
}
|
|
|
|
public func getChatListPeers(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, additionalFilter: ((Peer) -> Bool)?) -> [Peer] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.chatListTable.getChatListPeers(postbox: postbox, currentTransaction: self, groupId: groupId, filterPredicate: filterPredicate, additionalFilter: additionalFilter)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func getUnreadChatListPeerIds(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, additionalFilter: ((Peer) -> Bool)?, stopOnFirstMatch: Bool) -> [PeerId] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.chatListTable.getUnreadChatListPeerIds(postbox: postbox, currentTransaction: self, groupId: groupId, filterPredicate: filterPredicate, additionalFilter: additionalFilter, stopOnFirstMatch: stopOnFirstMatch)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func updatePeerChatListInclusion(_ id: PeerId, inclusion: PeerChatListInclusion) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updatePeerChatListInclusion(id, inclusion: inclusion)
|
|
}
|
|
|
|
public func removeAllChatListEntries(groupId: PeerGroupId, exceptPeerNamespace: PeerId.Namespace) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeAllChatListEntries(groupId: groupId, exceptPeerNamespace: exceptPeerNamespace)
|
|
}
|
|
|
|
public func chatListGetAllPeerIds() -> [PeerId] {
|
|
assert(!self.disposed)
|
|
return self.postbox?.chatListTable.getAllPeerIds() ?? []
|
|
}
|
|
|
|
public func chatListGetAllPeerIds(groupId: PeerGroupId) -> [PeerId] {
|
|
assert(!self.disposed)
|
|
return self.postbox?.chatListTable.getAllPeerIds(groupId: groupId) ?? []
|
|
}
|
|
|
|
public func updateCurrentPeerNotificationSettings(_ notificationSettings: [PeerId: PeerNotificationSettings]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updateCurrentPeerNotificationSettings(notificationSettings)
|
|
}
|
|
|
|
public func updatePendingPeerNotificationSettings(peerId: PeerId, settings: PeerNotificationSettings?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updatePendingPeerNotificationSettings(peerId: peerId, settings: settings)
|
|
}
|
|
|
|
public func resetAllPeerNotificationSettings(_ notificationSettings: PeerNotificationSettings) {
|
|
assert(!self.disposed)
|
|
self.postbox?.resetAllPeerNotificationSettings(notificationSettings)
|
|
}
|
|
|
|
public func getPeerIdsAndNotificationSettingsWithBehaviorTimestampLessThanOrEqualTo(_ timestamp: Int32) -> [(PeerId, PeerNotificationSettings)] {
|
|
assert(!self.disposed)
|
|
guard let postbox = self.postbox else {
|
|
return []
|
|
}
|
|
var result: [(PeerId, PeerNotificationSettings)] = []
|
|
for peerId in postbox.peerNotificationSettingsBehaviorTable.getEarlierThanOrEqualTo(timestamp: timestamp) {
|
|
if let notificationSettings = postbox.peerNotificationSettingsTable.getCurrent(peerId) {
|
|
result.append((peerId, notificationSettings))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
public func updatePeerCachedData(peerIds: Set<PeerId>, update: (PeerId, CachedPeerData?) -> CachedPeerData?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updatePeerCachedData(peerIds: peerIds, update: update)
|
|
}
|
|
|
|
public func getPeerCachedData(peerId: PeerId) -> CachedPeerData? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.cachedPeerDataTable.get(peerId)
|
|
}
|
|
|
|
public func updatePeerPresencesInternal(presences: [PeerId: PeerPresence], merge: (PeerPresence, PeerPresence) -> PeerPresence) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updatePeerPresences(presences: presences, merge: merge)
|
|
}
|
|
|
|
public func updatePeerPresenceInternal(peerId: PeerId, update: (PeerPresence) -> PeerPresence) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updatePeerPresence(peerId: peerId, update: update)
|
|
}
|
|
|
|
public func getPeerPresence(peerId: PeerId) -> PeerPresence? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.peerPresenceTable.get(peerId)
|
|
}
|
|
|
|
public func getContactPeerIds() -> Set<PeerId> {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.contactsTable.get()
|
|
} else {
|
|
return Set()
|
|
}
|
|
}
|
|
|
|
public func isPeerContact(peerId: PeerId) -> Bool {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.contactsTable.isContact(peerId: peerId)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
public func getRemoteContactCount() -> Int32 {
|
|
assert(!self.disposed)
|
|
return self.postbox?.metadataTable.getRemoteContactCount() ?? 0
|
|
}
|
|
|
|
public func replaceRemoteContactCount(_ count: Int32) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceRemoteContactCount(count)
|
|
}
|
|
|
|
public func replaceContactPeerIds(_ peerIds: Set<PeerId>) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceContactPeerIds(peerIds)
|
|
}
|
|
|
|
public func replaceAdditionalChatListItems(_ items: [AdditionalChatListItem]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceAdditionalChatListItems(items)
|
|
}
|
|
|
|
public func getAdditionalChatListItems() -> [AdditionalChatListItem] {
|
|
assert(!self.disposed)
|
|
return self.postbox?.additionalChatListItemsTable.get() ?? []
|
|
}
|
|
|
|
public func replaceRecentPeerIds(_ peerIds: [PeerId]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceRecentPeerIds(peerIds)
|
|
}
|
|
|
|
public func getRecentPeerIds() -> [PeerId] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.peerRatingTable.get()
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func updateMessage(_ id: MessageId, update: (Message) -> PostboxUpdateMessage) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updateMessage(transaction: self, id: id, update: update)
|
|
}
|
|
|
|
public func offsetPendingMessagesTimestamps(lowerBound: MessageId, excludeIds: Set<MessageId>, timestamp: Int32) {
|
|
assert(!self.disposed)
|
|
self.postbox?.offsetPendingMessagesTimestamps(lowerBound: lowerBound, excludeIds: excludeIds, timestamp: timestamp)
|
|
}
|
|
|
|
public func updateMessageGroupingKeysAtomically(_ ids: [MessageId], groupingKey: Int64) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updateMessageGroupingKeysAtomically(ids, groupingKey: groupingKey)
|
|
}
|
|
|
|
public func updateMedia(_ id: MediaId, update: Media?) -> Set<MessageIndex> {
|
|
assert(!self.disposed)
|
|
return self.postbox?.updateMedia(id, update: update) ?? Set()
|
|
}
|
|
|
|
public func storeMediaIfNotPresent(media: Media) {
|
|
assert(!self.disposed)
|
|
self.postbox?.storeMediaIfNotPresent(media: media)
|
|
}
|
|
|
|
public func replaceItemCollections(namespace: ItemCollectionId.Namespace, itemCollections: [(ItemCollectionId, ItemCollectionInfo, [ItemCollectionItem])]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceItemCollections(namespace: namespace, itemCollections: itemCollections)
|
|
}
|
|
|
|
public func replaceItemCollectionInfos(namespace: ItemCollectionId.Namespace, itemCollectionInfos: [(ItemCollectionId, ItemCollectionInfo)]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceItemCollectionInfos(namespace: namespace, itemCollectionInfos: itemCollectionInfos)
|
|
}
|
|
|
|
public func replaceItemCollectionItems(collectionId: ItemCollectionId, items: [ItemCollectionItem]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceItemCollectionItems(collectionId: collectionId, items: items)
|
|
}
|
|
|
|
public func removeItemCollection(collectionId: ItemCollectionId) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeItemCollection(collectionId: collectionId)
|
|
}
|
|
|
|
public func getItemCollectionsInfos(namespace: ItemCollectionId.Namespace) -> [(ItemCollectionId, ItemCollectionInfo)] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.itemCollectionInfoTable.getInfos(namespace: namespace).map { ($0.1, $0.2) }
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func getItemCollectionInfo(collectionId: ItemCollectionId) -> ItemCollectionInfo? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.itemCollectionInfoTable.getInfo(id: collectionId)
|
|
}
|
|
|
|
public func getItemCollectionItems(collectionId: ItemCollectionId) -> [ItemCollectionItem] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.itemCollectionItemTable.collectionItems(collectionId: collectionId)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func getCollectionsItems(namespace: ItemCollectionId.Namespace) -> [(ItemCollectionId, ItemCollectionInfo, [ItemCollectionItem])] {
|
|
assert(!self.disposed)
|
|
if let postbox = postbox {
|
|
let infos = postbox.itemCollectionInfoTable.getInfos(namespace: namespace)
|
|
var result: [(ItemCollectionId, ItemCollectionInfo, [ItemCollectionItem])] = []
|
|
for info in infos {
|
|
let items = getItemCollectionItems(collectionId: info.1)
|
|
result.append((info.1, info.2, items))
|
|
}
|
|
return result
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func getItemCollectionInfoItems(namespace: ItemCollectionId.Namespace, id: ItemCollectionId) -> (ItemCollectionInfo, [ItemCollectionItem])? {
|
|
assert(!self.disposed)
|
|
if let postbox = postbox {
|
|
let infos = postbox.itemCollectionInfoTable.getInfos(namespace: namespace)
|
|
for info in infos {
|
|
if info.1 == id {
|
|
return (info.2, getItemCollectionItems(collectionId: id))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
|
|
public func searchItemCollection(namespace: ItemCollectionId.Namespace, query: ItemCollectionSearchQuery) -> [ItemCollectionItem] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
let itemsByCollectionId = postbox.itemCollectionItemTable.searchIndexedItems(namespace: namespace, query: query)
|
|
let infoIds = postbox.itemCollectionInfoTable.getIds(namespace: namespace)
|
|
var infoIndices: [ItemCollectionId: Int] = [:]
|
|
for i in 0 ..< infoIds.count {
|
|
infoIndices[infoIds[i]] = i
|
|
}
|
|
let sortedKeys = itemsByCollectionId.keys.sorted(by: { lhs, rhs in
|
|
if let lhsIndex = infoIndices[lhs], let rhsIndex = infoIndices[rhs] {
|
|
return lhsIndex < rhsIndex
|
|
} else if let _ = infoIndices[lhs] {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
})
|
|
var result: [ItemCollectionItem] = []
|
|
for key in sortedKeys {
|
|
result.append(contentsOf: itemsByCollectionId[key]!)
|
|
}
|
|
return result
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func replaceOrderedItemListItems(collectionId: Int32, items: [OrderedItemListEntry]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceOrderedItemListItems(collectionId: collectionId, items: items)
|
|
}
|
|
|
|
public func addOrMoveToFirstPositionOrderedItemListItem(collectionId: Int32, item: OrderedItemListEntry, removeTailIfCountExceeds: Int?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.addOrMoveToFirstPositionOrderedItemListItem(collectionId: collectionId, item: item, removeTailIfCountExceeds: removeTailIfCountExceeds)
|
|
}
|
|
|
|
public func getOrderedListItemIds(collectionId: Int32) -> [MemoryBuffer] {
|
|
assert(!self.disposed)
|
|
if let postbox = postbox {
|
|
return postbox.getOrderedListItemIds(collectionId: collectionId)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func getOrderedListItems(collectionId: Int32) -> [OrderedItemListEntry] {
|
|
assert(!self.disposed)
|
|
if let postbox = postbox {
|
|
return postbox.orderedItemListTable.getItems(collectionId: collectionId)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func getOrderedItemListItem(collectionId: Int32, itemId: MemoryBuffer) -> OrderedItemListEntry? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getOrderedItemListItem(collectionId: collectionId, itemId: itemId)
|
|
}
|
|
|
|
public func updateOrderedItemListItem(collectionId: Int32, itemId: MemoryBuffer, item: CodableEntry) {
|
|
assert(!self.disposed)
|
|
self.postbox?.updateOrderedItemListItem(collectionId: collectionId, itemId: itemId, item: item)
|
|
}
|
|
|
|
public func removeOrderedItemListItem(collectionId: Int32, itemId: MemoryBuffer) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeOrderedItemListItem(collectionId: collectionId, itemId: itemId)
|
|
}
|
|
|
|
public func getMessage(_ id: MessageId) -> Message? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getMessage(id)
|
|
}
|
|
|
|
public func getMessageGroup(_ id: MessageId) -> [Message]? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getMessageGroup(at: id)
|
|
}
|
|
|
|
public func getMessageForwardedGroup(_ id: MessageId) -> [Message]? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getMessageForwardedGroup(id)
|
|
}
|
|
|
|
public func getMessageFailedGroup(_ id: MessageId) -> [Message]? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getMessageFailedGroup(id)
|
|
}
|
|
|
|
public func getMedia(_ id: MediaId) -> Media? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageHistoryTable.getMedia(id)
|
|
}
|
|
|
|
public func findMessageIdByTimestamp(peerId: PeerId, namespace: MessageId.Namespace, timestamp: Int32) -> MessageId? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageHistoryTable.findMessageId(peerId: peerId, namespace: namespace, timestamp: timestamp)
|
|
}
|
|
|
|
public func findClosestMessageIdByTimestamp(peerId: PeerId, timestamp: Int32) -> MessageId? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageHistoryTable.findClosestMessageIndex(peerId: peerId, timestamp: timestamp)?.id
|
|
}
|
|
|
|
public func findMessageAtAbsoluteIndex(peerId: PeerId, namespace: MessageId.Namespace, index: Int) -> MessageIndex? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageHistoryTable.findMessageAtAbsoluteIndex(peerId: peerId, namespace: namespace, index: index)
|
|
}
|
|
|
|
public func findRandomMessage(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, ignoreIds: ([MessageId], Set<MessageId>)) -> MessageIndex? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageHistoryTable.findRandomMessage(peerId: peerId, namespace: namespace, tag: tag, ignoreIds: ignoreIds)
|
|
}
|
|
|
|
public func firstMessageInRange(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, timestampMax: Int32, timestampMin: Int32) -> Message? {
|
|
assert(!self.disposed)
|
|
if let message = self.postbox?.messageHistoryTable.firstMessageInRange(peerId: peerId, namespace: namespace, tag: tag, timestampMax: timestampMax, timestampMin: timestampMin) {
|
|
return self.postbox?.renderIntermediateMessage(message)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func filterStoredMessageIds(_ messageIds: Set<MessageId>) -> Set<MessageId> {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.filterStoredMessageIds(messageIds)
|
|
}
|
|
return Set()
|
|
}
|
|
|
|
public func filterStoredMediaIds(namespace: MediaId.Namespace, ids: Set<Int64>) -> Set<Int64> {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.filterStoredMediaIds(namespace: namespace, ids: ids)
|
|
}
|
|
return Set()
|
|
}
|
|
|
|
public func storedMessageId(peerId: PeerId, namespace: MessageId.Namespace, timestamp: Int32) -> MessageId? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.storedMessageId(peerId: peerId, namespace: namespace, timestamp: timestamp)
|
|
}
|
|
|
|
public func putItemCacheEntry(id: ItemCacheEntryId, entry: CodableEntry) {
|
|
assert(!self.disposed)
|
|
self.postbox?.putItemCacheEntry(id: id, entry: entry)
|
|
}
|
|
|
|
public func removeItemCacheEntry(id: ItemCacheEntryId) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeItemCacheEntry(id: id)
|
|
}
|
|
|
|
public func retrieveItemCacheEntry(id: ItemCacheEntryId) -> CodableEntry? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.retrieveItemCacheEntry(id: id)
|
|
}
|
|
|
|
public func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
|
|
assert(!self.disposed)
|
|
self.postbox?.clearItemCacheCollection(collectionId: collectionId)
|
|
}
|
|
|
|
public func operationLogGetNextEntryLocalIndex(peerId: PeerId, tag: PeerOperationLogTag) -> Int32 {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: tag)
|
|
} else {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
public func operationLogResetIndices(peerId: PeerId, tag: PeerOperationLogTag, nextTagLocalIndex: Int32) {
|
|
assert(!self.disposed)
|
|
self.postbox?.peerOperationLogTable.resetIndices(peerId: peerId, tag: tag, nextTagLocalIndex: nextTagLocalIndex)
|
|
}
|
|
|
|
public func operationLogAddEntry(peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: StorePeerOperationLogEntryTagLocalIndex, tagMergedIndex: StorePeerOperationLogEntryTagMergedIndex, contents: PostboxCoding) {
|
|
assert(!self.disposed)
|
|
self.postbox?.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, tagMergedIndex: tagMergedIndex, contents: contents)
|
|
}
|
|
|
|
public func operationLogRemoveEntry(peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32) -> Bool {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
public func operationLogRemoveAllEntries(peerId: PeerId, tag: PeerOperationLogTag) {
|
|
assert(!self.disposed)
|
|
self.postbox?.operationLogRemoveAllEntries(peerId: peerId, tag: tag)
|
|
}
|
|
|
|
public func operationLogRemoveEntries(peerId: PeerId, tag: PeerOperationLogTag, withTagLocalIndicesEqualToOrLowerThan maxTagLocalIndex: Int32) {
|
|
assert(!self.disposed)
|
|
self.postbox?.operationLogRemoveEntries(peerId: peerId, tag: tag, withTagLocalIndicesEqualToOrLowerThan: maxTagLocalIndex)
|
|
}
|
|
|
|
public func operationLogUpdateEntry(peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: (PeerOperationLogEntry?) -> PeerOperationLogEntryUpdate) {
|
|
assert(!self.disposed)
|
|
self.postbox?.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, f)
|
|
}
|
|
|
|
public func operationLogEnumerateEntries(peerId: PeerId, tag: PeerOperationLogTag, _ f: (PeerOperationLogEntry) -> Bool) {
|
|
assert(!self.disposed)
|
|
self.postbox?.operationLogEnumerateEntries(peerId: peerId, tag: tag, f)
|
|
}
|
|
|
|
public func enumeratePreferencesEntries(_ f: (PreferencesEntry) -> Bool) {
|
|
assert(!self.disposed)
|
|
self.postbox?.enumeratePreferencesEntries(f)
|
|
}
|
|
|
|
public func getPreferencesEntry(key: ValueBoxKey) -> PreferencesEntry? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getPreferencesEntry(key: key)
|
|
}
|
|
|
|
public func setPreferencesEntry(key: ValueBoxKey, value: PreferencesEntry?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setPreferencesEntry(key: key, value: value)
|
|
}
|
|
|
|
public func updatePreferencesEntry(key: ValueBoxKey, _ f: (PreferencesEntry?) -> PreferencesEntry?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setPreferencesEntry(key: key, value: f(self.postbox?.getPreferencesEntry(key: key)))
|
|
}
|
|
|
|
public func globalNotificationSettingsUpdated() {
|
|
assert(!self.disposed)
|
|
self.postbox?.globalNotificationSettingsUpdated()
|
|
}
|
|
|
|
public func getPinnedItemIds(groupId: PeerGroupId) -> [PinnedItemId] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.getPinnedItemIds(groupId: groupId)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func setPinnedItemIds(groupId: PeerGroupId, itemIds: [PinnedItemId]) {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
postbox.setPinnedItemIds(groupId: groupId, itemIds: itemIds)
|
|
}
|
|
}
|
|
|
|
public func getTotalUnreadState(groupId: PeerGroupId) -> ChatListTotalUnreadState {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: groupId)
|
|
} else {
|
|
return ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:])
|
|
}
|
|
}
|
|
|
|
public func getChatCountMatchingPredicate(_ predicate: ChatListFilterPredicate) -> Int {
|
|
assert(!self.disposed)
|
|
guard let postbox = self.postbox else {
|
|
return 0
|
|
}
|
|
var includedPeerIds: [PeerId: Bool] = [:]
|
|
for peerId in predicate.includePeerIds {
|
|
includedPeerIds[peerId] = false
|
|
}
|
|
|
|
var count = 0
|
|
|
|
let globalNotificationSettings = self.getGlobalNotificationSettings()
|
|
|
|
var groupIds: [PeerGroupId] = [.root]
|
|
groupIds.append(contentsOf: predicate.includeAdditionalPeerGroupIds)
|
|
for groupId in groupIds {
|
|
count += postbox.chatListTable.countWithPredicate(groupId: groupId, predicate: { peerId in
|
|
if let peer = postbox.peerTable.get(peerId) {
|
|
let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
|
|
let notificationsPeerId = peer.notificationSettingsPeerId ?? peerId
|
|
let isContact = postbox.contactsTable.isContact(peerId: notificationsPeerId)
|
|
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId))
|
|
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, threadId: nil, calculation: predicate.messageTagSummary)
|
|
|
|
if predicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: messageTagSummaryResult) {
|
|
includedPeerIds[peer.id] = true
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
})
|
|
}
|
|
for (peerId, included) in includedPeerIds {
|
|
if !included {
|
|
if postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil {
|
|
count += 1
|
|
}
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
public func enumerateMediaMessages(lowerBound: MessageIndex?, upperBound: MessageIndex?, limit: Int) -> (messagesByMediaId: [MediaId: [MessageId]], mediaMap: [MediaId: Media], nextLowerBound: MessageIndex?) {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.messageHistoryTable.enumerateMediaMessages(lowerBound: lowerBound, upperBound: upperBound, limit: limit)
|
|
} else {
|
|
return ([:], [:], nil)
|
|
}
|
|
}
|
|
|
|
public func enumerateMedia(lowerBound: MessageIndex?, upperBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.messageHistoryTable.enumerateMedia(lowerBound: lowerBound, upperBound: upperBound, limit: limit)
|
|
} else {
|
|
return ([:], [:], nil)
|
|
}
|
|
}
|
|
|
|
public func replaceGlobalMessageTagsHole(globalTags: GlobalMessageTags, index: MessageIndex, with updatedIndex: MessageIndex?, messages: [StoreMessage]) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceGlobalMessageTagsHole(transaction: self, globalTags: globalTags, index: index, with: updatedIndex, messages: messages)
|
|
}
|
|
|
|
public func searchMessages(peerId: PeerId?, query: String, tags: MessageTags?) -> [Message] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.searchMessages(peerId: peerId, query: query, tags: tags)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public func unorderedItemListScan(tag: UnorderedItemListEntryTag, _ f: (UnorderedItemListEntry) -> Void) {
|
|
if let postbox = self.postbox {
|
|
postbox.unorderedItemListTable.scan(tag: tag, f)
|
|
}
|
|
}
|
|
|
|
public func unorderedItemListDifference(tag: UnorderedItemListEntryTag, updatedEntryInfos: [ValueBoxKey: UnorderedItemListEntryInfo]) -> (metaInfo: UnorderedItemListTagMetaInfo?, added: [ValueBoxKey], removed: [UnorderedItemListEntry], updated: [UnorderedItemListEntry]) {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.unorderedItemListTable.difference(tag: tag, updatedEntryInfos: updatedEntryInfos)
|
|
} else {
|
|
return (nil, [], [], [])
|
|
}
|
|
}
|
|
|
|
public func unorderedItemListApplyDifference(tag: UnorderedItemListEntryTag, previousInfo: UnorderedItemListTagMetaInfo?, updatedInfo: UnorderedItemListTagMetaInfo, setItems: [UnorderedItemListEntry], removeItemIds: [ValueBoxKey]) -> Bool {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.unorderedItemListTable.applyDifference(tag: tag, previousInfo: previousInfo, updatedInfo: updatedInfo, setItems: setItems, removeItemIds: removeItemIds)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
public func getAllNoticeEntries() -> [ValueBoxKey: CodableEntry] {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.noticeTable.getAll()
|
|
} else {
|
|
return [:]
|
|
}
|
|
}
|
|
|
|
public func getNoticeEntry(key: NoticeEntryKey) -> CodableEntry? {
|
|
assert(!self.disposed)
|
|
if let postbox = self.postbox {
|
|
return postbox.noticeTable.get(key: key)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func setNoticeEntry(key: NoticeEntryKey, value: CodableEntry?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setNoticeEntry(key: key, value: value)
|
|
}
|
|
|
|
public func clearNoticeEntries() {
|
|
assert(!self.disposed)
|
|
self.postbox?.clearNoticeEntries()
|
|
}
|
|
|
|
public func setPendingMessageAction(type: PendingMessageActionType, id: MessageId, action: PendingMessageActionData?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.setPendingMessageAction(type: type, id: id, action: action)
|
|
}
|
|
|
|
public func getPendingMessageAction(type: PendingMessageActionType, id: MessageId) -> PendingMessageActionData? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getPendingMessageAction(type: type, id: id)
|
|
}
|
|
|
|
public func getMessageTagSummary(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace, customTag: MemoryBuffer?) -> MessageHistoryTagNamespaceSummary? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: tagMask, peerId: peerId, threadId: threadId, namespace: namespace, customTag: customTag))
|
|
}
|
|
|
|
public func getMessageTagSummaryCustomTags(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace) -> [MemoryBuffer] {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryTagsSummaryTable.getCustomTags(tag: tagMask, peerId: peerId, threadId: threadId, namespace: namespace)
|
|
}
|
|
|
|
public func replaceMessageTagSummary(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace, customTag: MemoryBuffer?, count: Int32, maxId: MessageId.Id) {
|
|
assert(!self.disposed)
|
|
self.postbox?.replaceMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: tagMask, namespace: namespace, customTag: customTag, count: count, maxId: maxId)
|
|
}
|
|
|
|
public func getPendingMessageActionsSummary(peerId: PeerId, type: PendingMessageActionType, namespace: MessageId.Namespace) -> Int32? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.pendingMessageActionsMetadataTable.getCount(.peerNamespaceAction(peerId, namespace, type))
|
|
}
|
|
|
|
public func getMessageIndicesWithTag(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, tag: MessageTags) -> [MessageIndex] {
|
|
assert(!self.disposed)
|
|
guard let postbox = self.postbox else {
|
|
return []
|
|
}
|
|
if let threadId = threadId {
|
|
return postbox.messageHistoryThreadTagsTable.earlierIndices(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1000)
|
|
} else {
|
|
return postbox.messageHistoryTagsTable.earlierIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1000)
|
|
}
|
|
}
|
|
|
|
public func getMessagesWithThreadId(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64, from: MessageIndex, includeFrom: Bool, to: MessageIndex, limit: Int) -> [Message] {
|
|
assert(!self.disposed)
|
|
guard let postbox = self.postbox else {
|
|
return []
|
|
}
|
|
return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, customTag: nil, threadId: threadId, from: from, includeFrom: includeFrom, to: to, ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), limit: limit).map(postbox.renderIntermediateMessage(_:))
|
|
}
|
|
|
|
public func getMessagesWithCustomTag(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64?, customTag: MemoryBuffer, from: MessageIndex, includeFrom: Bool, to: MessageIndex, limit: Int) -> [Message] {
|
|
assert(!self.disposed)
|
|
guard let postbox = self.postbox else {
|
|
return []
|
|
}
|
|
return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, customTag: customTag, threadId: threadId, from: from, includeFrom: includeFrom, to: to, ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), limit: limit).map(postbox.renderIntermediateMessage(_:))
|
|
}
|
|
|
|
public func scanMessages(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) {
|
|
assert(!self.disposed)
|
|
self.postbox?.scanMessages(peerId: peerId, namespace: namespace, tag: tag, f)
|
|
}
|
|
|
|
public func scanMessages(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) {
|
|
assert(!self.disposed)
|
|
self.postbox?.scanMessages(peerId: peerId, threadId: threadId, namespace: namespace, tag: tag, f)
|
|
}
|
|
|
|
public func scanTopMessages(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (Message) -> Bool) {
|
|
assert(!self.disposed)
|
|
self.postbox?.scanTopMessages(peerId: peerId, namespace: namespace, limit: limit, f)
|
|
}
|
|
|
|
public func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (MessageId, [MessageAttribute]) -> Bool) {
|
|
self.postbox?.scanMessageAttributes(peerId: peerId, namespace: namespace, limit: limit, f)
|
|
}
|
|
|
|
public func getMessagesHistoryViewState(input: MessageHistoryViewInput, ignoreMessagesInTimestampRange: ClosedRange<Int32>?, ignoreMessageIds: Set<MessageId>, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, namespaces: MessageIdNamespaces) -> MessageHistoryView {
|
|
precondition(!self.disposed)
|
|
guard let postbox = self.postbox else {
|
|
preconditionFailure()
|
|
}
|
|
|
|
precondition(postbox.queue.isCurrent())
|
|
|
|
var view: MessageHistoryView?
|
|
|
|
let subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = Subscriber(next: { next in
|
|
view = next.0
|
|
}, error: { _ in }, completed: {})
|
|
|
|
let disposable = postbox.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: input, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, ignoreMessageIds: ignoreMessageIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: MessageHistoryViewOrderStatistics(), additionalData: [], useRootInterfaceStateForThread: false)
|
|
disposable.dispose()
|
|
|
|
return view!
|
|
}
|
|
|
|
public func invalidateMessageHistoryTagsSummary(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, tagMask: MessageTags, customTag: MemoryBuffer?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.invalidateMessageHistoryTagsSummary(peerId: peerId, threadId: threadId, namespace: namespace, tagMask: tagMask, customTag: customTag)
|
|
}
|
|
|
|
public func removeInvalidatedMessageHistoryTagsSummaryEntry(_ entry: InvalidatedMessageHistoryTagsSummaryEntry) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removeInvalidatedMessageHistoryTagsSummaryEntry(entry)
|
|
}
|
|
|
|
public func removeInvalidatedMessageHistoryTagsSummaryEntriesWithCustomTags(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, tagMask: MessageTags) {
|
|
self.postbox?.removeInvalidatedMessageHistoryTagsSummaryEntriesWithCustomTags(peerId: peerId, threadId: threadId, namespace: namespace, tagMask: tagMask)
|
|
}
|
|
|
|
public func getRelativeUnreadChatListIndex(filtered: Bool, position: ChatListRelativePosition, groupId: PeerGroupId) -> ChatListIndex? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.getRelativeUnreadChatListIndex(currentTransaction: self, filtered: filtered, position: position, groupId: groupId)
|
|
}
|
|
|
|
public func getDeviceContactImportInfo(_ identifier: ValueBoxKey) -> PostboxCoding? {
|
|
assert(!self.disposed)
|
|
return self.postbox?.deviceContactImportInfoTable.get(identifier)
|
|
}
|
|
|
|
public func setDeviceContactImportInfo(_ identifier: ValueBoxKey, value: PostboxCoding?) {
|
|
assert(!self.disposed)
|
|
self.postbox?.deviceContactImportInfoTable.set(identifier, value: value)
|
|
}
|
|
|
|
public func getDeviceContactImportInfoIdentifiers() -> [ValueBoxKey] {
|
|
assert(!self.disposed)
|
|
return self.postbox?.deviceContactImportInfoTable.getIdentifiers() ?? []
|
|
}
|
|
|
|
public func clearDeviceContactImportInfoIdentifiers() {
|
|
assert(!self.disposed)
|
|
self.postbox?.clearDeviceContactImportInfoIdentifiers()
|
|
}
|
|
|
|
public func enumerateDeviceContactImportInfoItems(_ f: (ValueBoxKey, PostboxCoding) -> Bool) {
|
|
assert(!self.disposed)
|
|
self.postbox?.deviceContactImportInfoTable.enumerateDeviceContactImportInfoItems(f)
|
|
}
|
|
|
|
public func getChatListNamespaceEntries(groupId: PeerGroupId, namespace: MessageId.Namespace, summaryTag: MessageTags?) -> [ChatListNamespaceEntry] {
|
|
assert(!self.disposed)
|
|
guard let postbox = self.postbox else {
|
|
return []
|
|
}
|
|
return postbox.chatListTable.getNamespaceEntries(groupId: groupId, namespace: namespace, summaryTag: summaryTag, messageIndexTable: postbox.messageHistoryIndexTable, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, readStateTable: postbox.readStateTable, summaryTable: postbox.messageHistoryTagsSummaryTable)
|
|
}
|
|
|
|
public func getTopChatListEntries(groupId: PeerGroupId, count: Int) -> [RenderedPeer] {
|
|
assert(!self.disposed)
|
|
guard let postbox = self.postbox else {
|
|
return []
|
|
}
|
|
return postbox.chatListTable.earlierEntryInfos(groupId: groupId, index: nil, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: count).compactMap { entry -> RenderedPeer? in
|
|
switch entry {
|
|
case let .message(index, _):
|
|
if let peer = self.getPeer(index.messageIndex.id.peerId) {
|
|
return RenderedPeer(peer: peer)
|
|
} else {
|
|
return nil
|
|
}
|
|
case .hole:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
public func addHolesEverywhere(peerNamespaces: [PeerId.Namespace], holeNamespace: MessageId.Namespace) {
|
|
assert(!self.disposed)
|
|
self.postbox?.addHolesEverywhere(peerNamespaces: peerNamespaces, holeNamespace: holeNamespace)
|
|
}
|
|
|
|
public func resetCustomTagHoles() {
|
|
assert(!self.disposed)
|
|
self.postbox?.resetCustomTagHoles()
|
|
}
|
|
|
|
public func reindexUnreadCounters() {
|
|
assert(!self.disposed)
|
|
self.postbox?.reindexUnreadCounters(currentTransaction: self)
|
|
}
|
|
|
|
public func searchPeers(query: String) -> [RenderedPeer] {
|
|
assert(!self.disposed)
|
|
return self.postbox?.searchPeers(query: query) ?? []
|
|
}
|
|
|
|
public func clearTimestampBasedAttribute(id: MessageId, tag: UInt16) {
|
|
assert(!self.disposed)
|
|
self.postbox?.clearTimestampBasedAttribute(id: id, tag: tag)
|
|
}
|
|
|
|
public func removePeerTimeoutAttributeEntry(peerId: PeerId, timestamp: UInt32) {
|
|
assert(!self.disposed)
|
|
self.postbox?.removePeerTimeoutAttributeEntry(peerId: peerId, timestamp: timestamp)
|
|
}
|
|
|
|
public func getMessageHistoryThreadIndex(peerId: PeerId, limit: Int) -> [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryThreadIndexTable.fetch(peerId: peerId, namespace: 0, start: .upperBound, end: .lowerBound, limit: limit)
|
|
}
|
|
|
|
public func getMessageHistoryThreadTopMessage(peerId: PeerId, threadId: Int64, namespaces: Set<MessageId.Namespace>) -> MessageIndex? {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryThreadsTable.getTop(peerId: peerId, threadId: threadId, namespaces: namespaces)
|
|
}
|
|
|
|
public func holeLowerBoundForTopValidRange(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace) -> MessageId.Id {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryThreadsTable.holeLowerBoundForTopValidRange(peerId: peerId, threadId: threadId, namespace: namespace, space: space, holeIndexTable: self.postbox!.messageHistoryThreadHoleIndexTable)
|
|
}
|
|
|
|
public func getMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64) -> StoredMessageHistoryThreadInfo? {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryThreadIndexTable.get(peerId: peerId, threadId: threadId)
|
|
}
|
|
|
|
public func setMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64, info: StoredMessageHistoryThreadInfo?) {
|
|
assert(!self.disposed)
|
|
self.postbox!.messageHistoryThreadIndexTable.set(peerId: peerId, threadId: threadId, info: info)
|
|
}
|
|
|
|
public func setMessageHistoryThreads(peerId: PeerId) -> [Int64] {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryThreadIndexTable.getAll(peerId: peerId).map(\.threadId)
|
|
}
|
|
|
|
public func getPeerThreadCombinedState(peerId: PeerId) -> StoredPeerThreadCombinedState? {
|
|
assert(!self.disposed)
|
|
return self.postbox!.peerThreadCombinedStateTable.get(peerId: peerId)
|
|
}
|
|
|
|
public func setPeerThreadCombinedState(peerId: PeerId, state: StoredPeerThreadCombinedState?) {
|
|
assert(!self.disposed)
|
|
return self.postbox!.setPeerThreadCombinedState(peerId: peerId, state: state)
|
|
}
|
|
|
|
public func setPeerPinnedThreads(peerId: PeerId, threadIds: [Int64]) {
|
|
assert(!self.disposed)
|
|
self.postbox!.setPeerPinnedThreads(peerId: peerId, threadIds: threadIds)
|
|
}
|
|
|
|
public func getPeerPinnedThreads(peerId: PeerId) -> [Int64] {
|
|
assert(!self.disposed)
|
|
return self.postbox!.messageHistoryThreadPinnedTable.get(peerId: peerId)
|
|
}
|
|
|
|
func addChatHidden(peerId: PeerId) -> Int {
|
|
assert(!self.disposed)
|
|
return self.postbox!.addChatHidden(peerId: peerId)
|
|
}
|
|
|
|
func removeChatHidden(peerId: PeerId, index: Int) {
|
|
assert(!self.disposed)
|
|
return self.postbox!.removeChatHidden(peerId: peerId, index: index)
|
|
}
|
|
|
|
public func getAllStorySubscriptions(key: PostboxStorySubscriptionsKey) -> (state: CodableEntry?, peerIds: [PeerId]) {
|
|
assert(!self.disposed)
|
|
return self.postbox!.getAllStorySubscriptions(key: key)
|
|
}
|
|
|
|
public func storySubscriptionsContains(key: PostboxStorySubscriptionsKey, peerId: PeerId) -> Bool {
|
|
assert(!self.disposed)
|
|
return self.postbox!.storySubscriptionsContains(key: key, peerId: peerId)
|
|
}
|
|
|
|
public func replaceAllStorySubscriptions(key: PostboxStorySubscriptionsKey, state: CodableEntry?, peerIds: [PeerId]) {
|
|
assert(!self.disposed)
|
|
self.postbox!.replaceAllStorySubscriptions(key: key, state: state, peerIds: peerIds)
|
|
}
|
|
|
|
public func getSubscriptionsStoriesState(key: PostboxStorySubscriptionsKey) -> CodableEntry? {
|
|
assert(!self.disposed)
|
|
return self.postbox!.getSubscriptionsStoriesState(key: key)
|
|
}
|
|
|
|
public func setSubscriptionsStoriesState(key: PostboxStorySubscriptionsKey, state: CodableEntry?) {
|
|
assert(!self.disposed)
|
|
self.postbox!.setSubscriptionsStoriesState(key: key, state: state)
|
|
}
|
|
|
|
public func getLocalStoryState() -> CodableEntry? {
|
|
assert(!self.disposed)
|
|
return self.postbox!.getLocalStoryState()
|
|
}
|
|
|
|
public func setLocalStoryState(state: CodableEntry?) {
|
|
assert(!self.disposed)
|
|
self.postbox!.setLocalStoryState(state: state)
|
|
}
|
|
|
|
public func getPeerStoryState(peerId: PeerId) -> StoredStoryPeerState? {
|
|
assert(!self.disposed)
|
|
return self.postbox!.getPeerStoryState(peerId: peerId)
|
|
}
|
|
|
|
public func setPeerStoryState(peerId: PeerId, state: StoredStoryPeerState?) {
|
|
assert(!self.disposed)
|
|
self.postbox!.setPeerStoryState(peerId: peerId, state: state)
|
|
}
|
|
|
|
public func setStoryItems(peerId: PeerId, items: [StoryItemsTableEntry]) {
|
|
assert(!self.disposed)
|
|
self.postbox!.setStoryItems(peerId: peerId, items: items)
|
|
}
|
|
|
|
public func setStoryItemsInexactMaxId(peerId: PeerId, id: Int32) {
|
|
assert(!self.disposed)
|
|
self.postbox!.setStoryItemsInexactMaxId(peerId: peerId, id: id)
|
|
}
|
|
|
|
public func clearStoryItemsInexactMaxId(peerId: PeerId) {
|
|
assert(!self.disposed)
|
|
self.postbox!.clearStoryItemsInexactMaxId(peerId: peerId)
|
|
}
|
|
|
|
public func getStoryItems(peerId: PeerId) -> [StoryItemsTableEntry] {
|
|
return self.postbox!.getStoryItems(peerId: peerId)
|
|
}
|
|
|
|
public func setStory(id: StoryId, value: CodableEntry) {
|
|
assert(!self.disposed)
|
|
self.postbox!.setStory(id: id, value: value)
|
|
}
|
|
|
|
public func getStory(id: StoryId) -> CodableEntry? {
|
|
return self.postbox!.getStory(id: id)
|
|
}
|
|
|
|
public func getExpiredStoryIds(belowTimestamp: Int32) -> [StoryId] {
|
|
return self.postbox!.storyItemsTable.getExpiredIds(belowTimestamp: belowTimestamp)
|
|
}
|
|
|
|
public func getPeerStoryStats(peerId: PeerId) -> PeerStoryStats? {
|
|
return fetchPeerStoryStats(postbox: self.postbox!, peerId: peerId)
|
|
}
|
|
|
|
public func searchSubPeers(peerId: PeerId, query: String, indexNameMapping: [PeerId: [PeerIndexNameRepresentation]]) -> [Peer] {
|
|
let allThreads = self.postbox!.messageHistoryThreadIndexTable.getAll(peerId: peerId)
|
|
var matchingPeers: [(Peer, MessageIndex)] = []
|
|
for (threadId, index, _) in allThreads {
|
|
if let peer = self.postbox!.peerTable.get(PeerId(threadId)) {
|
|
if let mappings = indexNameMapping[peer.id] {
|
|
inner: for mapping in mappings {
|
|
if mapping.matchesByTokens(query) {
|
|
matchingPeers.append((peer, index))
|
|
break inner
|
|
}
|
|
}
|
|
} else if peer.indexName.matchesByTokens(query) {
|
|
matchingPeers.append((peer, index))
|
|
}
|
|
}
|
|
}
|
|
return matchingPeers.sorted(by: { $0.1 > $1.1 }).map(\.0)
|
|
}
|
|
|
|
public func reindexSavedMessagesCustomTagsWithTagsIfNeeded(peerId: PeerId, threadId: Int64?, tag: MemoryBuffer) {
|
|
assert(!self.disposed)
|
|
self.postbox!.reindexSavedMessagesCustomTagsWithTagsIfNeeded(peerId: peerId, threadId: threadId, tag: tag)
|
|
}
|
|
}
|
|
|
|
public enum PostboxResult {
|
|
case upgrading(Float)
|
|
case postbox(Postbox)
|
|
case error
|
|
}
|
|
|
|
func debugSaveState(basePath: String, name: String) {
|
|
let path = basePath + name
|
|
let _ = try? FileManager.default.removeItem(atPath: path)
|
|
do {
|
|
try FileManager.default.copyItem(atPath: basePath, toPath: path)
|
|
} catch (let e) {
|
|
print("(Postbox debugSaveState: error \(e))")
|
|
}
|
|
}
|
|
|
|
func debugRestoreState(basePath: String, name: String) {
|
|
let path = basePath + name
|
|
if FileManager.default.fileExists(atPath: path) {
|
|
let _ = try? FileManager.default.removeItem(atPath: basePath)
|
|
do {
|
|
try FileManager.default.copyItem(atPath: path, toPath: basePath)
|
|
} catch (let e) {
|
|
print("(Postbox debugRestoreState: error \(e))")
|
|
}
|
|
} else {
|
|
print("(Postbox debugRestoreState: path doesn't exist")
|
|
}
|
|
}
|
|
|
|
public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isMainProcess: Bool, isTemporary: Bool, isReadOnly: Bool, useCopy: Bool, useCaches: Bool, removeDatabaseOnError: Bool) -> Signal<PostboxResult, NoError> {
|
|
let queue = Postbox.sharedQueue
|
|
return Signal { subscriber in
|
|
queue.async {
|
|
postboxLog("openPostbox, basePath: \(basePath), useCopy: \(useCopy)")
|
|
|
|
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
|
|
|
|
var tempDir: TempBoxDirectory?
|
|
let dbBasePath: String
|
|
if useCopy {
|
|
let directory = TempBox.shared.tempDirectory()
|
|
tempDir = directory
|
|
|
|
let originalBasePath = basePath + "/db"
|
|
if let originalFiles = try? FileManager.default.contentsOfDirectory(atPath: originalBasePath) {
|
|
do {
|
|
for fileName in originalFiles {
|
|
try FileManager.default.copyItem(atPath: originalBasePath + "/" + fileName, toPath: directory.path + "/" + fileName)
|
|
}
|
|
} catch let e {
|
|
postboxLog("openPostbox useCopy error: \(e)")
|
|
subscriber.putNext(.error)
|
|
return
|
|
}
|
|
} else {
|
|
postboxLog("openPostbox, error1")
|
|
subscriber.putNext(.error)
|
|
return
|
|
}
|
|
dbBasePath = directory.path
|
|
} else {
|
|
dbBasePath = basePath + "/db"
|
|
}
|
|
|
|
#if DEBUG
|
|
//debugSaveState(basePath: basePath + "/db", name: "previous2")
|
|
//debugRestoreState(basePath: basePath + "/db", name: "previous2")
|
|
#endif
|
|
|
|
let startTime = CFAbsoluteTimeGetCurrent()
|
|
|
|
postboxLog("openPostbox, initialize SqliteValueBox")
|
|
|
|
guard var valueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
|
postboxLog("openPostbox, SqliteValueBox upgrading progress \(progress)")
|
|
subscriber.putNext(.upgrading(progress))
|
|
}) else {
|
|
postboxLog("openPostbox, SqliteValueBox open error")
|
|
subscriber.putNext(.error)
|
|
return
|
|
}
|
|
|
|
loop: while true {
|
|
let metadataTable = MetadataTable(valueBox: valueBox, table: MetadataTable.tableSpec(0), useCaches: useCaches)
|
|
|
|
let userVersion: Int32? = metadataTable.userVersion()
|
|
let currentUserVersion: Int32 = 25
|
|
|
|
postboxLog("openPostbox, current userVersion: \(String(describing: userVersion ?? nil))")
|
|
|
|
if let userVersion = userVersion {
|
|
if userVersion != currentUserVersion {
|
|
if isTemporary {
|
|
postboxLog("openPostbox, isTemporary = true, not upgrading")
|
|
subscriber.putNext(.error)
|
|
return
|
|
} else {
|
|
if userVersion > currentUserVersion {
|
|
postboxLog("Version \(userVersion) is newer than supported")
|
|
assertionFailure("Version \(userVersion) is newer than supported")
|
|
valueBox.drop()
|
|
guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
|
subscriber.putNext(.upgrading(progress))
|
|
}) else {
|
|
subscriber.putNext(.error)
|
|
return
|
|
}
|
|
valueBox = updatedValueBox
|
|
} else {
|
|
if let operation = registeredUpgrades()[userVersion] {
|
|
switch operation {
|
|
case let .inplace(f):
|
|
valueBox.begin()
|
|
f(metadataTable, valueBox, { progress in
|
|
subscriber.putNext(.upgrading(progress))
|
|
})
|
|
valueBox.commit()
|
|
case let .standalone(f):
|
|
let updatedPath = f(queue, basePath, valueBox, encryptionParameters, { progress in
|
|
subscriber.putNext(.upgrading(progress))
|
|
})
|
|
if let updatedPath = updatedPath {
|
|
valueBox.internalClose()
|
|
let _ = try? FileManager.default.removeItem(atPath: dbBasePath)
|
|
let _ = try? FileManager.default.moveItem(atPath: updatedPath, toPath: dbBasePath)
|
|
guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
|
subscriber.putNext(.upgrading(progress))
|
|
}) else {
|
|
subscriber.putNext(.error)
|
|
return
|
|
}
|
|
valueBox = updatedValueBox
|
|
}
|
|
}
|
|
continue loop
|
|
} else {
|
|
assertionFailure("Couldn't find any upgrade for \(userVersion)")
|
|
postboxLog("Couldn't find any upgrade for \(userVersion)")
|
|
valueBox.drop()
|
|
guard let updatedValueBox = SqliteValueBox(basePath: dbBasePath, queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
|
subscriber.putNext(.upgrading(progress))
|
|
}) else {
|
|
subscriber.putNext(.error)
|
|
return
|
|
}
|
|
valueBox = updatedValueBox
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
metadataTable.setUserVersion(currentUserVersion)
|
|
}
|
|
|
|
let endTime = CFAbsoluteTimeGetCurrent()
|
|
postboxLog("Postbox load took \((endTime - startTime) * 1000.0) ms")
|
|
|
|
subscriber.putNext(.postbox(Postbox(queue: queue, basePath: basePath, seedConfiguration: seedConfiguration, valueBox: valueBox, timestampForAbsoluteTimeBasedOperations: timestampForAbsoluteTimeBasedOperations, isMainProcess: isMainProcess, isTemporary: isTemporary, tempDir: tempDir, useCaches: useCaches)))
|
|
|
|
postboxLog("openPostbox, putCompletion")
|
|
|
|
subscriber.putCompletion()
|
|
break
|
|
}
|
|
}
|
|
|
|
return EmptyDisposable
|
|
}
|
|
}
|
|
|
|
final class PostboxImpl {
|
|
public let queue: Queue
|
|
public let seedConfiguration: SeedConfiguration
|
|
private let basePath: String
|
|
let valueBox: SqliteValueBox
|
|
private let tempDir: TempBoxDirectory?
|
|
|
|
private let ipcNotificationsDisposable = MetaDisposable()
|
|
|
|
private var transactionStateVersion: Int64 = 0
|
|
|
|
private var viewTracker: ViewTracker!
|
|
private var nextViewId = 0
|
|
|
|
private var currentUpdatedState: PostboxCoding?
|
|
private var currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
|
|
private var currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion] = [:]
|
|
private var currentUnsentOperations: [IntermediateMessageHistoryUnsentOperation] = []
|
|
private var currentUpdatedSynchronizeReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?] = [:]
|
|
private var currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool] = [:]
|
|
private var currentUpdatedMedia: [MediaId: Media?] = [:]
|
|
private var currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation] = []
|
|
private var currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation] = []
|
|
|
|
private var currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:]
|
|
private var currentUpdatedPeers: [PeerId: Peer] = [:]
|
|
private var currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)] = [:]
|
|
private var currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp] = [:]
|
|
private var currentUpdatedCachedPeerData: [PeerId: CachedPeerData] = [:]
|
|
private var currentUpdatedPeerPresences: [PeerId: PeerPresence] = [:]
|
|
private var currentUpdatedPeerChatListEmbeddedStates = Set<PeerId>()
|
|
private var currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState] = [:]
|
|
private var currentUpdatedGroupTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary] = [:]
|
|
private var currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation] = []
|
|
private var currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation] = []
|
|
private var currentPreferencesOperations: [PreferencesOperation] = []
|
|
private var currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]] = [:]
|
|
private var currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]] = [:]
|
|
private var currentItemCollectionInfosOperations: [ItemCollectionInfosOperation] = []
|
|
private var currentUpdatedPeerChatStates = Set<PeerId>()
|
|
private var currentPendingMessageActionsOperations: [PendingMessageActionsOperation] = []
|
|
private var currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32] = [:]
|
|
private var currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey : MessageHistoryTagNamespaceSummary] = [:]
|
|
private var currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation] = []
|
|
private var currentUpdatedPendingPeerNotificationSettings = Set<PeerId>()
|
|
private var currentGroupIdsWithUpdatedReadStats = Set<PeerGroupId>()
|
|
|
|
private var currentChatListOperations: [PeerGroupId: [ChatListOperation]] = [:]
|
|
private var currentReplaceRemoteContactCount: Int32?
|
|
private var currentReplacedContactPeerIds: Set<PeerId>?
|
|
private var currentUpdatedMasterClientId: Int64?
|
|
|
|
private var currentReplacedAdditionalChatListItems: [AdditionalChatListItem]?
|
|
private var currentUpdatedNoticeEntryKeys = Set<NoticeEntryKey>()
|
|
private var currentUpdatedCacheEntryKeys = Set<ItemCacheEntryId>()
|
|
|
|
private var currentUpdatedPeerThreadCombinedStates = Set<PeerId>()
|
|
private var currentUpdatedPinnedThreads = Set<PeerId>()
|
|
|
|
private var currentHiddenChatIds: [PeerId: Bag<Void>] = [:]
|
|
private var currentUpdatedHiddenPeerIds: Bool = false
|
|
|
|
private var currentStoryGeneralStatesEvents: [StoryGeneralStatesTable.Event] = []
|
|
private var currentStoryPeerStatesEvents: [StoryPeerStatesTable.Event] = []
|
|
private var currentStorySubscriptionsEvents: [StorySubscriptionsTable.Event] = []
|
|
private var currentStoryItemsEvents: [StoryItemsTable.Event] = []
|
|
private var currentStoryTopItemEvents: [StoryTopItemsTable.Event] = []
|
|
private var currentStoryEvents: [StoryTable.Event] = []
|
|
|
|
var hiddenChatIds: Set<PeerId> {
|
|
if self.currentHiddenChatIds.isEmpty {
|
|
return Set()
|
|
} else {
|
|
var result = Set<PeerId>()
|
|
for (peerId, bag) in self.currentHiddenChatIds {
|
|
if !bag.isEmpty {
|
|
result.insert(peerId)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
|
|
func isChatHidden(peerId: PeerId) -> Bool {
|
|
if let bag = self.currentHiddenChatIds[peerId], !bag.isEmpty {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
var hasHiddenChatIds: Bool {
|
|
for (_, bag) in self.currentHiddenChatIds {
|
|
if !bag.isEmpty {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
private var currentNeedsReindexUnreadCounters: Bool = false
|
|
|
|
private let statePipe: ValuePipe<PostboxCoding> = ValuePipe()
|
|
private var masterClientId = Promise<Int64>()
|
|
|
|
private var sessionClientId: Int64 = {
|
|
var value: Int64 = 0
|
|
arc4random_buf(&value, 8)
|
|
return value
|
|
}()
|
|
|
|
private var nextUniqueId: UInt32 = 1
|
|
func takeNextUniqueId() -> UInt32 {
|
|
assert(self.queue.isCurrent())
|
|
let value = self.nextUniqueId
|
|
self.nextUniqueId += 1
|
|
return value
|
|
}
|
|
|
|
let tables: [Table]
|
|
|
|
let metadataTable: MetadataTable
|
|
let keychainTable: KeychainTable
|
|
let peerTable: PeerTable
|
|
let peerNotificationSettingsTable: PeerNotificationSettingsTable
|
|
let pendingPeerNotificationSettingsIndexTable: PendingPeerNotificationSettingsIndexTable
|
|
let peerNotificationSettingsBehaviorTable: PeerNotificationSettingsBehaviorTable
|
|
let peerNotificationSettingsBehaviorIndexTable: PeerNotificationSettingsBehaviorIndexTable
|
|
let cachedPeerDataTable: CachedPeerDataTable
|
|
let peerPresenceTable: PeerPresenceTable
|
|
let globalMessageIdsTable: GlobalMessageIdsTable
|
|
let globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable
|
|
let messageHistoryIndexTable: MessageHistoryIndexTable
|
|
let messageHistoryTable: MessageHistoryTable
|
|
let mediaTable: MessageMediaTable
|
|
let chatListIndexTable: ChatListIndexTable
|
|
let chatListTable: ChatListTable
|
|
let additionalChatListItemsTable: AdditionalChatListItemsTable
|
|
let messageHistoryMetadataTable: MessageHistoryMetadataTable
|
|
let messageHistoryUnsentTable: MessageHistoryUnsentTable
|
|
let messageHistoryFailedTable: MessageHistoryFailedTable
|
|
let messageHistoryTagsTable: MessageHistoryTagsTable
|
|
let messageHistoryThreadsTable: MessageHistoryThreadsTable
|
|
let peerThreadCombinedStateTable: PeerThreadCombinedStateTable
|
|
let peerThreadsSummaryTable: PeerThreadsSummaryTable
|
|
let messageHistoryThreadTagsTable: MessageHistoryThreadTagsTable
|
|
let messageHistoryThreadHoleIndexTable: MessageHistoryThreadHoleIndexTable
|
|
let messageHistoryThreadReverseIndexTable: MessageHistoryThreadReverseIndexTable
|
|
let messageHistoryThreadIndexTable: MessageHistoryThreadIndexTable
|
|
let messageHistoryThreadPinnedTable: MessageHistoryThreadPinnedTable
|
|
let globalMessageHistoryTagsTable: GlobalMessageHistoryTagsTable
|
|
let localMessageHistoryTagsTable: LocalMessageHistoryTagsTable
|
|
let peerChatStateTable: PeerChatStateTable
|
|
let readStateTable: MessageHistoryReadStateTable
|
|
let synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable
|
|
let synchronizeGroupMessageStatsTable: InvalidatedGroupMessageStatsTable
|
|
let contactsTable: ContactTable
|
|
let itemCollectionInfoTable: ItemCollectionInfoTable
|
|
let itemCollectionItemTable: ItemCollectionItemTable
|
|
let itemCollectionReverseIndexTable: ReverseIndexReferenceTable<ItemCollectionItemReverseIndexReference>
|
|
let peerChatInterfaceStateTable: PeerChatInterfaceStateTable
|
|
let peerChatThreadInterfaceStateTable: PeerChatThreadInterfaceStateTable
|
|
let itemCacheMetaTable: ItemCacheMetaTable
|
|
let itemCacheTable: ItemCacheTable
|
|
let peerNameTokenIndexTable: ReverseIndexReferenceTable<PeerIdReverseIndexReference>
|
|
let peerNameIndexTable: PeerNameIndexTable
|
|
let reverseAssociatedPeerTable: ReverseAssociatedPeerTable
|
|
let peerChatTopTaggedMessageIdsTable: PeerChatTopTaggedMessageIdsTable
|
|
let peerOperationLogMetadataTable: PeerOperationLogMetadataTable
|
|
let peerMergedOperationLogIndexTable: PeerMergedOperationLogIndexTable
|
|
let peerOperationLogTable: PeerOperationLogTable
|
|
let timestampBasedMessageAttributesTable: TimestampBasedMessageAttributesTable
|
|
let timestampBasedMessageAttributesIndexTable: TimestampBasedMessageAttributesIndexTable
|
|
let preferencesTable: PreferencesTable
|
|
let orderedItemListTable: OrderedItemListTable
|
|
let orderedItemListIndexTable: OrderedItemListIndexTable
|
|
let textIndexTable: MessageHistoryTextIndexTable
|
|
let unorderedItemListTable: UnorderedItemListTable
|
|
let noticeTable: NoticeTable
|
|
let messageHistoryTagsSummaryTable: MessageHistoryTagsSummaryTable
|
|
let invalidatedMessageHistoryTagsSummaryTable: InvalidatedMessageHistoryTagsSummaryTable
|
|
let pendingMessageActionsTable: PendingMessageActionsTable
|
|
let pendingMessageActionsMetadataTable: PendingMessageActionsMetadataTable
|
|
let deviceContactImportInfoTable: DeviceContactImportInfoTable
|
|
let messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable
|
|
let messageCustomTagIdTable: MessageCustomTagIdTable
|
|
let messageCustomTagHoleIndexTable: MessageCustomTagHoleIndexTable
|
|
let messageCustomTagWithTagHoleIndexTable: MessageCustomTagWithTagHoleIndexTable
|
|
let messageCustomTagTable: MessageCustomTagTable
|
|
let messageCustomTagWithTagTable: MessageCustomTagWithTagTable
|
|
let groupMessageStatsTable: GroupMessageStatsTable
|
|
let peerTimeoutPropertiesTable: PeerTimeoutPropertiesTable
|
|
let storyGeneralStatesTable: StoryGeneralStatesTable
|
|
let storyPeerStatesTable: StoryPeerStatesTable
|
|
let storySubscriptionsTable: StorySubscriptionsTable
|
|
let storyItemsTable: StoryItemsTable
|
|
let storyTopItemsTable: StoryTopItemsTable
|
|
let storyTable: StoryTable
|
|
|
|
//temporary
|
|
let peerRatingTable: RatingTable<PeerId>
|
|
|
|
var installedMessageActionsByPeerId: [PeerId: Bag<([StoreMessage], Transaction) -> Void>] = [:]
|
|
var installedStoreOrUpdateMessageActionsByPeerId: [PeerId: Bag<StoreOrUpdateMessageAction>] = [:]
|
|
|
|
init(queue: Queue, basePath: String, seedConfiguration: SeedConfiguration, valueBox: SqliteValueBox, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool, tempDir: TempBoxDirectory?, useCaches: Bool, isInTransaction: Atomic<Bool>) {
|
|
assert(queue.isCurrent())
|
|
|
|
let startTime = CFAbsoluteTimeGetCurrent()
|
|
|
|
self.queue = queue
|
|
self.basePath = basePath
|
|
self.seedConfiguration = seedConfiguration
|
|
self.tempDir = tempDir
|
|
|
|
self.valueBox = valueBox
|
|
|
|
self.isInTransaction = isInTransaction
|
|
|
|
self.metadataTable = MetadataTable(valueBox: self.valueBox, table: MetadataTable.tableSpec(0), useCaches: useCaches)
|
|
|
|
self.keychainTable = KeychainTable(valueBox: self.valueBox, table: KeychainTable.tableSpec(1), useCaches: useCaches)
|
|
self.reverseAssociatedPeerTable = ReverseAssociatedPeerTable(valueBox: self.valueBox, table:ReverseAssociatedPeerTable.tableSpec(40), useCaches: useCaches)
|
|
self.peerTimeoutPropertiesTable = PeerTimeoutPropertiesTable(valueBox: self.valueBox, table: PeerTimeoutPropertiesTable.tableSpec(64), useCaches: useCaches)
|
|
self.peerTable = PeerTable(valueBox: self.valueBox, table: PeerTable.tableSpec(2), useCaches: useCaches, reverseAssociatedTable: self.reverseAssociatedPeerTable, peerTimeoutPropertiesTable: self.peerTimeoutPropertiesTable)
|
|
self.globalMessageIdsTable = GlobalMessageIdsTable(valueBox: self.valueBox, table: GlobalMessageIdsTable.tableSpec(3), useCaches: useCaches, seedConfiguration: seedConfiguration)
|
|
self.globallyUniqueMessageIdsTable = MessageGloballyUniqueIdTable(valueBox: self.valueBox, table: MessageGloballyUniqueIdTable.tableSpec(32), useCaches: useCaches)
|
|
self.messageHistoryMetadataTable = MessageHistoryMetadataTable(valueBox: self.valueBox, table: MessageHistoryMetadataTable.tableSpec(10), useCaches: useCaches)
|
|
self.messageHistoryHoleIndexTable = MessageHistoryHoleIndexTable(valueBox: self.valueBox, table: MessageHistoryHoleIndexTable.tableSpec(56), useCaches: useCaches, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration)
|
|
self.messageCustomTagIdTable = MessageCustomTagIdTable(valueBox: valueBox, table: MessageCustomTagIdTable.tableSpec(81), useCaches: useCaches, metadataTable: self.messageHistoryMetadataTable)
|
|
self.messageCustomTagHoleIndexTable = MessageCustomTagHoleIndexTable(valueBox: valueBox, table: MessageCustomTagHoleIndexTable.tableSpec(82), useCaches: useCaches, seedConfiguration: seedConfiguration, metadataTable: self.messageHistoryMetadataTable, tagIdTable: self.messageCustomTagIdTable)
|
|
self.messageCustomTagWithTagHoleIndexTable = MessageCustomTagWithTagHoleIndexTable(valueBox: valueBox, table: MessageCustomTagWithTagHoleIndexTable.tableSpec(84), useCaches: useCaches, seedConfiguration: seedConfiguration, metadataTable: self.messageHistoryMetadataTable, tagIdTable: self.messageCustomTagIdTable)
|
|
self.messageCustomTagTable = MessageCustomTagTable(valueBox: valueBox, table: MessageCustomTagTable.tableSpec(83), useCaches: useCaches, messageCustomTagIdTable: self.messageCustomTagIdTable)
|
|
self.messageHistoryUnsentTable = MessageHistoryUnsentTable(valueBox: self.valueBox, table: MessageHistoryUnsentTable.tableSpec(11), useCaches: useCaches)
|
|
self.messageHistoryFailedTable = MessageHistoryFailedTable(valueBox: self.valueBox, table: MessageHistoryFailedTable.tableSpec(49), useCaches: useCaches)
|
|
self.invalidatedMessageHistoryTagsSummaryTable = InvalidatedMessageHistoryTagsSummaryTable(valueBox: self.valueBox, table: InvalidatedMessageHistoryTagsSummaryTable.tableSpec(47), useCaches: useCaches)
|
|
self.messageHistoryTagsSummaryTable = MessageHistoryTagsSummaryTable(valueBox: self.valueBox, table: MessageHistoryTagsSummaryTable.tableSpec(44), useCaches: useCaches, invalidateTable: self.invalidatedMessageHistoryTagsSummaryTable)
|
|
self.messageCustomTagWithTagTable = MessageCustomTagWithTagTable(valueBox: valueBox, table: MessageCustomTagWithTagTable.tableSpec(85), useCaches: useCaches, messageCustomTagIdTable: self.messageCustomTagIdTable, seedConfiguration: seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable)
|
|
self.pendingMessageActionsMetadataTable = PendingMessageActionsMetadataTable(valueBox: self.valueBox, table: PendingMessageActionsMetadataTable.tableSpec(45), useCaches: useCaches)
|
|
self.pendingMessageActionsTable = PendingMessageActionsTable(valueBox: self.valueBox, table: PendingMessageActionsTable.tableSpec(46), useCaches: useCaches, metadataTable: self.pendingMessageActionsMetadataTable)
|
|
self.messageHistoryTagsTable = MessageHistoryTagsTable(valueBox: self.valueBox, table: MessageHistoryTagsTable.tableSpec(12), useCaches: useCaches, seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable)
|
|
self.messageHistoryThreadsTable = MessageHistoryThreadsTable(valueBox: self.valueBox, table: MessageHistoryThreadsTable.tableSpec(62), useCaches: useCaches)
|
|
self.peerThreadCombinedStateTable = PeerThreadCombinedStateTable(valueBox: self.valueBox, table: PeerThreadCombinedStateTable.tableSpec(77), useCaches: useCaches)
|
|
self.peerThreadsSummaryTable = PeerThreadsSummaryTable(valueBox: self.valueBox, table: PeerThreadsSummaryTable.tableSpec(75), useCaches: useCaches, seedConfiguration: self.seedConfiguration)
|
|
self.messageHistoryThreadTagsTable = MessageHistoryThreadTagsTable(valueBox: self.valueBox, table: MessageHistoryThreadTagsTable.tableSpec(71), useCaches: useCaches, seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable)
|
|
self.messageHistoryThreadHoleIndexTable = MessageHistoryThreadHoleIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadHoleIndexTable.tableSpec(63), useCaches: useCaches, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration)
|
|
self.messageHistoryThreadReverseIndexTable = MessageHistoryThreadReverseIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadReverseIndexTable.tableSpec(72), useCaches: useCaches)
|
|
self.messageHistoryThreadIndexTable = MessageHistoryThreadIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadIndexTable.tableSpec(73), reverseIndexTable: self.messageHistoryThreadReverseIndexTable, seedConfiguration: seedConfiguration, useCaches: useCaches)
|
|
self.messageHistoryThreadPinnedTable = MessageHistoryThreadPinnedTable(valueBox: self.valueBox, table: MessageHistoryThreadPinnedTable.tableSpec(76), useCaches: useCaches)
|
|
self.globalMessageHistoryTagsTable = GlobalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(39), useCaches: useCaches)
|
|
self.localMessageHistoryTagsTable = LocalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(52), useCaches: useCaches)
|
|
self.messageHistoryIndexTable = MessageHistoryIndexTable(valueBox: self.valueBox, table: MessageHistoryIndexTable.tableSpec(4), useCaches: useCaches, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, globalMessageIdsTable: self.globalMessageIdsTable, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration)
|
|
self.mediaTable = MessageMediaTable(valueBox: self.valueBox, table: MessageMediaTable.tableSpec(6), useCaches: useCaches)
|
|
self.readStateTable = MessageHistoryReadStateTable(valueBox: self.valueBox, table: MessageHistoryReadStateTable.tableSpec(14), useCaches: useCaches, seedConfiguration: seedConfiguration)
|
|
self.synchronizeReadStateTable = MessageHistorySynchronizeReadStateTable(valueBox: self.valueBox, table: MessageHistorySynchronizeReadStateTable.tableSpec(15), useCaches: useCaches)
|
|
self.synchronizeGroupMessageStatsTable = InvalidatedGroupMessageStatsTable(valueBox: self.valueBox, table: InvalidatedGroupMessageStatsTable.tableSpec(59), useCaches: useCaches)
|
|
self.timestampBasedMessageAttributesIndexTable = TimestampBasedMessageAttributesIndexTable(valueBox: self.valueBox, table: TimestampBasedMessageAttributesTable.tableSpec(33), useCaches: useCaches)
|
|
self.timestampBasedMessageAttributesTable = TimestampBasedMessageAttributesTable(valueBox: self.valueBox, table: TimestampBasedMessageAttributesTable.tableSpec(34), useCaches: useCaches, indexTable: self.timestampBasedMessageAttributesIndexTable)
|
|
self.textIndexTable = MessageHistoryTextIndexTable(valueBox: self.valueBox, table: MessageHistoryTextIndexTable.tableSpec(41))
|
|
self.additionalChatListItemsTable = AdditionalChatListItemsTable(valueBox: self.valueBox, table: AdditionalChatListItemsTable.tableSpec(55), useCaches: useCaches)
|
|
self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), useCaches: useCaches, seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, threadsTable: self.messageHistoryThreadsTable, threadTagsTable: self.messageHistoryThreadTagsTable, customTagTable: self.messageCustomTagTable, customTagWithTagTable: self.messageCustomTagWithTagTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, timeBasedAttributesTable: self.timestampBasedMessageAttributesTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable)
|
|
self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13), useCaches: useCaches)
|
|
self.peerNameTokenIndexTable = ReverseIndexReferenceTable<PeerIdReverseIndexReference>(valueBox: self.valueBox, table: ReverseIndexReferenceTable<PeerIdReverseIndexReference>.tableSpec(26), useCaches: useCaches)
|
|
self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), useCaches: useCaches, peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable)
|
|
self.contactsTable = ContactTable(valueBox: self.valueBox, table: ContactTable.tableSpec(16), useCaches: useCaches, peerNameIndexTable: self.peerNameIndexTable)
|
|
self.peerRatingTable = RatingTable<PeerId>(valueBox: self.valueBox, table: RatingTable<PeerId>.tableSpec(17), useCaches: useCaches)
|
|
self.cachedPeerDataTable = CachedPeerDataTable(valueBox: self.valueBox, table: CachedPeerDataTable.tableSpec(18), useCaches: useCaches)
|
|
self.pendingPeerNotificationSettingsIndexTable = PendingPeerNotificationSettingsIndexTable(valueBox: self.valueBox, table: PendingPeerNotificationSettingsIndexTable.tableSpec(48), useCaches: useCaches)
|
|
self.peerNotificationSettingsBehaviorIndexTable = PeerNotificationSettingsBehaviorIndexTable(valueBox: self.valueBox, table: PeerNotificationSettingsBehaviorIndexTable.tableSpec(60), useCaches: useCaches)
|
|
self.peerNotificationSettingsBehaviorTable = PeerNotificationSettingsBehaviorTable(valueBox: self.valueBox, table: PeerNotificationSettingsBehaviorTable.tableSpec(61), useCaches: useCaches, indexTable: self.peerNotificationSettingsBehaviorIndexTable)
|
|
self.peerNotificationSettingsTable = PeerNotificationSettingsTable(valueBox: self.valueBox, table: PeerNotificationSettingsTable.tableSpec(19), useCaches: useCaches, pendingIndexTable: self.pendingPeerNotificationSettingsIndexTable, behaviorTable: self.peerNotificationSettingsBehaviorTable)
|
|
self.peerPresenceTable = PeerPresenceTable(valueBox: self.valueBox, table: PeerPresenceTable.tableSpec(20), useCaches: useCaches)
|
|
self.itemCollectionInfoTable = ItemCollectionInfoTable(valueBox: self.valueBox, table: ItemCollectionInfoTable.tableSpec(21), useCaches: useCaches)
|
|
self.itemCollectionReverseIndexTable = ReverseIndexReferenceTable<ItemCollectionItemReverseIndexReference>(valueBox: self.valueBox, table: ReverseIndexReferenceTable<ItemCollectionItemReverseIndexReference>.tableSpec(36), useCaches: useCaches)
|
|
self.itemCollectionItemTable = ItemCollectionItemTable(valueBox: self.valueBox, table: ItemCollectionItemTable.tableSpec(22), useCaches: useCaches, reverseIndexTable: self.itemCollectionReverseIndexTable)
|
|
self.peerChatInterfaceStateTable = PeerChatInterfaceStateTable(valueBox: self.valueBox, table: PeerChatInterfaceStateTable.tableSpec(67), useCaches: useCaches)
|
|
self.peerChatThreadInterfaceStateTable = PeerChatThreadInterfaceStateTable(valueBox: self.valueBox, table: PeerChatThreadInterfaceStateTable.tableSpec(68), useCaches: useCaches)
|
|
self.itemCacheMetaTable = ItemCacheMetaTable(valueBox: self.valueBox, table: ItemCacheMetaTable.tableSpec(24), useCaches: useCaches)
|
|
self.itemCacheTable = ItemCacheTable(valueBox: self.valueBox, table: ItemCacheTable.tableSpec(25), useCaches: useCaches)
|
|
self.chatListIndexTable = ChatListIndexTable(valueBox: self.valueBox, table: ChatListIndexTable.tableSpec(8), useCaches: useCaches, peerNameIndexTable: self.peerNameIndexTable, metadataTable: self.messageHistoryMetadataTable, readStateTable: self.readStateTable, notificationSettingsTable: self.peerNotificationSettingsTable)
|
|
self.chatListTable = ChatListTable(valueBox: self.valueBox, table: ChatListTable.tableSpec(9), useCaches: useCaches, indexTable: self.chatListIndexTable, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration)
|
|
self.peerChatTopTaggedMessageIdsTable = PeerChatTopTaggedMessageIdsTable(valueBox: self.valueBox, table: PeerChatTopTaggedMessageIdsTable.tableSpec(28), useCaches: useCaches)
|
|
self.peerOperationLogMetadataTable = PeerOperationLogMetadataTable(valueBox: self.valueBox, table: PeerOperationLogMetadataTable.tableSpec(29), useCaches: useCaches)
|
|
self.peerMergedOperationLogIndexTable = PeerMergedOperationLogIndexTable(valueBox: self.valueBox, table: PeerMergedOperationLogIndexTable.tableSpec(30), useCaches: useCaches, metadataTable: self.peerOperationLogMetadataTable)
|
|
self.peerOperationLogTable = PeerOperationLogTable(valueBox: self.valueBox, table: PeerOperationLogTable.tableSpec(31), useCaches: useCaches, metadataTable: self.peerOperationLogMetadataTable, mergedIndexTable: self.peerMergedOperationLogIndexTable)
|
|
self.preferencesTable = PreferencesTable(valueBox: self.valueBox, table: PreferencesTable.tableSpec(35), useCaches: useCaches)
|
|
self.orderedItemListIndexTable = OrderedItemListIndexTable(valueBox: self.valueBox, table: OrderedItemListIndexTable.tableSpec(37), useCaches: useCaches)
|
|
self.orderedItemListTable = OrderedItemListTable(valueBox: self.valueBox, table: OrderedItemListTable.tableSpec(38), useCaches: useCaches, indexTable: self.orderedItemListIndexTable)
|
|
self.unorderedItemListTable = UnorderedItemListTable(valueBox: self.valueBox, table: UnorderedItemListTable.tableSpec(42), useCaches: useCaches)
|
|
self.noticeTable = NoticeTable(valueBox: self.valueBox, table: NoticeTable.tableSpec(43), useCaches: useCaches)
|
|
self.deviceContactImportInfoTable = DeviceContactImportInfoTable(valueBox: self.valueBox, table: DeviceContactImportInfoTable.tableSpec(54), useCaches: useCaches)
|
|
self.groupMessageStatsTable = GroupMessageStatsTable(valueBox: self.valueBox, table: GroupMessageStatsTable.tableSpec(58), useCaches: useCaches)
|
|
|
|
self.storyGeneralStatesTable = StoryGeneralStatesTable(valueBox: self.valueBox, table: StoryGeneralStatesTable.tableSpec(65), useCaches: useCaches)
|
|
self.storyPeerStatesTable = StoryPeerStatesTable(valueBox: self.valueBox, table: StoryPeerStatesTable.tableSpec(79), useCaches: useCaches)
|
|
|
|
self.storySubscriptionsTable = StorySubscriptionsTable(valueBox: self.valueBox, table: StorySubscriptionsTable.tableSpec(78), useCaches: useCaches)
|
|
self.storyItemsTable = StoryItemsTable(valueBox: self.valueBox, table: StoryItemsTable.tableSpec(69), useCaches: useCaches)
|
|
self.storyTopItemsTable = StoryTopItemsTable(valueBox: self.valueBox, table: StoryTopItemsTable.tableSpec(80), useCaches: useCaches)
|
|
self.storyTable = StoryTable(valueBox: self.valueBox, table: StoryTable.tableSpec(70), useCaches: useCaches)
|
|
|
|
var tables: [Table] = []
|
|
tables.append(self.metadataTable)
|
|
tables.append(self.keychainTable)
|
|
tables.append(self.peerTable)
|
|
tables.append(self.globalMessageIdsTable)
|
|
tables.append(self.globallyUniqueMessageIdsTable)
|
|
tables.append(self.messageHistoryMetadataTable)
|
|
tables.append(self.messageHistoryUnsentTable)
|
|
tables.append(self.messageHistoryFailedTable)
|
|
tables.append(self.messageHistoryTagsTable)
|
|
tables.append(self.messageHistoryThreadsTable)
|
|
tables.append(self.peerThreadCombinedStateTable)
|
|
tables.append(self.peerThreadsSummaryTable)
|
|
tables.append(self.messageHistoryThreadTagsTable)
|
|
tables.append(self.messageHistoryThreadHoleIndexTable)
|
|
tables.append(self.messageHistoryThreadReverseIndexTable)
|
|
tables.append(self.messageHistoryThreadIndexTable)
|
|
tables.append(self.messageHistoryThreadPinnedTable)
|
|
tables.append(self.globalMessageHistoryTagsTable)
|
|
tables.append(self.localMessageHistoryTagsTable)
|
|
tables.append(self.messageHistoryIndexTable)
|
|
tables.append(self.mediaTable)
|
|
tables.append(self.readStateTable)
|
|
tables.append(self.synchronizeReadStateTable)
|
|
tables.append(self.synchronizeGroupMessageStatsTable)
|
|
tables.append(self.messageHistoryTable)
|
|
tables.append(self.chatListIndexTable)
|
|
tables.append(self.chatListTable)
|
|
tables.append(self.additionalChatListItemsTable)
|
|
tables.append(self.peerChatStateTable)
|
|
tables.append(self.contactsTable)
|
|
tables.append(self.peerRatingTable)
|
|
tables.append(self.peerNotificationSettingsTable)
|
|
tables.append(self.peerNotificationSettingsBehaviorIndexTable)
|
|
tables.append(self.peerNotificationSettingsBehaviorTable)
|
|
tables.append(self.cachedPeerDataTable)
|
|
tables.append(self.peerPresenceTable)
|
|
tables.append(self.itemCollectionInfoTable)
|
|
tables.append(self.itemCollectionItemTable)
|
|
tables.append(self.itemCollectionReverseIndexTable)
|
|
tables.append(self.peerChatInterfaceStateTable)
|
|
tables.append(self.peerChatThreadInterfaceStateTable)
|
|
tables.append(self.itemCacheMetaTable)
|
|
tables.append(self.itemCacheTable)
|
|
tables.append(self.peerNameIndexTable)
|
|
tables.append(self.reverseAssociatedPeerTable)
|
|
tables.append(self.peerNameTokenIndexTable)
|
|
tables.append(self.peerChatTopTaggedMessageIdsTable)
|
|
tables.append(self.peerOperationLogMetadataTable)
|
|
tables.append(self.peerMergedOperationLogIndexTable)
|
|
tables.append(self.peerOperationLogTable)
|
|
tables.append(self.timestampBasedMessageAttributesTable)
|
|
tables.append(self.timestampBasedMessageAttributesIndexTable)
|
|
tables.append(self.preferencesTable)
|
|
tables.append(self.orderedItemListTable)
|
|
tables.append(self.orderedItemListIndexTable)
|
|
tables.append(self.unorderedItemListTable)
|
|
tables.append(self.noticeTable)
|
|
tables.append(self.messageHistoryTagsSummaryTable)
|
|
tables.append(self.invalidatedMessageHistoryTagsSummaryTable)
|
|
tables.append(self.pendingMessageActionsTable)
|
|
tables.append(self.pendingMessageActionsMetadataTable)
|
|
tables.append(self.deviceContactImportInfoTable)
|
|
tables.append(self.messageHistoryHoleIndexTable)
|
|
tables.append(self.messageCustomTagIdTable)
|
|
tables.append(self.messageCustomTagHoleIndexTable)
|
|
tables.append(self.messageCustomTagWithTagHoleIndexTable)
|
|
tables.append(self.messageCustomTagTable)
|
|
tables.append(self.messageCustomTagWithTagTable)
|
|
tables.append(self.groupMessageStatsTable)
|
|
tables.append(self.peerTimeoutPropertiesTable)
|
|
tables.append(self.storyGeneralStatesTable)
|
|
tables.append(self.storyPeerStatesTable)
|
|
tables.append(self.storySubscriptionsTable)
|
|
tables.append(self.storyItemsTable)
|
|
tables.append(self.storyTopItemsTable)
|
|
tables.append(self.storyTable)
|
|
|
|
self.tables = tables
|
|
|
|
self.transactionStateVersion = self.metadataTable.transactionStateVersion()
|
|
|
|
self.viewTracker = ViewTracker(
|
|
queue: self.queue,
|
|
unsentMessageIds: self.messageHistoryUnsentTable.get(),
|
|
synchronizePeerReadStateOperations: self.synchronizeReadStateTable.get(getCombinedPeerReadState: { peerId in
|
|
return self.readStateTable.getCombinedState(peerId)
|
|
})
|
|
)
|
|
|
|
postboxLog("(Postbox initialization took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
|
|
|
let _ = self.transaction({ transaction -> Void in
|
|
let reindexUnreadVersion: Int32 = 2
|
|
if self.messageHistoryMetadataTable.getShouldReindexUnreadCountsState() != reindexUnreadVersion {
|
|
self.messageHistoryMetadataTable.setShouldReindexUnreadCounts(value: true)
|
|
self.messageHistoryMetadataTable.setShouldReindexUnreadCountsState(value: reindexUnreadVersion)
|
|
}
|
|
//#if DEBUG
|
|
if !isTemporary {
|
|
self.messageHistoryMetadataTable.setShouldReindexUnreadCounts(value: true)
|
|
}
|
|
//#endif
|
|
|
|
if !isTemporary && useCaches && self.messageHistoryMetadataTable.shouldReindexUnreadCounts() {
|
|
self.groupMessageStatsTable.removeAll()
|
|
let startTime = CFAbsoluteTimeGetCurrent()
|
|
let (totalStates, summaries) = self.chatListIndexTable.debugReindexUnreadCounts(postbox: self, currentTransaction: transaction)
|
|
|
|
self.messageHistoryMetadataTable.removeAllTotalUnreadStates()
|
|
for (groupId, state) in totalStates {
|
|
self.messageHistoryMetadataTable.setTotalUnreadState(groupId: groupId, state: state)
|
|
}
|
|
for (groupId, summary) in summaries {
|
|
self.groupMessageStatsTable.set(groupId: groupId, summary: summary)
|
|
}
|
|
postboxLog("reindexUnreadCounts took \(CFAbsoluteTimeGetCurrent() - startTime)")
|
|
self.messageHistoryMetadataTable.setShouldReindexUnreadCounts(value: false)
|
|
}
|
|
|
|
if !isTemporary {
|
|
for id in self.messageHistoryUnsentTable.get() {
|
|
transaction.updateMessage(id, update: { message in
|
|
if !message.flags.contains(.Failed) {
|
|
for media in message.media {
|
|
if media.preventsAutomaticMessageSendingFailure() {
|
|
return .skip
|
|
}
|
|
}
|
|
|
|
if Int64(message.timestamp) + 60 * 10 > Int64(timestampForAbsoluteTimeBasedOperations) {
|
|
return .skip
|
|
}
|
|
var flags = StoreMessageFlags(message.flags)
|
|
flags.remove(.Unsent)
|
|
flags.remove(.Sending)
|
|
flags.insert(.Failed)
|
|
var storeForwardInfo: StoreMessageForwardInfo?
|
|
if let forwardInfo = message.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)
|
|
}
|
|
return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media))
|
|
} else {
|
|
return .skip
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}).start()
|
|
}
|
|
|
|
deinit {
|
|
if let tempDir = self.tempDir {
|
|
TempBox.shared.dispose(tempDir)
|
|
}
|
|
}
|
|
|
|
private func takeNextViewId() -> Int {
|
|
let nextId = self.nextViewId
|
|
self.nextViewId += 1
|
|
return nextId
|
|
}
|
|
|
|
fileprivate func setState(_ state: PostboxCoding) {
|
|
self.currentUpdatedState = state
|
|
self.metadataTable.setState(state)
|
|
}
|
|
|
|
fileprivate func getState() -> PostboxCoding? {
|
|
return self.metadataTable.state()
|
|
}
|
|
|
|
public func keychainEntryForKey(_ key: String) -> Data? {
|
|
precondition(self.queue.isCurrent())
|
|
|
|
return self.keychainTable.get(key)
|
|
}
|
|
|
|
private var keychainOperationsDisposable = DisposableSet()
|
|
|
|
public func setKeychainEntryForKey(_ key: String, value: Data) {
|
|
let metaDisposable = MetaDisposable()
|
|
self.keychainOperationsDisposable.add(metaDisposable)
|
|
|
|
let disposable = (self.transaction({ transaction -> Void in
|
|
self.keychainTable.set(key, value: value)
|
|
}) |> afterDisposed { [weak self, weak metaDisposable] in
|
|
if let strongSelf = self, let metaDisposable = metaDisposable {
|
|
strongSelf.keychainOperationsDisposable.remove(metaDisposable)
|
|
}
|
|
}).start()
|
|
metaDisposable.set(disposable)
|
|
}
|
|
|
|
public func removeKeychainEntryForKey(_ key: String) {
|
|
let metaDisposable = MetaDisposable()
|
|
self.keychainOperationsDisposable.add(metaDisposable)
|
|
|
|
let disposable = (self.transaction({ transaction -> Void in
|
|
self.keychainTable.remove(key)
|
|
}) |> afterDisposed { [weak self, weak metaDisposable] in
|
|
if let strongSelf = self, let metaDisposable = metaDisposable {
|
|
strongSelf.keychainOperationsDisposable.remove(metaDisposable)
|
|
}
|
|
}).start()
|
|
metaDisposable.set(disposable)
|
|
}
|
|
|
|
fileprivate func addMessages(transaction: Transaction, messages: [StoreMessage], location: AddMessagesLocation) -> [Int64: MessageId] {
|
|
var addedMessagesByPeerId: [PeerId: [StoreMessage]] = [:]
|
|
let addResult = self.messageHistoryTable.addMessages(messages: messages, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, processMessages: { messagesByPeerId in
|
|
addedMessagesByPeerId = messagesByPeerId
|
|
})
|
|
|
|
for (peerId, peerMessages) in addedMessagesByPeerId {
|
|
switch location {
|
|
case .Random:
|
|
break
|
|
case .UpperHistoryBlock:
|
|
var earliestByNamespace: [MessageId.Namespace: MessageId] = [:]
|
|
for message in peerMessages {
|
|
if case let .Id(id) = message.id {
|
|
if let currentEarliestId = earliestByNamespace[id.namespace] {
|
|
if id < currentEarliestId {
|
|
earliestByNamespace[id.namespace] = id
|
|
}
|
|
} else {
|
|
earliestByNamespace[id.namespace] = id
|
|
}
|
|
}
|
|
}
|
|
for (_, id) in earliestByNamespace {
|
|
self.messageHistoryHoleIndexTable.remove(peerId: id.peerId, namespace: id.namespace, space: .everywhere, range: id.id ... (Int32.max - 1), operations: &self.currentPeerHoleOperations)
|
|
}
|
|
}
|
|
|
|
if let bag = self.installedMessageActionsByPeerId[peerId] {
|
|
for f in bag.copyItems() {
|
|
f(peerMessages, transaction)
|
|
}
|
|
}
|
|
|
|
if let bag = self.installedStoreOrUpdateMessageActionsByPeerId[peerId] {
|
|
for f in bag.copyItems() {
|
|
f.addOrUpdate(messages: peerMessages, transaction: transaction)
|
|
}
|
|
}
|
|
}
|
|
|
|
return addResult
|
|
}
|
|
|
|
fileprivate func countIncomingMessage(id: MessageId) {
|
|
let (combinedState, _) = self.readStateTable.addIncomingMessages(id.peerId, indices: Set([MessageIndex(id: id, timestamp: 1)]))
|
|
if self.currentOperationsByPeerId[id.peerId] == nil {
|
|
self.currentOperationsByPeerId[id.peerId] = []
|
|
}
|
|
if let combinedState = combinedState {
|
|
self.currentOperationsByPeerId[id.peerId]!.append(.UpdateReadState(id.peerId, combinedState))
|
|
}
|
|
}
|
|
|
|
fileprivate func addHole(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleOperationSpace, range: ClosedRange<MessageId.Id>) {
|
|
switch space {
|
|
case let .customTag(customTag, regularTag):
|
|
if let regularTag {
|
|
self.messageCustomTagWithTagHoleIndexTable.add(peerId: peerId, threadId: threadId, tag: customTag, regularTag: regularTag.rawValue, namespace: namespace, range: range, operations: &self.currentPeerHoleOperations)
|
|
} else {
|
|
self.messageCustomTagHoleIndexTable.add(peerId: peerId, threadId: threadId, tag: customTag, namespace: namespace, range: range, operations: &self.currentPeerHoleOperations)
|
|
}
|
|
case .everywhere, .tag:
|
|
let mappedSpace: MessageHistoryHoleSpace
|
|
if case let .tag(tag) = space {
|
|
mappedSpace = .tag(tag)
|
|
} else {
|
|
mappedSpace = .everywhere
|
|
}
|
|
if let threadId = threadId {
|
|
self.messageHistoryThreadHoleIndexTable.add(peerId: peerId, threadId: threadId, namespace: namespace, space: mappedSpace, range: range, operations: &self.currentPeerHoleOperations)
|
|
} else {
|
|
self.messageHistoryHoleIndexTable.add(peerId: peerId, namespace: namespace, space: mappedSpace, range: range, operations: &self.currentPeerHoleOperations)
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func removeHole(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleOperationSpace, range: ClosedRange<MessageId.Id>) {
|
|
switch space {
|
|
case let .customTag(customTag, regularTag):
|
|
if let regularTag {
|
|
self.messageCustomTagWithTagHoleIndexTable.remove(peerId: peerId, threadId: threadId, tag: customTag, regularTag: regularTag.rawValue, namespace: namespace, range: range, operations: &self.currentPeerHoleOperations)
|
|
} else {
|
|
self.messageCustomTagHoleIndexTable.remove(peerId: peerId, threadId: threadId, tag: customTag, namespace: namespace, range: range, operations: &self.currentPeerHoleOperations)
|
|
}
|
|
case .everywhere, .tag:
|
|
let mappedSpace: MessageHistoryHoleSpace
|
|
if case let .tag(tag) = space {
|
|
mappedSpace = .tag(tag)
|
|
} else {
|
|
mappedSpace = .everywhere
|
|
}
|
|
if let threadId = threadId {
|
|
self.messageHistoryThreadHoleIndexTable.remove(peerId: peerId, threadId: threadId, namespace: namespace, space: mappedSpace, range: range, operations: &self.currentPeerHoleOperations)
|
|
} else {
|
|
self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: mappedSpace, range: range, operations: &self.currentPeerHoleOperations)
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func recalculateChatListGroupStats(groupId: PeerGroupId) {
|
|
let summary = self.chatListIndexTable.reindexPeerGroupUnreadCounts(postbox: self, groupId: groupId)
|
|
self.groupMessageStatsTable.set(groupId: groupId, summary: summary)
|
|
self.currentUpdatedGroupTotalUnreadSummaries[groupId] = summary
|
|
}
|
|
|
|
fileprivate func replaceChatListHole(groupId: PeerGroupId, index: MessageIndex, hole: ChatListHole?) {
|
|
self.chatListTable.replaceHole(groupId: groupId, index: index, hole: hole, operations: &self.currentChatListOperations)
|
|
}
|
|
|
|
fileprivate func addChatListHole(groupId: PeerGroupId, hole: ChatListHole) {
|
|
self.chatListTable.addHole(groupId: groupId, hole: hole, operations: &self.currentChatListOperations)
|
|
}
|
|
|
|
fileprivate func deleteMessages(_ messageIds: [MessageId], forEachMedia: ((Media) -> Void)?) {
|
|
self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: ((Media) -> Void)?) {
|
|
self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
fileprivate func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace?, reversed: Bool = false, _ f: (Message) -> Bool) {
|
|
var indexes = self.messageHistoryTable.allMessageIndices(peerId: peerId, namespace: namespace)
|
|
if reversed { indexes.reverse() }
|
|
for index in indexes {
|
|
if let message = self.messageHistoryTable.getMessage(index) {
|
|
if !f(self.renderIntermediateMessage(message)) {
|
|
break
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func clearHistory(_ peerId: PeerId, threadId: Int64?, minTimestamp: Int32?, maxTimestamp: Int32?, namespaces: MessageIdNamespaces, forEachMedia: ((Media) -> Void)?) {
|
|
if let minTimestamp = minTimestamp, let maxTimestamp = maxTimestamp {
|
|
self.messageHistoryTable.clearHistoryInRange(peerId: peerId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
|
|
} else {
|
|
self.messageHistoryTable.clearHistory(peerId: peerId, threadId: threadId, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
|
|
for namespace in self.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: .everywhere) where namespaces.contains(namespace) {
|
|
self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... Int32.max - 1, operations: &self.currentPeerHoleOperations)
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: ((Media) -> Void)?) {
|
|
self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
fileprivate func removeAllMessagesWithGlobalTag(tag: GlobalMessageTags) {
|
|
self.messageHistoryTable.removeAllMessagesWithGlobalTag(tag: tag, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: { _ in })
|
|
}
|
|
|
|
fileprivate func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: ((Media) -> Void)?) {
|
|
self.messageHistoryTable.removeAllMessagesWithForwardAuthor(peerId: peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
|
|
}
|
|
|
|
fileprivate func resetIncomingReadStates(_ states: [PeerId: [MessageId.Namespace: PeerReadState]]) {
|
|
self.messageHistoryTable.resetIncomingReadStates(states, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
}
|
|
|
|
fileprivate func setNeedsIncomingReadStateSynchronization(_ peerId: PeerId) {
|
|
self.synchronizeReadStateTable.set(peerId, operation: .Validate, operations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
}
|
|
|
|
fileprivate func confirmSynchronizedIncomingReadState(_ peerId: PeerId) {
|
|
self.synchronizeReadStateTable.set(peerId, operation: nil, operations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
}
|
|
|
|
fileprivate func applyIncomingReadMaxId(_ messageId: MessageId) {
|
|
self.messageHistoryTable.applyIncomingReadMaxId(messageId, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
}
|
|
|
|
fileprivate func applyOutgoingReadMaxId(_ messageId: MessageId) {
|
|
self.messageHistoryTable.applyOutgoingReadMaxId(messageId, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
}
|
|
|
|
fileprivate func applyInteractiveReadMaxIndex(messageIndex: MessageIndex) -> [MessageId] {
|
|
let peerIds = self.peerIdsForLocation(.peer(peerId: messageIndex.id.peerId, threadId: nil), ignoreRelatedChats: false)
|
|
switch peerIds {
|
|
case let .associated(_, messageId):
|
|
if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 {
|
|
if let topMessage = self.messageHistoryTable.topMessage(peerId: messageId.peerId) {
|
|
let _ = self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: topMessage.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
let initialCombinedStates = self.readStateTable.getCombinedState(messageIndex.id.peerId)
|
|
var resultIds = self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: messageIndex, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
if let states = initialCombinedStates?.states {
|
|
for (namespace, state) in states {
|
|
if namespace != messageIndex.id.namespace && state.count != 0 {
|
|
if let item = self.messageHistoryTable.fetch(peerId: messageIndex.id.peerId, namespace: namespace, tag: nil, customTag: nil, threadId: nil, from: MessageIndex(id: MessageId(peerId: messageIndex.id.peerId, namespace: namespace, id: 1), timestamp: messageIndex.timestamp), includeFrom: true, to: MessageIndex.lowerBound(peerId: messageIndex.id.peerId, namespace: namespace), ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), limit: 1).first {
|
|
resultIds.append(contentsOf: self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: item.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return resultIds
|
|
}
|
|
|
|
func applyMarkUnread(peerId: PeerId, namespace: MessageId.Namespace, value: Bool, interactive: Bool) {
|
|
if let combinedState = self.readStateTable.applyInteractiveMarkUnread(peerId: peerId, namespace: namespace, value: value) {
|
|
if self.currentOperationsByPeerId[peerId] == nil {
|
|
self.currentOperationsByPeerId[peerId] = []
|
|
}
|
|
self.currentOperationsByPeerId[peerId]!.append(.UpdateReadState(peerId, combinedState))
|
|
if interactive {
|
|
self.synchronizeReadStateTable.set(peerId, operation: .Push(state: self.readStateTable.getCombinedState(peerId), thenSync: false), operations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func applyOutgoingReadMaxIndex(_ messageIndex: MessageIndex) -> [MessageId] {
|
|
return self.messageHistoryTable.applyOutgoingReadMaxIndex(messageIndex, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)
|
|
}
|
|
|
|
fileprivate func resetPeerGroupSummary(groupId: PeerGroupId, namespace: MessageId.Namespace, summary: PeerGroupUnreadCountersSummary) {
|
|
var combinedSummary = self.groupMessageStatsTable.get(groupId: groupId)
|
|
if combinedSummary.namespaces[namespace] != summary {
|
|
combinedSummary.namespaces[namespace] = summary
|
|
self.groupMessageStatsTable.set(groupId: groupId, summary: combinedSummary)
|
|
self.currentUpdatedGroupTotalUnreadSummaries[groupId] = combinedSummary
|
|
}
|
|
}
|
|
|
|
fileprivate func setNeedsPeerGroupMessageStatsSynchronization(groupId: PeerGroupId, namespace: MessageId.Namespace) {
|
|
self.synchronizeGroupMessageStatsTable.set(groupId: groupId, namespace: namespace, needsValidation: true, operations: &self.currentUpdatedGroupSummarySynchronizeOperations)
|
|
}
|
|
|
|
fileprivate func confirmSynchronizedPeerGroupMessageStats(groupId: PeerGroupId, namespace: MessageId.Namespace) {
|
|
self.synchronizeGroupMessageStatsTable.set(groupId: groupId, namespace: namespace, needsValidation: false, operations: &self.currentUpdatedGroupSummarySynchronizeOperations)
|
|
}
|
|
|
|
fileprivate func getAllStorySubscriptions(key: PostboxStorySubscriptionsKey) -> (state: CodableEntry?, peerIds: [PeerId]) {
|
|
return (
|
|
self.storyGeneralStatesTable.get(key: .subscriptions(key)),
|
|
self.storySubscriptionsTable.getAll(subscriptionsKey: key)
|
|
)
|
|
}
|
|
|
|
fileprivate func storySubscriptionsContains(key: PostboxStorySubscriptionsKey, peerId: PeerId) -> Bool {
|
|
return self.storySubscriptionsTable.contains(subscriptionsKey: key, peerId: peerId)
|
|
}
|
|
|
|
fileprivate func replaceAllStorySubscriptions(key: PostboxStorySubscriptionsKey, state: CodableEntry?, peerIds: [PeerId]) {
|
|
self.storyGeneralStatesTable.set(key: .subscriptions(key), value: state, events: &self.currentStoryGeneralStatesEvents)
|
|
self.storySubscriptionsTable.replaceAll(subscriptionsKey: key, peerIds: peerIds, events: &self.currentStorySubscriptionsEvents)
|
|
}
|
|
|
|
fileprivate func getLocalStoryState() -> CodableEntry? {
|
|
return self.storyGeneralStatesTable.get(key: .local)
|
|
}
|
|
|
|
fileprivate func getSubscriptionsStoriesState(key: PostboxStorySubscriptionsKey) -> CodableEntry? {
|
|
return self.storyGeneralStatesTable.get(key: .subscriptions(key))
|
|
}
|
|
|
|
fileprivate func setSubscriptionsStoriesState(key: PostboxStorySubscriptionsKey, state: CodableEntry?) {
|
|
self.storyGeneralStatesTable.set(key: .subscriptions(key), value: state, events: &self.currentStoryGeneralStatesEvents)
|
|
}
|
|
|
|
fileprivate func setLocalStoryState(state: CodableEntry?) {
|
|
self.storyGeneralStatesTable.set(key: .local, value: state, events: &self.currentStoryGeneralStatesEvents)
|
|
}
|
|
|
|
fileprivate func getPeerStoryState(peerId: PeerId) -> StoredStoryPeerState? {
|
|
return self.storyPeerStatesTable.get(key: .peer(peerId))
|
|
}
|
|
|
|
fileprivate func setPeerStoryState(peerId: PeerId, state: StoredStoryPeerState?) {
|
|
self.storyPeerStatesTable.set(key: .peer(peerId), value: state, events: &self.currentStoryPeerStatesEvents)
|
|
}
|
|
|
|
fileprivate func setStoryItems(peerId: PeerId, items: [StoryItemsTableEntry]) {
|
|
self.storyItemsTable.replace(peerId: peerId, entries: items, topItemTable: self.storyTopItemsTable, events: &self.currentStoryItemsEvents, topItemEvents: &self.currentStoryTopItemEvents)
|
|
for item in items {
|
|
self.storyTable.set(id: StoryId(peerId: peerId, id: item.id), value: item.value, events: &self.currentStoryEvents)
|
|
}
|
|
}
|
|
|
|
fileprivate func setStoryItemsInexactMaxId(peerId: PeerId, id: Int32) {
|
|
if let value = self.storyTopItemsTable.get(peerId: peerId), value.id >= id {
|
|
} else {
|
|
self.storyTopItemsTable.set(peerId: peerId, entry: StoryTopItemsTable.Entry(id: id, isExact: false), events: &self.currentStoryTopItemEvents)
|
|
}
|
|
}
|
|
|
|
fileprivate func clearStoryItemsInexactMaxId(peerId: PeerId) {
|
|
if let value = self.storyTopItemsTable.get(peerId: peerId), !value.isExact {
|
|
self.storyTopItemsTable.set(peerId: peerId, entry: nil, events: &self.currentStoryTopItemEvents)
|
|
}
|
|
}
|
|
|
|
fileprivate func getStoryItems(peerId: PeerId) -> [StoryItemsTableEntry] {
|
|
return self.storyItemsTable.get(peerId: peerId)
|
|
}
|
|
|
|
fileprivate func getStory(id: StoryId) -> CodableEntry? {
|
|
return self.storyTable.get(id: id)
|
|
}
|
|
|
|
fileprivate func setStory(id: StoryId, value: CodableEntry) {
|
|
self.storyTable.set(id: id, value: value, events: &self.currentStoryEvents)
|
|
}
|
|
|
|
fileprivate func reindexSavedMessagesCustomTagsWithTagsIfNeeded(peerId: PeerId, threadId: Int64?, tag: MemoryBuffer) {
|
|
let mappedTag = self.messageCustomTagIdTable.get(tag: tag)
|
|
if !self.messageHistoryMetadataTable.isPeerCustomTagReindexed(peerId: peerId, threadId: threadId, tag: mappedTag) {
|
|
let indices = self.messageCustomTagTable.laterIndices(threadId: threadId, tag: tag, peerId: peerId, namespace: 0, index: nil, includeFrom: false, count: 1000)
|
|
for index in indices {
|
|
if let message = self.messageHistoryTable.getMessage(index) {
|
|
for regularTag in message.tags {
|
|
if !self.messageCustomTagWithTagTable.entryExists(threadId: threadId, tag: tag, regularTag: regularTag.rawValue, index: index) {
|
|
self.messageCustomTagWithTagTable.add(threadId: threadId, tag: tag, regularTag: regularTag.rawValue, index: index, isNewlyAdded: true, updatedSummaries: &self.currentUpdatedMessageTagSummaries, invalidateSummaries: &self.currentInvalidateMessageTagSummaries)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
self.messageHistoryMetadataTable.setPeerCustomTagReindexed(peerId: peerId, threadId: threadId, tag: mappedTag)
|
|
}
|
|
}
|
|
|
|
func renderIntermediateMessage(_ message: IntermediateMessage) -> Message {
|
|
let renderedMessage = self.messageHistoryTable.renderMessage(message, peerTable: self.peerTable, threadIndexTable: self.messageHistoryThreadIndexTable, storyTable: self.storyTable)
|
|
|
|
return renderedMessage
|
|
}
|
|
|
|
private func afterBegin(transaction: Transaction) {
|
|
let currentTransactionStateVersion = self.metadataTable.transactionStateVersion()
|
|
if currentTransactionStateVersion != self.transactionStateVersion {
|
|
for table in self.tables {
|
|
table.clearMemoryCache()
|
|
}
|
|
self.viewTracker.refreshViewsDueToExternalTransaction(postbox: self, currentTransaction: transaction, fetchUnsentMessageIds: {
|
|
return self.messageHistoryUnsentTable.get()
|
|
}, fetchSynchronizePeerReadStateOperations: {
|
|
return self.synchronizeReadStateTable.get(getCombinedPeerReadState: { peerId in
|
|
return self.readStateTable.getCombinedState(peerId)
|
|
})
|
|
})
|
|
self.transactionStateVersion = currentTransactionStateVersion
|
|
|
|
self.masterClientId.set(.single(self.metadataTable.masterClientId()))
|
|
}
|
|
}
|
|
|
|
private func beforeCommit(currentTransaction: Transaction) -> (updatedTransactionStateVersion: Int64?, updatedMasterClientId: Int64?) {
|
|
self.chatListTable.replay(historyOperationsByPeerId: self.currentOperationsByPeerId, updatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, updatedChatListInclusions: self.currentUpdatedChatListInclusions, messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, operations: &self.currentChatListOperations)
|
|
|
|
self.peerChatTopTaggedMessageIdsTable.replay(historyOperationsByPeerId: self.currentOperationsByPeerId)
|
|
|
|
let alteredInitialPeerCombinedReadStates = self.readStateTable.transactionAlteredInitialPeerCombinedReadStates()
|
|
var updatedPeers = self.peerTable.transactionUpdatedPeers(contactsTable: self.contactsTable)
|
|
let updatedContacts = self.contactsTable.transactionUpdatedPeers()
|
|
if !updatedContacts.isEmpty {
|
|
var updatedPeerIdToIndex: [PeerId: Int] = [:]
|
|
var index = 0
|
|
for (_, peer) in updatedPeers {
|
|
updatedPeerIdToIndex[peer.0.id] = index
|
|
}
|
|
index += 1
|
|
for (peerId, change) in updatedContacts {
|
|
if let index = updatedPeerIdToIndex[peerId] {
|
|
if let (peer, _) = updatedPeers[index].0 {
|
|
updatedPeers[index].0 = (peer, change.0)
|
|
}
|
|
updatedPeers[index].1 = (updatedPeers[index].1.0, change.1)
|
|
} else if let peer = self.peerTable.get(peerId) {
|
|
updatedPeers.append(((peer, change.0), (peer, change.1)))
|
|
}
|
|
}
|
|
}
|
|
let updatedCachedPeerData = self.cachedPeerDataTable.transactionUpdatedPeers()
|
|
let transactionParticipationInTotalUnreadCountUpdates = self.peerNotificationSettingsTable.transactionParticipationInTotalUnreadCountUpdates(postbox: self, transaction: currentTransaction)
|
|
|
|
let updatedMessageThreadPeerIds = self.messageHistoryThreadIndexTable.replay(threadsTable: self.messageHistoryThreadsTable, namespaces: self.seedConfiguration.chatMessagesNamespaces, updatedIds: self.messageHistoryThreadsTable.updatedIds)
|
|
let alteredInitialPeerThreadsSummaries = self.peerThreadsSummaryTable.update(peerIds: updatedMessageThreadPeerIds.union(self.currentUpdatedPeerThreadCombinedStates), indexTable: self.messageHistoryThreadIndexTable, combinedStateTable: self.peerThreadCombinedStateTable, tagsSummaryTable: self.messageHistoryTagsSummaryTable)
|
|
|
|
self.chatListIndexTable.commitWithTransaction(
|
|
postbox: self,
|
|
currentTransaction: currentTransaction,
|
|
alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates,
|
|
updatedPeers: updatedPeers,
|
|
updatedCachedPeerData: updatedCachedPeerData,
|
|
transactionParticipationInTotalUnreadCountUpdates: transactionParticipationInTotalUnreadCountUpdates,
|
|
alteredInitialPeerThreadsSummaries: alteredInitialPeerThreadsSummaries,
|
|
updatedTotalUnreadStates: &self.currentUpdatedTotalUnreadStates,
|
|
updatedGroupTotalUnreadSummaries: &self.currentUpdatedGroupTotalUnreadSummaries,
|
|
currentUpdatedGroupSummarySynchronizeOperations: &self.currentUpdatedGroupSummarySynchronizeOperations
|
|
)
|
|
|
|
self.peerTable.commitDependentTables()
|
|
|
|
if self.currentNeedsReindexUnreadCounters {
|
|
self.reindexUnreadCounters(currentTransaction: currentTransaction)
|
|
}
|
|
|
|
let updatedPeerTimeoutAttributes = self.peerTimeoutPropertiesTable.hasUpdates
|
|
|
|
let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes, updatedMessageThreadPeerIds: updatedMessageThreadPeerIds, updatedPeerThreadCombinedStates: self.currentUpdatedPeerThreadCombinedStates, updatedPeerThreadsSummaries: Set(alteredInitialPeerThreadsSummaries.keys), updatedPinnedThreads: self.currentUpdatedPinnedThreads, updatedHiddenPeerIds: self.currentUpdatedHiddenPeerIds, storyGeneralStatesEvents: self.currentStoryGeneralStatesEvents, storyPeerStatesEvents: self.currentStoryPeerStatesEvents, storySubscriptionsEvents: self.currentStorySubscriptionsEvents, storyItemsEvents: self.currentStoryItemsEvents, currentStoryTopItemEvents: self.currentStoryTopItemEvents, storyEvents: self.currentStoryEvents)
|
|
var updatedTransactionState: Int64?
|
|
var updatedMasterClientId: Int64?
|
|
if !transaction.isEmpty {
|
|
self.viewTracker.updateViews(postbox: self, currentTransaction: currentTransaction, transaction: transaction)
|
|
self.transactionStateVersion = self.metadataTable.incrementTransactionStateVersion()
|
|
updatedTransactionState = self.transactionStateVersion
|
|
|
|
if let currentUpdatedMasterClientId = self.currentUpdatedMasterClientId {
|
|
self.metadataTable.setMasterClientId(currentUpdatedMasterClientId)
|
|
updatedMasterClientId = currentUpdatedMasterClientId
|
|
}
|
|
}
|
|
|
|
self.currentPeerHoleOperations.removeAll()
|
|
self.currentOperationsByPeerId.removeAll()
|
|
self.currentUpdatedChatListInclusions.removeAll()
|
|
self.currentUpdatedPeers.removeAll()
|
|
self.currentChatListOperations.removeAll()
|
|
self.currentUpdatedChatListInclusions.removeAll()
|
|
self.currentUnsentOperations.removeAll()
|
|
self.currentUpdatedSynchronizeReadStateOperations.removeAll()
|
|
self.currentUpdatedGroupSummarySynchronizeOperations.removeAll()
|
|
self.currentGlobalTagsOperations.removeAll()
|
|
self.currentLocalTagsOperations.removeAll()
|
|
self.currentUpdatedMedia.removeAll()
|
|
self.currentReplaceRemoteContactCount = nil
|
|
self.currentReplacedContactPeerIds = nil
|
|
self.currentReplacedAdditionalChatListItems = nil
|
|
self.currentUpdatedNoticeEntryKeys.removeAll()
|
|
self.currentUpdatedCacheEntryKeys.removeAll()
|
|
self.currentUpdatedPeerThreadCombinedStates.removeAll()
|
|
self.currentUpdatedMasterClientId = nil
|
|
self.currentUpdatedPeerNotificationSettings.removeAll()
|
|
self.currentUpdatedPeerNotificationBehaviorTimestamps.removeAll()
|
|
self.currentUpdatedCachedPeerData.removeAll()
|
|
self.currentUpdatedPeerPresences.removeAll()
|
|
self.currentUpdatedPeerChatListEmbeddedStates.removeAll()
|
|
self.currentUpdatedTotalUnreadStates.removeAll()
|
|
self.currentUpdatedGroupTotalUnreadSummaries.removeAll()
|
|
self.currentPeerMergedOperationLogOperations.removeAll()
|
|
self.currentTimestampBasedMessageAttributesOperations.removeAll()
|
|
self.currentPreferencesOperations.removeAll()
|
|
self.currentOrderedItemListOperations.removeAll()
|
|
self.currentItemCollectionItemsOperations.removeAll()
|
|
self.currentItemCollectionInfosOperations.removeAll()
|
|
self.currentUpdatedPeerChatStates.removeAll()
|
|
self.currentPendingMessageActionsOperations.removeAll()
|
|
self.currentUpdatedMessageActionsSummaries.removeAll()
|
|
self.currentUpdatedMessageTagSummaries.removeAll()
|
|
self.currentInvalidateMessageTagSummaries.removeAll()
|
|
self.currentUpdatedPendingPeerNotificationSettings.removeAll()
|
|
self.currentGroupIdsWithUpdatedReadStats.removeAll()
|
|
self.currentUpdatedPinnedThreads.removeAll()
|
|
self.currentUpdatedHiddenPeerIds = false
|
|
self.currentNeedsReindexUnreadCounters = false
|
|
self.currentStoryGeneralStatesEvents.removeAll()
|
|
self.currentStoryPeerStatesEvents.removeAll()
|
|
self.currentStorySubscriptionsEvents.removeAll()
|
|
self.currentStoryItemsEvents.removeAll()
|
|
self.currentStoryTopItemEvents.removeAll()
|
|
self.currentStoryEvents.removeAll()
|
|
|
|
for table in self.tables {
|
|
table.beforeCommit()
|
|
}
|
|
|
|
return (updatedTransactionState, updatedMasterClientId)
|
|
}
|
|
|
|
fileprivate func messageIdsForGlobalIds(_ ids: [Int32]) -> [MessageId] {
|
|
var result: [MessageId] = []
|
|
for globalId in ids {
|
|
if let id = self.globalMessageIdsTable.get(globalId) {
|
|
result.append(id)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
fileprivate func failedMessageIds(for peerId: PeerId) -> [MessageId] {
|
|
return self.messageHistoryFailedTable.get(peerId: peerId)
|
|
}
|
|
|
|
fileprivate func messageIdForGloballyUniqueMessageId(peerId: PeerId, id: Int64) -> MessageId? {
|
|
return self.globallyUniqueMessageIdsTable.get(peerId: peerId, globallyUniqueId: id)
|
|
}
|
|
|
|
fileprivate func updatePeers(_ peers: [Peer], update: (Peer?, Peer) -> Peer?) {
|
|
for peer in peers {
|
|
let currentPeer = self.peerTable.get(peer.id)
|
|
if let updatedPeer = update(currentPeer, peer) {
|
|
self.peerTable.set(updatedPeer)
|
|
self.currentUpdatedPeers[updatedPeer.id] = updatedPeer
|
|
var previousIndexNameWasEmpty = true
|
|
|
|
if let currentPeer = currentPeer {
|
|
if !currentPeer.indexName.isEmpty {
|
|
previousIndexNameWasEmpty = false
|
|
}
|
|
}
|
|
|
|
let indexNameIsEmpty = updatedPeer.indexName.isEmpty
|
|
|
|
if !previousIndexNameWasEmpty || !indexNameIsEmpty {
|
|
if currentPeer?.indexName != updatedPeer.indexName {
|
|
self.peerNameIndexTable.markPeerNameUpdated(peerId: peer.id, name: updatedPeer.indexName)
|
|
for reverseAssociatedPeerId in self.reverseAssociatedPeerTable.get(peerId: peer.id) {
|
|
self.peerNameIndexTable.markPeerNameUpdated(peerId: reverseAssociatedPeerId, name: updatedPeer.indexName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func getTopPeerMessageIndex(peerId: PeerId, namespace: MessageId.Namespace) -> MessageIndex? {
|
|
if let index = self.messageHistoryTable.topIndexEntry(peerId: peerId, namespace: namespace) {
|
|
return index
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fileprivate func getTopPeerMessageIndex(peerId: PeerId) -> MessageIndex? {
|
|
var indices: [MessageIndex] = []
|
|
for namespace in self.messageHistoryIndexTable.existingNamespaces(peerId: peerId) where self.seedConfiguration.chatMessagesNamespaces.contains(namespace) {
|
|
if let index = self.messageHistoryTable.topIndexEntry(peerId: peerId, namespace: namespace) {
|
|
indices.append(index)
|
|
}
|
|
}
|
|
return indices.max()
|
|
}
|
|
|
|
func getPeerChatListInclusion(_ id: PeerId) -> PeerChatListInclusion {
|
|
if let inclusion = self.currentUpdatedChatListInclusions[id] {
|
|
return inclusion
|
|
} else {
|
|
return self.chatListIndexTable.get(peerId: id).inclusion
|
|
}
|
|
}
|
|
|
|
fileprivate func updatePeerChatListInclusion(_ id: PeerId, inclusion: PeerChatListInclusion) {
|
|
self.chatListTable.updateInclusion(peerId: id, updatedChatListInclusions: &self.currentUpdatedChatListInclusions, { _ in
|
|
return inclusion
|
|
})
|
|
}
|
|
|
|
fileprivate func removeAllChatListEntries(groupId: PeerGroupId, exceptPeerNamespace: PeerId.Namespace) {
|
|
self.chatListTable.removeAllEntries(groupId: groupId, exceptPeerNamespace: exceptPeerNamespace, operations: &self.currentChatListOperations)
|
|
}
|
|
|
|
fileprivate func getPinnedItemIds(groupId: PeerGroupId) -> [PinnedItemId] {
|
|
var itemIds = self.chatListTable.getPinnedItemIds(groupId: groupId, messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable)
|
|
for (peerId, inclusion) in self.currentUpdatedChatListInclusions {
|
|
var found = false
|
|
inner: for i in 0 ..< itemIds.count {
|
|
if case .peer(peerId) = itemIds[i].id {
|
|
found = true
|
|
switch inclusion {
|
|
case let .ifHasMessagesOrOneOf(updatedGroupId, pinningIndex, _):
|
|
if updatedGroupId != groupId || pinningIndex == nil {
|
|
itemIds.remove(at: i)
|
|
}
|
|
default:
|
|
itemIds.remove(at: i)
|
|
}
|
|
break inner
|
|
}
|
|
}
|
|
if !found {
|
|
switch inclusion {
|
|
case let .ifHasMessagesOrOneOf(updatedGroupId, pinningIndex, _):
|
|
if updatedGroupId == groupId, let pinningIndex = pinningIndex {
|
|
itemIds.append((.peer(peerId), Int(pinningIndex)))
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return itemIds.sorted(by: { $0.1 < $1.1 }).map({ $0.0 })
|
|
}
|
|
|
|
fileprivate func setPinnedItemIds(groupId: PeerGroupId, itemIds: [PinnedItemId]) {
|
|
self.chatListTable.setPinnedItemIds(groupId: groupId, itemIds: itemIds, updatedChatListInclusions: &self.currentUpdatedChatListInclusions, messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable)
|
|
}
|
|
|
|
fileprivate func updateCurrentPeerNotificationSettings(_ notificationSettings: [PeerId: PeerNotificationSettings]) {
|
|
for (peerId, settings) in notificationSettings {
|
|
let previous: PeerNotificationSettings?
|
|
if let (value, _) = self.currentUpdatedPeerNotificationSettings[peerId] {
|
|
previous = value
|
|
} else {
|
|
previous = self.peerNotificationSettingsTable.getEffective(peerId)
|
|
}
|
|
if let updated = self.peerNotificationSettingsTable.setCurrent(id: peerId, settings: settings, updatedTimestamps: &self.currentUpdatedPeerNotificationBehaviorTimestamps) {
|
|
self.currentUpdatedPeerNotificationSettings[peerId] = (previous, updated)
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func updatePendingPeerNotificationSettings(peerId: PeerId, settings: PeerNotificationSettings?) {
|
|
let previous: PeerNotificationSettings?
|
|
if let (value, _) = self.currentUpdatedPeerNotificationSettings[peerId] {
|
|
previous = value
|
|
} else {
|
|
previous = self.peerNotificationSettingsTable.getEffective(peerId)
|
|
}
|
|
if let updated = self.peerNotificationSettingsTable.setPending(id: peerId, settings: settings, updatedSettings: &self.currentUpdatedPendingPeerNotificationSettings) {
|
|
self.currentUpdatedPeerNotificationSettings[peerId] = (previous, updated)
|
|
}
|
|
}
|
|
|
|
fileprivate func resetAllPeerNotificationSettings(_ notificationSettings: PeerNotificationSettings) {
|
|
for (peerId, previous) in self.peerNotificationSettingsTable.resetAll(to: notificationSettings, updatedSettings: &self.currentUpdatedPendingPeerNotificationSettings, updatedTimestamps: &self.currentUpdatedPeerNotificationBehaviorTimestamps) {
|
|
self.currentUpdatedPeerNotificationSettings[peerId] = (previous, notificationSettings)
|
|
}
|
|
}
|
|
|
|
fileprivate func updatePeerCachedData(peerIds: Set<PeerId>, update: (PeerId, CachedPeerData?) -> CachedPeerData?) {
|
|
for peerId in peerIds {
|
|
let currentData = self.cachedPeerDataTable.get(peerId)
|
|
if let updatedData = update(peerId, currentData) {
|
|
self.cachedPeerDataTable.set(id: peerId, data: updatedData)
|
|
self.currentUpdatedCachedPeerData[peerId] = updatedData
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func updatePeerPresences(presences: [PeerId: PeerPresence], merge: (PeerPresence, PeerPresence) -> PeerPresence) {
|
|
for (peerId, presence) in presences {
|
|
let updated: PeerPresence
|
|
let shouldUpdate: Bool
|
|
if let current = self.peerPresenceTable.get(peerId) {
|
|
updated = merge(current, presence)
|
|
shouldUpdate = !current.isEqual(to: updated)
|
|
} else {
|
|
updated = presence
|
|
shouldUpdate = true
|
|
}
|
|
if shouldUpdate {
|
|
self.peerPresenceTable.set(id: peerId, presence: updated)
|
|
self.currentUpdatedPeerPresences[peerId] = updated
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func updatePeerPresence(peerId: PeerId, update: (PeerPresence) -> PeerPresence) {
|
|
if let current = self.peerPresenceTable.get(peerId) {
|
|
let updated = update(current)
|
|
if !current.isEqual(to: updated) {
|
|
self.peerPresenceTable.set(id: peerId, presence: updated)
|
|
self.currentUpdatedPeerPresences[peerId] = updated
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func setPeerChatState(_ id: PeerId, state: PeerChatState) {
|
|
self.peerChatStateTable.set(id, state: CodableEntry(legacyValue: state))
|
|
self.currentUpdatedPeerChatStates.insert(id)
|
|
}
|
|
|
|
fileprivate func setPeerChatInterfaceState(_ id: PeerId, state: StoredPeerChatInterfaceState?) {
|
|
let updatedState = state
|
|
let (_, updatedEmbeddedState) = self.peerChatInterfaceStateTable.set(id, state: updatedState)
|
|
if updatedEmbeddedState {
|
|
self.currentUpdatedPeerChatListEmbeddedStates.insert(id)
|
|
}
|
|
}
|
|
|
|
fileprivate func setPeerChatThreadInterfaceState(_ id: PeerId, threadId: Int64, state: StoredPeerChatInterfaceState?) {
|
|
let updatedState = state
|
|
let _ = self.peerChatThreadInterfaceStateTable.set(PeerChatThreadId(peerId: id, threadId: threadId), state: updatedState)
|
|
self.currentUpdatedPeerChatListEmbeddedStates.insert(id)
|
|
}
|
|
|
|
fileprivate func replaceRemoteContactCount(_ count: Int32) {
|
|
self.metadataTable.setRemoteContactCount(count)
|
|
self.currentReplaceRemoteContactCount = count
|
|
}
|
|
|
|
fileprivate func replaceContactPeerIds(_ peerIds: Set<PeerId>) {
|
|
self.contactsTable.replace(peerIds)
|
|
|
|
self.currentReplacedContactPeerIds = peerIds
|
|
}
|
|
|
|
fileprivate func replaceAdditionalChatListItems(_ items: [AdditionalChatListItem]) {
|
|
if self.additionalChatListItemsTable.set(items) {
|
|
self.currentReplacedAdditionalChatListItems = items
|
|
}
|
|
}
|
|
|
|
fileprivate func replaceRecentPeerIds(_ peerIds: [PeerId]) {
|
|
self.peerRatingTable.replace(items: peerIds)
|
|
}
|
|
|
|
fileprivate func updateMessage(transaction: Transaction, id: MessageId, update: (Message) -> PostboxUpdateMessage) {
|
|
if let index = self.messageHistoryIndexTable.getIndex(id), let intermediateMessage = self.messageHistoryTable.getMessage(index) {
|
|
let message = self.renderIntermediateMessage(intermediateMessage)
|
|
if case let .update(updatedMessage) = update(message) {
|
|
self.messageHistoryTable.updateMessage(id, message: updatedMessage, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations)
|
|
|
|
if let bag = self.installedStoreOrUpdateMessageActionsByPeerId[id.peerId] {
|
|
for f in bag.copyItems() {
|
|
f.addOrUpdate(messages: [updatedMessage], transaction: transaction)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func offsetPendingMessagesTimestamps(lowerBound: MessageId, excludeIds: Set<MessageId>, timestamp: Int32) {
|
|
self.messageHistoryTable.offsetPendingMessagesTimestamps(lowerBound: lowerBound, excludeIds: excludeIds, timestamp: timestamp, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations)
|
|
}
|
|
|
|
fileprivate func updateMessageGroupingKeysAtomically(_ ids: [MessageId], groupingKey: Int64) {
|
|
self.messageHistoryTable.updateMessageGroupingKeysAtomically(ids: ids, groupingKey: groupingKey, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries)
|
|
}
|
|
|
|
fileprivate func updateMedia(_ id: MediaId, update: Media?) -> Set<MessageIndex> {
|
|
var updatedMessageIndices = Set<MessageIndex>()
|
|
self.messageHistoryTable.updateMedia(id, media: update, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, updatedMessageIndices: &updatedMessageIndices)
|
|
return updatedMessageIndices
|
|
}
|
|
|
|
fileprivate func storeMediaIfNotPresent(media: Media) {
|
|
self.messageHistoryTable.storeMediaIfNotPresent(media: media)
|
|
}
|
|
|
|
fileprivate func replaceItemCollections(namespace: ItemCollectionId.Namespace, itemCollections: [(ItemCollectionId, ItemCollectionInfo, [ItemCollectionItem])]) {
|
|
var infos: [(ItemCollectionId, ItemCollectionInfo)] = []
|
|
for (id, info, items) in itemCollections {
|
|
infos.append((id, info))
|
|
self.itemCollectionItemTable.replaceItems(collectionId: id, items: items)
|
|
if self.currentItemCollectionItemsOperations[id] == nil {
|
|
self.currentItemCollectionItemsOperations[id] = []
|
|
}
|
|
self.currentItemCollectionItemsOperations[id]!.append(.replaceItems)
|
|
}
|
|
self.itemCollectionInfoTable.replaceInfos(namespace: namespace, infos: infos)
|
|
self.currentItemCollectionInfosOperations.append(.replaceInfos(namespace))
|
|
}
|
|
|
|
fileprivate func replaceItemCollectionInfos(namespace: ItemCollectionId.Namespace, itemCollectionInfos: [(ItemCollectionId, ItemCollectionInfo)]) {
|
|
self.itemCollectionInfoTable.replaceInfos(namespace: namespace, infos: itemCollectionInfos)
|
|
self.currentItemCollectionInfosOperations.append(.replaceInfos(namespace))
|
|
}
|
|
|
|
fileprivate func replaceItemCollectionItems(collectionId: ItemCollectionId, items: [ItemCollectionItem]) {
|
|
self.itemCollectionItemTable.replaceItems(collectionId: collectionId, items: items)
|
|
if self.currentItemCollectionItemsOperations[collectionId] == nil {
|
|
self.currentItemCollectionItemsOperations[collectionId] = []
|
|
}
|
|
self.currentItemCollectionItemsOperations[collectionId]!.append(.replaceItems)
|
|
}
|
|
|
|
fileprivate func removeItemCollection(collectionId: ItemCollectionId) {
|
|
var infos = self.itemCollectionInfoTable.getInfos(namespace: collectionId.namespace)
|
|
if let index = infos.firstIndex(where: { $0.1 == collectionId }) {
|
|
infos.remove(at: index)
|
|
self.replaceItemCollectionInfos(namespace: collectionId.namespace, itemCollectionInfos: infos.map { ($0.1, $0.2) })
|
|
}
|
|
self.replaceItemCollectionItems(collectionId: collectionId, items: [])
|
|
}
|
|
|
|
fileprivate func filterStoredMessageIds(_ messageIds: Set<MessageId>) -> Set<MessageId> {
|
|
var filteredIds = Set<MessageId>()
|
|
|
|
for id in messageIds {
|
|
if self.messageHistoryIndexTable.exists(id) {
|
|
filteredIds.insert(id)
|
|
} else {
|
|
assert(true)
|
|
}
|
|
}
|
|
|
|
return filteredIds
|
|
}
|
|
|
|
fileprivate func filterStoredMediaIds(namespace: MediaId.Namespace, ids: Set<Int64>) -> Set<Int64> {
|
|
var filteredIds = Set<Int64>()
|
|
|
|
for id in ids {
|
|
if !self.mediaTable.exists(id: MediaId(namespace: namespace, id: id)) {
|
|
filteredIds.insert(id)
|
|
}
|
|
}
|
|
|
|
return filteredIds
|
|
}
|
|
|
|
fileprivate func storedMessageId(peerId: PeerId, namespace: MessageId.Namespace, timestamp: Int32) -> MessageId? {
|
|
if let id = self.messageHistoryTable.findMessageId(peerId: peerId, namespace: namespace, timestamp: timestamp), id.namespace == namespace {
|
|
return id
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fileprivate func putItemCacheEntry(id: ItemCacheEntryId, entry: CodableEntry) {
|
|
self.itemCacheTable.put(id: id, entry: entry, metaTable: self.itemCacheMetaTable)
|
|
self.currentUpdatedCacheEntryKeys.insert(id)
|
|
}
|
|
|
|
func retrieveItemCacheEntry(id: ItemCacheEntryId) -> CodableEntry? {
|
|
return self.itemCacheTable.retrieve(id: id, metaTable: self.itemCacheMetaTable)
|
|
}
|
|
|
|
func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
|
|
return self.itemCacheTable.removeAll(collectionId: collectionId)
|
|
}
|
|
|
|
fileprivate func removeItemCacheEntry(id: ItemCacheEntryId) {
|
|
self.itemCacheTable.remove(id: id, metaTable: self.itemCacheMetaTable)
|
|
self.currentUpdatedCacheEntryKeys.insert(id)
|
|
}
|
|
|
|
fileprivate func replaceGlobalMessageTagsHole(transaction: Transaction, globalTags: GlobalMessageTags, index: MessageIndex, with updatedIndex: MessageIndex?, messages: [StoreMessage]) {
|
|
var allTagsMatch = true
|
|
for tag in globalTags {
|
|
self.globalMessageHistoryTagsTable.ensureInitialized(tag)
|
|
|
|
if let entry = self.globalMessageHistoryTagsTable.get(tag, index: index), case .hole = entry {
|
|
|
|
} else {
|
|
allTagsMatch = false
|
|
}
|
|
}
|
|
if allTagsMatch {
|
|
for tag in globalTags {
|
|
self.globalMessageHistoryTagsTable.remove(tag, index: index)
|
|
self.currentGlobalTagsOperations.append(.remove([(tag, index)]))
|
|
|
|
if let updatedIndex = updatedIndex {
|
|
self.globalMessageHistoryTagsTable.addHole(tag, index: updatedIndex)
|
|
self.currentGlobalTagsOperations.append(.insertHole(tag, updatedIndex))
|
|
}
|
|
}
|
|
|
|
let _ = self.addMessages(transaction: transaction, messages: messages, location: .Random)
|
|
}
|
|
}
|
|
|
|
fileprivate func setNoticeEntry(key: NoticeEntryKey, value: CodableEntry?) {
|
|
let current = self.noticeTable.get(key: key)
|
|
let updated: Bool
|
|
if let current = current, let value = value {
|
|
updated = current.data != value.data
|
|
} else if (current != nil) != (value != nil) {
|
|
updated = true
|
|
} else {
|
|
updated = false
|
|
}
|
|
if updated {
|
|
self.noticeTable.set(key: key, value: value)
|
|
self.currentUpdatedNoticeEntryKeys.insert(key)
|
|
}
|
|
}
|
|
|
|
fileprivate func clearNoticeEntries() {
|
|
self.noticeTable.clear()
|
|
}
|
|
|
|
fileprivate func setPendingMessageAction(type: PendingMessageActionType, id: MessageId, action: PendingMessageActionData?) {
|
|
self.messageHistoryTable.setPendingMessageAction(id: id, type: type, action: action, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries)
|
|
}
|
|
|
|
fileprivate func getPendingMessageAction(type: PendingMessageActionType, id: MessageId) -> PendingMessageActionData? {
|
|
return self.pendingMessageActionsTable.getAction(id: id, type: type)
|
|
}
|
|
|
|
fileprivate func replaceMessageTagSummary(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace, customTag: MemoryBuffer?, count: Int32, maxId: MessageId.Id) {
|
|
let key = MessageHistoryTagsSummaryKey(tag: tagMask, peerId: peerId, threadId: threadId, namespace: namespace, customTag: customTag)
|
|
self.messageHistoryTagsSummaryTable.replace(key: key, count: count, maxId: maxId, updatedSummaries: &self.currentUpdatedMessageTagSummaries)
|
|
}
|
|
|
|
fileprivate func searchMessages(peerId: PeerId?, query: String, tags: MessageTags?) -> [Message] {
|
|
var result: [Message] = []
|
|
for messageId in self.textIndexTable.search(peerId: peerId, text: query, tags: tags) {
|
|
if let index = self.messageHistoryIndexTable.getIndex(messageId), let message = self.messageHistoryTable.getMessage(index) {
|
|
result.append(self.messageHistoryTable.renderMessage(message, peerTable: self.peerTable, threadIndexTable: self.messageHistoryThreadIndexTable, storyTable: self.storyTable))
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
let canBeginTransactionsValue = Atomic<Bool>(value: true)
|
|
public func setCanBeginTransactions(_ value: Bool, afterTransactionIfRunning: @escaping () -> Void) {
|
|
self.queue.async {
|
|
let previous = self.canBeginTransactionsValue.swap(value)
|
|
if previous != value && value {
|
|
let fs = self.queuedInternalTransactions.swap([])
|
|
for f in fs {
|
|
f()
|
|
}
|
|
}
|
|
afterTransactionIfRunning()
|
|
}
|
|
}
|
|
|
|
private var queuedInternalTransactions = Atomic<[() -> Void]>(value: [])
|
|
|
|
private func beginInternalTransaction(ignoreDisabled: Bool = false, _ f: @escaping () -> Void) {
|
|
assert(self.queue.isCurrent())
|
|
if ignoreDisabled || self.canBeginTransactionsValue.with({ $0 }) {
|
|
f()
|
|
} else {
|
|
let _ = self.queuedInternalTransactions.modify { fs in
|
|
var fs = fs
|
|
fs.append(f)
|
|
return fs
|
|
}
|
|
}
|
|
}
|
|
|
|
let isInTransaction: Atomic<Bool>
|
|
|
|
private func internalTransaction<T>(_ f: (Transaction) -> T, file: String = #file, line: Int = #line) -> (result: T, updatedTransactionStateVersion: Int64?, updatedMasterClientId: Int64?) {
|
|
let _ = self.isInTransaction.swap(true)
|
|
|
|
let startTime = CFAbsoluteTimeGetCurrent()
|
|
|
|
self.valueBox.begin()
|
|
let transaction = Transaction(queue: self.queue, postbox: self)
|
|
self.afterBegin(transaction: transaction)
|
|
let result = f(transaction)
|
|
let (updatedTransactionState, updatedMasterClientId) = self.beforeCommit(currentTransaction: transaction)
|
|
transaction.disposed = true
|
|
self.valueBox.commit()
|
|
|
|
let endTime = CFAbsoluteTimeGetCurrent()
|
|
let transactionDuration = endTime - startTime
|
|
if transactionDuration > 0.1 {
|
|
postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file), on:\(line)")
|
|
}
|
|
|
|
let _ = self.isInTransaction.swap(false)
|
|
|
|
if let currentUpdatedState = self.currentUpdatedState {
|
|
self.statePipe.putNext(currentUpdatedState)
|
|
}
|
|
self.currentUpdatedState = nil
|
|
|
|
return (result, updatedTransactionState, updatedMasterClientId)
|
|
}
|
|
|
|
public func transactionSignal<T, E>(userInteractive: Bool = false, _ f: @escaping(Subscriber<T, E>, Transaction) -> Disposable) -> Signal<T, E> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
let f: () -> Void = {
|
|
self.beginInternalTransaction {
|
|
let (_, updatedTransactionState, updatedMasterClientId) = self.internalTransaction({ transaction in
|
|
disposable.set(f(subscriber, transaction))
|
|
})
|
|
|
|
if updatedTransactionState != nil || updatedMasterClientId != nil {
|
|
//self.pipeNotifier.notify()
|
|
}
|
|
|
|
if let updatedMasterClientId = updatedMasterClientId {
|
|
self.masterClientId.set(.single(updatedMasterClientId))
|
|
}
|
|
}
|
|
}
|
|
if userInteractive {
|
|
self.queue.justDispatchWithQoS(qos: DispatchQoS.userInteractive, f)
|
|
} else {
|
|
self.queue.justDispatch(f)
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
|
|
public func transaction<T>(userInteractive: Bool = false, ignoreDisabled: Bool = false, _ f: @escaping(Transaction) -> T, file: String = #file, line: Int = #line) -> Signal<T, NoError> {
|
|
return Signal { subscriber in
|
|
let f: () -> Void = {
|
|
self.beginInternalTransaction(ignoreDisabled: ignoreDisabled, {
|
|
let (result, updatedTransactionState, updatedMasterClientId) = self.internalTransaction({ transaction in
|
|
return f(transaction)
|
|
}, file: file, line: line)
|
|
|
|
if updatedTransactionState != nil || updatedMasterClientId != nil {
|
|
//self.pipeNotifier.notify()
|
|
}
|
|
|
|
if let updatedMasterClientId = updatedMasterClientId {
|
|
self.masterClientId.set(.single(updatedMasterClientId))
|
|
}
|
|
|
|
subscriber.putNext(result)
|
|
subscriber.putCompletion()
|
|
})
|
|
}
|
|
if self.queue.isCurrent() && Queue.mainQueue().isCurrent() {
|
|
f()
|
|
} else if userInteractive {
|
|
self.queue.justDispatchWithQoS(qos: DispatchQoS.userInteractive, f)
|
|
} else {
|
|
self.queue.justDispatch(f)
|
|
}
|
|
return EmptyDisposable
|
|
}
|
|
}
|
|
|
|
func resolvedChatLocationInput(chatLocation: ChatLocationInput) -> Signal<(ResolvedChatLocationInput, Bool), NoError> {
|
|
switch chatLocation {
|
|
case let .peer(peerId, threadId):
|
|
return .single((.peer(peerId: peerId, threadId: threadId), false))
|
|
case .thread(_, _, let data):
|
|
return Signal { subscriber in
|
|
var isHoleFill = false
|
|
return (data
|
|
|> map { value -> (ResolvedChatLocationInput, Bool) in
|
|
let wasHoleFill = isHoleFill
|
|
isHoleFill = true
|
|
return (.external(value), wasHoleFill)
|
|
}).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
|
|
}
|
|
case .customChatContents:
|
|
assert(false)
|
|
return .never()
|
|
}
|
|
}
|
|
|
|
func peerIdsForLocation(_ chatLocation: ResolvedChatLocationInput, ignoreRelatedChats: Bool) -> MessageHistoryViewInput {
|
|
var peerIds: MessageHistoryViewInput
|
|
switch chatLocation {
|
|
case let .peer(peerId, threadId):
|
|
peerIds = .single(peerId: peerId, threadId: threadId)
|
|
if !ignoreRelatedChats, threadId == nil, let associatedMessageId = self.cachedPeerDataTable.get(peerId)?.associatedHistoryMessageId, associatedMessageId.peerId != peerId {
|
|
peerIds = .associated(peerId, associatedMessageId)
|
|
}
|
|
case let .external(input):
|
|
peerIds = .external(input)
|
|
}
|
|
return peerIds
|
|
}
|
|
|
|
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange<Int32>?, ignoreMessageIds: Set<MessageId>, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tag: HistoryViewInputTag?, appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, customUnreadMessageId: MessageId?, additionalData: [AdditionalMessageHistoryViewData], useRootInterfaceStateForThread: Bool) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
|
return self.resolvedChatLocationInput(chatLocation: chatLocation)
|
|
|> mapToSignal { chatLocationData in
|
|
let (chatLocation, isHoleFill) = chatLocationData
|
|
|
|
let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal(userInteractive: true, { subscriber, transaction in
|
|
let peerIds = self.peerIdsForLocation(chatLocation, ignoreRelatedChats: false)
|
|
|
|
var anchor: HistoryViewInputAnchor = .upperBound
|
|
switch peerIds {
|
|
case let .single(peerId, threadId):
|
|
if let customUnreadMessageId = customUnreadMessageId {
|
|
anchor = .message(customUnreadMessageId)
|
|
} else if threadId == nil, self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil {
|
|
if let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 {
|
|
switch state.1 {
|
|
case let .idBased(maxIncomingReadId, _, _, _, _):
|
|
anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId))
|
|
case let .indexBased(maxIncomingReadIndex, _, _, _):
|
|
anchor = .index(maxIncomingReadIndex)
|
|
}
|
|
} else if let scrollIndex = self.peerChatInterfaceStateTable.get(peerId)?.historyScrollMessageIndex {
|
|
anchor = .index(scrollIndex)
|
|
}
|
|
}
|
|
case let .associated(mainId, associatedId):
|
|
var ids: [PeerId] = []
|
|
ids.append(mainId)
|
|
if let associatedId = associatedId {
|
|
ids.append(associatedId.peerId)
|
|
}
|
|
|
|
var found = false
|
|
loop: for peerId in ids.reversed() {
|
|
if self.chatListTable.getPeerChatListIndex(peerId: mainId) != nil, let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 {
|
|
found = true
|
|
switch state.1 {
|
|
case let .idBased(maxIncomingReadId, _, _, _, _):
|
|
anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId))
|
|
case let .indexBased(maxIncomingReadIndex, _, _, _):
|
|
anchor = .index(maxIncomingReadIndex)
|
|
}
|
|
break loop
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
if let scrollIndex = self.peerChatInterfaceStateTable.get(mainId)?.historyScrollMessageIndex {
|
|
anchor = .index(scrollIndex)
|
|
}
|
|
}
|
|
case let .external(input):
|
|
if let maxReadMessageId = input.maxReadIncomingMessageId {
|
|
anchor = .message(maxReadMessageId)
|
|
} else {
|
|
anchor = .upperBound
|
|
}
|
|
}
|
|
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, ignoreMessageIds: ignoreMessageIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData, useRootInterfaceStateForThread: useRootInterfaceStateForThread)
|
|
})
|
|
|
|
return signal
|
|
|> map { (view, updateType, data) -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in
|
|
if isHoleFill {
|
|
return (view, .FillHole, data)
|
|
} else {
|
|
return (view, updateType, data)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange<Int32>?, ignoreMessageIds: Set<MessageId>, count: Int, clipHoles: Bool = true, ignoreRelatedChats: Bool = false, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tag: HistoryViewInputTag?, appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = [], useRootInterfaceStateForThread: Bool = false) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
|
return self.resolvedChatLocationInput(chatLocation: chatLocation)
|
|
|> mapToSignal { chatLocationData in
|
|
let (chatLocation, isHoleFill) = chatLocationData
|
|
let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal { subscriber, transaction in
|
|
let peerIds = self.peerIdsForLocation(chatLocation, ignoreRelatedChats: ignoreRelatedChats)
|
|
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, ignoreMessageIds: ignoreMessageIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData, useRootInterfaceStateForThread: useRootInterfaceStateForThread)
|
|
}
|
|
|
|
return signal
|
|
|> map { (view, updateType, data) -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in
|
|
if isHoleFill {
|
|
return (view, .FillHole, data)
|
|
} else {
|
|
return (view, updateType, data)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange<Int32>?, ignoreMessageIds: Set<MessageId>, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, ignoreRelatedChats: Bool = false, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tag: HistoryViewInputTag?, appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = [], useRootInterfaceStateForThread: Bool = false) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
|
return self.resolvedChatLocationInput(chatLocation: chatLocation)
|
|
|> mapToSignal { chatLocationData -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in
|
|
let (chatLocation, isHoleFill) = chatLocationData
|
|
let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal { subscriber, transaction in
|
|
let peerIds = self.peerIdsForLocation(chatLocation, ignoreRelatedChats: ignoreRelatedChats)
|
|
|
|
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, ignoreMessageIds: ignoreMessageIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData, useRootInterfaceStateForThread: useRootInterfaceStateForThread)
|
|
}
|
|
|
|
return signal
|
|
|> map { viewData -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in
|
|
let (view, updateType, data) = viewData
|
|
if isHoleFill {
|
|
return (view, .FillHole, data)
|
|
} else {
|
|
return (view, updateType, data)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func syncAroundMessageHistoryViewForPeerId(
|
|
subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>,
|
|
peerIds: MessageHistoryViewInput,
|
|
ignoreMessagesInTimestampRange: ClosedRange<Int32>?,
|
|
ignoreMessageIds: Set<MessageId>,
|
|
count: Int,
|
|
clipHoles: Bool,
|
|
anchor: HistoryViewInputAnchor,
|
|
fixedCombinedReadStates: MessageHistoryViewReadState?,
|
|
topTaggedMessageIdNamespaces: Set<MessageId.Namespace>,
|
|
tag: HistoryViewInputTag?,
|
|
appendMessagesFromTheSameGroup: Bool,
|
|
namespaces: MessageIdNamespaces,
|
|
orderStatistics: MessageHistoryViewOrderStatistics,
|
|
additionalData: [AdditionalMessageHistoryViewData],
|
|
useRootInterfaceStateForThread: Bool
|
|
) -> Disposable {
|
|
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
|
|
var mainPeerIdForTopTaggedMessages: PeerId?
|
|
switch peerIds {
|
|
case let .single(id, threadId):
|
|
if threadId == nil {
|
|
mainPeerIdForTopTaggedMessages = id
|
|
}
|
|
case let .associated(id, _):
|
|
mainPeerIdForTopTaggedMessages = id
|
|
case .external:
|
|
mainPeerIdForTopTaggedMessages = nil
|
|
}
|
|
if let peerId = mainPeerIdForTopTaggedMessages {
|
|
for namespace in topTaggedMessageIdNamespaces {
|
|
if let messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId, namespace: namespace) {
|
|
if let index = self.messageHistoryIndexTable.getIndex(messageId) {
|
|
if let message = self.messageHistoryTable.getMessage(index) {
|
|
topTaggedMessages[namespace] = MessageHistoryTopTaggedMessage.intermediate(message)
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
} else {
|
|
//assertionFailure()
|
|
}
|
|
} else {
|
|
let item: MessageHistoryTopTaggedMessage? = nil
|
|
topTaggedMessages[namespace] = item
|
|
}
|
|
}
|
|
}
|
|
|
|
var additionalDataEntries: [AdditionalMessageHistoryViewDataEntry] = []
|
|
for data in additionalData {
|
|
switch data {
|
|
case let .cachedPeerData(peerId):
|
|
additionalDataEntries.append(.cachedPeerData(peerId, self.cachedPeerDataTable.get(peerId)))
|
|
case let .cachedPeerDataMessages(peerId):
|
|
var messages: [MessageId: Message] = [:]
|
|
if let messageIds = self.cachedPeerDataTable.get(peerId)?.messageIds {
|
|
for id in messageIds {
|
|
if let message = self.getMessage(id) {
|
|
messages[id] = message
|
|
}
|
|
}
|
|
}
|
|
additionalDataEntries.append(.cachedPeerDataMessages(peerId, messages))
|
|
case let .message(id):
|
|
let messages = self.getMessageGroup(at: id)
|
|
additionalDataEntries.append(.message(id, messages ?? []))
|
|
case let .peerChatState(peerId):
|
|
additionalDataEntries.append(.peerChatState(peerId, self.peerChatStateTable.get(peerId)?.getLegacy() as? PeerChatState))
|
|
case .totalUnreadState:
|
|
additionalDataEntries.append(.totalUnreadState(self.messageHistoryMetadataTable.getTotalUnreadState(groupId: .root)))
|
|
case let .peerNotificationSettings(peerId):
|
|
var notificationPeerId = peerId
|
|
if let peer = self.peerTable.get(peerId), let associatedPeerId = peer.associatedPeerId {
|
|
notificationPeerId = associatedPeerId
|
|
}
|
|
additionalDataEntries.append(.peerNotificationSettings(self.peerNotificationSettingsTable.getEffective(notificationPeerId)))
|
|
case let .cacheEntry(entryId):
|
|
additionalDataEntries.append(.cacheEntry(entryId, self.retrieveItemCacheEntry(id: entryId)))
|
|
case let .preferencesEntry(key):
|
|
additionalDataEntries.append(.preferencesEntry(key, self.preferencesTable.get(key: key)))
|
|
case let .peerIsContact(peerId):
|
|
let value: Bool
|
|
if let contactPeer = self.peerTable.get(peerId), let associatedPeerId = contactPeer.associatedPeerId {
|
|
value = self.contactsTable.isContact(peerId: associatedPeerId)
|
|
} else {
|
|
value = self.contactsTable.isContact(peerId: peerId)
|
|
}
|
|
additionalDataEntries.append(.peerIsContact(peerId, value))
|
|
case let .peer(peerId):
|
|
additionalDataEntries.append(.peer(peerId, self.peerTable.get(peerId)))
|
|
}
|
|
}
|
|
|
|
var readStates: MessageHistoryViewReadState?
|
|
var transientReadStates: MessageHistoryViewReadState?
|
|
switch peerIds {
|
|
case let .single(peerId, threadId):
|
|
if threadId == nil, let readState = self.readStateTable.getCombinedState(peerId) {
|
|
transientReadStates = .peer([peerId: readState])
|
|
}
|
|
case let .associated(peerId, _):
|
|
if let readState = self.readStateTable.getCombinedState(peerId) {
|
|
transientReadStates = .peer([peerId: readState])
|
|
}
|
|
case .external:
|
|
transientReadStates = nil
|
|
}
|
|
|
|
if let fixedCombinedReadStates = fixedCombinedReadStates {
|
|
readStates = fixedCombinedReadStates
|
|
} else {
|
|
readStates = transientReadStates
|
|
}
|
|
|
|
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, clipHoles: clipHoles, trackHoles: true, peerIds: peerIds, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, ignoreMessageIds: ignoreMessageIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries)
|
|
|
|
let initialUpdateType: ViewUpdateType = .Initial
|
|
|
|
let (index, signal) = self.viewTracker.addMessageHistoryView(mutableView)
|
|
|
|
let initialData: InitialMessageHistoryData
|
|
switch peerIds {
|
|
case let .single(peerId, threadId):
|
|
initialData = self.initialMessageHistoryData(peerId: peerId, threadId: useRootInterfaceStateForThread ? nil : threadId)
|
|
case let .associated(peerId, _):
|
|
initialData = self.initialMessageHistoryData(peerId: peerId, threadId: nil)
|
|
case let .external(input):
|
|
switch input.content {
|
|
case let .thread(peerId, id, _):
|
|
initialData = self.initialMessageHistoryData(peerId: peerId, threadId: id)
|
|
case .messages:
|
|
initialData = InitialMessageHistoryData(
|
|
peer: nil,
|
|
storedInterfaceState: nil,
|
|
associatedMessages: [:]
|
|
)
|
|
}
|
|
}
|
|
|
|
subscriber.putNext((MessageHistoryView(mutableView), initialUpdateType, initialData))
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext((next.0, next.1, nil))
|
|
})
|
|
|
|
final class MarkedActionDisposable: Disposable {
|
|
let disposable: ActionDisposable
|
|
|
|
init(_ f: @escaping () -> Void) {
|
|
self.disposable = ActionDisposable(action: f)
|
|
}
|
|
|
|
func dispose() {
|
|
self.disposable.dispose()
|
|
}
|
|
}
|
|
|
|
return MarkedActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.justDispatch {
|
|
strongSelf.viewTracker.removeMessageHistoryView(index: index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func initialMessageHistoryData(peerId: PeerId, threadId: Int64?) -> InitialMessageHistoryData {
|
|
if let threadId = threadId {
|
|
let chatInterfaceState = self.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: peerId, threadId: threadId))
|
|
var associatedMessages: [MessageId: Message] = [:]
|
|
if let chatInterfaceState = chatInterfaceState {
|
|
for id in chatInterfaceState.associatedMessageIds {
|
|
if let message = self.getMessage(id) {
|
|
associatedMessages[message.id] = message
|
|
}
|
|
}
|
|
}
|
|
return InitialMessageHistoryData(peer: self.peerTable.get(peerId), storedInterfaceState: chatInterfaceState, associatedMessages: associatedMessages)
|
|
} else {
|
|
let chatInterfaceState = self.peerChatInterfaceStateTable.get(peerId)
|
|
var associatedMessages: [MessageId: Message] = [:]
|
|
if let chatInterfaceState = chatInterfaceState {
|
|
for id in chatInterfaceState.associatedMessageIds {
|
|
if let message = self.getMessage(id) {
|
|
associatedMessages[message.id] = message
|
|
}
|
|
}
|
|
}
|
|
return InitialMessageHistoryData(peer: self.peerTable.get(peerId), storedInterfaceState: chatInterfaceState, associatedMessages: associatedMessages)
|
|
}
|
|
}
|
|
|
|
public func messageIndexAtId(_ id: MessageId) -> Signal<MessageIndex?, NoError> {
|
|
return self.transaction { transaction -> Signal<MessageIndex?, NoError> in
|
|
if let index = self.messageHistoryIndexTable.getIndex(id) {
|
|
return .single(index)
|
|
} else {
|
|
return .single(nil)
|
|
}
|
|
}
|
|
|> switchToLatest
|
|
}
|
|
|
|
public func messageAtId(_ id: MessageId) -> Signal<Message?, NoError> {
|
|
return self.transaction { transaction -> Signal<Message?, NoError> in
|
|
if let index = self.messageHistoryIndexTable.getIndex(id) {
|
|
if let message = self.messageHistoryTable.getMessage(index) {
|
|
return .single(self.renderIntermediateMessage(message))
|
|
} else {
|
|
return .single(nil)
|
|
}
|
|
} else {
|
|
return .single(nil)
|
|
}
|
|
}
|
|
|> switchToLatest
|
|
}
|
|
|
|
public func messagesAtIds(_ ids: [MessageId]) -> Signal<[Message], NoError> {
|
|
return self.transaction { transaction -> Signal<[Message], NoError> in
|
|
var messages: [Message] = []
|
|
for id in ids {
|
|
if let index = self.messageHistoryIndexTable.getIndex(id) {
|
|
if let message = self.messageHistoryTable.getMessage(index) {
|
|
messages.append(self.renderIntermediateMessage(message))
|
|
}
|
|
}
|
|
}
|
|
return .single(messages)
|
|
}
|
|
|> switchToLatest
|
|
}
|
|
|
|
public func tailChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, count: Int, summaryComponents: ChatListEntrySummaryComponents, extractCachedData: ((CachedPeerData) -> AnyHashable?)?, accountPeerId: PeerId?) -> Signal<(ChatListView, ViewUpdateType), NoError> {
|
|
return self.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: ChatListIndex.absoluteUpperBound, count: count, summaryComponents: summaryComponents, userInteractive: true, extractCachedData: extractCachedData, accountPeerId: accountPeerId)
|
|
}
|
|
|
|
public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false, extractCachedData: ((CachedPeerData) -> AnyHashable?)?, accountPeerId: PeerId?) -> Signal<(ChatListView, ViewUpdateType), NoError> {
|
|
return self.transactionSignal(userInteractive: userInteractive, { subscriber, transaction in
|
|
let mutableView = MutableChatListView(postbox: self, currentTransaction: transaction, groupId: groupId, filterPredicate: filterPredicate, aroundIndex: index, count: count, summaryComponents: summaryComponents, extractCachedData: extractCachedData, accountPeerId: accountPeerId)
|
|
mutableView.render(postbox: self)
|
|
|
|
let (index, signal) = self.viewTracker.addChatListView(mutableView)
|
|
|
|
subscriber.putNext((ChatListView(mutableView), .Generic))
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeChatListView(index)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
public func contactPeerIdsView() -> Signal<ContactPeerIdsView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutableContactPeerIdsView(remoteTotalCount: self.metadataTable.getRemoteContactCount(), peerIds: self.contactsTable.get())
|
|
let (index, signal) = self.viewTracker.addContactPeerIdsView(view)
|
|
|
|
subscriber.putNext(ContactPeerIdsView(view))
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeContactPeerIdsView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func searchContacts(query: String) -> Signal<([Peer], [PeerId: PeerPresence]), NoError> {
|
|
return self.transaction { transaction -> Signal<([Peer], [PeerId: PeerPresence]), NoError> in
|
|
let (_, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable)
|
|
|
|
var contactPeers: [Peer] = []
|
|
var presences: [PeerId: PeerPresence] = [:]
|
|
for peerId in contactPeerIds {
|
|
if let peer = self.peerTable.get(peerId) {
|
|
contactPeers.append(peer)
|
|
if let presence = self.peerPresenceTable.get(peerId) {
|
|
presences[peerId] = presence
|
|
}
|
|
}
|
|
}
|
|
|
|
contactPeers.sort(by: { $0.indexName.indexName(.lastNameFirst) < $1.indexName.indexName(.lastNameFirst) })
|
|
return .single((contactPeers, presences))
|
|
} |> switchToLatest
|
|
}
|
|
|
|
public func searchPeers(query: String) -> Signal<[RenderedPeer], NoError> {
|
|
return self.transaction { transaction -> Signal<[RenderedPeer], NoError> in
|
|
return .single(transaction.searchPeers(query: query))
|
|
} |> switchToLatest
|
|
}
|
|
|
|
fileprivate func searchPeers(query: String) -> [RenderedPeer] {
|
|
var peerIds = Set<PeerId>()
|
|
var chatPeers: [RenderedPeer] = []
|
|
|
|
var (chatPeerIds, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.chats, .contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable)
|
|
|
|
var additionalChatPeerIds: [PeerId] = []
|
|
for peerId in chatPeerIds {
|
|
for associatedId in self.reverseAssociatedPeerTable.get(peerId: peerId) {
|
|
let inclusionIndex = self.chatListIndexTable.get(peerId: associatedId)
|
|
if inclusionIndex.includedIndex(peerId: associatedId) != nil {
|
|
additionalChatPeerIds.append(associatedId)
|
|
}
|
|
}
|
|
}
|
|
chatPeerIds.append(contentsOf: additionalChatPeerIds)
|
|
|
|
if let peerId = self.searchLocalPeerId(query: query) {
|
|
chatPeerIds.append(peerId)
|
|
}
|
|
|
|
for peerId in chatPeerIds {
|
|
if let peer = self.peerTable.get(peerId) {
|
|
var peers = SimpleDictionary<PeerId, Peer>()
|
|
peers[peer.id] = peer
|
|
if let associatedPeerId = peer.associatedPeerId {
|
|
if let associatedPeer = self.peerTable.get(associatedPeerId) {
|
|
peers[associatedPeer.id] = associatedPeer
|
|
}
|
|
}
|
|
chatPeers.append(RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: self, peers: peers)))
|
|
peerIds.insert(peerId)
|
|
}
|
|
}
|
|
|
|
var contactPeers: [RenderedPeer] = []
|
|
for peerId in contactPeerIds {
|
|
if !peerIds.contains(peerId) {
|
|
if let peer = self.peerTable.get(peerId) {
|
|
var peers = SimpleDictionary<PeerId, Peer>()
|
|
peers[peer.id] = peer
|
|
contactPeers.append(RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: self, peers: peers)))
|
|
}
|
|
}
|
|
}
|
|
|
|
contactPeers.sort(by: { lhs, rhs in
|
|
lhs.peers[lhs.peerId]!.indexName.indexName(.lastNameFirst) < rhs.peers[rhs.peerId]!.indexName.indexName(.lastNameFirst)
|
|
})
|
|
return chatPeers + contactPeers
|
|
}
|
|
|
|
public func peerView(id: PeerId) -> Signal<PeerView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutablePeerView(postbox: self, peerId: id, components: .all)
|
|
let (index, signal) = self.viewTracker.addPeerView(view)
|
|
|
|
subscriber.putNext(PeerView(view))
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removePeerView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func multiplePeersView(_ ids: [PeerId]) -> Signal<MultiplePeersView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutableMultiplePeersView(peerIds: ids, getPeer: { self.peerTable.get($0) }, getPeerPresence: { self.peerPresenceTable.get($0) })
|
|
let (index, signal) = self.viewTracker.addMultiplePeersView(view)
|
|
|
|
subscriber.putNext(MultiplePeersView(view))
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeMultiplePeersView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func loadedPeerWithId(_ id: PeerId) -> Signal<Peer, NoError> {
|
|
return self.transaction { transaction -> Signal<Peer, NoError> in
|
|
if let peer = self.peerTable.get(id) {
|
|
return .single(peer)
|
|
} else {
|
|
return .never()
|
|
}
|
|
} |> switchToLatest
|
|
}
|
|
|
|
public func unreadMessageCountsView(items: [UnreadMessageCountsItem]) -> Signal<UnreadMessageCountsView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutableUnreadMessageCountsView(postbox: self, items: items)
|
|
let (index, signal) = self.viewTracker.addUnreadMessageCountsView(view)
|
|
|
|
subscriber.putNext(UnreadMessageCountsView(view))
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeUnreadMessageCountsView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func recentPeers() -> Signal<[Peer], NoError> {
|
|
return self.transaction { transaction -> Signal<[Peer], NoError> in
|
|
let peerIds = self.peerRatingTable.get()
|
|
var peers: [Peer] = []
|
|
for peerId in peerIds {
|
|
if let peer: Peer = self.peerTable.get(peerId) {
|
|
peers.append(peer)
|
|
}
|
|
}
|
|
return .single(peers)
|
|
} |> switchToLatest
|
|
}
|
|
|
|
public func stateView() -> Signal<PostboxStateView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let mutableView = MutablePostboxStateView(state: self.getState())
|
|
|
|
subscriber.putNext(PostboxStateView(mutableView))
|
|
|
|
let (index, signal) = self.viewTracker.addPostboxStateView(mutableView)
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removePostboxStateView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func messageHistoryHolesView() -> Signal<MessageHistoryHolesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
self.queue.async {
|
|
disposable.set(self.viewTracker.messageHistoryHolesViewSignal().start(next: { view in
|
|
subscriber.putNext(view)
|
|
}))
|
|
}
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func chatListHolesView() -> Signal<ChatListHolesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
self.queue.async {
|
|
disposable.set(self.viewTracker.chatListHolesViewSignal().start(next: { view in
|
|
subscriber.putNext(view)
|
|
}))
|
|
}
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func forumTopicListHolesView() -> Signal<ForumTopicListHolesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
self.queue.async {
|
|
disposable.set(self.viewTracker.forumTopicListHolesViewSignal().start(next: { view in
|
|
subscriber.putNext(view)
|
|
}))
|
|
}
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func unsentMessageIdsView() -> Signal<UnsentMessageIdsView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
self.queue.async {
|
|
disposable.set(self.viewTracker.unsentMessageIdsViewSignal().start(next: { view in
|
|
postboxLog("unsentMessageIdsView contents: \(view.ids)")
|
|
subscriber.putNext(view)
|
|
}))
|
|
}
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func synchronizePeerReadStatesView() -> Signal<SynchronizePeerReadStatesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
self.queue.async {
|
|
disposable.set(self.viewTracker.synchronizePeerReadStatesViewSignal().start(next: { view in
|
|
subscriber.putNext(view)
|
|
}))
|
|
}
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func itemCollectionsView(orderedItemListCollectionIds: [Int32], namespaces: [ItemCollectionId.Namespace], aroundIndex: ItemCollectionViewEntryIndex?, count: Int) -> Signal<ItemCollectionsView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let itemListViews = orderedItemListCollectionIds.map { collectionId -> MutableOrderedItemListView in
|
|
return MutableOrderedItemListView(postbox: self, collectionId: collectionId)
|
|
}
|
|
|
|
let mutableView = MutableItemCollectionsView(postbox: self, orderedItemListsViews: itemListViews, namespaces: namespaces, aroundIndex: aroundIndex, count: count)
|
|
|
|
subscriber.putNext(ItemCollectionsView(mutableView))
|
|
|
|
let (index, signal) = self.viewTracker.addItemCollectionView(mutableView)
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeItemCollectionView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func mergedOperationLogView(tag: PeerOperationLogTag, filterByPeerId: PeerId?, limit: Int) -> Signal<PeerMergedOperationLogView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutablePeerMergedOperationLogView(postbox: self, tag: tag, filterByPeerId: filterByPeerId, limit: limit)
|
|
|
|
subscriber.putNext(PeerMergedOperationLogView(view))
|
|
|
|
let (index, signal) = self.viewTracker.addPeerMergedOperationLogView(view)
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removePeerMergedOperationLogView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func timestampBasedMessageAttributesView(tag: UInt16) -> Signal<TimestampBasedMessageAttributesView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutableTimestampBasedMessageAttributesView(postbox: self, tag: tag)
|
|
let (index, signal) = self.viewTracker.addTimestampBasedMessageAttributesView(view)
|
|
|
|
subscriber.putNext(TimestampBasedMessageAttributesView(view))
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeTimestampBasedMessageAttributesView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func operationLogGetNextEntryLocalIndex(peerId: PeerId, tag: PeerOperationLogTag) -> Int32 {
|
|
return self.peerOperationLogTable.getNextEntryLocalIndex(peerId: peerId, tag: tag)
|
|
}
|
|
|
|
fileprivate func operationLogAddEntry(peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: StorePeerOperationLogEntryTagLocalIndex, tagMergedIndex: StorePeerOperationLogEntryTagMergedIndex, contents: PostboxCoding) {
|
|
self.peerOperationLogTable.addEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, tagMergedIndex: tagMergedIndex, contents: contents, operations: &self.currentPeerMergedOperationLogOperations)
|
|
}
|
|
|
|
fileprivate func operationLogRemoveEntry(peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32) -> Bool {
|
|
return self.peerOperationLogTable.removeEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, operations: &self.currentPeerMergedOperationLogOperations)
|
|
}
|
|
|
|
fileprivate func operationLogRemoveAllEntries(peerId: PeerId, tag: PeerOperationLogTag) {
|
|
self.peerOperationLogTable.removeAllEntries(peerId: peerId, tag: tag, operations: &self.currentPeerMergedOperationLogOperations)
|
|
}
|
|
|
|
fileprivate func operationLogRemoveEntries(peerId: PeerId, tag: PeerOperationLogTag, withTagLocalIndicesEqualToOrLowerThan maxTagLocalIndex: Int32) {
|
|
self.peerOperationLogTable.removeEntries(peerId: peerId, tag: tag, withTagLocalIndicesEqualToOrLowerThan: maxTagLocalIndex, operations: &self.currentPeerMergedOperationLogOperations)
|
|
}
|
|
|
|
fileprivate func operationLogUpdateEntry(peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: (PeerOperationLogEntry?) -> PeerOperationLogEntryUpdate) {
|
|
self.peerOperationLogTable.updateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, f: f, operations: &self.currentPeerMergedOperationLogOperations)
|
|
}
|
|
|
|
fileprivate func operationLogEnumerateEntries(peerId: PeerId, tag: PeerOperationLogTag, _ f: (PeerOperationLogEntry) -> Bool) {
|
|
self.peerOperationLogTable.enumerateEntries(peerId: peerId, tag: tag, f)
|
|
}
|
|
|
|
public func messageView(_ messageId: MessageId) -> Signal<MessageView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutableMessageView(messageId: messageId, message: transaction.getMessage(messageId))
|
|
|
|
subscriber.putNext(MessageView(view))
|
|
|
|
let (index, signal) = self.viewTracker.addMessageView(view)
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeMessageView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func preferencesView(keys: [ValueBoxKey]) -> Signal<PreferencesView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutablePreferencesView(postbox: self, keys: Set(keys))
|
|
let (index, signal) = self.viewTracker.addPreferencesView(view)
|
|
|
|
subscriber.putNext(PreferencesView(view))
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removePreferencesView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func combinedView(keys: [PostboxViewKey]) -> Signal<CombinedView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
var views: [PostboxViewKey: MutablePostboxView] = [:]
|
|
for key in keys {
|
|
views[key] = postboxViewForKey(postbox: self, key: key)
|
|
}
|
|
let view = CombinedMutableView(views: views)
|
|
let (index, signal) = self.viewTracker.addCombinedView(view)
|
|
|
|
subscriber.putNext(view.immutableView())
|
|
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeCombinedView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func enumeratePreferencesEntries(_ f: (PreferencesEntry) -> Bool) {
|
|
self.preferencesTable.enumerateEntries(f)
|
|
}
|
|
|
|
fileprivate func getPreferencesEntry(key: ValueBoxKey) -> PreferencesEntry? {
|
|
return self.preferencesTable.get(key: key)
|
|
}
|
|
|
|
fileprivate func setPreferencesEntry(key: ValueBoxKey, value: PreferencesEntry?) {
|
|
self.preferencesTable.set(key: key, value: value, operations: &self.currentPreferencesOperations)
|
|
}
|
|
|
|
fileprivate func globalNotificationSettingsUpdated() {
|
|
self.currentNeedsReindexUnreadCounters = true
|
|
}
|
|
|
|
fileprivate func replaceOrderedItemListItems(collectionId: Int32, items: [OrderedItemListEntry]) {
|
|
self.orderedItemListTable.replaceItems(collectionId: collectionId, items: items, operations: &self.currentOrderedItemListOperations)
|
|
}
|
|
|
|
fileprivate func addOrMoveToFirstPositionOrderedItemListItem(collectionId: Int32, item: OrderedItemListEntry, removeTailIfCountExceeds: Int?) {
|
|
self.orderedItemListTable.addItemOrMoveToFirstPosition(collectionId: collectionId, item: item, removeTailIfCountExceeds: removeTailIfCountExceeds, operations: &self.currentOrderedItemListOperations)
|
|
}
|
|
|
|
fileprivate func removeOrderedItemListItem(collectionId: Int32, itemId: MemoryBuffer) {
|
|
self.orderedItemListTable.remove(collectionId: collectionId, itemId: itemId, operations: &self.currentOrderedItemListOperations)
|
|
}
|
|
|
|
fileprivate func getOrderedListItemIds(collectionId: Int32) -> [MemoryBuffer] {
|
|
return self.orderedItemListTable.getItemIds(collectionId: collectionId)
|
|
}
|
|
|
|
fileprivate func getOrderedItemListItem(collectionId: Int32, itemId: MemoryBuffer) -> OrderedItemListEntry? {
|
|
return self.orderedItemListTable.getItem(collectionId: collectionId, itemId: itemId)
|
|
}
|
|
|
|
fileprivate func updateOrderedItemListItem(collectionId: Int32, itemId: MemoryBuffer, item: CodableEntry) {
|
|
self.orderedItemListTable.updateItem(collectionId: collectionId, itemId: itemId, item: item, operations: &self.currentOrderedItemListOperations)
|
|
}
|
|
|
|
public func installStoreMessageAction(peerId: PeerId, _ f: @escaping ([StoreMessage], Transaction) -> Void) -> Disposable {
|
|
let disposable = MetaDisposable()
|
|
self.queue.async {
|
|
if self.installedMessageActionsByPeerId[peerId] == nil {
|
|
self.installedMessageActionsByPeerId[peerId] = Bag()
|
|
}
|
|
let index = self.installedMessageActionsByPeerId[peerId]!.add(f)
|
|
disposable.set(ActionDisposable {
|
|
self.queue.async {
|
|
if let bag = self.installedMessageActionsByPeerId[peerId] {
|
|
bag.remove(index)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
return disposable
|
|
}
|
|
|
|
public func installStoreOrUpdateMessageAction(peerId: PeerId, action: StoreOrUpdateMessageAction) -> Disposable {
|
|
let disposable = MetaDisposable()
|
|
self.queue.async {
|
|
if self.installedStoreOrUpdateMessageActionsByPeerId[peerId] == nil {
|
|
self.installedStoreOrUpdateMessageActionsByPeerId[peerId] = Bag()
|
|
}
|
|
let index = self.installedStoreOrUpdateMessageActionsByPeerId[peerId]!.add(action)
|
|
disposable.set(ActionDisposable {
|
|
self.queue.async {
|
|
if let bag = self.installedStoreOrUpdateMessageActionsByPeerId[peerId] {
|
|
bag.remove(index)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
return disposable
|
|
}
|
|
|
|
public func addHiddenChatFilterAndChatIds(peerIds: [PeerId]) -> Disposable {
|
|
let disposable = MetaDisposable()
|
|
|
|
let queue = self.queue
|
|
let _ = (self.transaction { transaction -> [PeerId: Int] in
|
|
var peerIndices: [PeerId: Int] = [:]
|
|
for peerId in peerIds {
|
|
peerIndices[peerId] = transaction.addChatHidden(peerId: peerId)
|
|
}
|
|
return peerIndices
|
|
}).start(next: { peerIndices in
|
|
disposable.set(ActionDisposable { [weak self] in
|
|
queue.async {
|
|
guard let `self` = self else {
|
|
return
|
|
}
|
|
let _ = (self.transaction { transaction -> Void in
|
|
for peerId in peerIds {
|
|
if let index = peerIndices[peerId] {
|
|
transaction.removeChatHidden(peerId: peerId, index: index)
|
|
}
|
|
}
|
|
}).start()
|
|
}
|
|
})
|
|
})
|
|
|
|
return disposable
|
|
}
|
|
|
|
fileprivate func scanMessages(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) {
|
|
var index = MessageIndex.lowerBound(peerId: peerId, namespace: namespace)
|
|
while true {
|
|
let indices = self.messageHistoryTagsTable.laterIndices(tag: tag, peerId: peerId, namespace: namespace, index: index, includeFrom: false, count: 10)
|
|
for index in indices {
|
|
if let message = self.messageHistoryTable.getMessage(index) {
|
|
if !f(self.renderIntermediateMessage(message)) {
|
|
break
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
break
|
|
}
|
|
}
|
|
if let last = indices.last {
|
|
index = last
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func scanMessages(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) {
|
|
var index = MessageIndex.lowerBound(peerId: peerId, namespace: namespace)
|
|
while true {
|
|
let indices = self.messageHistoryThreadTagsTable.laterIndices(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace, index: index, includeFrom: false, count: 10)
|
|
for index in indices {
|
|
if let message = self.messageHistoryTable.getMessage(index) {
|
|
if !f(self.renderIntermediateMessage(message)) {
|
|
break
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
break
|
|
}
|
|
}
|
|
if let last = indices.last {
|
|
index = last
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func scanTopMessages(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (Message) -> Bool) {
|
|
let lowerBound = MessageIndex.lowerBound(peerId: peerId, namespace: namespace)
|
|
var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace)
|
|
var remainingLimit = limit
|
|
while remainingLimit > 0 {
|
|
let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, customTag: nil, threadId: nil, from: index, includeFrom: false, to: lowerBound, ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), limit: 10)
|
|
remainingLimit -= 10
|
|
for message in messages {
|
|
if !f(self.renderIntermediateMessage(message)) {
|
|
break
|
|
}
|
|
}
|
|
if let last = messages.last {
|
|
index = last.index
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (MessageId, [MessageAttribute]) -> Bool) {
|
|
var remainingLimit = limit
|
|
var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace)
|
|
while remainingLimit > 0 {
|
|
let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, customTag: nil, threadId: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), limit: 32)
|
|
for message in messages {
|
|
let attributes = MessageHistoryTable.renderMessageAttributes(message)
|
|
if !f(message.id, attributes) {
|
|
break
|
|
}
|
|
}
|
|
remainingLimit -= messages.count
|
|
if let last = messages.last {
|
|
index = last.index
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func invalidateMessageHistoryTagsSummary(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, tagMask: MessageTags, customTag: MemoryBuffer?) {
|
|
self.invalidatedMessageHistoryTagsSummaryTable.insert(InvalidatedMessageHistoryTagsSummaryKey(peerId: peerId, namespace: namespace, tagMask: tagMask, threadId: threadId, customTag: customTag), operations: &self.currentInvalidateMessageTagSummaries)
|
|
}
|
|
|
|
fileprivate func removeInvalidatedMessageHistoryTagsSummaryEntry(_ entry: InvalidatedMessageHistoryTagsSummaryEntry) {
|
|
self.invalidatedMessageHistoryTagsSummaryTable.remove(entry, operations: &self.currentInvalidateMessageTagSummaries)
|
|
}
|
|
|
|
fileprivate func removeInvalidatedMessageHistoryTagsSummaryEntriesWithCustomTags(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, tagMask: MessageTags) {
|
|
self.invalidatedMessageHistoryTagsSummaryTable.removeEntriesWithCustomTags(peerId: peerId, threadId: threadId, namespace: namespace, tagMask: tagMask, operations: &self.currentInvalidateMessageTagSummaries)
|
|
}
|
|
|
|
fileprivate func getRelativeUnreadChatListIndex(currentTransaction: Transaction, filtered: Bool, position: ChatListRelativePosition, groupId: PeerGroupId) -> ChatListIndex? {
|
|
return self.chatListTable.getRelativeUnreadChatListIndex(postbox: self, currentTransaction: currentTransaction, filtered: filtered, position: position, groupId: groupId)
|
|
}
|
|
|
|
func getMessage(_ id: MessageId) -> Message? {
|
|
if let index = self.messageHistoryIndexTable.getIndex(id) {
|
|
if let message = self.messageHistoryTable.getMessage(index) {
|
|
return self.renderIntermediateMessage(message)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getMessageGroup(at id: MessageId) -> [Message]? {
|
|
guard let index = self.messageHistoryIndexTable.getIndex(id) else {
|
|
return nil
|
|
}
|
|
if let messages = self.messageHistoryTable.getMessageGroup(at: index, limit: 16) {
|
|
return messages.map(self.renderIntermediateMessage)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fileprivate func getMessageForwardedGroup(_ id: MessageId) -> [Message]? {
|
|
guard let index = self.messageHistoryIndexTable.getIndex(id) else {
|
|
return nil
|
|
}
|
|
if let messages = self.messageHistoryTable.getMessageForwardedGroup(at: index, limit: 200) {
|
|
return messages.map(self.renderIntermediateMessage)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fileprivate func getMessageFailedGroup(_ id: MessageId) -> [Message]? {
|
|
guard let index = self.messageHistoryIndexTable.getIndex(id) else {
|
|
return nil
|
|
}
|
|
if let messages = self.messageHistoryTable.getMessageFailedGroup(at: index, limit: 100) {
|
|
return messages.sorted(by: { lhs, rhs in
|
|
return lhs.index < rhs.index
|
|
}).map(self.renderIntermediateMessage)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fileprivate func clearDeviceContactImportInfoIdentifiers() {
|
|
let identifiers = self.deviceContactImportInfoTable.getIdentifiers()
|
|
for identifier in identifiers {
|
|
self.deviceContactImportInfoTable.set(identifier, value: nil)
|
|
}
|
|
}
|
|
|
|
func getGlobalNotificationSettings(transaction: Transaction) -> PostboxGlobalNotificationSettings {
|
|
return self.seedConfiguration.getGlobalNotificationSettings(transaction) ?? self.seedConfiguration.defaultGlobalNotificationSettings
|
|
}
|
|
|
|
public func isMasterClient() -> Signal<Bool, NoError> {
|
|
return self.transaction { transaction -> Signal<Bool, NoError> in
|
|
let sessionClientId = self.sessionClientId
|
|
return self.masterClientId.get()
|
|
|> distinctUntilChanged
|
|
|> map({ $0 == sessionClientId })
|
|
} |> switchToLatest
|
|
}
|
|
|
|
public func becomeMasterClient() {
|
|
let _ = self.transaction({ transaction in
|
|
if self.metadataTable.masterClientId() != self.sessionClientId {
|
|
self.currentUpdatedMasterClientId = self.sessionClientId
|
|
}
|
|
}).start()
|
|
}
|
|
|
|
public func clearCaches() {
|
|
let _ = self.transaction({ _ in
|
|
for table in self.tables {
|
|
table.clearMemoryCache()
|
|
}
|
|
}).start()
|
|
}
|
|
|
|
public func optimizeStorage() -> Signal<Never, NoError> {
|
|
return Signal { subscriber in
|
|
self.valueBox.vacuum()
|
|
subscriber.putCompletion()
|
|
|
|
return EmptyDisposable
|
|
}
|
|
}
|
|
|
|
fileprivate func addHolesEverywhere(peerNamespaces: [PeerId.Namespace], holeNamespace: MessageId.Namespace) {
|
|
for peerId in self.chatListIndexTable.getAllPeerIds() {
|
|
if peerNamespaces.contains(peerId.namespace) && self.messageHistoryMetadataTable.isInitialized(peerId) {
|
|
self.addHole(peerId: peerId, threadId: nil, namespace: holeNamespace, space: .everywhere, range: 1 ... Int32.max - 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func resetCustomTagHoles() {
|
|
self.messageCustomTagHoleIndexTable.resetAll()
|
|
self.messageCustomTagWithTagHoleIndexTable.resetAll()
|
|
}
|
|
|
|
fileprivate func clearTimestampBasedAttribute(id: MessageId, tag: UInt16) {
|
|
self.timestampBasedMessageAttributesTable.remove(tag: tag, id: id, operations: &self.currentTimestampBasedMessageAttributesOperations)
|
|
}
|
|
|
|
fileprivate func removePeerTimeoutAttributeEntry(peerId: PeerId, timestamp: UInt32) {
|
|
self.peerTimeoutPropertiesTable.remove(peerId: peerId, timestamp: timestamp)
|
|
}
|
|
|
|
fileprivate func setPeerThreadCombinedState(peerId: PeerId, state: StoredPeerThreadCombinedState?) {
|
|
self.currentUpdatedPeerThreadCombinedStates.insert(peerId)
|
|
self.peerThreadCombinedStateTable.set(peerId: peerId, state: state)
|
|
}
|
|
|
|
fileprivate func setPeerPinnedThreads(peerId: PeerId, threadIds: [Int64]) {
|
|
self.currentUpdatedPinnedThreads.insert(peerId)
|
|
self.messageHistoryThreadPinnedTable.set(peerId: peerId, threadIds: threadIds)
|
|
}
|
|
|
|
fileprivate func addChatHidden(peerId: PeerId) -> Int {
|
|
let bag: Bag<Void>
|
|
if let current = self.currentHiddenChatIds[peerId] {
|
|
bag = current
|
|
} else {
|
|
bag = Bag()
|
|
self.currentHiddenChatIds[peerId] = bag
|
|
}
|
|
self.currentUpdatedHiddenPeerIds = true
|
|
return bag.add(Void())
|
|
}
|
|
|
|
fileprivate func removeChatHidden(peerId: PeerId, index: Int) {
|
|
if let current = self.currentHiddenChatIds[peerId] {
|
|
current.remove(index)
|
|
self.currentUpdatedHiddenPeerIds = true
|
|
}
|
|
}
|
|
|
|
fileprivate func reindexUnreadCounters(currentTransaction: Transaction) {
|
|
self.groupMessageStatsTable.removeAll()
|
|
let _ = CFAbsoluteTimeGetCurrent()
|
|
let (totalStates, summaries) = self.chatListIndexTable.debugReindexUnreadCounts(postbox: self, currentTransaction: currentTransaction)
|
|
|
|
self.messageHistoryMetadataTable.removeAllTotalUnreadStates()
|
|
for (groupId, state) in totalStates {
|
|
self.messageHistoryMetadataTable.setTotalUnreadState(groupId: groupId, state: state)
|
|
}
|
|
self.currentUpdatedTotalUnreadStates = totalStates
|
|
|
|
for (groupId, summary) in summaries {
|
|
self.groupMessageStatsTable.set(groupId: groupId, summary: summary)
|
|
self.currentUpdatedGroupTotalUnreadSummaries[groupId] = summary
|
|
}
|
|
}
|
|
|
|
public func failedMessageIdsView(peerId: PeerId) -> Signal<FailedMessageIdsView, NoError> {
|
|
return self.transactionSignal { subscriber, transaction in
|
|
let view = MutableFailedMessageIdsView(peerId: peerId, ids: self.failedMessageIds(for: peerId))
|
|
let (index, signal) = self.viewTracker.addFailedMessageIdsView(view)
|
|
subscriber.putNext(view.immutableView())
|
|
let disposable = signal.start(next: { next in
|
|
subscriber.putNext(next)
|
|
})
|
|
|
|
return ActionDisposable { [weak self] in
|
|
disposable.dispose()
|
|
if let strongSelf = self {
|
|
strongSelf.queue.async {
|
|
strongSelf.viewTracker.removeFailedMessageIdsView(index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public class Postbox {
|
|
public static let sharedQueue = Queue(name: "org.telegram.postbox.Postbox")
|
|
|
|
let queue: Queue
|
|
private let impl: QueueLocalObject<PostboxImpl>
|
|
|
|
public let seedConfiguration: SeedConfiguration
|
|
public let mediaBox: MediaBox
|
|
|
|
private let isInTransaction = Atomic<Bool>(value: false)
|
|
|
|
init(
|
|
queue: Queue,
|
|
basePath: String,
|
|
seedConfiguration: SeedConfiguration,
|
|
valueBox: SqliteValueBox,
|
|
timestampForAbsoluteTimeBasedOperations: Int32,
|
|
isMainProcess: Bool,
|
|
isTemporary: Bool,
|
|
tempDir: TempBoxDirectory?,
|
|
useCaches: Bool
|
|
) {
|
|
self.queue = queue
|
|
|
|
self.seedConfiguration = seedConfiguration
|
|
|
|
postboxLog("MediaBox path: \(basePath + "/media")")
|
|
self.mediaBox = MediaBox(basePath: basePath + "/media", isMainProcess: isMainProcess)
|
|
|
|
let isInTransaction = self.isInTransaction
|
|
|
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
|
let impl = PostboxImpl(
|
|
queue: queue,
|
|
basePath: basePath,
|
|
seedConfiguration: seedConfiguration,
|
|
valueBox: valueBox,
|
|
timestampForAbsoluteTimeBasedOperations: timestampForAbsoluteTimeBasedOperations,
|
|
isTemporary: isTemporary,
|
|
tempDir: tempDir,
|
|
useCaches: useCaches,
|
|
isInTransaction: isInTransaction
|
|
)
|
|
return impl
|
|
})
|
|
}
|
|
|
|
public func keychainEntryForKey(_ key: String) -> Data? {
|
|
return self.impl.syncWith { impl -> Data? in
|
|
return impl.keychainEntryForKey(key)
|
|
}
|
|
}
|
|
|
|
public func setKeychainEntryForKey(_ key: String, value: Data) {
|
|
self.impl.with { impl in
|
|
impl.setKeychainEntryForKey(key, value: value)
|
|
}
|
|
}
|
|
public func removeKeychainEntryForKey(_ key: String) {
|
|
self.impl.with { impl in
|
|
impl.removeKeychainEntryForKey(key)
|
|
}
|
|
}
|
|
|
|
public func setCanBeginTransactions(_ value: Bool, afterTransactionIfRunning: @escaping () -> Void = {}) {
|
|
let storageBox = self.mediaBox.storageBox
|
|
self.impl.with { impl in
|
|
impl.setCanBeginTransactions(value, afterTransactionIfRunning: {
|
|
storageBox.setCanBeginTransactions(value, afterTransactionIfRunning: {
|
|
afterTransactionIfRunning()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
public func transactionSignal<T, E>(userInteractive: Bool = false, _ f: @escaping(Subscriber<T, E>, Transaction) -> Disposable) -> Signal<T, E> {
|
|
return Signal<T, E> { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.transactionSignal(userInteractive: userInteractive, f).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func transaction<T>(userInteractive: Bool = false, ignoreDisabled: Bool = false, _ f: @escaping(Transaction) -> T, file: String = #file, line: Int = #line) -> Signal<T, NoError> {
|
|
return Signal<T, NoError> { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.transaction(userInteractive: userInteractive, ignoreDisabled: ignoreDisabled, f, file: file, line: line).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func aroundMessageOfInterestHistoryViewForChatLocation(
|
|
_ chatLocation: ChatLocationInput,
|
|
ignoreMessagesInTimestampRange: ClosedRange<Int32>?,
|
|
ignoreMessageIds: Set<MessageId>,
|
|
count: Int,
|
|
clipHoles: Bool = true,
|
|
topTaggedMessageIdNamespaces: Set<MessageId.Namespace>,
|
|
tag: HistoryViewInputTag?,
|
|
appendMessagesFromTheSameGroup: Bool,
|
|
namespaces: MessageIdNamespaces,
|
|
orderStatistics: MessageHistoryViewOrderStatistics,
|
|
customUnreadMessageId: MessageId?,
|
|
additionalData: [AdditionalMessageHistoryViewData],
|
|
useRootInterfaceStateForThread: Bool
|
|
) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.aroundMessageOfInterestHistoryViewForChatLocation(
|
|
chatLocation,
|
|
ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange,
|
|
ignoreMessageIds: ignoreMessageIds,
|
|
count: count,
|
|
clipHoles: clipHoles,
|
|
topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces,
|
|
tag: tag,
|
|
appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup,
|
|
namespaces: namespaces,
|
|
orderStatistics: orderStatistics,
|
|
customUnreadMessageId: customUnreadMessageId,
|
|
additionalData: additionalData,
|
|
useRootInterfaceStateForThread: useRootInterfaceStateForThread
|
|
).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable.strict()
|
|
}
|
|
}
|
|
|
|
public func aroundIdMessageHistoryViewForLocation(
|
|
_ chatLocation: ChatLocationInput,
|
|
ignoreMessagesInTimestampRange: ClosedRange<Int32>?,
|
|
ignoreMessageIds: Set<MessageId>,
|
|
count: Int,
|
|
clipHoles: Bool = true,
|
|
ignoreRelatedChats: Bool = false,
|
|
messageId: MessageId,
|
|
topTaggedMessageIdNamespaces: Set<MessageId.Namespace>,
|
|
tag: HistoryViewInputTag?,
|
|
appendMessagesFromTheSameGroup: Bool,
|
|
namespaces: MessageIdNamespaces,
|
|
orderStatistics: MessageHistoryViewOrderStatistics,
|
|
additionalData: [AdditionalMessageHistoryViewData] = [],
|
|
useRootInterfaceStateForThread: Bool = false
|
|
) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.aroundIdMessageHistoryViewForLocation(
|
|
chatLocation,
|
|
ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange,
|
|
ignoreMessageIds: ignoreMessageIds,
|
|
count: count,
|
|
clipHoles: clipHoles,
|
|
ignoreRelatedChats: ignoreRelatedChats,
|
|
messageId: messageId,
|
|
topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces,
|
|
tag: tag,
|
|
appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup,
|
|
namespaces: namespaces,
|
|
orderStatistics: orderStatistics,
|
|
additionalData: additionalData,
|
|
useRootInterfaceStateForThread: useRootInterfaceStateForThread
|
|
).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable.strict()
|
|
}
|
|
}
|
|
|
|
public func aroundMessageHistoryViewForLocation(
|
|
_ chatLocation: ChatLocationInput,
|
|
anchor: HistoryViewInputAnchor,
|
|
ignoreMessagesInTimestampRange: ClosedRange<Int32>?,
|
|
ignoreMessageIds: Set<MessageId>,
|
|
count: Int,
|
|
clipHoles: Bool = true,
|
|
ignoreRelatedChats: Bool = false,
|
|
fixedCombinedReadStates: MessageHistoryViewReadState?,
|
|
topTaggedMessageIdNamespaces: Set<MessageId.Namespace>,
|
|
tag: HistoryViewInputTag?,
|
|
appendMessagesFromTheSameGroup: Bool,
|
|
namespaces: MessageIdNamespaces,
|
|
orderStatistics: MessageHistoryViewOrderStatistics,
|
|
additionalData: [AdditionalMessageHistoryViewData] = [],
|
|
useRootInterfaceStateForThread: Bool = false
|
|
) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.aroundMessageHistoryViewForLocation(
|
|
chatLocation,
|
|
ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange,
|
|
ignoreMessageIds: ignoreMessageIds,
|
|
anchor: anchor,
|
|
count: count,
|
|
clipHoles: clipHoles,
|
|
ignoreRelatedChats: ignoreRelatedChats,
|
|
fixedCombinedReadStates: fixedCombinedReadStates,
|
|
topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces,
|
|
tag: tag,
|
|
appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup,
|
|
namespaces: namespaces,
|
|
orderStatistics: orderStatistics,
|
|
additionalData: additionalData,
|
|
useRootInterfaceStateForThread: useRootInterfaceStateForThread
|
|
).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable.strict()
|
|
}
|
|
}
|
|
|
|
public func messageIndexAtId(_ id: MessageId) -> Signal<MessageIndex?, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.messageIndexAtId(id).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func messageAtId(_ id: MessageId) -> Signal<Message?, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.messageAtId(id).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func messagesAtIds(_ ids: [MessageId]) -> Signal<[Message], NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.messagesAtIds(ids).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func tailChatListView(
|
|
groupId: PeerGroupId,
|
|
filterPredicate: ChatListFilterPredicate? = nil,
|
|
count: Int,
|
|
summaryComponents: ChatListEntrySummaryComponents,
|
|
extractCachedData: ((CachedPeerData) -> AnyHashable?)? = nil,
|
|
accountPeerId: PeerId? = nil
|
|
) -> Signal<(ChatListView, ViewUpdateType), NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.tailChatListView(
|
|
groupId: groupId,
|
|
filterPredicate: filterPredicate,
|
|
count: count,
|
|
summaryComponents: summaryComponents,
|
|
extractCachedData: extractCachedData,
|
|
accountPeerId: accountPeerId
|
|
).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func aroundChatListView(
|
|
groupId: PeerGroupId,
|
|
filterPredicate: ChatListFilterPredicate? = nil,
|
|
index: ChatListIndex,
|
|
count: Int,
|
|
summaryComponents: ChatListEntrySummaryComponents,
|
|
userInteractive: Bool = false,
|
|
extractCachedData: ((CachedPeerData) -> AnyHashable?)? = nil,
|
|
accountPeerId: PeerId? = nil
|
|
) -> Signal<(ChatListView, ViewUpdateType), NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.aroundChatListView(
|
|
groupId: groupId,
|
|
filterPredicate: filterPredicate,
|
|
index: index,
|
|
count: count,
|
|
summaryComponents: summaryComponents,
|
|
userInteractive: userInteractive,
|
|
extractCachedData: extractCachedData,
|
|
accountPeerId: accountPeerId
|
|
).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func contactPeerIdsView() -> Signal<ContactPeerIdsView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.contactPeerIdsView().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func searchContacts(query: String) -> Signal<([Peer], [PeerId: PeerPresence]), NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.searchContacts(query: query).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func searchPeers(query: String) -> Signal<[RenderedPeer], NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.searchPeers(query: query).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func peerView(id: PeerId) -> Signal<PeerView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.peerView(id: id).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func multiplePeersView(_ ids: [PeerId]) -> Signal<MultiplePeersView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.multiplePeersView(ids).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func loadedPeerWithId(_ id: PeerId) -> Signal<Peer, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.loadedPeerWithId(id).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func unreadMessageCountsView(items: [UnreadMessageCountsItem]) -> Signal<UnreadMessageCountsView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.unreadMessageCountsView(items: items).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func recentPeers() -> Signal<[Peer], NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.recentPeers().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func stateView() -> Signal<PostboxStateView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.stateView().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func messageHistoryHolesView() -> Signal<MessageHistoryHolesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.messageHistoryHolesView().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func chatListHolesView() -> Signal<ChatListHolesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.chatListHolesView().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func forumTopicListHolesView() -> Signal<ForumTopicListHolesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.forumTopicListHolesView().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func unsentMessageIdsView() -> Signal<UnsentMessageIdsView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.unsentMessageIdsView().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func synchronizePeerReadStatesView() -> Signal<SynchronizePeerReadStatesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.synchronizePeerReadStatesView().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func itemCollectionsView(
|
|
orderedItemListCollectionIds: [Int32],
|
|
namespaces: [ItemCollectionId.Namespace],
|
|
aroundIndex: ItemCollectionViewEntryIndex?,
|
|
count: Int
|
|
) -> Signal<ItemCollectionsView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.itemCollectionsView(
|
|
orderedItemListCollectionIds: orderedItemListCollectionIds,
|
|
namespaces: namespaces,
|
|
aroundIndex: aroundIndex,
|
|
count: count
|
|
).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func mergedOperationLogView(tag: PeerOperationLogTag, filterByPeerId: PeerId? = nil, limit: Int) -> Signal<PeerMergedOperationLogView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.mergedOperationLogView(tag: tag, filterByPeerId: filterByPeerId, limit: limit).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func timestampBasedMessageAttributesView(tag: UInt16) -> Signal<TimestampBasedMessageAttributesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.timestampBasedMessageAttributesView(tag: tag).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func messageView(_ messageId: MessageId) -> Signal<MessageView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.messageView(messageId).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func preferencesView(keys: [ValueBoxKey]) -> Signal<PreferencesView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.preferencesView(keys: keys).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func combinedView(keys: [PostboxViewKey]) -> Signal<CombinedView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.combinedView(keys: keys).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func installStoreMessageAction(peerId: PeerId, _ f: @escaping ([StoreMessage], Transaction) -> Void) -> Disposable {
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.installStoreMessageAction(peerId: peerId, f))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
|
|
public func installStoreOrUpdateMessageAction(peerId: PeerId, action: StoreOrUpdateMessageAction) -> Disposable {
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.installStoreOrUpdateMessageAction(peerId: peerId, action: action))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
|
|
public func addHiddenChatIds(peerIds: [PeerId]) -> Disposable {
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.addHiddenChatFilterAndChatIds(peerIds: peerIds))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
|
|
public func isMasterClient() -> Signal<Bool, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.isMasterClient().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func becomeMasterClient() {
|
|
self.impl.with { impl in
|
|
impl.becomeMasterClient()
|
|
}
|
|
}
|
|
|
|
public func clearCaches() {
|
|
self.impl.with { impl in
|
|
impl.clearCaches()
|
|
}
|
|
}
|
|
|
|
public func optimizeStorage() -> Signal<Never, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.optimizeStorage().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
|
|
public func failedMessageIdsView(peerId: PeerId) -> Signal<FailedMessageIdsView, NoError> {
|
|
return Signal { subscriber in
|
|
let disposable = MetaDisposable()
|
|
|
|
self.impl.with { impl in
|
|
disposable.set(impl.failedMessageIdsView(peerId: peerId).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
|
|
}
|
|
|
|
return disposable
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: Swiftgram
|
|
extension PostboxImpl {
|
|
func searchLocalPeerId(query: String) -> PeerId? {
|
|
var result: PeerId? = nil
|
|
let minus100Prefix = "-100"
|
|
var query = query
|
|
if query.hasPrefix(minus100Prefix) {
|
|
query = String(query.dropFirst(minus100Prefix.count))
|
|
}
|
|
guard let queryInt64 = Int64(query) else { return nil }
|
|
let possiblePeerId = PeerId(queryInt64)
|
|
|
|
|
|
if self.cachedPeerDataTable.get(possiblePeerId) != nil {
|
|
#if DEBUG
|
|
print("Found peer \(queryInt64) in cachedPeerDataTable")
|
|
#endif
|
|
return possiblePeerId
|
|
}
|
|
|
|
if self.peerTable.get(possiblePeerId) != nil {
|
|
#if DEBUG
|
|
print("Found peer \(queryInt64) in peerTable")
|
|
#endif
|
|
return possiblePeerId
|
|
}
|
|
|
|
self.valueBox.scanInt64(self.chatListIndexTable.table, keys: { key in
|
|
let peerId = PeerId(key)
|
|
let peerIdInt64 = peerId.id._internalGetInt64Value()
|
|
if queryInt64 == peerIdInt64 /* /* For basic groups */ || abs(queryInt64) == peerIdInt64 */ {
|
|
#if DEBUG
|
|
print("Found peer \(queryInt64) in chatListIndexTable")
|
|
#endif
|
|
result = peerId
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
|
|
return result
|
|
}
|
|
}
|