Fix public chat attachment syncing

This commit is contained in:
Niels Andriesse 2019-11-15 13:56:35 +11:00
parent fc44b1c191
commit 1d33b62790
8 changed files with 123 additions and 136 deletions

View File

@ -62,7 +62,7 @@ public final class LokiRSSFeedPoller : NSObject {
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
envelope.setContent(try! content.build().serializedData())
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0)
}
}
}

View File

@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
+ (void)processIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript
serverID:(uint64_t)serverID
attachmentHandler:(void (^)(
NSArray<TSAttachmentStream *> *attachmentStreams))attachmentHandler
transaction:(YapDatabaseReadWriteTransaction *)transaction;

View File

@ -17,6 +17,7 @@
#import "TSQuotedMessage.h"
#import "TSThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import "OWSPrimaryStorage+Loki.h"
NS_ASSUME_NONNULL_BEGIN
@ -60,6 +61,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
+ (void)processIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)transcript
serverID:(uint64_t)serverID
attachmentHandler:(void (^)(
NSArray<TSAttachmentStream *> *attachmentStreams))attachmentHandler
transaction:(YapDatabaseReadWriteTransaction *)transaction
@ -102,6 +104,10 @@ NS_ASSUME_NONNULL_BEGIN
quotedMessage:transcript.quotedMessage
contactShare:transcript.contact
linkPreview:transcript.linkPreview];
if (serverID != 0) {
outgoingMessage.groupChatServerID = serverID;
}
NSArray<TSAttachmentPointer *> *attachmentPointers =
[TSAttachmentPointer attachmentPointersFromProtos:transcript.attachmentPointerProtos
@ -129,6 +135,9 @@ NS_ASSUME_NONNULL_BEGIN
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[outgoingMessage setQuotedMessageThumbnailAttachmentStream:attachmentStream];
[outgoingMessage saveWithTransaction:transaction];
if (serverID != 0) {
[OWSPrimaryStorage.sharedManager setIDForMessageWithServerID:serverID to:outgoingMessage.uniqueId in:transaction];
}
}];
}
failure:^(NSError *error) {

View File

@ -130,6 +130,14 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
print("[Loki] Ignoring public chat message with invalid signature.")
return nil
}
var existingMessageID: String? = nil
storage.dbReadConnection.read { transaction in
existingMessageID = storage.getIDForMessage(withServerID: UInt(result.serverID!), in: transaction)
}
guard existingMessageID == nil else {
print("[Loki] Ignorning duplicate message.")
return nil
}
return result
}.sorted { $0.timestamp < $1.timestamp }
}

View File

