mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Merge branch 'postbox-mac'
This commit is contained in:
commit
ab26601177
@ -56,8 +56,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -98,30 +97,31 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
|
||||
|
||||
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil))), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|
||||
|> mapToSignal { account -> Signal<Account?, NoError> in
|
||||
if let account = account {
|
||||
switch account {
|
||||
case .upgrading:
|
||||
return .complete()
|
||||
case let .authorized(account):
|
||||
return applicationSettings(accountManager: accountManager)
|
||||
|> deliverOnMainQueue
|
||||
|> map { settings -> Account in
|
||||
accountCache = account
|
||||
Logger.shared.logToFile = settings.logging.logToFile
|
||||
Logger.shared.logToConsole = settings.logging.logToConsole
|
||||
|
||||
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
||||
return account
|
||||
}
|
||||
case .unauthorized:
|
||||
return .complete()
|
||||
|> mapToSignal { account -> Signal<Account?, NoError> in
|
||||
if let account = account {
|
||||
switch account {
|
||||
case .upgrading:
|
||||
return .complete()
|
||||
case let .authorized(account):
|
||||
account.shouldKeepOnlinePresence.set(.single(false))
|
||||
return applicationSettings(accountManager: accountManager)
|
||||
|> deliverOnMainQueue
|
||||
|> map { settings -> Account in
|
||||
accountCache = account
|
||||
Logger.shared.logToFile = settings.logging.logToFile
|
||||
Logger.shared.logToConsole = settings.logging.logToConsole
|
||||
|
||||
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
||||
return account
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
case .unauthorized:
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
|> take(1)
|
||||
}
|
||||
|> take(1)
|
||||
}
|
||||
self.accountPromise.set(account)
|
||||
}
|
||||
@ -145,36 +145,37 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
@available(iOSApplicationExtension 11.0, *)
|
||||
var sendMessageRecipientResulutionResult: INSendMessageRecipientResolutionResult {
|
||||
switch self {
|
||||
case let .success(person):
|
||||
return .success(with: person)
|
||||
case let .disambiguation(persons):
|
||||
return .disambiguation(with: persons)
|
||||
case .needsValue:
|
||||
return .needsValue()
|
||||
case .noResult:
|
||||
return .unsupported()
|
||||
case .skip:
|
||||
return .notRequired()
|
||||
case let .success(person):
|
||||
return .success(with: person)
|
||||
case let .disambiguation(persons):
|
||||
return .disambiguation(with: persons)
|
||||
case .needsValue:
|
||||
return .needsValue()
|
||||
case .noResult:
|
||||
return .unsupported()
|
||||
case .skip:
|
||||
return .notRequired()
|
||||
}
|
||||
}
|
||||
|
||||
var personResolutionResult: INPersonResolutionResult {
|
||||
switch self {
|
||||
case let .success(person):
|
||||
return .success(with: person)
|
||||
case let .disambiguation(persons):
|
||||
return .disambiguation(with: persons)
|
||||
case .needsValue:
|
||||
return .needsValue()
|
||||
case .noResult:
|
||||
return .unsupported()
|
||||
case .skip:
|
||||
return .notRequired()
|
||||
case let .success(person):
|
||||
return .success(with: person)
|
||||
case let .disambiguation(persons):
|
||||
return .disambiguation(with: persons)
|
||||
case .needsValue:
|
||||
return .needsValue()
|
||||
case .noResult:
|
||||
return .unsupported()
|
||||
case .skip:
|
||||
return .notRequired()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func resolve(persons: [INPerson]?, with completion: @escaping ([ResolveResult]) -> Void) {
|
||||
let account = self.accountPromise.get()
|
||||
guard let initialPersons = persons, !initialPersons.isEmpty else {
|
||||
completion([.needsValue])
|
||||
return
|
||||
@ -231,20 +232,18 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
return nil
|
||||
})
|
||||
|
||||
let account = self.accountPromise.get()
|
||||
|
||||
let signal = matchingDeviceContacts(stableIds: stableIds)
|
||||
|> take(1)
|
||||
|> mapToSignal { matchedContacts in
|
||||
return account
|
||||
|> introduceError(IntentContactsError.self)
|
||||
|> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in
|
||||
if let account = account {
|
||||
return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts)
|
||||
|> introduceError(IntentContactsError.self)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> introduceError(IntentContactsError.self)
|
||||
|> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in
|
||||
if let account = account {
|
||||
return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts)
|
||||
|> introduceError(IntentContactsError.self)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.resolvePersonsDisposable.set((signal
|
||||
@ -276,36 +275,34 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
@available(iOSApplicationExtension 11.0, *)
|
||||
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
|
||||
if let peerId = intent.conversationIdentifier.flatMap(Int64.init) {
|
||||
let account = self.accountPromise.get()
|
||||
|
||||
let signal = account
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
|
||||
if let account = account {
|
||||
return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId))
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> map { user -> INPerson? in
|
||||
if let user = user {
|
||||
return personWithUser(stableId: "tg\(peerId)", user: user)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
let signal = self.accountPromise.get()
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
|
||||
if let account = account {
|
||||
return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId))
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> map { user -> INPerson? in
|
||||
if let user = user {
|
||||
return personWithUser(stableId: "tg\(peerId)", user: user)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|
||||
self.resolvePersonsDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { person in
|
||||
if let person = person {
|
||||
completion([INSendMessageRecipientResolutionResult.success(with: person)])
|
||||
} else {
|
||||
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
||||
}
|
||||
}, error: { error in
|
||||
completion([INSendMessageRecipientResolutionResult.unsupported(forReason: .noAccount)])
|
||||
}))
|
||||
|> deliverOnMainQueue).start(next: { person in
|
||||
if let person = person {
|
||||
completion([INSendMessageRecipientResolutionResult.success(with: person)])
|
||||
} else {
|
||||
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
||||
}
|
||||
}, error: { error in
|
||||
completion([INSendMessageRecipientResolutionResult.unsupported(forReason: .noAccount)])
|
||||
}))
|
||||
} else {
|
||||
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
|
||||
completion([INSendMessageRecipientResolutionResult.notRequired()])
|
||||
@ -342,48 +339,48 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
|
||||
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
|
||||
self.actionDisposable.set((self.accountPromise.get()
|
||||
|> take(1)
|
||||
|> take(1)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||
guard let account = account else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let recipient = intent.recipients?.first, let customIdentifier = recipient.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
let peerId = PeerId(peerIdValue)
|
||||
if peerId.namespace != Namespaces.Peer.CloudUser {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||
return standaloneSendMessage(account: account, peerId: peerId, text: intent.content ?? "", attributes: [], media: nil, replyToMessageId: nil)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||
guard let account = account else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let recipient = intent.recipients?.first, let customIdentifier = recipient.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
let peerId = PeerId(peerIdValue)
|
||||
if peerId.namespace != Namespaces.Peer.CloudUser {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||
return standaloneSendMessage(account: account, peerId: peerId, text: intent.content ?? "", attributes: [], media: nil, replyToMessageId: nil)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||
return .complete()
|
||||
}
|
||||
|> afterDisposed {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||
return .complete()
|
||||
}
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||
let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||
completion(response)
|
||||
}, completed: {
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
|
||||
completion(response)
|
||||
}))
|
||||
|> afterDisposed {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||
let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||
completion(response)
|
||||
}, completed: {
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
|
||||
completion(response)
|
||||
}))
|
||||
}
|
||||
|
||||
// MARK: - INSearchForMessagesIntentHandling
|
||||
@ -394,42 +391,48 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
|
||||
func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
|
||||
self.actionDisposable.set((self.accountPromise.get()
|
||||
|> take(1)
|
||||
|> take(1)
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> mapToSignal { account -> Signal<[INMessage], IntentHandlingError> in
|
||||
guard let account = account else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||
account.resetStateManagement()
|
||||
|
||||
let completion: Signal<Void, NoError> = account.stateManager.pollStateUpdateCompletion()
|
||||
|> map { _ in
|
||||
return Void()
|
||||
}
|
||||
|
||||
return (completion |> timeout(4.0, queue: Queue.mainQueue(), alternate: .single(Void())))
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> mapToSignal { account -> Signal<[INMessage], IntentHandlingError> in
|
||||
guard let account = account else {
|
||||
return .fail(.generic)
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in
|
||||
let messages: Signal<[INMessage], NoError>
|
||||
if let identifiers = intent.identifiers, !identifiers.isEmpty {
|
||||
messages = getMessages(account: account, ids: identifiers.compactMap(MessageId.init(string:)))
|
||||
} else {
|
||||
messages = unreadMessages(account: account)
|
||||
}
|
||||
|
||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||
account.resetStateManagement()
|
||||
|
||||
let completion: Signal<Void, NoError> = account.stateManager.pollStateUpdateCompletion()
|
||||
|> map { _ in
|
||||
return Void()
|
||||
}
|
||||
|
||||
return (completion |> timeout(4.0, queue: Queue.mainQueue(), alternate: .single(Void())))
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in
|
||||
return unreadMessages(account: account)
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> afterDisposed {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
}
|
||||
return messages
|
||||
|> introduceError(IntentHandlingError.self)
|
||||
|> afterDisposed {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { messages in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
||||
response.messages = messages
|
||||
completion(response)
|
||||
}, error: { _ in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||
let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||
completion(response)
|
||||
}))
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { messages in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
||||
response.messages = messages
|
||||
completion(response)
|
||||
}, error: { _ in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||
let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||
completion(response)
|
||||
}))
|
||||
}
|
||||
|
||||
// MARK: - INSetMessageAttributeIntentHandling
|
||||
@ -449,61 +452,61 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
|
||||
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
|
||||
self.actionDisposable.set((self.accountPromise.get()
|
||||
|> take(1)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
|> take(1)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||
guard let account = account else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||
guard let account = account else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
var signals: [Signal<Void, IntentHandlingError>] = []
|
||||
var maxMessageIdsToApply: [PeerId: MessageId] = [:]
|
||||
if let identifiers = intent.identifiers {
|
||||
for identifier in identifiers {
|
||||
let components = identifier.components(separatedBy: "_")
|
||||
if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) {
|
||||
let peerId = PeerId(peerId)
|
||||
let messageId = MessageId(peerId: peerId, namespace: namespace, id: id)
|
||||
if let currentMessageId = maxMessageIdsToApply[peerId] {
|
||||
if currentMessageId < messageId {
|
||||
maxMessageIdsToApply[peerId] = messageId
|
||||
}
|
||||
} else {
|
||||
|
||||
var signals: [Signal<Void, IntentHandlingError>] = []
|
||||
var maxMessageIdsToApply: [PeerId: MessageId] = [:]
|
||||
if let identifiers = intent.identifiers {
|
||||
for identifier in identifiers {
|
||||
let components = identifier.components(separatedBy: "_")
|
||||
if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) {
|
||||
let peerId = PeerId(peerId)
|
||||
let messageId = MessageId(peerId: peerId, namespace: namespace, id: id)
|
||||
if let currentMessageId = maxMessageIdsToApply[peerId] {
|
||||
if currentMessageId < messageId {
|
||||
maxMessageIdsToApply[peerId] = messageId
|
||||
}
|
||||
} else {
|
||||
maxMessageIdsToApply[peerId] = messageId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (_, messageId) in maxMessageIdsToApply {
|
||||
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
||||
|> introduceError(IntentHandlingError.self))
|
||||
}
|
||||
|
||||
if signals.isEmpty {
|
||||
return .complete()
|
||||
} else {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||
return combineLatest(signals)
|
||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||
return .complete()
|
||||
}
|
||||
|> afterDisposed {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
}
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||
let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity)
|
||||
completion(response)
|
||||
}, completed: {
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
||||
completion(response)
|
||||
}))
|
||||
|
||||
for (_, messageId) in maxMessageIdsToApply {
|
||||
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
||||
|> introduceError(IntentHandlingError.self))
|
||||
}
|
||||
|
||||
if signals.isEmpty {
|
||||
return .complete()
|
||||
} else {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||
return combineLatest(signals)
|
||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||
return .complete()
|
||||
}
|
||||
|> afterDisposed {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
}
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||
let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity)
|
||||
completion(response)
|
||||
}, completed: {
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
||||
completion(response)
|
||||
}))
|
||||
}
|
||||
|
||||
// MARK: - INStartAudioCallIntentHandling
|
||||
@ -518,38 +521,43 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
})
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, *)
|
||||
func resolveDestinationType(for intent: INStartAudioCallIntent, with completion: @escaping (INCallDestinationTypeResolutionResult) -> Void) {
|
||||
completion(.success(with: .normal))
|
||||
}
|
||||
|
||||
func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) {
|
||||
self.actionDisposable.set((self.accountPromise.get()
|
||||
|> take(1)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
|> take(1)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in
|
||||
guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in
|
||||
guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
let peerId = PeerId(peerIdValue)
|
||||
if peerId.namespace != Namespaces.Peer.CloudUser {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return .single(peerId)
|
||||
|
||||
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peerId in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||
userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"]
|
||||
let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
|
||||
completion(response)
|
||||
}, error: { _ in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||
let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||
completion(response)
|
||||
}))
|
||||
|
||||
let peerId = PeerId(peerIdValue)
|
||||
if peerId.namespace != Namespaces.Peer.CloudUser {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return .single(peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peerId in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||
userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"]
|
||||
let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
|
||||
completion(response)
|
||||
}, error: { _ in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||
let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||
completion(response)
|
||||
}))
|
||||
}
|
||||
|
||||
// MARK: - INSearchCallHistoryIntentHandling
|
||||
|
||||
@ -1,11 +1,33 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Contacts
|
||||
import Intents
|
||||
|
||||
extension MessageId {
|
||||
init?(string: String) {
|
||||
let components = string.components(separatedBy: "_")
|
||||
if components.count == 3, let peerIdValue = Int64(components[0]), let namespaceValue = Int32(components[1]), let idValue = Int32(components[2]) {
|
||||
self.init(peerId: PeerId(peerIdValue), namespace: namespaceValue, id: idValue)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getMessages(account: Account, ids: [MessageId]) -> Signal<[INMessage], NoError> {
|
||||
return account.postbox.transaction { transaction -> [INMessage] in
|
||||
var messages: [INMessage] = []
|
||||
for id in ids {
|
||||
if let message = transaction.getMessage(id).flatMap(messageWithTelegramMessage) {
|
||||
messages.append(message)
|
||||
}
|
||||
}
|
||||
return messages.sorted { $0.dateSent!.compare($1.dateSent!) == .orderedDescending }
|
||||
}
|
||||
}
|
||||
|
||||
func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
||||
return account.postbox.tailChatListView(groupId: .root, count: 20, summaryComponents: ChatListEntrySummaryComponents())
|
||||
|> take(1)
|
||||
@ -31,7 +53,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
||||
}
|
||||
|
||||
if !isMuted && hasUnread {
|
||||
signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(index.messageIndex.id.peerId), anchor: .upperBound, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tagMask: nil, excludeNamespaces: [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: .combinedLocation)
|
||||
signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(index.messageIndex.id.peerId), anchor: .upperBound, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tagMask: nil, namespaces: .not([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal]), orderStatistics: .combinedLocation)
|
||||
|> take(1)
|
||||
|> map { view -> [INMessage] in
|
||||
var messages: [INMessage] = []
|
||||
@ -42,7 +64,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
||||
}
|
||||
|
||||
if !isRead {
|
||||
if let message = messageWithTelegramMessage(entry.message, account: account) {
|
||||
if let message = messageWithTelegramMessage(entry.message) {
|
||||
messages.append(message)
|
||||
}
|
||||
}
|
||||
@ -58,7 +80,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
||||
} else {
|
||||
return combineLatest(signals)
|
||||
|> map { results -> [INMessage] in
|
||||
return results.flatMap { $0 }.sorted(by: { $0.dateSent!.compare($1.dateSent!) == ComparisonResult.orderedDescending })
|
||||
return results.flatMap { $0 }.sorted { $0.dateSent!.compare($1.dateSent!) == .orderedDescending }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,7 +116,7 @@ func missedCalls(account: Account) -> Signal<[CallRecord], NoError> {
|
||||
break
|
||||
}
|
||||
}
|
||||
return calls.sorted(by: { $0.date.compare($1.date) == ComparisonResult.orderedDescending })
|
||||
return calls.sorted { $0.date.compare($1.date) == .orderedDescending }
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +158,7 @@ private func callWithTelegramMessage(_ telegramMessage: Message, account: Accoun
|
||||
return CallRecord(identifier: identifier, date: date, caller: caller, duration: duration, unseen: true)
|
||||
}
|
||||
|
||||
private func messageWithTelegramMessage(_ telegramMessage: Message, account: Account) -> INMessage? {
|
||||
private func messageWithTelegramMessage(_ telegramMessage: Message) -> INMessage? {
|
||||
guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser, user.id.id != 777000 else {
|
||||
return nil
|
||||
}
|
||||
@ -181,7 +203,7 @@ private func messageWithTelegramMessage(_ telegramMessage: Message, account: Acc
|
||||
messageType = .mediaAudio
|
||||
break loop
|
||||
} else if file.isVoice {
|
||||
messageType = .audio
|
||||
messageType = .mediaAudio
|
||||
break loop
|
||||
} else if file.isSticker || file.isAnimatedSticker {
|
||||
messageType = .sticker
|
||||
@ -189,6 +211,9 @@ private func messageWithTelegramMessage(_ telegramMessage: Message, account: Acc
|
||||
} else if file.isAnimated {
|
||||
messageType = .mediaVideo
|
||||
break loop
|
||||
} else if #available(iOSApplicationExtension 12.0, *) {
|
||||
messageType = .file
|
||||
break loop
|
||||
}
|
||||
} else if media is TelegramMediaMap {
|
||||
messageType = .mediaLocation
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
<string>merchant.privatbank.test.telergramios</string>
|
||||
<string>merchant.privatbank.prod.telergram</string>
|
||||
</array>
|
||||
<key>com.apple.developer.carplay-messaging</key><true/>
|
||||
<key>com.apple.developer.carplay-calling</key><true/>
|
||||
<key>com.apple.developer.carplay-messaging</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -4610,6 +4610,7 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"ScheduledMessages.EditTime" = "Reschedule";
|
||||
"ScheduledMessages.ClearAll" = "Clear All";
|
||||
"ScheduledMessages.Delete" = "Delete";
|
||||
"ScheduledMessages.EmptyPlaceholder" = "No scheduled messages here yet...";
|
||||
|
||||
"Conversation.SendMessage.SetReminder" = "Set a Reminder";
|
||||
|
||||
|
||||
@ -368,16 +368,13 @@ public class GalleryController: ViewController {
|
||||
switch source {
|
||||
case .peerMessagesAtId:
|
||||
if let tags = tagsForMessage(message!) {
|
||||
var excludeNamespaces: [MessageId.Namespace]
|
||||
if message!.id.namespace == Namespaces.Message.ScheduledCloud {
|
||||
excludeNamespaces = [Namespaces.Message.Cloud, Namespaces.Message.Local, Namespaces.Message.SecretIncoming]
|
||||
let namespaces: HistoryViewNamespaces
|
||||
if Namespaces.Message.allScheduled.contains(message!.id.namespace) {
|
||||
namespaces = .just(Namespaces.Message.allScheduled)
|
||||
} else {
|
||||
excludeNamespaces = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal]
|
||||
namespaces = .not(Namespaces.Message.allScheduled)
|
||||
}
|
||||
|
||||
let view = context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, excludeNamespaces: excludeNamespaces, orderStatistics: [.combinedLocation])
|
||||
|
||||
return view
|
||||
return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
|
||||
@ -224,10 +224,27 @@ public enum HistoryViewInputAnchor: Equatable {
|
||||
case unread
|
||||
}
|
||||
|
||||
public enum HistoryViewNamespaces {
|
||||
case all
|
||||
case just(Set<MessageId.Namespace>)
|
||||
case not(Set<MessageId.Namespace>)
|
||||
|
||||
public func contains(_ namespace: MessageId.Namespace) -> Bool {
|
||||
switch self {
|
||||
case .all:
|
||||
return true
|
||||
case let .just(namespaces):
|
||||
return namespaces.contains(namespace)
|
||||
case let .not(namespaces):
|
||||
return !namespaces.contains(namespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class MutableMessageHistoryView {
|
||||
private(set) var peerIds: MessageHistoryViewPeerIds
|
||||
let tag: MessageTags?
|
||||
let excludeNamespaces: [MessageId.Namespace]
|
||||
let namespaces: HistoryViewNamespaces
|
||||
private let orderStatistics: MessageHistoryViewOrderStatistics
|
||||
private let anchor: HistoryViewInputAnchor
|
||||
|
||||
@ -242,7 +259,7 @@ final class MutableMessageHistoryView {
|
||||
|
||||
fileprivate(set) var sampledState: HistoryViewSample
|
||||
|
||||
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace], count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
|
||||
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: HistoryViewNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
|
||||
self.anchor = inputAnchor
|
||||
|
||||
self.orderStatistics = orderStatistics
|
||||
@ -250,17 +267,17 @@ final class MutableMessageHistoryView {
|
||||
self.combinedReadStates = combinedReadStates
|
||||
self.transientReadStates = transientReadStates
|
||||
self.tag = tag
|
||||
self.excludeNamespaces = excludeNamespaces
|
||||
self.namespaces = namespaces
|
||||
self.fillCount = count
|
||||
self.topTaggedMessages = topTaggedMessages
|
||||
self.additionalDatas = additionalDatas
|
||||
|
||||
self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, excludeNamespaces: excludeNamespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds)
|
||||
self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds)
|
||||
if case let .loading(loadingState) = self.state {
|
||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||
switch sampledState {
|
||||
case let .ready(anchor, holes):
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, excludeNamespaces: excludeNamespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds, postbox: postbox, holes: holes))
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds, postbox: postbox, holes: holes))
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
case .loadHole:
|
||||
break
|
||||
@ -272,12 +289,12 @@ final class MutableMessageHistoryView {
|
||||
}
|
||||
|
||||
private func reset(postbox: Postbox) {
|
||||
self.state = HistoryViewState(postbox: postbox, inputAnchor: self.anchor, tag: self.tag, excludeNamespaces: self.excludeNamespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds)
|
||||
self.state = HistoryViewState(postbox: postbox, inputAnchor: self.anchor, tag: self.tag, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds)
|
||||
if case let .loading(loadingState) = self.state {
|
||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||
switch sampledState {
|
||||
case let .ready(anchor, holes):
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, excludeNamespaces: self.excludeNamespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
case .loadHole:
|
||||
break
|
||||
}
|
||||
@ -286,7 +303,7 @@ final class MutableMessageHistoryView {
|
||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||
switch sampledState {
|
||||
case let .ready(anchor, holes):
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, excludeNamespaces: self.excludeNamespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
case .loadHole:
|
||||
break
|
||||
}
|
||||
@ -472,7 +489,7 @@ final class MutableMessageHistoryView {
|
||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||
switch sampledState {
|
||||
case let .ready(anchor, holes):
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, excludeNamespaces: self.excludeNamespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
case .loadHole:
|
||||
break
|
||||
}
|
||||
@ -672,6 +689,7 @@ final class MutableMessageHistoryView {
|
||||
|
||||
public final class MessageHistoryView {
|
||||
public let tagMask: MessageTags?
|
||||
public let namespaces: HistoryViewNamespaces
|
||||
public let anchorIndex: MessageHistoryAnchorIndex
|
||||
public let earlierId: MessageIndex?
|
||||
public let laterId: MessageIndex?
|
||||
@ -686,6 +704,7 @@ public final class MessageHistoryView {
|
||||
|
||||
init(_ mutableView: MutableMessageHistoryView) {
|
||||
self.tagMask = mutableView.tag
|
||||
self.namespaces = mutableView.namespaces
|
||||
var entries: [MessageHistoryEntry]
|
||||
switch mutableView.sampledState {
|
||||
case .loading:
|
||||
@ -714,7 +733,7 @@ public final class MessageHistoryView {
|
||||
entries = []
|
||||
if let transientReadStates = mutableView.transientReadStates, case let .peer(states) = transientReadStates {
|
||||
for entry in state.entries {
|
||||
if !mutableView.excludeNamespaces.contains(entry.message.id.namespace) {
|
||||
if mutableView.namespaces.contains(entry.message.id.namespace) {
|
||||
let read: Bool
|
||||
if entry.message.flags.contains(.Incoming) {
|
||||
read = false
|
||||
@ -728,7 +747,7 @@ public final class MessageHistoryView {
|
||||
}
|
||||
} else {
|
||||
for entry in state.entries {
|
||||
if !mutableView.excludeNamespaces.contains(entry.message.id.namespace) {
|
||||
if mutableView.namespaces.contains(entry.message.id.namespace) {
|
||||
entries.append(MessageHistoryEntry(message: entry.message, isRead: false, location: entry.location, monthLocation: entry.monthLocation, attributes: entry.attributes))
|
||||
}
|
||||
}
|
||||
|
||||
@ -748,7 +748,7 @@ struct HistoryViewLoadedSample {
|
||||
final class HistoryViewLoadedState {
|
||||
let anchor: HistoryViewAnchor
|
||||
let tag: MessageTags?
|
||||
let excludeNamespaces: [MessageId.Namespace]
|
||||
let namespaces: HistoryViewNamespaces
|
||||
let statistics: MessageHistoryViewOrderStatistics
|
||||
let halfLimit: Int
|
||||
let seedConfiguration: SeedConfiguration
|
||||
@ -756,11 +756,11 @@ final class HistoryViewLoadedState {
|
||||
var holes: HistoryViewHoles
|
||||
var spacesWithRemovals = Set<PeerIdAndNamespace>()
|
||||
|
||||
init(anchor: HistoryViewAnchor, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace], statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) {
|
||||
init(anchor: HistoryViewAnchor, tag: MessageTags?, namespaces: HistoryViewNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) {
|
||||
precondition(halfLimit >= 3)
|
||||
self.anchor = anchor
|
||||
self.tag = tag
|
||||
self.excludeNamespaces = excludeNamespaces
|
||||
self.namespaces = namespaces
|
||||
self.statistics = statistics
|
||||
self.halfLimit = halfLimit
|
||||
self.seedConfiguration = postbox.seedConfiguration
|
||||
@ -781,7 +781,7 @@ final class HistoryViewLoadedState {
|
||||
var spaces: [PeerIdAndNamespace] = []
|
||||
for peerId in peerIds {
|
||||
for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: peerId) {
|
||||
if !excludeNamespaces.contains(namespace) {
|
||||
if namespaces.contains(namespace) {
|
||||
spaces.append(PeerIdAndNamespace(peerId: peerId, namespace: namespace))
|
||||
}
|
||||
}
|
||||
@ -1178,7 +1178,7 @@ final class HistoryViewLoadedState {
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace]) -> [PeerIdAndNamespace: IndexSet] {
|
||||
private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: HistoryViewNamespaces) -> [PeerIdAndNamespace: IndexSet] {
|
||||
var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:]
|
||||
var peerIds: [PeerId] = []
|
||||
switch locations {
|
||||
@ -1193,7 +1193,7 @@ private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds,
|
||||
let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere
|
||||
for peerId in peerIds {
|
||||
for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) {
|
||||
if !excludeNamespaces.contains(namespace) {
|
||||
if namespaces.contains(namespace) {
|
||||
let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1))
|
||||
if !indices.isEmpty {
|
||||
let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace)
|
||||
@ -1217,11 +1217,11 @@ final class HistoryViewLoadingState {
|
||||
let halfLimit: Int
|
||||
var holes: HistoryViewHoles
|
||||
|
||||
init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace], messageId: MessageId, halfLimit: Int) {
|
||||
init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: HistoryViewNamespaces, messageId: MessageId, halfLimit: Int) {
|
||||
self.messageId = messageId
|
||||
self.tag = tag
|
||||
self.halfLimit = halfLimit
|
||||
self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))
|
||||
self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))
|
||||
}
|
||||
|
||||
func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
|
||||
@ -1261,14 +1261,14 @@ enum HistoryViewState {
|
||||
case loaded(HistoryViewLoadedState)
|
||||
case loading(HistoryViewLoadingState)
|
||||
|
||||
init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace], statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds) {
|
||||
init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, namespaces: HistoryViewNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds) {
|
||||
switch inputAnchor {
|
||||
case let .index(index):
|
||||
self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))))
|
||||
self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
|
||||
case .lowerBound:
|
||||
self = .loaded(HistoryViewLoadedState(anchor: .lowerBound, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))))
|
||||
self = .loaded(HistoryViewLoadedState(anchor: .lowerBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
|
||||
case .upperBound:
|
||||
self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))))
|
||||
self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
|
||||
case .unread:
|
||||
let anchorPeerId: PeerId
|
||||
switch locations {
|
||||
@ -1301,26 +1301,26 @@ enum HistoryViewState {
|
||||
}
|
||||
}
|
||||
if let messageId = messageId {
|
||||
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces, messageId: messageId, halfLimit: halfLimit)
|
||||
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit)
|
||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||
switch sampledState {
|
||||
case let .ready(anchor, holes):
|
||||
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
|
||||
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
|
||||
case .loadHole:
|
||||
self = .loading(loadingState)
|
||||
}
|
||||
} else {
|
||||
self = .loaded(HistoryViewLoadedState(anchor: anchor ?? .upperBound, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))))
|
||||
self = .loaded(HistoryViewLoadedState(anchor: anchor ?? .upperBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
|
||||
}
|
||||
} else {
|
||||
preconditionFailure()
|
||||
}
|
||||
case let .message(messageId):
|
||||
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces, messageId: messageId, halfLimit: halfLimit)
|
||||
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit)
|
||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||
switch sampledState {
|
||||
case let .ready(anchor, holes):
|
||||
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
|
||||
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
|
||||
case .loadHole:
|
||||
self = .loading(loadingState)
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
||||
}
|
||||
}
|
||||
self.anchor = anchor
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: .single(peerId), anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, excludeNamespaces: [], count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: .single(peerId), anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
let _ = self.updateFromView()
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
||||
|
||||
if self.anchor != anchor {
|
||||
self.anchor = anchor
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: .single(peerId), anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, excludeNamespaces: [], count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: .single(peerId), anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||
return self.updateFromView()
|
||||
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
|
||||
return self.updateFromView()
|
||||
|
||||
@ -2219,7 +2219,7 @@ public final class Postbox {
|
||||
return peerIds
|
||||
}
|
||||
|
||||
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, excludeNamespaces: [MessageId.Namespace], orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: HistoryViewNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
return self.transactionSignal(userInteractive: true, { subscriber, transaction in
|
||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||
|
||||
@ -2263,26 +2263,26 @@ public final class Postbox {
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
})
|
||||
}
|
||||
|
||||
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, excludeNamespaces: [MessageId.Namespace], orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: HistoryViewNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
return self.transactionSignal { subscriber, transaction in
|
||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
|
||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, excludeNamespaces: [MessageId.Namespace], orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: HistoryViewNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
return self.transactionSignal { subscriber, transaction in
|
||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
|
||||
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, excludeNamespaces: [MessageId.Namespace], orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
|
||||
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: HistoryViewNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
|
||||
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
|
||||
var mainPeerId: PeerId?
|
||||
switch peerIds {
|
||||
@ -2371,7 +2371,7 @@ public final class Postbox {
|
||||
readStates = transientReadStates
|
||||
}
|
||||
|
||||
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, peerIds: peerIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tagMask, excludeNamespaces: excludeNamespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in
|
||||
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, peerIds: peerIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tagMask, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in
|
||||
if let tagMask = tagMask {
|
||||
return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound))
|
||||
} else {
|
||||
|
||||
@ -1243,6 +1243,7 @@ public class Account {
|
||||
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
|
||||
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
||||
|
||||
@ -143,7 +143,6 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(EmojiKeywordCollectionInfo.self, f: { EmojiKeywordCollectionInfo(decoder: $0) })
|
||||
declareEncodable(EmojiKeywordItem.self, f: { EmojiKeywordItem(decoder: $0) })
|
||||
declareEncodable(SynchronizeEmojiKeywordsOperation.self, f: { SynchronizeEmojiKeywordsOperation(decoder: $0) })
|
||||
|
||||
declareEncodable(CloudPhotoSizeMediaResource.self, f: { CloudPhotoSizeMediaResource(decoder: $0) })
|
||||
declareEncodable(CloudDocumentSizeMediaResource.self, f: { CloudDocumentSizeMediaResource(decoder: $0) })
|
||||
declareEncodable(CloudPeerPhotoSizeMediaResource.self, f: { CloudPeerPhotoSizeMediaResource(decoder: $0) })
|
||||
@ -153,6 +152,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(OutgoingScheduleInfoMessageAttribute.self, f: { OutgoingScheduleInfoMessageAttribute(decoder: $0) })
|
||||
declareEncodable(UpdateMessageReactionsAction.self, f: { UpdateMessageReactionsAction(decoder: $0) })
|
||||
declareEncodable(RestrictedContentMessageAttribute.self, f: { RestrictedContentMessageAttribute(decoder: $0) })
|
||||
declareEncodable(SendScheduledMessageImmediatelyAction.self, f: { SendScheduledMessageImmediatelyAction(decoder: $0) })
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
@ -189,7 +189,7 @@ private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocation, additi
|
||||
switch chatLocation {
|
||||
case let .peer(peerId):
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
if result.index(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil {
|
||||
if result.firstIndex(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil {
|
||||
result.append(.peerChatState(peerId))
|
||||
}
|
||||
}
|
||||
@ -872,9 +872,7 @@ public final class AccountViewTracker {
|
||||
strongSelf.updatePolls(viewId: viewId, messageIds: pollMessageIds, messages: pollMessageDict)
|
||||
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0)
|
||||
}/* else if case .group = chatLocation {
|
||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}, disposed: { [weak self] viewId in
|
||||
@ -887,8 +885,6 @@ public final class AccountViewTracker {
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil)
|
||||
}
|
||||
/*case .group:
|
||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil)*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -915,28 +911,54 @@ public final class AccountViewTracker {
|
||||
}
|
||||
|
||||
public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocation) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
return self.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil, tagMask: nil, excludeNamespaces: [Namespaces.Message.Cloud, Namespaces.Message.Local], orderStatistics: [])
|
||||
if let account = self.account {
|
||||
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: .upperBound, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: nil, namespaces: .just(Namespaces.Message.allScheduled), orderStatistics: [], additionalData: [])
|
||||
return withState(signal, { [weak self] () -> Int32 in
|
||||
if let strongSelf = self {
|
||||
return OSAtomicIncrement32(&strongSelf.nextViewId)
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}, next: { [weak self] next, viewId in
|
||||
if let strongSelf = self {
|
||||
strongSelf.queue.async {
|
||||
let (messageIds, localWebpages) = pendingWebpages(entries: next.0.entries)
|
||||
strongSelf.updatePendingWebpages(viewId: viewId, messageIds: messageIds, localWebpages: localWebpages)
|
||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation)
|
||||
}
|
||||
}
|
||||
}, disposed: { [weak self] viewId in
|
||||
if let strongSelf = self {
|
||||
strongSelf.queue.async {
|
||||
strongSelf.updatePendingWebpages(viewId: viewId, messageIds: [], localWebpages: [:])
|
||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return .never()
|
||||
}
|
||||
}
|
||||
|
||||
public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, tagMask: MessageTags? = nil, excludeNamespaces: [MessageId.Namespace] = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
if let account = self.account {
|
||||
let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||
let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
||||
} else {
|
||||
return .never()
|
||||
}
|
||||
}
|
||||
|
||||
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, excludeNamespaces: [MessageId.Namespace] = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
if let account = self.account {
|
||||
let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||
let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
||||
} else {
|
||||
return .never()
|
||||
}
|
||||
}
|
||||
|
||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, excludeNamespaces: [MessageId.Namespace] = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
if let account = self.account {
|
||||
let inputAnchor: HistoryViewInputAnchor
|
||||
switch index {
|
||||
@ -947,7 +969,7 @@ public final class AccountViewTracker {
|
||||
case let .message(index):
|
||||
inputAnchor = .index(index)
|
||||
}
|
||||
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: inputAnchor, count: count, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: inputAnchor, count: count, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
||||
} else {
|
||||
return .never()
|
||||
@ -1163,18 +1185,18 @@ public final class AccountViewTracker {
|
||||
let pendingKey: PostboxViewKey = .pendingMessageActionsSummary(type: .consumeUnseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud)
|
||||
let summaryKey: PostboxViewKey = .historyTagSummaryView(tag: .unseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud)
|
||||
return account.postbox.combinedView(keys: [pendingKey, summaryKey])
|
||||
|> map { views -> Int32 in
|
||||
var count: Int32 = 0
|
||||
if let view = views.views[pendingKey] as? PendingMessageActionsSummaryView {
|
||||
count -= view.count
|
||||
|> map { views -> Int32 in
|
||||
var count: Int32 = 0
|
||||
if let view = views.views[pendingKey] as? PendingMessageActionsSummaryView {
|
||||
count -= view.count
|
||||
}
|
||||
if let view = views.views[summaryKey] as? MessageHistoryTagSummaryView {
|
||||
if let unseenCount = view.count {
|
||||
count += unseenCount
|
||||
}
|
||||
if let view = views.views[summaryKey] as? MessageHistoryTagSummaryView {
|
||||
if let unseenCount = view.count {
|
||||
count += unseenCount
|
||||
}
|
||||
}
|
||||
return max(0, count)
|
||||
} |> distinctUntilChanged
|
||||
}
|
||||
return max(0, count)
|
||||
} |> distinctUntilChanged
|
||||
} else {
|
||||
return .never()
|
||||
}
|
||||
|
||||
@ -387,14 +387,17 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
}
|
||||
|
||||
var messageNamespace = Namespaces.Message.Local
|
||||
var effectiveTimestamp = timestamp
|
||||
for attribute in attributes {
|
||||
if attribute is OutgoingScheduleInfoMessageAttribute {
|
||||
if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute {
|
||||
messageNamespace = Namespaces.Message.ScheduledLocal
|
||||
effectiveTimestamp = attribute.scheduleTime
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList))
|
||||
|
||||
storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList))
|
||||
case let .forward(source, grouping, requestedAttributes):
|
||||
let sourceMessage = transaction.getMessage(source)
|
||||
if let sourceMessage = sourceMessage, let author = sourceMessage.author ?? sourceMessage.peers[sourceMessage.id.peerId] {
|
||||
@ -506,12 +509,14 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
|
||||
var messageNamespace = Namespaces.Message.Local
|
||||
var entitiesAttribute: TextEntitiesMessageAttribute?
|
||||
var effectiveTimestamp = timestamp
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entitiesAttribute = attribute
|
||||
}
|
||||
if attribute is OutgoingScheduleInfoMessageAttribute {
|
||||
if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute {
|
||||
messageNamespace = Namespaces.Message.ScheduledLocal
|
||||
effectiveTimestamp = attribute.scheduleTime
|
||||
}
|
||||
}
|
||||
|
||||
@ -543,7 +548,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
augmentedMediaList = augmentedMediaList.map(convertForwardedMediaForSecretChat)
|
||||
}
|
||||
|
||||
storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList))
|
||||
storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,610 +0,0 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
import TelegramApiMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private final class HistoryStateValidationBatch {
|
||||
private let disposable: Disposable
|
||||
let invalidatedState: HistoryState
|
||||
|
||||
var cancelledMessageIds = Set<MessageId>()
|
||||
|
||||
init(disposable: Disposable, invalidatedState: HistoryState) {
|
||||
self.disposable = disposable
|
||||
self.invalidatedState = invalidatedState
|
||||
}
|
||||
|
||||
deinit {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class HistoryStateValidationContext {
|
||||
var batchReferences: [MessageId: HistoryStateValidationBatch] = [:]
|
||||
}
|
||||
|
||||
private enum HistoryState {
|
||||
case channel(PeerId, ChannelState)
|
||||
//case group(PeerGroupId, TelegramPeerGroupState)
|
||||
|
||||
var hasInvalidationIndex: Bool {
|
||||
switch self {
|
||||
case let .channel(_, state):
|
||||
return state.invalidatedPts != nil
|
||||
/*case let .group(_, state):
|
||||
return state.invalidatedStateIndex != nil*/
|
||||
}
|
||||
}
|
||||
|
||||
func isMessageValid(_ message: Message) -> Bool {
|
||||
switch self {
|
||||
case let .channel(_, state):
|
||||
if let invalidatedPts = state.invalidatedPts {
|
||||
var messagePts: Int32?
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? ChannelMessageStateVersionAttribute {
|
||||
messagePts = attribute.pts
|
||||
break inner
|
||||
}
|
||||
}
|
||||
var requiresValidation = false
|
||||
if let messagePts = messagePts {
|
||||
if messagePts < invalidatedPts {
|
||||
requiresValidation = true
|
||||
}
|
||||
} else {
|
||||
requiresValidation = true
|
||||
}
|
||||
|
||||
return !requiresValidation
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
/*case let .group(_, state):
|
||||
if let invalidatedStateIndex = state.invalidatedStateIndex {
|
||||
var messageStateIndex: Int32?
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? PeerGroupMessageStateVersionAttribute {
|
||||
messageStateIndex = attribute.stateIndex
|
||||
break inner
|
||||
}
|
||||
}
|
||||
var requiresValidation = false
|
||||
if let messageStateIndex = messageStateIndex {
|
||||
if messageStateIndex < invalidatedStateIndex {
|
||||
requiresValidation = true
|
||||
}
|
||||
} else {
|
||||
requiresValidation = true
|
||||
}
|
||||
return !requiresValidation
|
||||
} else {
|
||||
return true
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
func matchesPeerId(_ peerId: PeerId) -> Bool {
|
||||
switch self {
|
||||
case let .channel(statePeerId, state):
|
||||
return statePeerId == peerId
|
||||
/*case .group:
|
||||
return true*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func slicedForValidationMessages(_ messages: [MessageId]) -> [[MessageId]] {
|
||||
let block = 64
|
||||
|
||||
if messages.count <= block {
|
||||
return [messages]
|
||||
} else {
|
||||
var result: [[MessageId]] = []
|
||||
var offset = 0
|
||||
while offset < messages.count {
|
||||
result.append(Array(messages[offset ..< min(offset + block, messages.count)]))
|
||||
offset += block
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
final class HistoryViewStateValidationContexts {
|
||||
private let queue: Queue
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let accountPeerId: PeerId
|
||||
|
||||
private var contexts: [Int32: HistoryStateValidationContext] = [:]
|
||||
|
||||
init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId) {
|
||||
self.queue = queue
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.accountPeerId = accountPeerId
|
||||
}
|
||||
|
||||
func updateView(id: Int32, view: MessageHistoryView?) {
|
||||
assert(self.queue.isCurrent())
|
||||
guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage else {
|
||||
if self.contexts[id] != nil {
|
||||
self.contexts.removeValue(forKey: id)
|
||||
}
|
||||
return
|
||||
}
|
||||
var historyState: HistoryState?
|
||||
for entry in view.additionalData {
|
||||
if case let .peerChatState(peerId, chatState) = entry {
|
||||
if let chatState = chatState as? ChannelState {
|
||||
historyState = .channel(peerId, chatState)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let historyState = historyState, historyState.hasInvalidationIndex {
|
||||
var rangesToInvalidate: [[MessageId]] = []
|
||||
let addToRange: (MessageId, inout [[MessageId]]) -> Void = { id, ranges in
|
||||
if ranges.isEmpty {
|
||||
ranges = [[id]]
|
||||
} else {
|
||||
ranges[ranges.count - 1].append(id)
|
||||
}
|
||||
}
|
||||
|
||||
let addRangeBreak: (inout [[MessageId]]) -> Void = { ranges in
|
||||
if ranges.last?.count != 0 {
|
||||
ranges.append([])
|
||||
}
|
||||
}
|
||||
|
||||
for entry in view.entries {
|
||||
if historyState.matchesPeerId(entry.message.id.peerId) && entry.message.id.namespace == Namespaces.Message.Cloud {
|
||||
if !historyState.isMessageValid(entry.message) {
|
||||
addToRange(entry.message.id, &rangesToInvalidate)
|
||||
} else {
|
||||
addRangeBreak(&rangesToInvalidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !rangesToInvalidate.isEmpty && rangesToInvalidate[rangesToInvalidate.count - 1].isEmpty {
|
||||
rangesToInvalidate.removeLast()
|
||||
}
|
||||
|
||||
var invalidatedMessageIds = Set<MessageId>()
|
||||
|
||||
if !rangesToInvalidate.isEmpty {
|
||||
let context: HistoryStateValidationContext
|
||||
if let current = self.contexts[id] {
|
||||
context = current
|
||||
} else {
|
||||
context = HistoryStateValidationContext()
|
||||
self.contexts[id] = context
|
||||
}
|
||||
|
||||
var addedRanges: [[MessageId]] = []
|
||||
for messages in rangesToInvalidate {
|
||||
for id in messages {
|
||||
invalidatedMessageIds.insert(id)
|
||||
|
||||
if context.batchReferences[id] != nil {
|
||||
addRangeBreak(&addedRanges)
|
||||
} else {
|
||||
addToRange(id, &addedRanges)
|
||||
}
|
||||
}
|
||||
addRangeBreak(&addedRanges)
|
||||
}
|
||||
|
||||
if !addedRanges.isEmpty && addedRanges[addedRanges.count - 1].isEmpty {
|
||||
addedRanges.removeLast()
|
||||
}
|
||||
|
||||
for rangeMessages in addedRanges {
|
||||
for messages in slicedForValidationMessages(rangeMessages) {
|
||||
let disposable = MetaDisposable()
|
||||
let batch = HistoryStateValidationBatch(disposable: disposable, invalidatedState: historyState)
|
||||
for messageId in messages {
|
||||
context.batchReferences[messageId] = batch
|
||||
}
|
||||
|
||||
disposable.set((validateBatch(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, tag: view.tagMask, messageIds: messages, historyState: historyState)
|
||||
|> deliverOn(self.queue)).start(completed: { [weak self, weak batch] in
|
||||
if let strongSelf = self, let context = strongSelf.contexts[id], let batch = batch {
|
||||
var completedMessageIds: [MessageId] = []
|
||||
for (messageId, messageBatch) in context.batchReferences {
|
||||
if messageBatch === batch {
|
||||
completedMessageIds.append(messageId)
|
||||
}
|
||||
}
|
||||
for messageId in completedMessageIds {
|
||||
context.batchReferences.removeValue(forKey: messageId)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let context = self.contexts[id] {
|
||||
var removeIds: [MessageId] = []
|
||||
|
||||
for batchMessageId in context.batchReferences.keys {
|
||||
if !invalidatedMessageIds.contains(batchMessageId) {
|
||||
removeIds.append(batchMessageId)
|
||||
}
|
||||
}
|
||||
|
||||
for messageId in removeIds {
|
||||
context.batchReferences.removeValue(forKey: messageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func hashForMessages(_ messages: [Message], withChannelIds: Bool) -> Int32 {
|
||||
var acc: UInt32 = 0
|
||||
|
||||
let sorted = messages.sorted(by: { $0.index > $1.index })
|
||||
|
||||
for message in sorted {
|
||||
if withChannelIds {
|
||||
acc = (acc &* 20261) &+ UInt32(message.id.peerId.id)
|
||||
}
|
||||
|
||||
acc = (acc &* 20261) &+ UInt32(message.id.id)
|
||||
var timestamp = message.timestamp
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
timestamp = attribute.date
|
||||
break inner
|
||||
}
|
||||
}
|
||||
acc = (acc &* 20261) &+ UInt32(timestamp)
|
||||
}
|
||||
return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
|
||||
}
|
||||
|
||||
private func hashForMessages(_ messages: [StoreMessage], withChannelIds: Bool) -> Int32 {
|
||||
var acc: UInt32 = 0
|
||||
|
||||
for message in messages {
|
||||
if case let .Id(id) = message.id {
|
||||
if withChannelIds {
|
||||
acc = (acc &* 20261) &+ UInt32(id.peerId.id)
|
||||
}
|
||||
acc = (acc &* 20261) &+ UInt32(id.id)
|
||||
var timestamp = message.timestamp
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
timestamp = attribute.date
|
||||
break inner
|
||||
}
|
||||
}
|
||||
acc = (acc &* 20261) &+ UInt32(timestamp)
|
||||
}
|
||||
}
|
||||
return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
|
||||
}
|
||||
|
||||
private enum ValidatedMessages {
|
||||
case notModified
|
||||
case messages([Api.Message], [Api.Chat], [Api.User], Int32?)
|
||||
}
|
||||
|
||||
private func validateBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, tag: MessageTags?, messageIds: [MessageId], historyState: HistoryState) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
var previousMessages: [Message] = []
|
||||
var previous: [MessageId: Message] = [:]
|
||||
for messageId in messageIds {
|
||||
if let message = transaction.getMessage(messageId) {
|
||||
previousMessages.append(message)
|
||||
previous[message.id] = message
|
||||
}
|
||||
}
|
||||
|
||||
var signal: Signal<ValidatedMessages, MTRpcError>
|
||||
switch historyState {
|
||||
case let .channel(peerId, _):
|
||||
let hash = hashForMessages(previousMessages, withChannelIds: false)
|
||||
Logger.shared.log("HistoryValidation", "validate batch for \(peerId): \(previousMessages.map({ $0.id }))")
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
let requestSignal: Signal<Api.messages.Messages, MTRpcError>
|
||||
if let tag = tag {
|
||||
if tag == MessageTags.unseenPersonalMessage {
|
||||
requestSignal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1))
|
||||
} else {
|
||||
assertionFailure()
|
||||
requestSignal = .complete()
|
||||
}
|
||||
} else {
|
||||
requestSignal = network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, offsetDate: 0, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash))
|
||||
}
|
||||
|
||||
signal = requestSignal
|
||||
|> map { result -> ValidatedMessages in
|
||||
let messages: [Api.Message]
|
||||
let chats: [Api.Chat]
|
||||
let users: [Api.User]
|
||||
var channelPts: Int32?
|
||||
|
||||
switch result {
|
||||
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
channelPts = pts
|
||||
case .messagesNotModified:
|
||||
return .notModified
|
||||
}
|
||||
return .messages(messages, chats, users, channelPts)
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
return signal
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<ValidatedMessages?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
guard let result = result else {
|
||||
return .complete()
|
||||
}
|
||||
switch result {
|
||||
case let .messages(messages, chats, users, channelPts):
|
||||
var storeMessages: [StoreMessage] = []
|
||||
|
||||
for message in messages {
|
||||
if let storeMessage = StoreMessage(apiMessage: message) {
|
||||
var attributes = storeMessage.attributes
|
||||
|
||||
if let channelPts = channelPts {
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||
}
|
||||
|
||||
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
|
||||
}
|
||||
}
|
||||
|
||||
var validMessageIds = Set<MessageId>()
|
||||
for message in storeMessages {
|
||||
if case let .Id(id) = message.id {
|
||||
validMessageIds.insert(id)
|
||||
}
|
||||
}
|
||||
|
||||
var maybeRemovedMessageIds: [MessageId] = []
|
||||
for id in previous.keys {
|
||||
if !validMessageIds.contains(id) {
|
||||
maybeRemovedMessageIds.append(id)
|
||||
}
|
||||
}
|
||||
|
||||
let actuallyRemovedMessagesSignal: Signal<Set<MessageId>, NoError>
|
||||
if maybeRemovedMessageIds.isEmpty {
|
||||
actuallyRemovedMessagesSignal = .single(Set())
|
||||
} else {
|
||||
actuallyRemovedMessagesSignal = postbox.transaction { transaction -> Signal<Set<MessageId>, NoError> in
|
||||
switch historyState {
|
||||
case let .channel(peerId, _):
|
||||
if let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) {
|
||||
return network.request(Api.functions.channels.getMessages(channel: inputChannel, id: maybeRemovedMessageIds.map({ Api.InputMessage.inputMessageID(id: $0.id) })))
|
||||
|> map { result -> Set<MessageId> in
|
||||
let apiMessages: [Api.Message]
|
||||
switch result {
|
||||
case let .channelMessages(_, _, _, messages, _, _):
|
||||
apiMessages = messages
|
||||
case let .messages(messages, _, _):
|
||||
apiMessages = messages
|
||||
case let .messagesSlice(_, _, _, messages, _, _):
|
||||
apiMessages = messages
|
||||
case .messagesNotModified:
|
||||
return Set()
|
||||
}
|
||||
var ids = Set<MessageId>()
|
||||
for message in apiMessages {
|
||||
if let parsedMessage = StoreMessage(apiMessage: message), case let .Id(id) = parsedMessage.id {
|
||||
if let tag = tag {
|
||||
if parsedMessage.tags.contains(tag) {
|
||||
ids.insert(id)
|
||||
}
|
||||
} else {
|
||||
ids.insert(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Set(maybeRemovedMessageIds).subtracting(ids)
|
||||
}
|
||||
|> `catch` { _ -> Signal<Set<MessageId>, NoError> in
|
||||
return .single(Set(maybeRemovedMessageIds))
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(Set(maybeRemovedMessageIds))
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
return actuallyRemovedMessagesSignal
|
||||
|> mapToSignal { removedMessageIds -> Signal<Void, NoError> in
|
||||
return postbox.transaction { transaction -> Void in
|
||||
var validMessageIds = Set<MessageId>()
|
||||
for message in storeMessages {
|
||||
if case let .Id(id) = message.id {
|
||||
validMessageIds.insert(id)
|
||||
let previousMessage = previous[id] ?? transaction.getMessage(id)
|
||||
|
||||
if let previousMessage = previousMessage {
|
||||
var updatedTimestamp = message.timestamp
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
updatedTimestamp = attribute.date
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
var timestamp = previousMessage.timestamp
|
||||
inner: for attribute in previousMessage.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
timestamp = attribute.date
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
transaction.updateMessage(id, update: { currentMessage in
|
||||
if updatedTimestamp != timestamp {
|
||||
var updatedLocalTags = message.localTags
|
||||
if currentMessage.localTags.contains(.OutgoingLiveLocation) {
|
||||
updatedLocalTags.insert(.OutgoingLiveLocation)
|
||||
}
|
||||
return .update(message.withUpdatedLocalTags(updatedLocalTags))
|
||||
} else {
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
if let channelPts = channelPts {
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
}
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||
}
|
||||
|
||||
let updatedFlags = StoreMessageFlags(currentMessage.flags)
|
||||
|
||||
return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
}
|
||||
})
|
||||
|
||||
if previous[id] == nil {
|
||||
print("\(id) missing")
|
||||
}
|
||||
} else {
|
||||
let _ = transaction.addMessages([message], location: .Random)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let tag = tag {
|
||||
for (_, previousMessage) in previous {
|
||||
if !validMessageIds.contains(previousMessage.id) {
|
||||
transaction.updateMessage(previousMessage.id, update: { currentMessage in
|
||||
var updatedTags = currentMessage.tags
|
||||
updatedTags.remove(tag)
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
switch historyState {
|
||||
case .channel:
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch historyState {
|
||||
case let .channel(_, channelState):
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for id in removedMessageIds {
|
||||
if !validMessageIds.contains(id) {
|
||||
if let tag = tag {
|
||||
transaction.updateMessage(id, update: { currentMessage in
|
||||
var updatedTags = currentMessage.tags
|
||||
updatedTags.remove(tag)
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
switch historyState {
|
||||
case .channel:
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch historyState {
|
||||
case let .channel(_, channelState):
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
} else {
|
||||
switch historyState {
|
||||
case .channel:
|
||||
deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [id])
|
||||
Logger.shared.log("HistoryValidation", "deleting message \(id) in \(id.peerId)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case .notModified:
|
||||
return postbox.transaction { transaction -> Void in
|
||||
for id in previous.keys {
|
||||
transaction.updateMessage(id, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
switch historyState {
|
||||
case .channel:
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch historyState {
|
||||
case let .channel(_, channelState):
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
@ -0,0 +1,695 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
import TelegramApiMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private final class HistoryStateValidationBatch {
|
||||
private let disposable: Disposable
|
||||
let invalidatedState: HistoryState?
|
||||
|
||||
var cancelledMessageIds = Set<MessageId>()
|
||||
|
||||
init(disposable: Disposable, invalidatedState: HistoryState? = nil) {
|
||||
self.disposable = disposable
|
||||
self.invalidatedState = invalidatedState
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class HistoryStateValidationContext {
|
||||
var batchReferences: [MessageId: HistoryStateValidationBatch] = [:]
|
||||
var batch: HistoryStateValidationBatch?
|
||||
}
|
||||
|
||||
private enum HistoryState {
|
||||
case channel(PeerId, ChannelState)
|
||||
//case group(PeerGroupId, TelegramPeerGroupState)
|
||||
case scheduledMessages(PeerId)
|
||||
|
||||
var hasInvalidationIndex: Bool {
|
||||
switch self {
|
||||
case let .channel(_, state):
|
||||
return state.invalidatedPts != nil
|
||||
/*case let .group(_, state):
|
||||
return state.invalidatedStateIndex != nil*/
|
||||
case .scheduledMessages:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isMessageValid(_ message: Message) -> Bool {
|
||||
switch self {
|
||||
case let .channel(_, state):
|
||||
if let invalidatedPts = state.invalidatedPts {
|
||||
var messagePts: Int32?
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? ChannelMessageStateVersionAttribute {
|
||||
messagePts = attribute.pts
|
||||
break inner
|
||||
}
|
||||
}
|
||||
var requiresValidation = false
|
||||
if let messagePts = messagePts {
|
||||
if messagePts < invalidatedPts {
|
||||
requiresValidation = true
|
||||
}
|
||||
} else {
|
||||
requiresValidation = true
|
||||
}
|
||||
|
||||
return !requiresValidation
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
/*case let .group(_, state):
|
||||
if let invalidatedStateIndex = state.invalidatedStateIndex {
|
||||
var messageStateIndex: Int32?
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? PeerGroupMessageStateVersionAttribute {
|
||||
messageStateIndex = attribute.stateIndex
|
||||
break inner
|
||||
}
|
||||
}
|
||||
var requiresValidation = false
|
||||
if let messageStateIndex = messageStateIndex {
|
||||
if messageStateIndex < invalidatedStateIndex {
|
||||
requiresValidation = true
|
||||
}
|
||||
} else {
|
||||
requiresValidation = true
|
||||
}
|
||||
return !requiresValidation
|
||||
} else {
|
||||
return true
|
||||
}*/
|
||||
case .scheduledMessages:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func matchesPeerId(_ peerId: PeerId) -> Bool {
|
||||
switch self {
|
||||
case let .channel(statePeerId, _):
|
||||
return statePeerId == peerId
|
||||
/*case .group:
|
||||
return true*/
|
||||
case let .scheduledMessages(statePeerId):
|
||||
return statePeerId == peerId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func slicedForValidationMessages(_ messages: [MessageId]) -> [[MessageId]] {
|
||||
let block = 64
|
||||
|
||||
if messages.count <= block {
|
||||
return [messages]
|
||||
} else {
|
||||
var result: [[MessageId]] = []
|
||||
var offset = 0
|
||||
while offset < messages.count {
|
||||
result.append(Array(messages[offset ..< min(offset + block, messages.count)]))
|
||||
offset += block
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
final class HistoryViewStateValidationContexts {
|
||||
private let queue: Queue
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let accountPeerId: PeerId
|
||||
|
||||
private var contexts: [Int32: HistoryStateValidationContext] = [:]
|
||||
|
||||
init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId) {
|
||||
self.queue = queue
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.accountPeerId = accountPeerId
|
||||
}
|
||||
|
||||
func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocation? = nil) {
|
||||
assert(self.queue.isCurrent())
|
||||
guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage else {
|
||||
if self.contexts[id] != nil {
|
||||
self.contexts.removeValue(forKey: id)
|
||||
}
|
||||
return
|
||||
}
|
||||
var historyState: HistoryState?
|
||||
for entry in view.additionalData {
|
||||
if case let .peerChatState(peerId, chatState) = entry {
|
||||
if let chatState = chatState as? ChannelState {
|
||||
historyState = .channel(peerId, chatState)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let historyState = historyState, historyState.hasInvalidationIndex {
|
||||
var rangesToInvalidate: [[MessageId]] = []
|
||||
let addToRange: (MessageId, inout [[MessageId]]) -> Void = { id, ranges in
|
||||
if ranges.isEmpty {
|
||||
ranges = [[id]]
|
||||
} else {
|
||||
ranges[ranges.count - 1].append(id)
|
||||
}
|
||||
}
|
||||
|
||||
let addRangeBreak: (inout [[MessageId]]) -> Void = { ranges in
|
||||
if ranges.last?.count != 0 {
|
||||
ranges.append([])
|
||||
}
|
||||
}
|
||||
|
||||
for entry in view.entries {
|
||||
if historyState.matchesPeerId(entry.message.id.peerId) && entry.message.id.namespace == Namespaces.Message.Cloud {
|
||||
if !historyState.isMessageValid(entry.message) {
|
||||
addToRange(entry.message.id, &rangesToInvalidate)
|
||||
} else {
|
||||
addRangeBreak(&rangesToInvalidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !rangesToInvalidate.isEmpty && rangesToInvalidate[rangesToInvalidate.count - 1].isEmpty {
|
||||
rangesToInvalidate.removeLast()
|
||||
}
|
||||
|
||||
var invalidatedMessageIds = Set<MessageId>()
|
||||
|
||||
if !rangesToInvalidate.isEmpty {
|
||||
let context: HistoryStateValidationContext
|
||||
if let current = self.contexts[id] {
|
||||
context = current
|
||||
} else {
|
||||
context = HistoryStateValidationContext()
|
||||
self.contexts[id] = context
|
||||
}
|
||||
|
||||
var addedRanges: [[MessageId]] = []
|
||||
for messages in rangesToInvalidate {
|
||||
for id in messages {
|
||||
invalidatedMessageIds.insert(id)
|
||||
|
||||
if context.batchReferences[id] != nil {
|
||||
addRangeBreak(&addedRanges)
|
||||
} else {
|
||||
addToRange(id, &addedRanges)
|
||||
}
|
||||
}
|
||||
addRangeBreak(&addedRanges)
|
||||
}
|
||||
|
||||
if !addedRanges.isEmpty && addedRanges[addedRanges.count - 1].isEmpty {
|
||||
addedRanges.removeLast()
|
||||
}
|
||||
|
||||
for rangeMessages in addedRanges {
|
||||
for messages in slicedForValidationMessages(rangeMessages) {
|
||||
let disposable = MetaDisposable()
|
||||
let batch = HistoryStateValidationBatch(disposable: disposable, invalidatedState: historyState)
|
||||
for messageId in messages {
|
||||
context.batchReferences[messageId] = batch
|
||||
}
|
||||
|
||||
disposable.set((validateChannelMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, tag: view.tagMask, messageIds: messages, historyState: historyState)
|
||||
|> deliverOn(self.queue)).start(completed: { [weak self, weak batch] in
|
||||
if let strongSelf = self, let context = strongSelf.contexts[id], let batch = batch {
|
||||
var completedMessageIds: [MessageId] = []
|
||||
for (messageId, messageBatch) in context.batchReferences {
|
||||
if messageBatch === batch {
|
||||
completedMessageIds.append(messageId)
|
||||
}
|
||||
}
|
||||
for messageId in completedMessageIds {
|
||||
context.batchReferences.removeValue(forKey: messageId)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let context = self.contexts[id] {
|
||||
var removeIds: [MessageId] = []
|
||||
|
||||
for batchMessageId in context.batchReferences.keys {
|
||||
if !invalidatedMessageIds.contains(batchMessageId) {
|
||||
removeIds.append(batchMessageId)
|
||||
}
|
||||
}
|
||||
|
||||
for messageId in removeIds {
|
||||
context.batchReferences.removeValue(forKey: messageId)
|
||||
}
|
||||
}
|
||||
} else if view.namespaces.contains(Namespaces.Message.ScheduledCloud) {
|
||||
if let _ = self.contexts[id] {
|
||||
} else if let location = location, case let .peer(peerId) = location {
|
||||
let context = HistoryStateValidationContext()
|
||||
self.contexts[id] = context
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
let batch = HistoryStateValidationBatch(disposable: disposable)
|
||||
context.batch = batch
|
||||
|
||||
let messages: [Message] = view.entries.map { $0.message }
|
||||
|
||||
disposable.set((validateScheduledMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: peerId, tag: nil, messages: messages, historyState: .scheduledMessages(peerId))
|
||||
|> deliverOn(self.queue)).start(completed: { [weak self] in
|
||||
if let strongSelf = self, let context = strongSelf.contexts[id] {
|
||||
context.batch = nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func hashForMessages(_ messages: [Message], withChannelIds: Bool) -> Int32 {
|
||||
var acc: UInt32 = 0
|
||||
|
||||
let sorted = messages.sorted(by: { $0.index > $1.index })
|
||||
|
||||
for message in sorted {
|
||||
if withChannelIds {
|
||||
acc = (acc &* 20261) &+ UInt32(message.id.peerId.id)
|
||||
}
|
||||
|
||||
acc = (acc &* 20261) &+ UInt32(message.id.id)
|
||||
var timestamp = message.timestamp
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
timestamp = attribute.date
|
||||
break inner
|
||||
}
|
||||
}
|
||||
acc = (acc &* 20261) &+ UInt32(timestamp)
|
||||
}
|
||||
return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
|
||||
}
|
||||
|
||||
private func hashForMessages(_ messages: [StoreMessage], withChannelIds: Bool) -> Int32 {
|
||||
var acc: UInt32 = 0
|
||||
|
||||
for message in messages {
|
||||
if case let .Id(id) = message.id {
|
||||
if withChannelIds {
|
||||
acc = (acc &* 20261) &+ UInt32(id.peerId.id)
|
||||
}
|
||||
acc = (acc &* 20261) &+ UInt32(id.id)
|
||||
var timestamp = message.timestamp
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
timestamp = attribute.date
|
||||
break inner
|
||||
}
|
||||
}
|
||||
acc = (acc &* 20261) &+ UInt32(timestamp)
|
||||
}
|
||||
}
|
||||
return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
|
||||
}
|
||||
|
||||
private enum ValidatedMessages {
|
||||
case notModified
|
||||
case messages([Api.Message], [Api.Chat], [Api.User], Int32?)
|
||||
}
|
||||
|
||||
private func validateChannelMessagesBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, tag: MessageTags?, messageIds: [MessageId], historyState: HistoryState) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
var previousMessages: [Message] = []
|
||||
var previous: [MessageId: Message] = [:]
|
||||
for messageId in messageIds {
|
||||
if let message = transaction.getMessage(messageId) {
|
||||
previousMessages.append(message)
|
||||
previous[message.id] = message
|
||||
}
|
||||
}
|
||||
|
||||
var signal: Signal<ValidatedMessages, MTRpcError>
|
||||
switch historyState {
|
||||
case let .channel(peerId, _):
|
||||
let hash = hashForMessages(previousMessages, withChannelIds: false)
|
||||
Logger.shared.log("HistoryValidation", "validate batch for \(peerId): \(previousMessages.map({ $0.id }))")
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
let requestSignal: Signal<Api.messages.Messages, MTRpcError>
|
||||
if let tag = tag {
|
||||
if tag == MessageTags.unseenPersonalMessage {
|
||||
requestSignal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1))
|
||||
} else {
|
||||
assertionFailure()
|
||||
requestSignal = .complete()
|
||||
}
|
||||
} else {
|
||||
requestSignal = network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, offsetDate: 0, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash))
|
||||
}
|
||||
|
||||
signal = requestSignal
|
||||
|> map { result -> ValidatedMessages in
|
||||
let messages: [Api.Message]
|
||||
let chats: [Api.Chat]
|
||||
let users: [Api.User]
|
||||
var channelPts: Int32?
|
||||
|
||||
switch result {
|
||||
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
channelPts = pts
|
||||
case .messagesNotModified:
|
||||
return .notModified
|
||||
}
|
||||
return .messages(messages, chats, users, channelPts)
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
default:
|
||||
signal = .complete()
|
||||
}
|
||||
|
||||
return validateBatch(postbox: postbox, network: network, transaction: transaction, accountPeerId: accountPeerId, tag: tag, historyState: historyState, signal: signal, previous: previous, messageNamespace: Namespaces.Message.Cloud)
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
private func validateScheduledMessagesBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, tag: MessageTags?, messages: [Message], historyState: HistoryState) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
var signal: Signal<ValidatedMessages, MTRpcError>
|
||||
switch historyState {
|
||||
case let .scheduledMessages(peerId):
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
signal = network.request(Api.functions.messages.getScheduledHistory(peer: inputPeer, hash: 0))
|
||||
|> map { result -> ValidatedMessages in
|
||||
let messages: [Api.Message]
|
||||
let chats: [Api.Chat]
|
||||
let users: [Api.User]
|
||||
|
||||
switch result {
|
||||
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers):
|
||||
messages = apiMessages
|
||||
chats = apiChats
|
||||
users = apiUsers
|
||||
case .messagesNotModified:
|
||||
return .notModified
|
||||
}
|
||||
return .messages(messages, chats, users, nil)
|
||||
}
|
||||
} else {
|
||||
signal = .complete()
|
||||
}
|
||||
default:
|
||||
signal = .complete()
|
||||
}
|
||||
var previous: [MessageId: Message] = [:]
|
||||
for message in messages {
|
||||
previous[message.id] = message
|
||||
}
|
||||
return validateBatch(postbox: postbox, network: network, transaction: transaction, accountPeerId: accountPeerId, tag: tag, historyState: historyState, signal: signal, previous: previous, messageNamespace: Namespaces.Message.ScheduledCloud)
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
private func validateBatch(postbox: Postbox, network: Network, transaction: Transaction, accountPeerId: PeerId, tag: MessageTags?, historyState: HistoryState, signal: Signal<ValidatedMessages, MTRpcError>, previous: [MessageId: Message], messageNamespace: MessageId.Namespace) -> Signal<Void, NoError> {
|
||||
return signal
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<ValidatedMessages?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
guard let result = result else {
|
||||
return .complete()
|
||||
}
|
||||
switch result {
|
||||
case let .messages(messages, _, users, channelPts):
|
||||
var storeMessages: [StoreMessage] = []
|
||||
|
||||
for message in messages {
|
||||
if let storeMessage = StoreMessage(apiMessage: message, namespace: messageNamespace) {
|
||||
var attributes = storeMessage.attributes
|
||||
if let channelPts = channelPts {
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||
}
|
||||
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
|
||||
}
|
||||
}
|
||||
|
||||
var validMessageIds = Set<MessageId>()
|
||||
for message in storeMessages {
|
||||
if case let .Id(id) = message.id {
|
||||
validMessageIds.insert(id)
|
||||
}
|
||||
}
|
||||
|
||||
var maybeRemovedMessageIds: [MessageId] = []
|
||||
for id in previous.keys {
|
||||
if !validMessageIds.contains(id) {
|
||||
maybeRemovedMessageIds.append(id)
|
||||
}
|
||||
}
|
||||
|
||||
let actuallyRemovedMessagesSignal: Signal<Set<MessageId>, NoError>
|
||||
if maybeRemovedMessageIds.isEmpty {
|
||||
actuallyRemovedMessagesSignal = .single(Set())
|
||||
} else {
|
||||
switch historyState {
|
||||
case let .channel(peerId, _):
|
||||
actuallyRemovedMessagesSignal = postbox.transaction { transaction -> Signal<Set<MessageId>, NoError> in
|
||||
if let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) {
|
||||
return network.request(Api.functions.channels.getMessages(channel: inputChannel, id: maybeRemovedMessageIds.map({ Api.InputMessage.inputMessageID(id: $0.id) })))
|
||||
|> map { result -> Set<MessageId> in
|
||||
let apiMessages: [Api.Message]
|
||||
switch result {
|
||||
case let .channelMessages(_, _, _, messages, _, _):
|
||||
apiMessages = messages
|
||||
case let .messages(messages, _, _):
|
||||
apiMessages = messages
|
||||
case let .messagesSlice(_, _, _, messages, _, _):
|
||||
apiMessages = messages
|
||||
case .messagesNotModified:
|
||||
return Set()
|
||||
}
|
||||
var ids = Set<MessageId>()
|
||||
for message in apiMessages {
|
||||
if let parsedMessage = StoreMessage(apiMessage: message), case let .Id(id) = parsedMessage.id {
|
||||
if let tag = tag {
|
||||
if parsedMessage.tags.contains(tag) {
|
||||
ids.insert(id)
|
||||
}
|
||||
} else {
|
||||
ids.insert(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Set(maybeRemovedMessageIds).subtracting(ids)
|
||||
}
|
||||
|> `catch` { _ -> Signal<Set<MessageId>, NoError> in
|
||||
return .single(Set(maybeRemovedMessageIds))
|
||||
}
|
||||
}
|
||||
return .single(Set(maybeRemovedMessageIds))
|
||||
} |> switchToLatest
|
||||
default:
|
||||
actuallyRemovedMessagesSignal = .single(Set(maybeRemovedMessageIds))
|
||||
}
|
||||
}
|
||||
|
||||
return actuallyRemovedMessagesSignal
|
||||
|> mapToSignal { removedMessageIds -> Signal<Void, NoError> in
|
||||
return postbox.transaction { transaction -> Void in
|
||||
var validMessageIds = Set<MessageId>()
|
||||
for message in storeMessages {
|
||||
if case let .Id(id) = message.id {
|
||||
validMessageIds.insert(id)
|
||||
let previousMessage = previous[id] ?? transaction.getMessage(id)
|
||||
|
||||
if let previousMessage = previousMessage {
|
||||
var updatedTimestamp = message.timestamp
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
updatedTimestamp = attribute.date
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
var timestamp = previousMessage.timestamp
|
||||
inner: for attribute in previousMessage.attributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
timestamp = attribute.date
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
transaction.updateMessage(id, update: { currentMessage in
|
||||
if updatedTimestamp != timestamp {
|
||||
var updatedLocalTags = message.localTags
|
||||
if currentMessage.localTags.contains(.OutgoingLiveLocation) {
|
||||
updatedLocalTags.insert(.OutgoingLiveLocation)
|
||||
}
|
||||
return .update(message.withUpdatedLocalTags(updatedLocalTags))
|
||||
} else {
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
if let channelPts = channelPts {
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
}
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||
}
|
||||
|
||||
let updatedFlags = StoreMessageFlags(currentMessage.flags)
|
||||
|
||||
return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
}
|
||||
})
|
||||
|
||||
if previous[id] == nil {
|
||||
print("\(id) missing")
|
||||
}
|
||||
} else {
|
||||
let _ = transaction.addMessages([message], location: .Random)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let tag = tag {
|
||||
for (_, previousMessage) in previous {
|
||||
if !validMessageIds.contains(previousMessage.id) {
|
||||
transaction.updateMessage(previousMessage.id, update: { currentMessage in
|
||||
var updatedTags = currentMessage.tags
|
||||
updatedTags.remove(tag)
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
switch historyState {
|
||||
case .channel:
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
switch historyState {
|
||||
case let .channel(_, channelState):
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
default:
|
||||
break
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for id in removedMessageIds {
|
||||
if !validMessageIds.contains(id) {
|
||||
if let tag = tag {
|
||||
transaction.updateMessage(id, update: { currentMessage in
|
||||
var updatedTags = currentMessage.tags
|
||||
updatedTags.remove(tag)
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
switch historyState {
|
||||
case .channel:
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
switch historyState {
|
||||
case let .channel(_, channelState):
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
default:
|
||||
break
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
} else {
|
||||
deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [id])
|
||||
Logger.shared.log("HistoryValidation", "deleting message \(id) in \(id.peerId)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case .notModified:
|
||||
return postbox.transaction { transaction -> Void in
|
||||
for id in previous.keys {
|
||||
transaction.updateMessage(id, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
switch historyState {
|
||||
case .channel:
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
switch historyState {
|
||||
case let .channel(_, channelState):
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
default:
|
||||
break
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,16 +151,57 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
|
||||
var implicitelyFillHole = false
|
||||
let minMaxRange: ClosedRange<MessageId.Id>
|
||||
|
||||
if namespace == Namespaces.Message.ScheduledCloud {
|
||||
switch direction {
|
||||
case .aroundId, .range:
|
||||
implicitelyFillHole = true
|
||||
}
|
||||
minMaxRange = 1 ... (Int32.max - 1)
|
||||
request = source.request(Api.functions.messages.getScheduledHistory(peer: inputPeer, hash: 0))
|
||||
} else {
|
||||
switch space {
|
||||
case .everywhere:
|
||||
switch space {
|
||||
case .everywhere:
|
||||
let offsetId: Int32
|
||||
let addOffset: Int32
|
||||
let selectedLimit = limit
|
||||
let maxId: Int32
|
||||
let minId: Int32
|
||||
|
||||
switch direction {
|
||||
case let .range(start, end):
|
||||
if start.id <= end.id {
|
||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||
addOffset = Int32(-selectedLimit)
|
||||
maxId = end.id
|
||||
minId = start.id - 1
|
||||
|
||||
let rangeStartId = start.id
|
||||
let rangeEndId = min(end.id, Int32.max - 1)
|
||||
if rangeStartId <= rangeEndId {
|
||||
minMaxRange = rangeStartId ... rangeEndId
|
||||
} else {
|
||||
minMaxRange = rangeStartId ... rangeStartId
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||
addOffset = 0
|
||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||
minId = end.id
|
||||
|
||||
let rangeStartId = end.id
|
||||
let rangeEndId = min(start.id, Int32.max - 1)
|
||||
if rangeStartId <= rangeEndId {
|
||||
minMaxRange = rangeStartId ... rangeEndId
|
||||
} else {
|
||||
minMaxRange = rangeStartId ... rangeStartId
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
case let .aroundId(id):
|
||||
offsetId = id.id
|
||||
addOffset = Int32(-selectedLimit / 2)
|
||||
maxId = Int32.max
|
||||
minId = 1
|
||||
minMaxRange = 1 ... Int32.max - 1
|
||||
}
|
||||
|
||||
request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
||||
case let .tag(tag):
|
||||
assert(tag.containsSingleElement)
|
||||
if tag == .unseenPersonalMessage {
|
||||
let offsetId: Int32
|
||||
let addOffset: Int32
|
||||
let selectedLimit = limit
|
||||
@ -203,122 +244,72 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
|
||||
addOffset = Int32(-selectedLimit / 2)
|
||||
maxId = Int32.max
|
||||
minId = 1
|
||||
|
||||
minMaxRange = 1 ... Int32.max - 1
|
||||
}
|
||||
|
||||
request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
||||
case let .tag(tag):
|
||||
assert(tag.containsSingleElement)
|
||||
if tag == .unseenPersonalMessage {
|
||||
let offsetId: Int32
|
||||
let addOffset: Int32
|
||||
let selectedLimit = limit
|
||||
let maxId: Int32
|
||||
let minId: Int32
|
||||
|
||||
switch direction {
|
||||
case let .range(start, end):
|
||||
if start.id <= end.id {
|
||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||
addOffset = Int32(-selectedLimit)
|
||||
maxId = end.id
|
||||
minId = start.id - 1
|
||||
|
||||
let rangeStartId = start.id
|
||||
let rangeEndId = min(end.id, Int32.max - 1)
|
||||
if rangeStartId <= rangeEndId {
|
||||
minMaxRange = rangeStartId ... rangeEndId
|
||||
} else {
|
||||
minMaxRange = rangeStartId ... rangeStartId
|
||||
assertionFailure()
|
||||
}
|
||||
request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
|
||||
} else if tag == .liveLocation {
|
||||
let selectedLimit = limit
|
||||
|
||||
switch direction {
|
||||
case .aroundId, .range:
|
||||
implicitelyFillHole = true
|
||||
}
|
||||
minMaxRange = 1 ... (Int32.max - 1)
|
||||
request = source.request(Api.functions.messages.getRecentLocations(peer: inputPeer, limit: Int32(selectedLimit), hash: 0))
|
||||
} else if let filter = messageFilterForTagMask(tag) {
|
||||
let offsetId: Int32
|
||||
let addOffset: Int32
|
||||
let selectedLimit = limit
|
||||
let maxId: Int32
|
||||
let minId: Int32
|
||||
|
||||
switch direction {
|
||||
case let .range(start, end):
|
||||
if start.id <= end.id {
|
||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||
addOffset = Int32(-selectedLimit)
|
||||
maxId = end.id
|
||||
minId = start.id - 1
|
||||
|
||||
let rangeStartId = start.id
|
||||
let rangeEndId = min(end.id, Int32.max - 1)
|
||||
if rangeStartId <= rangeEndId {
|
||||
minMaxRange = rangeStartId ... rangeEndId
|
||||
} else {
|
||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||
addOffset = 0
|
||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||
minId = end.id
|
||||
|
||||
let rangeStartId = end.id
|
||||
let rangeEndId = min(start.id, Int32.max - 1)
|
||||
if rangeStartId <= rangeEndId {
|
||||
minMaxRange = rangeStartId ... rangeEndId
|
||||
} else {
|
||||
minMaxRange = rangeStartId ... rangeStartId
|
||||
assertionFailure()
|
||||
}
|
||||
minMaxRange = rangeStartId ... rangeStartId
|
||||
assertionFailure()
|
||||
}
|
||||
case let .aroundId(id):
|
||||
offsetId = id.id
|
||||
addOffset = Int32(-selectedLimit / 2)
|
||||
maxId = Int32.max
|
||||
minId = 1
|
||||
|
||||
minMaxRange = 1 ... Int32.max - 1
|
||||
}
|
||||
|
||||
request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
|
||||
} else if tag == .liveLocation {
|
||||
let selectedLimit = limit
|
||||
|
||||
switch direction {
|
||||
case .aroundId, .range:
|
||||
implicitelyFillHole = true
|
||||
}
|
||||
minMaxRange = 1 ... (Int32.max - 1)
|
||||
request = source.request(Api.functions.messages.getRecentLocations(peer: inputPeer, limit: Int32(selectedLimit), hash: 0))
|
||||
} else if let filter = messageFilterForTagMask(tag) {
|
||||
let offsetId: Int32
|
||||
let addOffset: Int32
|
||||
let selectedLimit = limit
|
||||
let maxId: Int32
|
||||
let minId: Int32
|
||||
|
||||
switch direction {
|
||||
case let .range(start, end):
|
||||
if start.id <= end.id {
|
||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||
addOffset = Int32(-selectedLimit)
|
||||
maxId = end.id
|
||||
minId = start.id - 1
|
||||
|
||||
let rangeStartId = start.id
|
||||
let rangeEndId = min(end.id, Int32.max - 1)
|
||||
if rangeStartId <= rangeEndId {
|
||||
minMaxRange = rangeStartId ... rangeEndId
|
||||
} else {
|
||||
minMaxRange = rangeStartId ... rangeStartId
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||
addOffset = 0
|
||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||
minId = end.id
|
||||
|
||||
let rangeStartId = end.id
|
||||
let rangeEndId = min(start.id, Int32.max - 1)
|
||||
if rangeStartId <= rangeEndId {
|
||||
minMaxRange = rangeStartId ... rangeEndId
|
||||
} else {
|
||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||
addOffset = 0
|
||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||
minId = end.id
|
||||
|
||||
let rangeStartId = end.id
|
||||
let rangeEndId = min(start.id, Int32.max - 1)
|
||||
if rangeStartId <= rangeEndId {
|
||||
minMaxRange = rangeStartId ... rangeEndId
|
||||
} else {
|
||||
minMaxRange = rangeStartId ... rangeStartId
|
||||
assertionFailure()
|
||||
}
|
||||
minMaxRange = rangeStartId ... rangeStartId
|
||||
assertionFailure()
|
||||
}
|
||||
case let .aroundId(id):
|
||||
offsetId = id.id
|
||||
addOffset = Int32(-selectedLimit / 2)
|
||||
maxId = Int32.max
|
||||
minId = 1
|
||||
|
||||
minMaxRange = 1 ... (Int32.max - 1)
|
||||
}
|
||||
}
|
||||
case let .aroundId(id):
|
||||
offsetId = id.id
|
||||
addOffset = Int32(-selectedLimit / 2)
|
||||
maxId = Int32.max
|
||||
minId = 1
|
||||
|
||||
request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
||||
} else {
|
||||
assertionFailure()
|
||||
minMaxRange = 1 ... 1
|
||||
request = .never()
|
||||
}
|
||||
minMaxRange = 1 ... (Int32.max - 1)
|
||||
}
|
||||
|
||||
request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
||||
} else {
|
||||
assertionFailure()
|
||||
minMaxRange = 1 ... 1
|
||||
request = .never()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tagLocalIndex:
|
||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
var result: PeerMergedOperationLogEntry?
|
||||
transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SynchronizeEmojiKeywords, tagLocalIndex: tagLocalIndex, { entry in
|
||||
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeEmojiKeywordsOperation {
|
||||
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeEmojiKeywordsOperation {
|
||||
result = entry.mergedEntry!
|
||||
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
||||
} else {
|
||||
|
||||
@ -12,6 +12,8 @@ public struct Namespaces {
|
||||
public static let SecretIncoming: Int32 = 2
|
||||
public static let ScheduledCloud: Int32 = 3
|
||||
public static let ScheduledLocal: Int32 = 4
|
||||
|
||||
public static let allScheduled: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal])
|
||||
}
|
||||
|
||||
public struct Media {
|
||||
@ -110,6 +112,7 @@ public extension LocalMessageTags {
|
||||
public extension PendingMessageActionType {
|
||||
static let consumeUnseenPersonalMessage = PendingMessageActionType(rawValue: 0)
|
||||
static let updateReaction = PendingMessageActionType(rawValue: 1)
|
||||
static let sendScheduledMessageImmediately = PendingMessageActionType(rawValue: 2)
|
||||
}
|
||||
|
||||
let peerIdNamespacesWithInitialCloudMessageHoles = [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel]
|
||||
|
||||
@ -9,21 +9,171 @@ import Foundation
|
||||
import TelegramApi
|
||||
#endif
|
||||
|
||||
public func sendScheduledMessageNow(account: Account, messageId: MessageId) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
if let _ = transaction.getMessage(messageId), let peer = transaction.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.sendScheduledMessages(peer: inputPeer, id: [messageId.id]))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
if let updates = updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
final class SendScheduledMessageImmediatelyAction: PendingMessageActionData {
|
||||
init() {
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
}
|
||||
|
||||
func isEqual(to: PendingMessageActionData) -> Bool {
|
||||
if let _ = to as? SendScheduledMessageImmediatelyAction {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func sendScheduledMessageNowInteractively(postbox: Postbox, messageId: MessageId) -> Signal<Never, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
transaction.setPendingMessageAction(type: .sendScheduledMessageImmediately, id: messageId, action: SendScheduledMessageImmediatelyAction())
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
private final class ManagedApplyPendingScheduledMessagesActionsHelper {
|
||||
var operationDisposables: [MessageId: Disposable] = [:]
|
||||
|
||||
func update(entries: [PendingMessageActionsEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) {
|
||||
var disposeOperations: [Disposable] = []
|
||||
var beginOperations: [(PendingMessageActionsEntry, MetaDisposable)] = []
|
||||
|
||||
var hasRunningOperationForPeerId = Set<PeerId>()
|
||||
var validIds = Set<MessageId>()
|
||||
for entry in entries {
|
||||
if !hasRunningOperationForPeerId.contains(entry.id.peerId) {
|
||||
hasRunningOperationForPeerId.insert(entry.id.peerId)
|
||||
validIds.insert(entry.id)
|
||||
|
||||
if self.operationDisposables[entry.id] == nil {
|
||||
let disposable = MetaDisposable()
|
||||
beginOperations.append((entry, disposable))
|
||||
self.operationDisposables[entry.id] = disposable
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
} |> switchToLatest
|
||||
|
||||
var removeMergedIds: [MessageId] = []
|
||||
for (id, disposable) in self.operationDisposables {
|
||||
if !validIds.contains(id) {
|
||||
removeMergedIds.append(id)
|
||||
disposeOperations.append(disposable)
|
||||
}
|
||||
}
|
||||
|
||||
for id in removeMergedIds {
|
||||
self.operationDisposables.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
return (disposeOperations, beginOperations)
|
||||
}
|
||||
|
||||
func reset() -> [Disposable] {
|
||||
let disposables = Array(self.operationDisposables.values)
|
||||
self.operationDisposables.removeAll()
|
||||
return disposables
|
||||
}
|
||||
}
|
||||
|
||||
private func withTakenAction(postbox: Postbox, type: PendingMessageActionType, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal<Never, NoError>) -> Signal<Never, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
var result: PendingMessageActionsEntry?
|
||||
|
||||
if let action = transaction.getPendingMessageAction(type: type, id: id) as? SendScheduledMessageImmediatelyAction {
|
||||
result = PendingMessageActionsEntry(id: id, action: action)
|
||||
}
|
||||
|
||||
return f(transaction, result)
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
func managedApplyPendingScheduledMessagesActions(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||
return Signal { _ in
|
||||
let helper = Atomic<ManagedApplyPendingScheduledMessagesActionsHelper>(value: ManagedApplyPendingScheduledMessagesActionsHelper())
|
||||
|
||||
let actionsKey = PostboxViewKey.pendingMessageActions(type: .sendScheduledMessageImmediately)
|
||||
let disposable = postbox.combinedView(keys: [actionsKey]).start(next: { view in
|
||||
var entries: [PendingMessageActionsEntry] = []
|
||||
if let v = view.views[actionsKey] as? PendingMessageActionsView {
|
||||
entries = v.entries
|
||||
}
|
||||
|
||||
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) in
|
||||
return helper.update(entries: entries)
|
||||
}
|
||||
|
||||
for disposable in disposeOperations {
|
||||
disposable.dispose()
|
||||
}
|
||||
|
||||
for (entry, disposable) in beginOperations {
|
||||
let signal = withTakenAction(postbox: postbox, type: .sendScheduledMessageImmediately, id: entry.id, { transaction, entry -> Signal<Never, NoError> in
|
||||
if let entry = entry {
|
||||
if let _ = entry.action as? SendScheduledMessageImmediatelyAction {
|
||||
return sendScheduledMessageNow(postbox: postbox, network: network, stateManager: stateManager, messageId: entry.id)
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
})
|
||||
|> then(
|
||||
postbox.transaction { transaction -> Void in
|
||||
transaction.deleteMessages([entry.id])
|
||||
}
|
||||
|> ignoreValues
|
||||
)
|
||||
|
||||
disposable.set(signal.start())
|
||||
}
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
let disposables = helper.with { helper -> [Disposable] in
|
||||
return helper.reset()
|
||||
}
|
||||
for disposable in disposables {
|
||||
disposable.dispose()
|
||||
}
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SendScheduledMessageNowError {
|
||||
case generic
|
||||
}
|
||||
|
||||
private func sendScheduledMessageNow(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal<Never, SendScheduledMessageNowError> {
|
||||
return postbox.transaction { transaction -> Peer? in
|
||||
guard let peer = transaction.getPeer(messageId.peerId) else {
|
||||
return nil
|
||||
}
|
||||
return peer
|
||||
}
|
||||
|> introduceError(SendScheduledMessageNowError.self)
|
||||
|> mapToSignal { peer -> Signal<Never, SendScheduledMessageNowError> in
|
||||
guard let peer = peer else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let inputPeer = apiInputPeer(peer) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return network.request(Api.functions.messages.sendScheduledMessages(peer: inputPeer, id: [messageId.id]))
|
||||
|> mapError { _ -> SendScheduledMessageNowError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Never, SendScheduledMessageNowError> in
|
||||
stateManager.addUpdates(updates)
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
|
||||
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
|
||||
if let item = entry.contents as? SavedStickerItem {
|
||||
for representation in item.stringRepresentations {
|
||||
if representation == query {
|
||||
if representation.hasPrefix(query) {
|
||||
result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations))
|
||||
break
|
||||
}
|
||||
@ -115,7 +115,7 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
|
||||
if let item = entry.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile {
|
||||
if !currentItems.contains(file.fileId) {
|
||||
for case let .Sticker(sticker) in file.attributes {
|
||||
if sticker.displayText == query {
|
||||
if sticker.displayText.hasPrefix(query) {
|
||||
matchingRecentItemsIds.insert(file.fileId)
|
||||
}
|
||||
recentItemsIds.insert(file.fileId)
|
||||
@ -130,9 +130,14 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
|
||||
}
|
||||
}
|
||||
|
||||
var searchQuery: ItemCollectionSearchQuery = .exact(ValueBoxKey(query))
|
||||
if query == "\u{2764}" {
|
||||
searchQuery = .matching([ValueBoxKey("\u{2764}"), ValueBoxKey("\u{2764}\u{fe0f}")])
|
||||
}
|
||||
|
||||
var installedItems: [FoundStickerItem] = []
|
||||
var installedAnimatedItems: [FoundStickerItem] = []
|
||||
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: .exact(ValueBoxKey(query))) {
|
||||
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) {
|
||||
if let item = item as? StickerPackItem {
|
||||
if !currentItems.contains(item.file.fileId) {
|
||||
var stringRepresentations: [String] = []
|
||||
|
||||
@ -501,8 +501,8 @@
|
||||
D098908022942E3B0053F151 /* ActiveSessionsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */; };
|
||||
D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
|
||||
D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
|
||||
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; };
|
||||
D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; };
|
||||
D099D7491EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */; };
|
||||
D099D74A1EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */; };
|
||||
D099E222229420D600561B75 /* BlockedPeersContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099E221229420D600561B75 /* BlockedPeersContext.swift */; };
|
||||
D099E223229420D600561B75 /* BlockedPeersContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099E221229420D600561B75 /* BlockedPeersContext.swift */; };
|
||||
D099EA1C1DE72867001AF5A8 /* PeerCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */; };
|
||||
@ -1055,7 +1055,7 @@
|
||||
D093D805206539D000BC3599 /* SaveSecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveSecureIdValue.swift; sourceTree = "<group>"; };
|
||||
D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionsContext.swift; sourceTree = "<group>"; };
|
||||
D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMessageStateVersionAttribute.swift; sourceTree = "<group>"; };
|
||||
D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewChannelStateValidation.swift; sourceTree = "<group>"; };
|
||||
D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewStateValidation.swift; sourceTree = "<group>"; };
|
||||
D099E221229420D600561B75 /* BlockedPeersContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedPeersContext.swift; sourceTree = "<group>"; };
|
||||
D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerCommands.swift; sourceTree = "<group>"; };
|
||||
D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannel.swift; sourceTree = "<group>"; };
|
||||
@ -1552,7 +1552,7 @@
|
||||
D0BEAF5C1E54941B00BD963D /* Authorization.swift */,
|
||||
D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */,
|
||||
D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */,
|
||||
D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */,
|
||||
D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */,
|
||||
D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */,
|
||||
D06ECFC720B810D300C576C2 /* TermsOfService.swift */,
|
||||
D051DB16215ECC4D00F30F92 /* AppChangelog.swift */,
|
||||
@ -2235,7 +2235,7 @@
|
||||
D08984FB2118816A00918162 /* Reachability.m in Sources */,
|
||||
D0DA1D321F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */,
|
||||
0925903722F0D02D003D6283 /* ManagedAnimatedEmojiUpdates.swift in Sources */,
|
||||
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
|
||||
D099D7491EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */,
|
||||
C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */,
|
||||
D0D376E622DCCFD600FA7D7C /* SlowMode.swift in Sources */,
|
||||
D00422D321677F4500719B67 /* ManagedAccountPresence.swift in Sources */,
|
||||
@ -2649,7 +2649,7 @@
|
||||
D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */,
|
||||
D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */,
|
||||
D08CAA881ED81DD40000FDA8 /* LocalizationInfo.swift in Sources */,
|
||||
D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
|
||||
D099D74A1EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */,
|
||||
D0AF32231FAC95C20097362B /* StandaloneUploadedMedia.swift in Sources */,
|
||||
D04554A721B43440007A6DD9 /* CancelAccountReset.swift in Sources */,
|
||||
D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1461,44 +1461,38 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.sendScheduledMessagesNow(messageIds)
|
||||
}
|
||||
}, editScheduledMessagesTime: { [weak self] messageIds in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = self, let messageId = messageIds.first {
|
||||
let mode: ChatScheduleTimeControllerMode
|
||||
if case let .peer(peerId) = strongSelf.presentationInterfaceState.chatLocation, peerId == strongSelf.context.account.peerId {
|
||||
mode = .reminders
|
||||
} else {
|
||||
mode = .scheduledMessages
|
||||
}
|
||||
let controller = ChatScheduleTimeController(context: strongSelf.context, mode: mode, completion: { [weak self] scheduleTime in
|
||||
if let strongSelf = self, let messageId = messageIds.first {
|
||||
let signal = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
|
||||
return transaction.getMessage(messageId)
|
||||
}
|
||||
|> introduceError(RequestEditMessageError.self)
|
||||
|> mapToSignal({ message -> Signal<RequestEditMessageResult, RequestEditMessageError> in
|
||||
if let message = message {
|
||||
var entities: TextEntitiesMessageAttribute?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entities = attribute
|
||||
break
|
||||
}
|
||||
}
|
||||
return requestEditMessage(account: strongSelf.context.account, messageId: messageId, text: message.text, media: .keep
|
||||
, entities: entities, disableUrlPreview: false, scheduleTime: scheduleTime)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}))
|
||||
|
||||
strongSelf.editMessageDisposable.set((signal |> deliverOnMainQueue).start(next: { result in
|
||||
|
||||
}, error: { error in
|
||||
|
||||
}))
|
||||
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
|
||||
return transaction.getMessage(messageId)
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] message in
|
||||
guard let strongSelf = self, let message = message else {
|
||||
return
|
||||
}
|
||||
let controller = ChatScheduleTimeController(context: strongSelf.context, mode: mode, currentTime: message.timestamp, completion: { [weak self] scheduleTime in
|
||||
if let strongSelf = self {
|
||||
var entities: TextEntitiesMessageAttribute?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entities = attribute
|
||||
break
|
||||
}
|
||||
}
|
||||
let signal = requestEditMessage(account: strongSelf.context.account, messageId: messageId, text: message.text, media: .keep, entities: entities, disableUrlPreview: false, scheduleTime: scheduleTime)
|
||||
strongSelf.editMessageDisposable.set((signal |> deliverOnMainQueue).start(next: { result in
|
||||
}, error: { error in
|
||||
}))
|
||||
}
|
||||
})
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
})
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
}
|
||||
}, performTextSelectionAction: { [weak self] _, text, action in
|
||||
guard let strongSelf = self else {
|
||||
@ -1638,8 +1632,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
||||
}
|
||||
|
||||
if !isScheduledMessages {
|
||||
hasScheduledMessages = context.account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil, tagMask: nil, excludeNamespaces: [Namespaces.Message.Cloud, Namespaces.Message.Local], orderStatistics: [])
|
||||
if !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
hasScheduledMessages = context.account.viewTracker.scheduledMessagesViewForLocation(chatLocation)
|
||||
|> map { view, _, _ in
|
||||
return !view.entries.isEmpty
|
||||
}
|
||||
@ -5299,7 +5293,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
private func sendScheduledMessagesNow(_ messageId: [MessageId]) {
|
||||
let _ = sendScheduledMessageNow(account: self.context.account, messageId: messageId.first!).start()
|
||||
let _ = sendScheduledMessageNowInteractively(postbox: self.context.account.postbox, messageId: messageId.first!).start()
|
||||
}
|
||||
|
||||
private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) {
|
||||
|
||||
@ -33,7 +33,7 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod
|
||||
self.currentStrings = interfaceState.strings
|
||||
|
||||
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
||||
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: serviceColor.primaryText)
|
||||
self.textNode.attributedText = NSAttributedString(string: interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: serviceColor.primaryText)
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0)
|
||||
|
||||
@ -30,7 +30,6 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A
|
||||
if scheduled {
|
||||
var preloaded = false
|
||||
var fadeIn = false
|
||||
let count = 100
|
||||
return account.viewTracker.scheduledMessagesViewForLocation(chatLocation)
|
||||
|> map { view, updateType, initialData -> ChatHistoryViewUpdate in
|
||||
let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation)
|
||||
|
||||
@ -382,14 +382,18 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
}
|
||||
|
||||
if data.messageActions.options.contains(.sendScheduledNow) {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { _ in return nil }, action: { _, f in
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
}
|
||||
|
||||
if data.messageActions.options.contains(.editScheduledTime) {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_EditTime, icon: { _ in return nil }, action: { _, f in
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_EditTime, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.editScheduledMessagesTime(selectAll ? messages.map { $0.id } : [message.id])
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
|
||||
@ -329,13 +329,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
|
||||
self.effectiveAuthorId = effectiveAuthor?.id
|
||||
|
||||
let timestamp: Int32
|
||||
if let scheduleTime = content.firstMessage.scheduleTime {
|
||||
timestamp = scheduleTime
|
||||
} else {
|
||||
timestamp = content.index.timestamp
|
||||
}
|
||||
self.header = ChatMessageDateHeader(timestamp: timestamp, scheduled: associatedData.isScheduledMessages, presentationData: presentationData, context: context, action: { timestamp in
|
||||
self.header = ChatMessageDateHeader(timestamp: content.index.timestamp, scheduled: associatedData.isScheduledMessages, presentationData: presentationData, context: context, action: { timestamp in
|
||||
var calendar = NSCalendar.current
|
||||
calendar.timeZone = TimeZone(abbreviation: "UTC")!
|
||||
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
|
||||
|
||||
@ -105,7 +105,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
self.contentContainerNode.addSubnode(self.doneButton)
|
||||
|
||||
self.pickerView.timeZone = TimeZone.current
|
||||
self.pickerView.minuteInterval = 5
|
||||
self.pickerView.minuteInterval = 1
|
||||
self.pickerView.setValue(self.presentationData.theme.actionSheet.primaryTextColor, forKey: "textColor")
|
||||
|
||||
self.contentContainerNode.view.addSubview(self.pickerView)
|
||||
@ -265,8 +265,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
insets.top = max(10.0, insets.top)
|
||||
|
||||
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
let titleAreaHeight: CGFloat = 54.0
|
||||
let contentHeight = titleAreaHeight + bottomInset + 285.0
|
||||
let titleHeight: CGFloat = 54.0
|
||||
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
|
||||
let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight)
|
||||
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight
|
||||
|
||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
||||
|
||||
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||
@ -281,11 +284,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleAreaHeight))
|
||||
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
|
||||
let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleAreaHeight))
|
||||
let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight))
|
||||
let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize)
|
||||
transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
|
||||
|
||||
@ -293,9 +296,9 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
let buttonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
|
||||
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - buttonHeight - insets.bottom - 10.0, width: contentFrame.width, height: buttonHeight))
|
||||
|
||||
self.pickerView.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: 216.0))
|
||||
self.pickerView.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight))
|
||||
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,15 @@ import MusicAlbumArtResources
|
||||
private enum PeerMessagesMediaPlaylistLoadAnchor {
|
||||
case messageId(MessageId)
|
||||
case index(MessageIndex)
|
||||
|
||||
var id: MessageId {
|
||||
switch self {
|
||||
case let .messageId(id):
|
||||
return id
|
||||
case let .index(index):
|
||||
return index.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeerMessagesMediaPlaylistNavigation {
|
||||
@ -477,6 +486,14 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
private func loadItem(anchor: PeerMessagesMediaPlaylistLoadAnchor, navigation: PeerMessagesMediaPlaylistNavigation) {
|
||||
self.loadingItem = true
|
||||
self.updateState()
|
||||
|
||||
let namespaces: HistoryViewNamespaces
|
||||
if Namespaces.Message.allScheduled.contains(anchor.id.namespace) {
|
||||
namespaces = .just(Namespaces.Message.allScheduled)
|
||||
} else {
|
||||
namespaces = .not(Namespaces.Message.allScheduled)
|
||||
}
|
||||
|
||||
switch anchor {
|
||||
case let .messageId(messageId):
|
||||
if case let .messages(peerId, tagMask, _) = self.messagesLocation {
|
||||
@ -486,7 +503,8 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
guard let message = message else {
|
||||
return .single(nil)
|
||||
}
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, excludeNamespaces: [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: [])
|
||||
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
|
||||
return .single((message, aroundMessages))
|
||||
@ -560,7 +578,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
}
|
||||
let historySignal = inputIndex
|
||||
|> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(inputIndex), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, excludeNamespaces: [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: [])
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(inputIndex), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||
let position: NavigatedMessageFromViewPosition
|
||||
switch navigation {
|
||||
@ -590,7 +608,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
} else {
|
||||
viewIndex = .lowerBound
|
||||
}
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, excludeNamespaces: [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: [])
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||
let position: NavigatedMessageFromViewPosition
|
||||
switch navigation {
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user