import Foundation import GRDB import Martin enum MessageType: String, Codable, DatabaseValueConvertible { case chat case groupchat case error } enum AttachmentType: Int, Codable, DatabaseValueConvertible { case image case video case audio case file } struct Attachment: Codable & Equatable, DatabaseValueConvertible { let type: AttachmentType var localName: String? var thumbnailName: String? var remotePath: String? var localPath: URL? { guard let attachmentLocalName = localName else { return nil } return Const.fileFolder.appendingPathComponent(attachmentLocalName) } var thumbnailPath: URL? { guard let attachmentThumbnailName = thumbnailName else { return nil } return Const.fileFolder.appendingPathComponent(attachmentThumbnailName) } } enum MessageContentType: Codable & Equatable, DatabaseValueConvertible { case text case typing case invite case attachment(Attachment) var isAttachment: Bool { if case .attachment = self { return true } return false } } enum MessageStatus: Int, Codable, DatabaseValueConvertible { case pending case sent case error } struct Message: DBStorable, Equatable { static let databaseTableName = "messages" let id: String var type: MessageType var date: Date var contentType: MessageContentType var status: MessageStatus var from: String var to: String? var body: String? var subject: String? var thread: String? var oobUrl: String? } extension Message { func save() async throws { try await Database.shared.dbQueue.write { db in try self.insert(db) } } func setStatus(_ status: MessageStatus) async throws { try await Database.shared.dbQueue.write { db in var updatedMessage = self updatedMessage.status = status try updatedMessage.update(db, columns: ["status"]) } } static var blank: Message { Message( id: UUID().uuidString, type: .chat, date: Date(), contentType: .text, status: .pending, from: "", to: nil, body: nil, subject: nil, thread: nil, oobUrl: nil ) } } extension Message { static func map(_ martinMessage: Martin.Message) -> Message? { // Check that the message type is supported let chatTypes: [StanzaType] = [.chat, .groupchat] guard let mType = martinMessage.type, chatTypes.contains(mType) else { #if DEBUG print("Unsupported martinMessage type: \(martinMessage.type?.rawValue ?? "nil")") #endif return nil } // Type let type = MessageType(rawValue: martinMessage.type?.rawValue ?? "") ?? .chat // Content type var contentType: MessageContentType = .text if let oob = martinMessage.oob { contentType = .attachment(.init( type: oob.attachmentType, localName: nil, thumbnailName: nil, remotePath: oob )) } else if martinMessage.hints.contains(.noStore) { contentType = .typing // skip for now return nil } // From/To let from = martinMessage.from?.bareJid.stringValue ?? "" let to = martinMessage.to?.bareJid.stringValue // Msg let msg = Message( id: martinMessage.id ?? UUID().uuidString, type: type, date: Date(), contentType: contentType, status: .sent, from: from, to: to, body: martinMessage.body, subject: martinMessage.subject, thread: martinMessage.thread, oobUrl: martinMessage.oob ) return msg } }