@ -48,135 +48,8 @@ public final class LokiPublicChatPoller : NSObject {
// MARK: Polling
private func pollForNewMessages() {
// Prepare
let publicChat = self.publicChat
let userHexEncodedPublicKey = self.userHexEncodedPublicKey
// Processing logic for incoming messages
func processIncomingMessage(_ message: LokiPublicChatMessage) {
let storage = OWSPrimaryStorage.shared()
var masterHexEncodedPublicKey: String? = nil
storage.dbReadConnection.read { transaction in
masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: message.hexEncodedPublicKey, in: transaction)
}
let senderHexEncodedPublicKey = masterHexEncodedPublicKey ?? message.hexEncodedPublicKey
func generateDisplayName(from rawDisplayName: String) -> String {
let endIndex = senderHexEncodedPublicKey.endIndex
let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
return "\(rawDisplayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
}
var senderDisplayName = ""
if let masterHexEncodedPublicKey = masterHexEncodedPublicKey {
senderDisplayName = DisplayNameUtilities.getPublicChatDisplayName(for: senderHexEncodedPublicKey, in: publicChat.channel, on: publicChat.server) ?? generateDisplayName(from: NSLocalizedString("Anonymous", comment: ""))
} else {
senderDisplayName = generateDisplayName(from: message.displayName)
}
let id = publicChat.idAsData
let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
groupContext.setName(publicChat.displayName)
let dataMessage = SSKProtoDataMessage.builder()
let attachments: [SSKProtoAttachmentPointer] = message.attachments.compactMap { attachment in
guard attachment.kind == .attachment else { return nil }
let result = SSKProtoAttachmentPointer.builder(id: attachment.serverID)
result.setContentType(attachment.contentType)
result.setSize(UInt32(attachment.size))
result.setFileName(attachment.fileName)
result.setFlags(UInt32(attachment.flags))
result.setWidth(UInt32(attachment.width))
result.setHeight(UInt32(attachment.height))
if let caption = attachment.caption {
result.setCaption(caption)
}
result.setUrl(attachment.url)
return try! result.build()
}
dataMessage.setAttachments(attachments)
if let linkPreview = message.attachments.first(where: { $0.kind == .linkPreview }) {
let signalLinkPreview = SSKProtoDataMessagePreview.builder(url: linkPreview.linkPreviewURL!)
signalLinkPreview.setTitle(linkPreview.linkPreviewTitle!)
let attachment = SSKProtoAttachmentPointer.builder(id: linkPreview.serverID)
attachment.setContentType(linkPreview.contentType)
attachment.setSize(UInt32(linkPreview.size))
attachment.setFileName(linkPreview.fileName)
attachment.setFlags(UInt32(linkPreview.flags))
attachment.setWidth(UInt32(linkPreview.width))
attachment.setHeight(UInt32(linkPreview.height))
if let caption = linkPreview.caption {
attachment.setCaption(caption)
}
attachment.setUrl(linkPreview.url)
signalLinkPreview.setImage(try! attachment.build())
dataMessage.setPreview([ try! signalLinkPreview.build() ])
}
dataMessage.setTimestamp(message.timestamp)
dataMessage.setGroup(try! groupContext.build())
if let quote = message.quote {
let signalQuote = SSKProtoDataMessageQuote.builder(id: quote.quotedMessageTimestamp, author: quote.quoteeHexEncodedPublicKey)
signalQuote.setText(quote.quotedMessageBody)
dataMessage.setQuote(try! signalQuote.build())
}
let body = (message.body == message.timestamp.description) ? "" : message.body // Workaround for the fact that the back-end doesn't accept messages without a body
dataMessage.setBody(body)
if let messageServerID = message.serverID {
let publicChatInfo = SSKProtoPublicChatInfo.builder()
publicChatInfo.setServerID(messageServerID)
dataMessage.setPublicChatInfo(try! publicChatInfo.build())
}
let content = SSKProtoContent.builder()
content.setDataMessage(try! dataMessage.build())
let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp)
envelope.setSource(senderHexEncodedPublicKey)
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
envelope.setContent(try! content.build().serializedData())
storage.dbReadWriteConnection.readWrite { transaction in
transaction.setObject(senderDisplayName, forKey: senderHexEncodedPublicKey, inCollection: publicChat.id)
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
}
}
// Processing logic for outgoing messages
func processOutgoingMessage(_ message: LokiPublicChatMessage) {
guard let messageServerID = message.serverID else { return }
let storage = OWSPrimaryStorage.shared()
var isDuplicate = false
storage.dbReadConnection.read { transaction in
let id = storage.getIDForMessage(withServerID: UInt(messageServerID), in: transaction)
isDuplicate = id != nil
}
guard !isDuplicate else { return }
let groupID = publicChat.idAsData
let thread = TSGroupThread.getOrCreateThread(withGroupId: groupID)
let signalQuote: TSQuotedMessage?
if let quote = message.quote {
signalQuote = TSQuotedMessage(timestamp: quote.quotedMessageTimestamp, authorId: quote.quoteeHexEncodedPublicKey, body: quote.quotedMessageBody, quotedAttachmentsForSending: [])
} else {
signalQuote = nil
}
var attachmentIDs: [String] = []
// TODO: Restore attachments
let signalLinkPreview: OWSLinkPreview?
if let linkPreview = message.attachments.first(where: { $0.kind == .linkPreview }) {
let attachment = TSAttachmentPointer(serverId: linkPreview.serverID, encryptionKey: nil, byteCount: UInt32(linkPreview.size), contentType: linkPreview.contentType, sourceFilename: linkPreview.fileName, caption: linkPreview.caption, albumMessageId: nil)
attachment.save()
signalLinkPreview = OWSLinkPreview(urlString: linkPreview.linkPreviewURL!, title: linkPreview.linkPreviewTitle!, imageAttachmentId: attachment.uniqueId!, isDirectAttachment: false)
} else {
signalLinkPreview = nil
}
let body = (message.body == message.timestamp.description) ? "" : message.body // Workaround for the fact that the back-end doesn't accept messages without a body
let signalMessage = TSOutgoingMessage(outgoingMessageWithTimestamp: message.timestamp, in: thread, messageBody: body, attachmentIds: NSMutableArray(array: attachmentIDs), expiresInSeconds: 0,
expireStartedAt: 0, isVoiceMessage: false, groupMetaMessage: .deliver, quotedMessage: signalQuote, contactShare: nil, linkPreview: signalLinkPreview)
signalMessage.actualSenderHexEncodedPublicKey = message.hexEncodedPublicKey
storage.dbReadWriteConnection.readWrite { transaction in
signalMessage.update(withSentRecipient: publicChat.server, wasSentByUD: false, transaction: transaction)
signalMessage.saveGroupChatServerID(messageServerID, in: transaction)
guard let messageID = signalMessage.uniqueId else { return print("[Loki] Failed to save public chat message.") }
storage.setIDForMessageWithServerID(UInt(messageServerID), to: messageID, in: transaction)
}
DispatchQueue.main.async {
if let linkPreviewURL = OWSLinkPreview.previewUrl(forMessageBodyText: message.body, selectedRange: nil) {
signalMessage.generateLinkPreviewIfNeeded(fromURL: linkPreviewURL)
}
}
}
// Poll
let _ = LokiPublicChatAPI.getMessages(for: publicChat.channel, on: publicChat.server).done(on: DispatchQueue.global()) { messages in
let uniqueHexEncodedPublicKeys = Set(messages.map { $0.hexEncodedPublicKey })
func proceed() {
@ -194,10 +67,97 @@ public final class LokiPublicChatPoller : NSObject {
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
wasSentByCurrentUser = LokiDatabaseUtilities.isUserLinkedDevice(message.hexEncodedPublicKey, transaction: transaction)
}
if !wasSentByCurrentUser {
processIncomingMessage(message)
var masterHexEncodedPublicKey: String? = nil
storage.dbReadConnection.read { transaction in
masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: message.hexEncodedPublicKey, in: transaction)
}
let senderHexEncodedPublicKey = masterHexEncodedPublicKey ?? message.hexEncodedPublicKey
func generateDisplayName(from rawDisplayName: String) -> String {
let endIndex = senderHexEncodedPublicKey.endIndex
let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
return "\(rawDisplayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
}
var senderDisplayName = ""
if let masterHexEncodedPublicKey = masterHexEncodedPublicKey {
senderDisplayName = DisplayNameUtilities.getPublicChatDisplayName(for: senderHexEncodedPublicKey, in: publicChat.channel, on: publicChat.server) ?? generateDisplayName(from: NSLocalizedString("Anonymous", comment: ""))
} else {
processOutgoingMessage(message)
senderDisplayName = generateDisplayName(from: message.displayName)
}
let id = publicChat.idAsData
let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
groupContext.setName(publicChat.displayName)
let dataMessage = SSKProtoDataMessage.builder()
let attachments: [SSKProtoAttachmentPointer] = message.attachments.compactMap { attachment in
guard attachment.kind == .attachment else { return nil }
let result = SSKProtoAttachmentPointer.builder(id: attachment.serverID)
result.setContentType(attachment.contentType)
result.setSize(UInt32(attachment.size))
result.setFileName(attachment.fileName)
result.setFlags(UInt32(attachment.flags))
result.setWidth(UInt32(attachment.width))
result.setHeight(UInt32(attachment.height))
if let caption = attachment.caption {
result.setCaption(caption)
}
result.setUrl(attachment.url)
return try! result.build()
}
dataMessage.setAttachments(attachments)
if let linkPreview = message.attachments.first(where: { $0.kind == .linkPreview }) {
let signalLinkPreview = SSKProtoDataMessagePreview.builder(url: linkPreview.linkPreviewURL!)
signalLinkPreview.setTitle(linkPreview.linkPreviewTitle!)
let attachment = SSKProtoAttachmentPointer.builder(id: linkPreview.serverID)
attachment.setContentType(linkPreview.contentType)
attachment.setSize(UInt32(linkPreview.size))
attachment.setFileName(linkPreview.fileName)
attachment.setFlags(UInt32(linkPreview.flags))
attachment.setWidth(UInt32(linkPreview.width))
attachment.setHeight(UInt32(linkPreview.height))
if let caption = linkPreview.caption {
attachment.setCaption(caption)
}
attachment.setUrl(linkPreview.url)
signalLinkPreview.setImage(try! attachment.build())
dataMessage.setPreview([ try! signalLinkPreview.build() ])
}
dataMessage.setTimestamp(message.timestamp)
dataMessage.setGroup(try! groupContext.build())
if let quote = message.quote {
let signalQuote = SSKProtoDataMessageQuote.builder(id: quote.quotedMessageTimestamp, author: quote.quoteeHexEncodedPublicKey)
signalQuote.setText(quote.quotedMessageBody)
dataMessage.setQuote(try! signalQuote.build())
}
let body = (message.body == message.timestamp.description) ? "" : message.body // Workaround for the fact that the back-end doesn't accept messages without a body
dataMessage.setBody(body)
if let messageServerID = message.serverID {
let publicChatInfo = SSKProtoPublicChatInfo.builder()
publicChatInfo.setServerID(messageServerID)
dataMessage.setPublicChatInfo(try! publicChatInfo.build())
}
let content = SSKProtoContent.builder()
if !wasSentByCurrentUser {
content.setDataMessage(try! dataMessage.build())
} else {
let syncMessageSentBuilder = SSKProtoSyncMessageSent.builder()
syncMessageSentBuilder.setMessage(try! dataMessage.build())
syncMessageSentBuilder.setDestination(userHexEncodedPublicKey)
syncMessageSentBuilder.setTimestamp(message.timestamp)
let syncMessageSent = try! syncMessageSentBuilder.build()
let syncMessageBuilder = SSKProtoSyncMessage.builder()
syncMessageBuilder.setSent(syncMessageSent)
content.setSyncMessage(try! syncMessageBuilder.build())
}
let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp)
envelope.setSource(senderHexEncodedPublicKey)
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
envelope.setContent(try! content.build().serializedData())
storage.dbReadWriteConnection.readWrite { transaction in
transaction.setObject(senderDisplayName, forKey: senderHexEncodedPublicKey, inCollection: publicChat.id)
let messageServerID = message.serverID
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: messageServerID ?? 0)
}
}
}

