Re-implement external sharing to secret chats

This commit is contained in:
Ali 2023-11-06 20:18:59 +04:00
parent 6e60c784b7
commit 21af13cfdb
12 changed files with 565 additions and 102 deletions

View File

@ -52,6 +52,30 @@ final class PeerMergedOperationLogIndexTable: Table {
return result
}
func getTagLocalIndices(tag: PeerOperationLogTag, peerId: PeerId, fromMergedIndex: Int32, limit: Int) -> [(PeerId, Int32, Int32)] {
var result: [(PeerId, Int32, Int32)] = []
self.valueBox.range(self.table, start: self.key(tag: tag, index: fromMergedIndex == 0 ? 0 : fromMergedIndex - 1), end: self.key(tag: tag, index: Int32.max), values: { key, value in
assert(key.getUInt8(0) == tag.rawValue)
var peerIdValue: Int64 = 0
var tagLocalIndexValue: Int32 = 0
value.read(&peerIdValue, offset: 0, length: 8)
value.read(&tagLocalIndexValue, offset: 0, length: 4)
let parsedPeerId = PeerId(peerIdValue)
if parsedPeerId != peerId {
return true
}
result.append((parsedPeerId, tagLocalIndexValue, key.getInt32(1)))
if result.count >= limit {
return false
}
return true
}, limit: 0)
return result
}
func tailIndex(tag: PeerOperationLogTag) -> Int32? {
var result: Int32?
self.valueBox.range(self.table, start: self.key(tag: tag, index: Int32.max), end: self.key(tag: tag, index: 0), keys: {

View File

@ -2,23 +2,52 @@ import Foundation
final class MutablePeerMergedOperationLogView {
let tag: PeerOperationLogTag
let filterByPeerId: PeerId?
var entries: [PeerMergedOperationLogEntry]
var tailIndex: Int32?
let limit: Int
init(postbox: PostboxImpl, tag: PeerOperationLogTag, limit: Int) {
init(postbox: PostboxImpl, tag: PeerOperationLogTag, filterByPeerId: PeerId?, limit: Int) {
self.tag = tag
self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: 0, limit: limit)
self.filterByPeerId = filterByPeerId
if let filterByPeerId = self.filterByPeerId {
self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, peerId: filterByPeerId, fromIndex: 0, limit: limit)
} else {
self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: 0, limit: limit)
}
self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: tag)
self.limit = limit
}
func replay(postbox: PostboxImpl, operations: [PeerMergedOperationLogOperation]) -> Bool {
var updated = false
var invalidatedTail = false
for operation in operations {
switch operation {
if let filterByPeerId = self.filterByPeerId {
if operations.contains(where: { operation in
switch operation {
case let .append(entry):
if entry.tag == self.tag && entry.peerId == filterByPeerId {
return true
}
case let .remove(tag, peerId, _):
if tag == self.tag && peerId == filterByPeerId {
return true
}
case let .updateContents(entry):
if entry.tag == self.tag && entry.peerId == filterByPeerId {
return true
}
}
return false
}) {
self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, peerId: filterByPeerId, fromIndex: 0, limit: limit)
updated = true
}
} else {
var invalidatedTail = false
for operation in operations {
switch operation {
case let .append(entry):
if entry.tag == self.tag {
if let tailIndex = self.tailIndex {
@ -39,15 +68,15 @@ final class MutablePeerMergedOperationLogView {
}
case let .updateContents(entry):
if entry.tag == self.tag {
loop: for i in 0 ..< self.entries.count {
if self.entries[i].tagLocalIndex == entry.tagLocalIndex {
self.entries[i] = entry
updated = true
break loop
}
loop: for i in 0 ..< self.entries.count {
if self.entries[i].tagLocalIndex == entry.tagLocalIndex {
self.entries[i] = entry
updated = true
break loop
}
}
case let .remove(tag, mergedIndices):
}
case let .remove(tag, _, mergedIndices):
if tag == self.tag {
updated = true
for i in (0 ..< self.entries.count).reversed() {
@ -60,37 +89,38 @@ final class MutablePeerMergedOperationLogView {
invalidatedTail = true
}
}
}
}
}
if updated {
if invalidatedTail {
self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: self.tag)
}
if self.entries.count < self.limit {
if let tailIndex = self.tailIndex {
if self.entries.isEmpty || self.entries.last!.mergedIndex < tailIndex {
var fromIndex: Int32 = 0
if !self.entries.isEmpty {
fromIndex = self.entries.last!.mergedIndex + 1
}
for entry in postbox.peerOperationLogTable.getMergedEntries(tag: self.tag, fromIndex: fromIndex, limit: self.limit - self.entries.count) {
self.entries.append(entry)
}
for i in 0 ..< self.entries.count {
if i != 0 {
assert(self.entries[i].mergedIndex >= self.entries[i - 1].mergedIndex + 1)
if updated {
if invalidatedTail {
self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: self.tag)
}
if self.entries.count < self.limit {
if let tailIndex = self.tailIndex {
if self.entries.isEmpty || self.entries.last!.mergedIndex < tailIndex {
var fromIndex: Int32 = 0
if !self.entries.isEmpty {
fromIndex = self.entries.last!.mergedIndex + 1
}
for entry in postbox.peerOperationLogTable.getMergedEntries(tag: self.tag, fromIndex: fromIndex, limit: self.limit - self.entries.count) {
self.entries.append(entry)
}
for i in 0 ..< self.entries.count {
if i != 0 {
assert(self.entries[i].mergedIndex >= self.entries[i - 1].mergedIndex + 1)
}
}
if !self.entries.isEmpty {
assert(self.entries.last!.mergedIndex <= tailIndex)
}
}
if !self.entries.isEmpty {
assert(self.entries.last!.mergedIndex <= tailIndex)
}
}
}
} else {
assert(self.tailIndex != nil)
if let tailIndex = self.tailIndex {
assert(self.entries.last!.mergedIndex <= tailIndex)
} else {
assert(self.tailIndex != nil)
if let tailIndex = self.tailIndex {
assert(self.entries.last!.mergedIndex <= tailIndex)
}
}
}
}

View File

@ -2,7 +2,7 @@ import Foundation
enum PeerMergedOperationLogOperation {
case append(PeerMergedOperationLogEntry)
case remove(tag: PeerOperationLogTag, mergedIndices: Set<Int32>)
case remove(tag: PeerOperationLogTag, peerId: PeerId, mergedIndices: Set<Int32>)
case updateContents(PeerMergedOperationLogEntry)
}
@ -197,7 +197,7 @@ final class PeerOperationLogTable: Table {
if !mergedIndices.isEmpty {
self.mergedIndexTable.remove(tag: tag, mergedIndices: mergedIndices)
operations.append(.remove(tag: tag, mergedIndices: Set(mergedIndices)))
operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set(mergedIndices)))
}
return removed
}
@ -224,7 +224,7 @@ final class PeerOperationLogTable: Table {
if !mergedIndices.isEmpty {
self.mergedIndexTable.remove(tag: tag, mergedIndices: mergedIndices)
operations.append(.remove(tag: tag, mergedIndices: Set(mergedIndices)))
operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set(mergedIndices)))
}
}
@ -250,7 +250,7 @@ final class PeerOperationLogTable: Table {
if !mergedIndices.isEmpty {
self.mergedIndexTable.remove(tag: tag, mergedIndices: mergedIndices)
operations.append(.remove(tag: tag, mergedIndices: Set(mergedIndices)))
operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set(mergedIndices)))
}
}
@ -271,6 +271,23 @@ final class PeerOperationLogTable: Table {
return entries
}
func getMergedEntries(tag: PeerOperationLogTag, peerId: PeerId, fromIndex: Int32, limit: Int) -> [PeerMergedOperationLogEntry] {
var entries: [PeerMergedOperationLogEntry] = []
for (peerId, tagLocalIndex, mergedIndex) in self.mergedIndexTable.getTagLocalIndices(tag: tag, peerId: peerId, fromMergedIndex: fromIndex, limit: limit) {
if let value = self.valueBox.get(self.table, key: self.key(peerId: peerId, tag: tag, index: tagLocalIndex)) {
if let entry = parseMergedEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, value) {
entries.append(entry)
} else {
assertionFailure()
}
} else {
self.mergedIndexTable.remove(tag: tag, mergedIndices: [mergedIndex])
assertionFailure()
}
}
return entries
}
func enumerateEntries(peerId: PeerId, tag: PeerOperationLogTag, _ f: (PeerOperationLogEntry) -> Bool) {
self.valueBox.range(self.table, start: self.key(peerId: peerId, tag: tag, index: 0).predecessor, end: self.key(peerId: peerId, tag: tag, index: Int32.max).successor, values: { key, value in
if let entry = parseEntry(peerId: peerId, tag: tag, tagLocalIndex: key.getInt32(9), value) {
@ -317,12 +334,12 @@ final class PeerOperationLogTable: Table {
if let mergedIndexValue = mergedIndex {
mergedIndex = nil
self.mergedIndexTable.remove(tag: tag, mergedIndices: [mergedIndexValue])
operations.append(.remove(tag: tag, mergedIndices: Set([mergedIndexValue])))
operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set([mergedIndexValue])))
}
case .newAutomatic:
if let mergedIndexValue = mergedIndex {
self.mergedIndexTable.remove(tag: tag, mergedIndices: [mergedIndexValue])
operations.append(.remove(tag: tag, mergedIndices: Set([mergedIndexValue])))
operations.append(.remove(tag: tag, peerId: peerId, mergedIndices: Set([mergedIndexValue])))
}
let updatedMergedIndexValue = self.mergedIndexTable.add(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex)
mergedIndex = updatedMergedIndexValue

View File

@ -3654,9 +3654,9 @@ final class PostboxImpl {
}
}
public func mergedOperationLogView(tag: PeerOperationLogTag, limit: Int) -> Signal<PeerMergedOperationLogView, NoError> {
public func mergedOperationLogView(tag: PeerOperationLogTag, filterByPeerId: PeerId?, limit: Int) -> Signal<PeerMergedOperationLogView, NoError> {
return self.transactionSignal { subscriber, transaction in
let view = MutablePeerMergedOperationLogView(postbox: self, tag: tag, limit: limit)
let view = MutablePeerMergedOperationLogView(postbox: self, tag: tag, filterByPeerId: filterByPeerId, limit: limit)
subscriber.putNext(PeerMergedOperationLogView(view))
@ -4638,12 +4638,12 @@ public class Postbox {
}
}
public func mergedOperationLogView(tag: PeerOperationLogTag, limit: Int) -> Signal<PeerMergedOperationLogView, NoError> {
public func mergedOperationLogView(tag: PeerOperationLogTag, filterByPeerId: PeerId? = nil, limit: Int) -> Signal<PeerMergedOperationLogView, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.mergedOperationLogView(tag: tag, limit: limit).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
disposable.set(impl.mergedOperationLogView(tag: tag, filterByPeerId: filterByPeerId, limit: limit).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
}
return disposable

View File

@ -971,9 +971,6 @@ public final class ShareController: ViewController {
if self.environment.isMainApp {
useLegacy = true
}
if peerIds.contains(where: { $0.namespace == Namespaces.Peer.SecretChat }) {
useLegacy = true
}
if let currentContext = self.currentContext as? ShareControllerAppAccountContext, let data = currentContext.context.currentAppConfiguration.with({ $0 }).data {
if let _ = data["ios_disable_modern_sharing"] {
useLegacy = true

View File

@ -1153,7 +1153,7 @@ public class Account {
pendingMessageManager?.updatePendingMessageIds(view.ids)
}))
self.managedOperationsDisposable.add(managedSecretChatOutgoingOperations(auxiliaryMethods: auxiliaryMethods, postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedSecretChatOutgoingOperations(auxiliaryMethods: auxiliaryMethods, postbox: self.postbox, network: self.network, accountPeerId: peerId, mode: .all).start())
self.managedOperationsDisposable.add(managedCloudChatRemoveMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(network: self.network, postbox: self.postbox, isRemove: true).start())
self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(network: self.network, postbox: self.postbox, isRemove: false).start())

View File

@ -963,5 +963,15 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
}
}
}
|> take(until: { result in
var complete = false
switch result {
case .content:
complete = true
case .progress:
complete = false
}
return SignalTakeAction(passthrough: true, complete: complete)
})
}
}

View File

@ -125,7 +125,12 @@ public func standaloneSendEnqueueMessages(
threadId: Int64?,
messages: [StandaloneSendEnqueueMessage]
) -> Signal<StandaloneSendMessageStatus, StandaloneSendMessagesError> {
let signals: [Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>] = messages.map { message in
struct MessageResult {
var result: PendingMessageUploadedContentResult
var media: [Media]
}
let signals: [Signal<MessageResult, PendingMessageUploadError>] = messages.map { message in
var attributes: [MessageAttribute] = []
var text: String = ""
var media: [Media] = []
@ -184,6 +189,9 @@ public func standaloneSendEnqueueMessages(
contentResult = .single(value)
}
return contentResult
|> map { contentResult in
return MessageResult(result: contentResult, media: media)
}
}
return combineLatest(signals)
@ -192,21 +200,21 @@ public func standaloneSendEnqueueMessages(
}
|> mapToSignal { contentResults -> Signal<StandaloneSendMessageStatus, StandaloneSendMessagesError> in
var progressSum: Float = 0.0
var allResults: [PendingMessageUploadedContentAndReuploadInfo] = []
var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media])] = []
var allDone = true
for status in contentResults {
switch status {
for result in contentResults {
switch result.result {
case let .progress(value):
allDone = false
progressSum += value
case let .content(content):
allResults.append(content)
allResults.append((content, result.media))
}
}
if allDone {
var sendSignals: [Signal<Never, StandaloneSendMessagesError>] = []
for content in allResults {
for (content, media) in allResults {
var text: String = ""
switch content.content {
case let .text(textValue):
@ -218,6 +226,7 @@ public func standaloneSendEnqueueMessages(
}
sendSignals.append(sendUploadedMessageContent(
auxiliaryMethods: auxiliaryMethods,
postbox: postbox,
network: network,
stateManager: stateManager,
@ -226,6 +235,7 @@ public func standaloneSendEnqueueMessages(
content: content,
text: text,
attributes: [],
media: media,
threadId: threadId
))
}
@ -241,12 +251,70 @@ public func standaloneSendEnqueueMessages(
}
}
private func sendUploadedMessageContent(postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: PeerId, peerId: PeerId, content: PendingMessageUploadedContentAndReuploadInfo, text: String, attributes: [MessageAttribute], threadId: Int64?) -> Signal<Never, StandaloneSendMessagesError> {
private func sendUploadedMessageContent(
auxiliaryMethods: AccountAuxiliaryMethods,
postbox: Postbox,
network: Network,
stateManager: AccountStateManager,
accountPeerId: PeerId,
peerId: PeerId,
content: PendingMessageUploadedContentAndReuploadInfo,
text: String,
attributes: [MessageAttribute],
media: [Media],
threadId: Int64?
) -> Signal<Never, StandaloneSendMessagesError> {
return postbox.transaction { transaction -> Signal<Never, StandaloneSendMessagesError> in
if peerId.namespace == Namespaces.Peer.SecretChat {
assertionFailure()
//PendingMessageManager.sendSecretMessageContent(transaction: transaction, message: message, content: content)
return .complete()
var secretFile: SecretChatOutgoingFile?
switch content.content {
case let .secretMedia(file, size, key):
if let fileReference = SecretChatOutgoingFileReference(file) {
secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key)
}
default:
break
}
var layer: SecretChatLayer?
let state = transaction.getPeerChatState(peerId) as? SecretChatState
if let state = state {
switch state.embeddedState {
case .terminated, .handshake:
break
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
}
}
if let state = state, let layer = layer {
let messageContents = StandaloneSecretMessageContents(
id: Int64.random(in: Int64.min ... Int64.max),
text: text,
attributes: attributes,
media: media.first,
file: secretFile
)
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state)
if updatedState != state {
transaction.setPeerChatState(peerId, state: updatedState)
}
return managedSecretChatOutgoingOperations(
auxiliaryMethods: auxiliaryMethods,
postbox: postbox,
network: network,
accountPeerId: accountPeerId,
mode: .standaloneComplete(peerId: peerId)
)
|> castError(StandaloneSendMessagesError.self)
|> ignoreValues
} else {
return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none))
}
} else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var uniqueId: Int64 = 0
var forwardSourceInfoAttribute: ForwardSourceInfoAttribute?

View File

@ -105,11 +105,20 @@ private func takenImmutableOperation(postbox: Postbox, peerId: PeerId, tagLocalI
}
}
func managedSecretChatOutgoingOperations(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
return Signal { _ in
enum ManagedSecretChatOutgoingOperationsMode {
case all
case standaloneComplete(peerId: PeerId)
}
func managedSecretChatOutgoingOperations(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network, accountPeerId: PeerId, mode: ManagedSecretChatOutgoingOperationsMode) -> Signal<Void, NoError> {
return Signal { subscriber in
let helper = Atomic<ManagedSecretChatOutgoingOperationsHelper>(value: ManagedSecretChatOutgoingOperationsHelper())
let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SecretOutgoing, limit: 10).start(next: { view in
var filterByPeerId: PeerId?
if case let .standaloneComplete(peerId) = mode {
filterByPeerId = peerId
}
let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SecretOutgoing, filterByPeerId: filterByPeerId, limit: 10).start(next: { view in
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in
return helper.update(view.entries)
}
@ -118,6 +127,10 @@ func managedSecretChatOutgoingOperations(auxiliaryMethods: AccountAuxiliaryMetho
disposable.dispose()
}
if case .standaloneComplete = mode, view.entries.isEmpty {
subscriber.putCompletion()
}
for (entry, disposable) in beginOperations {
let signal = takenImmutableOperation(postbox: postbox, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex)
|> mapToSignal { entry -> Signal<Void, NoError> in
@ -128,6 +141,8 @@ func managedSecretChatOutgoingOperations(auxiliaryMethods: AccountAuxiliaryMetho
return initialHandshakeAccept(postbox: postbox, network: network, peerId: entry.peerId, accessHash: accessHash, gA: gA, b: b, tagLocalIndex: entry.tagLocalIndex)
case let .sendMessage(layer, id, file):
return sendMessage(auxiliaryMethods: auxiliaryMethods, postbox: postbox, network: network, messageId: id, file: file, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered, layer: layer)
case let .sendStandaloneMessage(layer, contents):
return sendStandaloneMessage(auxiliaryMethods: auxiliaryMethods, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: entry.peerId, contents: contents, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered, layer: layer)
case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .reportLayerSupport(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, layerSupport: layerSupport), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered)
case let .deleteMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
@ -1711,9 +1726,9 @@ private func resourceThumbnailData(auxiliaryMethods: AccountAuxiliaryMethods, me
}
}
private func messageWithThumbnailData(auxiliaryMethods: AccountAuxiliaryMethods, mediaBox: MediaBox, message: Message) -> Signal<[MediaId: (PixelDimensions, Data)], NoError> {
private func messageWithThumbnailData(auxiliaryMethods: AccountAuxiliaryMethods, mediaBox: MediaBox, media: [Media]) -> Signal<[MediaId: (PixelDimensions, Data)], NoError> {
var signals: [Signal<(MediaId, PixelDimensions, Data)?, NoError>] = []
for media in message.media {
for media in media {
if let image = media as? TelegramMediaImage {
if let smallestRepresentation = smallestImageRepresentation(image.representations) {
signals.append(resourceThumbnailData(auxiliaryMethods: auxiliaryMethods, mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: image.imageId))
@ -1739,7 +1754,7 @@ private func messageWithThumbnailData(auxiliaryMethods: AccountAuxiliaryMethods,
private func sendMessage(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network, messageId: MessageId, file: SecretChatOutgoingFile?, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<[MediaId: (PixelDimensions, Data)], NoError> in
if let message = transaction.getMessage(messageId) {
return messageWithThumbnailData(auxiliaryMethods: auxiliaryMethods, mediaBox: postbox.mediaBox, message: message)
return messageWithThumbnailData(auxiliaryMethods: auxiliaryMethods, mediaBox: postbox.mediaBox, media: message.media)
} else {
return .single([:])
}
@ -1840,6 +1855,166 @@ private func sendMessage(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Pos
}
}
private func sendStandaloneMessage(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, contents: StandaloneSecretMessageContents, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<[MediaId: (PixelDimensions, Data)], NoError> in
var media: [Media] = []
if let value = contents.media {
media.append(value)
}
return messageWithThumbnailData(auxiliaryMethods: auxiliaryMethods, mediaBox: postbox.mediaBox, media: media)
}
|> switchToLatest
|> mapToSignal { thumbnailData -> Signal<Void, NoError> in
return postbox.transaction { transaction -> Signal<Void, NoError> in
guard let state = transaction.getPeerChatState(peerId) as? SecretChatState, let peer = transaction.getPeer(peerId) as? TelegramSecretChat else {
return .complete()
}
let globallyUniqueId = contents.id
var media: [Media] = []
if let value = contents.media {
media.append(value)
}
let message = Message(
stableId: 1,
stableVersion: 0,
id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 1),
globallyUniqueId: globallyUniqueId,
groupingKey: nil,
groupInfo: nil,
threadId: nil,
timestamp: 1,
flags: [],
tags: [],
globalTags: [],
localTags: [],
forwardInfo: nil,
author: nil,
text: contents.text,
attributes: contents.attributes,
media: media,
peers: SimpleDictionary(),
associatedMessages: SimpleDictionary(),
associatedMessageIds: [],
associatedMedia: [:],
associatedThreadInfo: nil,
associatedStories: [:]
)
let decryptedMessage = boxedDecryptedMessage(transaction: transaction, message: message, globallyUniqueId: globallyUniqueId, uploadedFile: contents.file, thumbnailData: [:], layer: layer)
return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: contents.file, silent: message.muted, asService: wasDelivered, wasDelivered: wasDelivered)
|> mapToSignal { result -> Signal<Void, NoError> in
return postbox.transaction { transaction -> Void in
let forceRemove: Bool
switch result {
case .message:
forceRemove = false
case .error:
forceRemove = true
}
markOutgoingOperationAsCompleted(transaction: transaction, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: forceRemove)
var timestamp: Int32?
var encryptedFile: SecretChatFileReference?
if case let .message(result) = result {
switch result {
case let .sentEncryptedMessage(date):
timestamp = date
case let .sentEncryptedFile(date, file):
timestamp = date
encryptedFile = SecretChatFileReference(file)
}
}
if let timestamp = timestamp {
var updatedMedia: [Media] = []
for item in media {
if let file = item as? TelegramMediaFile, let encryptedFile = encryptedFile, let sourceFile = contents.file {
let updatedFile = TelegramMediaFile(
fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: encryptedFile.id),
partialReference: nil,
resource: SecretFileMediaResource(fileId: encryptedFile.id, accessHash: encryptedFile.accessHash, containerSize: encryptedFile.size, decryptedSize: sourceFile.size, datacenterId: Int(encryptedFile.datacenterId), key: sourceFile.key),
previewRepresentations: file.previewRepresentations,
videoThumbnails: file.videoThumbnails,
immediateThumbnailData: file.immediateThumbnailData,
mimeType: file.mimeType,
size: file.size,
attributes: file.attributes
)
updatedMedia.append(updatedFile)
} else if let image = item as? TelegramMediaImage, let encryptedFile = encryptedFile, let sourceFile = contents.file, let representation = image.representations.last {
let updatedImage = TelegramMediaImage(
imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: encryptedFile.id),
representations: [
TelegramMediaImageRepresentation(
dimensions: representation.dimensions,
resource: SecretFileMediaResource(fileId: encryptedFile.id, accessHash: encryptedFile.accessHash, containerSize: encryptedFile.size, decryptedSize: sourceFile.size, datacenterId: Int(encryptedFile.datacenterId), key: sourceFile.key),
progressiveSizes: [],
immediateThumbnailData: image.immediateThumbnailData,
hasVideo: false,
isPersonal: false
)],
immediateThumbnailData: nil,
reference: nil,
partialReference: nil,
flags: []
)
updatedMedia.append(updatedImage)
} else {
updatedMedia.append(item)
}
}
let entitiesAttribute = message.textEntitiesAttribute
let (tags, globalTags) = tagsForStoreMessage(incoming: false, attributes: contents.attributes, media: updatedMedia, textEntities: entitiesAttribute?.entities, isPinned: false)
let storedMessage = StoreMessage(
peerId: peerId,
namespace: Namespaces.Message.Local,
globallyUniqueId: globallyUniqueId,
groupingKey: nil,
threadId: nil,
timestamp: timestamp,
flags: [],
tags: tags,
globalTags: globalTags,
localTags: [],
forwardInfo: nil,
authorId: accountPeerId,
text: message.text,
attributes: message.attributes,
media: updatedMedia
)
let idMapping = transaction.addMessages([storedMessage], location: .Random)
if let id = idMapping[globallyUniqueId] {
maybeReadSecretOutgoingMessage(transaction: transaction, index: MessageIndex(id: id, timestamp: timestamp))
}
var sentStickers: [TelegramMediaFile] = []
for media in message.media {
if let file = media as? TelegramMediaFile {
if file.isSticker {
sentStickers.append(file)
}
}
}
for file in sentStickers {
addRecentlyUsedSticker(transaction: transaction, fileReference: .standalone(media: file))
}
if case .error(.chatCancelled) = result {
}
}
}
}
}
|> switchToLatest
}
}
private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> in
if let state = transaction.getPeerChatState(peerId) as? SecretChatState, let peer = transaction.getPeer(peerId) as? TelegramSecretChat {

View File

@ -7,47 +7,97 @@ private enum SecretChatOutgoingFileValue: Int32 {
case uploadedLarge = 2
}
public enum SecretChatOutgoingFileReference: PostboxCoding {
public enum SecretChatOutgoingFileReference: PostboxCoding, Codable {
case remote(id: Int64, accessHash: Int64)
case uploadedRegular(id: Int64, partCount: Int32, md5Digest: String, keyFingerprint: Int32)
case uploadedLarge(id: Int64, partCount: Int32, keyFingerprint: Int32)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("v", orElse: 0) {
case SecretChatOutgoingFileValue.remote.rawValue:
self = .remote(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("a", orElse: 0))
case SecretChatOutgoingFileValue.uploadedRegular.rawValue:
self = .uploadedRegular(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), md5Digest: decoder.decodeStringForKey("d", orElse: ""), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0))
case SecretChatOutgoingFileValue.uploadedLarge.rawValue:
self = .uploadedLarge(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0))
default:
assertionFailure()
self = .remote(id: 0, accessHash: 0)
case SecretChatOutgoingFileValue.remote.rawValue:
self = .remote(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("a", orElse: 0))
case SecretChatOutgoingFileValue.uploadedRegular.rawValue:
self = .uploadedRegular(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), md5Digest: decoder.decodeStringForKey("d", orElse: ""), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0))
case SecretChatOutgoingFileValue.uploadedLarge.rawValue:
self = .uploadedLarge(id: decoder.decodeInt64ForKey("i", orElse: 0), partCount: decoder.decodeInt32ForKey("p", orElse: 0), keyFingerprint: decoder.decodeInt32ForKey("f", orElse: 0))
default:
assertionFailure()
self = .remote(id: 0, accessHash: 0)
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
switch try container.decode(Int32.self, forKey: "v") {
case SecretChatOutgoingFileValue.remote.rawValue:
self = .remote(
id: try container.decode(Int64.self, forKey: "i"),
accessHash: try container.decode(Int64.self, forKey: "a")
)
case SecretChatOutgoingFileValue.uploadedRegular.rawValue:
self = .uploadedRegular(
id: try container.decode(Int64.self, forKey: "i"),
partCount: try container.decode(Int32.self, forKey: "p"),
md5Digest: try container.decode(String.self, forKey: "d"),
keyFingerprint: try container.decode(Int32.self, forKey: "f")
)
case SecretChatOutgoingFileValue.uploadedLarge.rawValue:
self = .uploadedLarge(
id: try container.decode(Int64.self, forKey: "i"),
partCount: try container.decode(Int32.self, forKey: "p"),
keyFingerprint: try container.decode(Int32.self, forKey: "f")
)
default:
assertionFailure()
self = .remote(id: 0, accessHash: 0)
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case let .remote(id, accessHash):
encoder.encodeInt32(SecretChatOutgoingFileValue.remote.rawValue, forKey: "v")
encoder.encodeInt64(id, forKey: "i")
encoder.encodeInt64(accessHash, forKey: "a")
case let .uploadedRegular(id, partCount, md5Digest, keyFingerprint):
encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedRegular.rawValue, forKey: "v")
encoder.encodeInt64(id, forKey: "i")
encoder.encodeInt32(partCount, forKey: "p")
encoder.encodeString(md5Digest, forKey: "d")
encoder.encodeInt32(keyFingerprint, forKey: "f")
case let .uploadedLarge(id, partCount, keyFingerprint):
encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedLarge.rawValue, forKey: "v")
encoder.encodeInt64(id, forKey: "i")
encoder.encodeInt32(partCount, forKey: "p")
encoder.encodeInt32(keyFingerprint, forKey: "f")
case let .remote(id, accessHash):
encoder.encodeInt32(SecretChatOutgoingFileValue.remote.rawValue, forKey: "v")
encoder.encodeInt64(id, forKey: "i")
encoder.encodeInt64(accessHash, forKey: "a")
case let .uploadedRegular(id, partCount, md5Digest, keyFingerprint):
encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedRegular.rawValue, forKey: "v")
encoder.encodeInt64(id, forKey: "i")
encoder.encodeInt32(partCount, forKey: "p")
encoder.encodeString(md5Digest, forKey: "d")
encoder.encodeInt32(keyFingerprint, forKey: "f")
case let .uploadedLarge(id, partCount, keyFingerprint):
encoder.encodeInt32(SecretChatOutgoingFileValue.uploadedLarge.rawValue, forKey: "v")
encoder.encodeInt64(id, forKey: "i")
encoder.encodeInt32(partCount, forKey: "p")
encoder.encodeInt32(keyFingerprint, forKey: "f")
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
switch self {
case let .remote(id, accessHash):
try container.encode(SecretChatOutgoingFileValue.remote.rawValue, forKey: "v")
try container.encode(id, forKey: "i")
try container.encode(accessHash, forKey: "a")
case let .uploadedRegular(id, partCount, md5Digest, keyFingerprint):
try container.encode(SecretChatOutgoingFileValue.uploadedRegular.rawValue, forKey: "v")
try container.encode(id, forKey: "i")
try container.encode(partCount, forKey: "p")
try container.encode(md5Digest, forKey: "d")
try container.encode(keyFingerprint, forKey: "f")
case let .uploadedLarge(id, partCount, keyFingerprint):
try container.encode(SecretChatOutgoingFileValue.uploadedLarge.rawValue, forKey: "v")
try container.encode(id, forKey: "i")
try container.encode(partCount, forKey: "p")
try container.encode(keyFingerprint, forKey: "f")
}
}
}
public struct SecretChatOutgoingFile: PostboxCoding {
public struct SecretChatOutgoingFile: PostboxCoding, Codable {
public let reference: SecretChatOutgoingFileReference
public let size: Int64
public let key: SecretFileEncryptionKey
@ -68,12 +118,32 @@ public struct SecretChatOutgoingFile: PostboxCoding {
self.key = SecretFileEncryptionKey(aesKey: decoder.decodeBytesForKey("k")!.makeData(), aesIv: decoder.decodeBytesForKey("i")!.makeData())
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.reference = try container.decode(SecretChatOutgoingFileReference.self, forKey: "r")
self.size = try container.decode(Int64.self, forKey: "s64")
self.key = SecretFileEncryptionKey(
aesKey: try container.decode(Data.self, forKey: "k"),
aesIv: try container.decode(Data.self, forKey: "i")
)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.reference, forKey: "r")
encoder.encodeInt64(self.size, forKey: "s64")
encoder.encodeBytes(MemoryBuffer(data: self.key.aesKey), forKey: "k")
encoder.encodeBytes(MemoryBuffer(data: self.key.aesIv), forKey: "i")
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.reference, forKey: "r")
try container.encode(self.size, forKey: "s64")
try container.encode(self.key.aesKey, forKey: "k")
try container.encode(self.key.aesIv, forKey: "i")
}
}
public enum SecretChatSequenceBasedLayer: Int32 {
@ -112,11 +182,72 @@ private enum SecretChatOutgoingOperationValue: Int32 {
case noop = 12
case setMessageAutoremoveTimeout = 13
case terminate = 14
case sendStandaloneMessage = 15
}
public struct StandaloneSecretMessageContents: Codable {
private enum CodingKeys: String, CodingKey {
case id = "i"
case text = "t"
case attributes = "a"
case media = "m"
case file = "f"
}
public var id: Int64
public var text: String
public var attributes: [MessageAttribute]
public var media: Media?
public var file: SecretChatOutgoingFile?
public init(id: Int64, text: String, attributes: [MessageAttribute], media: Media?, file: SecretChatOutgoingFile?) {
self.id = id
self.text = text
self.attributes = attributes
self.media = media
self.file = file
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
self.text = try container.decode(String.self, forKey: .text)
let attributes = try container.decode([Data].self, forKey: .attributes)
self.attributes = attributes.compactMap { attribute -> MessageAttribute? in
return PostboxDecoder(buffer: MemoryBuffer(data: attribute)).decodeRootObject() as? MessageAttribute
}
self.media = (try container.decodeIfPresent(Data.self, forKey: .media)).flatMap { media in
return PostboxDecoder(buffer: MemoryBuffer(data: media)).decodeRootObject() as? Media
}
self.file = try container.decodeIfPresent(SecretChatOutgoingFile.self, forKey: .file)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.text, forKey: .text)
let attributes = self.attributes.map { attribute -> Data in
let innerEncoder = PostboxEncoder()
innerEncoder.encodeRootObject(attribute)
return innerEncoder.makeData()
}
try container.encode(attributes, forKey: .attributes)
try container.encodeIfPresent(self.media.flatMap { media in
let innerEncoder = PostboxEncoder()
innerEncoder.encodeRootObject(media)
return innerEncoder.makeData()
}, forKey: .media)
try container.encodeIfPresent(self.file, forKey: .file)
}
}
public enum SecretChatOutgoingOperationContents: PostboxCoding {
case initialHandshakeAccept(gA: MemoryBuffer, accessHash: Int64, b: MemoryBuffer)
case sendMessage(layer: SecretChatLayer, id: MessageId, file: SecretChatOutgoingFile?)
case sendStandaloneMessage(layer: SecretChatLayer, contents: StandaloneSecretMessageContents)
case readMessagesContent(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64])
case deleteMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64])
case screenshotMessages(layer: SecretChatLayer, actionGloballyUniqueId: Int64, globallyUniqueIds: [Int64], messageId: MessageId)
@ -137,6 +268,11 @@ public enum SecretChatOutgoingOperationContents: PostboxCoding {
self = .initialHandshakeAccept(gA: decoder.decodeBytesForKey("g")!, accessHash: decoder.decodeInt64ForKey("h", orElse: 0), b: decoder.decodeBytesForKey("b")!)
case SecretChatOutgoingOperationValue.sendMessage.rawValue:
self = .sendMessage(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("i.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt32ForKey("i.i", orElse: 0)), file: decoder.decodeObjectForKey("f", decoder: { SecretChatOutgoingFile(decoder: $0) }) as? SecretChatOutgoingFile)
case SecretChatOutgoingOperationValue.sendStandaloneMessage.rawValue:
self = .sendStandaloneMessage(
layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!,
contents: decoder.decodeCodable(StandaloneSecretMessageContents.self, forKey: "c") ?? StandaloneSecretMessageContents(id: 0, text: "", attributes: [], media: nil, file: nil)
)
case SecretChatOutgoingOperationValue.readMessagesContent.rawValue:
self = .readMessagesContent(layer: SecretChatLayer(rawValue: decoder.decodeInt32ForKey("l", orElse: 0))!, actionGloballyUniqueId: decoder.decodeInt64ForKey("i", orElse: 0), globallyUniqueIds: decoder.decodeInt64ArrayForKey("u"))
case SecretChatOutgoingOperationValue.deleteMessages.rawValue:
@ -187,6 +323,10 @@ public enum SecretChatOutgoingOperationContents: PostboxCoding {
} else {
encoder.encodeNil(forKey: "f")
}
case let .sendStandaloneMessage(layer, contents):
encoder.encodeInt32(SecretChatOutgoingOperationValue.sendStandaloneMessage.rawValue, forKey: "r")
encoder.encodeInt32(layer.rawValue, forKey: "l")
encoder.encodeCodable(contents, forKey: "c")
case let .readMessagesContent(layer, actionGloballyUniqueId, globallyUniqueIds):
encoder.encodeInt32(SecretChatOutgoingOperationValue.readMessagesContent.rawValue, forKey: "r")
encoder.encodeInt32(layer.rawValue, forKey: "l")

View File

@ -520,7 +520,7 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco
}
if let _ = dataDetector {
let detectedUrls = detectUrls(inputText)
if detectedUrls != currentQuery?.detectedUrls {
if detectedUrls != (currentQuery?.detectedUrls ?? []) {
if !detectedUrls.isEmpty {
return (UrlPreviewState(detectedUrls: detectedUrls), webpagePreview(account: context.account, urls: detectedUrls)
|> mapToSignal { result -> Signal<(TelegramMediaWebpage, String)?, NoError> in

View File

@ -3741,17 +3741,19 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
} else {
var children: [UIAction] = []
children.append(UIAction(title: self.strings?.TextFormat_Quote ?? "Quote", image: nil) { [weak self] (action) in
if let strongSelf = self {
strongSelf.formatAttributesQuote(strongSelf)
}
})
var hasSpoilers = true
if self.presentationInterfaceState?.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat {
hasSpoilers = false
}
if hasSpoilers {
children.append(UIAction(title: self.strings?.TextFormat_Quote ?? "Quote", image: nil) { [weak self] (action) in
if let strongSelf = self {
strongSelf.formatAttributesQuote(strongSelf)
}
})
}
if hasSpoilers {
children.append(UIAction(title: self.strings?.TextFormat_Spoiler ?? "Spoiler", image: nil) { [weak self] (action) in
if let strongSelf = self {