Parse attachment pointers

This commit is contained in:
nielsandriesse 2020-11-20 11:10:53 +11:00
parent 7d207ddfb7
commit 961878f74c
10 changed files with 150 additions and 10 deletions

View File

@ -39,8 +39,8 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC
Configuration.shared.storage.withAsync({ transaction in // Intentionally capture self
Threading.workQueue.async {
do {
let message = try MessageReceiver.parse(self.data, messageServerID: self.messageServerID, using: transaction)
try MessageReceiver.handle(message, using: transaction)
let (message, proto) = try MessageReceiver.parse(self.data, messageServerID: self.messageServerID, using: transaction)
try MessageReceiver.handle(message, associatedWithProto: proto, using: transaction)
self.handleSuccess()
} catch {
SNLog("Couldn't parse message due to error: \(error).")

View File

@ -0,0 +1,60 @@
import CoreGraphics
import SessionUtilitiesKit
public extension VisibleMessage {
@objc(SNAttachment)
class Attachment : NSObject, NSCoding {
public var fileName: String?
public var contentType: String?
public var key: Data?
public var digest: Data?
public var kind: Kind?
public var caption: String?
public var size: CGSize?
public var sizeInBytes: UInt?
public var url: String?
public var isValid: Bool {
fileName != nil && contentType != nil && key != nil && digest != nil && kind != nil && size != nil && sizeInBytes != nil && url != nil
}
public enum Kind : String {
case voiceMessage, generic
}
public override init() { super.init() }
public required init?(coder: NSCoder) {
if let fileName = coder.decodeObject(forKey: "fileName") as! String? { self.fileName = fileName }
if let contentType = coder.decodeObject(forKey: "contentType") as! String? { self.contentType = contentType }
if let key = coder.decodeObject(forKey: "key") as! Data? { self.key = key }
if let digest = coder.decodeObject(forKey: "digest") as! Data? { self.digest = digest }
if let rawKind = coder.decodeObject(forKey: "kind") as! String? { self.kind = Kind(rawValue: rawKind) }
if let caption = coder.decodeObject(forKey: "caption") as! String? { self.caption = caption }
if let size = coder.decodeObject(forKey: "size") as! CGSize? { self.size = size }
if let sizeInBytes = coder.decodeObject(forKey: "sizeInBytes") as! UInt? { self.sizeInBytes = sizeInBytes }
if let url = coder.decodeObject(forKey: "url") as! String? { self.url = url }
}
public func encode(with coder: NSCoder) {
coder.encode(fileName, forKey: "fileName")
coder.encode(contentType, forKey: "contentType")
coder.encode(key, forKey: "key")
coder.encode(digest, forKey: "digest")
coder.encode(kind?.rawValue, forKey: "kind")
coder.encode(caption, forKey: "caption")
coder.encode(size, forKey: "size")
coder.encode(sizeInBytes, forKey: "sizeInBytes")
coder.encode(url, forKey: "url")
}
public static func fromProto(_ proto: SNProtoAttachmentPointer) -> Attachment? {
preconditionFailure("Use MessageReceiverDelegate.parseAttachments(from:) instead.")
}
public func toProto() -> SNProtoDataMessageQuote? {
fatalError("Not implemented.")
}
}
}

View File

@ -38,7 +38,7 @@ public final class VisibleMessage : Message {
guard let dataMessage = proto.dataMessage else { return nil }
let result = VisibleMessage()
result.text = dataMessage.body
result.attachmentIDs = [] // TODO: Attachments
// Attachments are handled in MessageReceiver
if let quoteProto = dataMessage.quote, let quote = Quote.fromProto(quoteProto) { result.quote = quote }
if let linkPreviewProto = dataMessage.preview.first, let linkPreview = LinkPreview.fromProto(linkPreviewProto) { result.linkPreview = linkPreview }
// TODO: Contact

View File

@ -47,7 +47,7 @@ internal enum MessageReceiver {
}
}
internal static func parse(_ data: Data, messageServerID: UInt64?, using transaction: Any) throws -> Message {
internal static func parse(_ data: Data, messageServerID: UInt64?, using transaction: Any) throws -> (Message, SNProtoContent) {
// Parse the envelope
let envelope = try SNProtoEnvelope.parseData(data)
// Decrypt the contents
@ -90,13 +90,13 @@ internal enum MessageReceiver {
message.groupPublicKey = groupPublicKey
message.openGroupServerMessageID = messageServerID
guard message.isValid else { throw Error.invalidMessage }
return message
return (message, proto)
} else {
throw Error.unknownMessage
}
}
internal static func handle(_ message: Message, using transaction: Any) throws {
internal static func handle(_ message: Message, associatedWithProto proto: SNProtoContent, using transaction: Any) throws {
switch message {
case let message as ReadReceipt: handleReadReceipt(message, using: transaction)
case let message as SessionRequest: handleSessionRequest(message, using: transaction)
@ -104,7 +104,7 @@ internal enum MessageReceiver {
case let message as TypingIndicator: handleTypingIndicator(message, using: transaction)
case let message as ClosedGroupUpdate: handleClosedGroupUpdate(message, using: transaction)
case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction)
case let message as VisibleMessage: try handleVisibleMessage(message, using: transaction)
case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, using: transaction)
default: fatalError()
}
}
@ -148,14 +148,18 @@ internal enum MessageReceiver {
}
}
private static func handleVisibleMessage(_ message: VisibleMessage, using transaction: Any) throws {
private static func handleVisibleMessage(_ message: VisibleMessage, associatedWithProto proto: SNProtoContent, using transaction: Any) throws {
let delegate = Configuration.shared.messageReceiverDelegate
let storage = Configuration.shared.storage
// Handle attachments
let attachments = delegate.parseAttachments(from: proto.dataMessage!.attachments)
message.attachmentIDs = storage.save(attachments, using: transaction)
// Update profile if needed
if let profile = message.profile {
delegate.updateProfile(for: message.sender!, from: profile, using: transaction)
}
// Persist the message
guard let (threadID, tsIncomingMessage) = Configuration.shared.storage.persist(message, groupPublicKey: message.groupPublicKey, using: transaction) else { throw Error.noThread }
guard let (threadID, tsIncomingMessage) = storage.persist(message, groupPublicKey: message.groupPublicKey, using: transaction) else { throw Error.noThread }
message.threadID = threadID
// Cancel any typing indicators
delegate.cancelTypingIndicatorsIfNeeded(for: message.sender!)

View File

@ -14,4 +14,5 @@ public protocol MessageReceiverDelegate {
func handleGroupUpdate(_ message: ClosedGroupUpdate, using transaction: Any)
func handleSenderKeyRequest(_ message: ClosedGroupUpdate, using transaction: Any)
func handleSenderKey(_ message: ClosedGroupUpdate, using transaction: Any)
func parseAttachments(from protos: [SNProtoAttachmentPointer]) -> [VisibleMessage.Attachment]
}

View File

@ -42,7 +42,6 @@ public protocol SessionMessagingKitStorageProtocol {
func setOpenGroupPublicKey(for server: String, to newValue: String, using transaction: Any)
// MARK: - Last Message Server ID
func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64?
func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any)
func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any)
@ -64,4 +63,6 @@ public protocol SessionMessagingKitStorageProtocol {
/// Returns the ID of the thread the message was stored under along with the `TSIncomingMessage` that was constructed.
func persist(_ message: VisibleMessage, groupPublicKey: String?, using transaction: Any) -> (String, Any)?
/// Returns the IDs of the saved attachments.
func save(_ attachments: [VisibleMessage.Attachment], using transaction: Any) -> [String]
}

View File

@ -585,6 +585,8 @@
C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35E8AAD2485E51D00ACB629 /* IP2Country.swift */; };
C3645350252449260045C478 /* VoiceMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C364534F252449260045C478 /* VoiceMessageView.swift */; };
C364535C252467900045C478 /* AudioUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C364535B252467900045C478 /* AudioUtilities.swift */; };
C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C379DCF3256735770002D4EB /* VisibleMessage+Attachment.swift */; };
C379DCFE25673DBC0002D4EB /* Attachment+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C379DCFD25673DBC0002D4EB /* Attachment+Conversion.swift */; };
C37F5385255B94F6002AEA92 /* SelectRecipientViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF34E255B6DC8007E1867 /* SelectRecipientViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C37F5396255B95BD002AEA92 /* OWSAnyTouchGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF302255B6DBE007E1867 /* OWSAnyTouchGestureRecognizer.h */; settings = {ATTRIBUTES = (Public, ); }; };
C37F53A7255B96E0002AEA92 /* OWSAudioPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -1679,6 +1681,8 @@
C35E8AAD2485E51D00ACB629 /* IP2Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IP2Country.swift; sourceTree = "<group>"; };
C364534F252449260045C478 /* VoiceMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageView.swift; sourceTree = "<group>"; };
C364535B252467900045C478 /* AudioUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioUtilities.swift; sourceTree = "<group>"; };
C379DCF3256735770002D4EB /* VisibleMessage+Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Attachment.swift"; sourceTree = "<group>"; };
C379DCFD25673DBC0002D4EB /* Attachment+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Attachment+Conversion.swift"; sourceTree = "<group>"; };
C37F53E8255BA9BB002AEA92 /* Environment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Environment.h; sourceTree = "<group>"; };
C37F5402255BA9ED002AEA92 /* Environment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Environment.m; sourceTree = "<group>"; };
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SNProtoEnvelope+Conversion.swift"; sourceTree = "<group>"; };
@ -2631,6 +2635,7 @@
isa = PBXGroup;
children = (
C3C2A74C2553A39700C340D1 /* VisibleMessage.swift */,
C379DCF3256735770002D4EB /* VisibleMessage+Attachment.swift */,
C3C2A7552553A3AB00C340D1 /* VisibleMessage+Quote.swift */,
C3C2A75E2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift */,
C3C2A7672553A3D900C340D1 /* VisibleMessage+Contact.swift */,
@ -3279,6 +3284,7 @@
C38BBA0B255E31EC0041B9A3 /* Attachments */ = {
isa = PBXGroup;
children = (
C379DCFD25673DBC0002D4EB /* Attachment+Conversion.swift */,
C38EF224255B6D5D007E1867 /* SignalAttachment.swift */,
C33FDC15255A581E00E217F9 /* TSAttachment.h */,
C33FDAC2255A580200E217F9 /* TSAttachment.m */,
@ -4957,6 +4963,7 @@
C33FDD3D255A582000E217F9 /* TSQuotedMessage.m in Sources */,
C38EF273255B6D7A007E1867 /* OWSDatabaseMigrationRunner.m in Sources */,
C33FDD12255A582000E217F9 /* OWSPrimaryStorage+Loki.m in Sources */,
C379DCFE25673DBC0002D4EB /* Attachment+Conversion.swift in Sources */,
C38EF31A255B6DBF007E1867 /* OWSAnyTouchGestureRecognizer.m in Sources */,
C33FDCFB255A582000E217F9 /* MIMETypeUtil.m in Sources */,
C38EF36C255B6DCC007E1867 /* SharingThreadPickerViewController.m in Sources */,
@ -5263,6 +5270,7 @@
C3BBE0B52554F0E10050F1E3 /* ProofOfWork.swift in Sources */,
C3A721902558C0CD0043A11F /* FileServerAPI.swift in Sources */,
C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */,
C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */,
C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */,
C3C2A7682553A3D900C340D1 /* VisibleMessage+Contact.swift in Sources */,
C3A721392558BDFA0043A11F /* OpenGroupAPI.swift in Sources */,

View File

@ -32,4 +32,12 @@ extension Storage {
message.save(with: transaction)
return (thread.uniqueId!, message)
}
public func save(_ attachments: [VisibleMessage.Attachment], using transaction: Any) -> [String] {
return attachments.map { attachment in
let tsAttachment = TSAttachmentPointer.from(attachment)
tsAttachment.save(with: transaction as! YapDatabaseReadWriteTransaction)
return tsAttachment.uniqueId!
}
}
}

View File

@ -0,0 +1,22 @@
extension TSAttachmentPointer {
public static func from(_ attachment: VisibleMessage.Attachment) -> TSAttachmentPointer {
let kind: TSAttachmentType
switch attachment.kind! {
case .generic: kind = .default
case .voiceMessage: kind = .voiceMessage
}
return TSAttachmentPointer(
serverId: 0,
key: attachment.key,
digest: attachment.digest,
byteCount: UInt32(attachment.sizeInBytes!),
contentType: attachment.contentType!,
sourceFilename: attachment.fileName,
caption: attachment.caption,
albumMessageId: nil,
attachmentType: kind,
mediaSize: attachment.size!)
}
}

View File

@ -312,4 +312,40 @@ public final class MessageReceiverDelegate : SessionMessagingKit.MessageReceiver
let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: [])
Storage.shared.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: message.sender!, ratchet: ratchet, using: transaction)
}
// MARK: - Attachments
public func parseAttachments(from protos: [SNProtoAttachmentPointer]) -> [VisibleMessage.Attachment] {
return protos.compactMap { proto in
let result = VisibleMessage.Attachment()
result.fileName = proto.fileName
func inferContentType() -> String {
guard let fileName = result.fileName, let fileExtension = URL(string: fileName)?.pathExtension else { return OWSMimeTypeApplicationOctetStream }
return MIMETypeUtil.mimeType(forFileExtension: fileExtension) ?? OWSMimeTypeApplicationOctetStream
}
result.contentType = proto.contentType ?? inferContentType()
result.key = proto.key
result.digest = proto.digest
let kind: VisibleMessage.Attachment.Kind
if proto.hasFlags && (proto.flags & UInt32(SNProtoAttachmentPointer.SNProtoAttachmentPointerFlags.voiceMessage.rawValue)) > 0 {
kind = .voiceMessage
} else {
kind = .generic
}
result.kind = kind
result.caption = proto.hasCaption ? proto.caption : nil
let size: CGSize
if proto.hasWidth && proto.width > 0 && proto.hasHeight && proto.height > 0 {
size = CGSize(width: Int(proto.width), height: Int(proto.height))
} else {
size = CGSize.zero
}
result.size = size
result.sizeInBytes = proto.size > 0 ? UInt(proto.size) : nil
result.url = proto.url
return result.isValid ? result : nil
}
}
}