View File

@ -445,7 +445,8 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo
[self.messageManager throws_processEnvelope:envelope
plaintextData:job.plaintextData
wasReceivedByUD:job.wasReceivedByUD
transaction:transaction];
transaction:transaction
serverID:0];
}
} @catch (NSException *exception) {
// OWSFailDebug(@"Received an invalid envelope: %@", exception.debugDescription);

View File

@ -22,7 +22,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)throws_processEnvelope:(SSKProtoEnvelope *)envelope
plaintextData:(NSData *_Nullable)plaintextData
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction;
transaction:(YapDatabaseReadWriteTransaction *)transaction
serverID:(uint64_t)serverID;
// This should be invoked by the main app when the app is ready.
- (void)startObserving;

View File

@ -242,6 +242,7 @@ NS_ASSUME_NONNULL_BEGIN
plaintextData:(NSData *_Nullable)plaintextData
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction
serverID:(uint64_t)serverID
{
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
@ -287,7 +288,8 @@ NS_ASSUME_NONNULL_BEGIN
[self throws_handleEnvelope:envelope
plaintextData:plaintextData
wasReceivedByUD:wasReceivedByUD
transaction:transaction];
transaction:transaction
serverID:serverID];
break;
case SSKProtoEnvelopeTypeReceipt:
OWSAssertDebug(!plaintextData);
@ -380,6 +382,7 @@ NS_ASSUME_NONNULL_BEGIN
plaintextData:(NSData *)plaintextData
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction
serverID:(uint64_t)serverID
{
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
@ -465,7 +468,8 @@ NS_ASSUME_NONNULL_BEGIN
if (contentProto.syncMessage) {
[self throws_handleIncomingEnvelope:envelope
withSyncMessage:contentProto.syncMessage
transaction:transaction];
transaction:transaction
serverID:serverID];
[[OWSDeviceManager sharedManager] setHasReceivedSyncMessage];
} else if (contentProto.dataMessage) {
@ -888,6 +892,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)throws_handleIncomingEnvelope:(SSKProtoEnvelope *)envelope
withSyncMessage:(SSKProtoSyncMessage *)syncMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
serverID:(uint64_t)serverID
{
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
@ -934,6 +939,7 @@ NS_ASSUME_NONNULL_BEGIN
if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
serverID:0
attachmentHandler:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
@ -955,6 +961,7 @@ NS_ASSUME_NONNULL_BEGIN
} else {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
serverID:serverID
attachmentHandler:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSLogDebug(@"successfully fetched transcript attachments: %lu",
(unsigned long)attachmentStreams.count);
@ -1836,7 +1843,7 @@ NS_ASSUME_NONNULL_BEGIN
SignalRecipient *_Nullable recipient =
[SignalRecipient registeredRecipientForRecipientId:localNumber mustHaveDevices:NO transaction:transaction];
if (!recipient) {
OWSFailDebug(@"No local SignalRecipient.");
// OWSFailDebug(@"No local SignalRecipient.");
} else {
BOOL isRecipientDevice = [recipient.devices containsObject:@(envelope.sourceDevice)];
if (!isRecipientDevice) {