TM-SGNL-iOS/SignalServiceKit/Contacts/TSThread+SDS.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

1052 lines
56 KiB
Swift

//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public import GRDB
// NOTE: This file is generated by /Scripts/sds_codegen/sds_generate.py.
// Do not manually edit it, instead run `sds_codegen.sh`.
// MARK: - Record
public struct ThreadRecord: SDSRecord {
public weak var delegate: SDSRecordDelegate?
public var tableMetadata: SDSTableMetadata {
TSThreadSerializer.table
}
public static var databaseTableName: String {
TSThreadSerializer.table.tableName
}
public var id: Int64?
// This defines all of the columns used in the table
// where this model (and any subclasses) are persisted.
public let recordType: SDSRecordType
public let uniqueId: String
// Properties
public let conversationColorName: String
public let creationDate: Double?
public let isArchived: Bool
public let lastInteractionRowId: UInt64
public let messageDraft: String?
public let mutedUntilDate: Double?
public let shouldThreadBeVisible: Bool
public let contactPhoneNumber: String?
public let contactUUID: String?
public let groupModel: Data?
public let hasDismissedOffers: Bool?
public let isMarkedUnread: Bool
public let lastVisibleSortIdOnScreenPercentage: Double
public let lastVisibleSortId: UInt64
public let messageDraftBodyRanges: Data?
public let mentionNotificationMode: UInt
public let mutedUntilTimestamp: UInt64
public let allowsReplies: Bool?
public let lastSentStoryTimestamp: UInt64?
public let name: String?
public let addresses: Data?
public let storyViewMode: UInt
public let editTargetTimestamp: UInt64?
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
case id
case recordType
case uniqueId
case conversationColorName
case creationDate
case isArchived
case lastInteractionRowId
case messageDraft
case mutedUntilDate
case shouldThreadBeVisible
case contactPhoneNumber
case contactUUID
case groupModel
case hasDismissedOffers
case isMarkedUnread
case lastVisibleSortIdOnScreenPercentage
case lastVisibleSortId
case messageDraftBodyRanges
case mentionNotificationMode
case mutedUntilTimestamp
case allowsReplies
case lastSentStoryTimestamp
case name
case addresses
case storyViewMode
case editTargetTimestamp
}
public static func columnName(_ column: ThreadRecord.CodingKeys, fullyQualified: Bool = false) -> String {
fullyQualified ? "\(databaseTableName).\(column.rawValue)" : column.rawValue
}
public func didInsert(with rowID: Int64, for column: String?) {
guard let delegate = delegate else {
owsFailDebug("Missing delegate.")
return
}
delegate.updateRowId(rowID)
}
}
// MARK: - Row Initializer
public extension ThreadRecord {
static var databaseSelection: [SQLSelectable] {
CodingKeys.allCases
}
init(row: Row) {
id = row[0]
recordType = row[1]
uniqueId = row[2]
conversationColorName = row[3]
creationDate = row[4]
isArchived = row[5]
lastInteractionRowId = row[6]
messageDraft = row[7]
mutedUntilDate = row[8]
shouldThreadBeVisible = row[9]
contactPhoneNumber = row[10]
contactUUID = row[11]
groupModel = row[12]
hasDismissedOffers = row[13]
isMarkedUnread = row[14]
lastVisibleSortIdOnScreenPercentage = row[15]
lastVisibleSortId = row[16]
messageDraftBodyRanges = row[17]
mentionNotificationMode = row[18]
mutedUntilTimestamp = row[19]
allowsReplies = row[20]
lastSentStoryTimestamp = row[21]
name = row[22]
addresses = row[23]
storyViewMode = row[24]
editTargetTimestamp = row[25]
}
}
// MARK: - StringInterpolation
public extension String.StringInterpolation {
mutating func appendInterpolation(threadColumn column: ThreadRecord.CodingKeys) {
appendLiteral(ThreadRecord.columnName(column))
}
mutating func appendInterpolation(threadColumnFullyQualified column: ThreadRecord.CodingKeys) {
appendLiteral(ThreadRecord.columnName(column, fullyQualified: true))
}
}
// MARK: - Deserialization
extension TSThread {
// This method defines how to deserialize a model, given a
// database row. The recordType column is used to determine
// the corresponding model class.
class func fromRecord(_ record: ThreadRecord) throws -> TSThread {
guard let recordId = record.id else {
throw SDSError.invalidValue()
}
switch record.recordType {
case .contactThread:
let uniqueId: String = record.uniqueId
let conversationColorNameObsolete: String = record.conversationColorName
let creationDateInterval: Double? = record.creationDate
let creationDate: Date? = SDSDeserialization.optionalDoubleAsDate(creationDateInterval, name: "creationDate")
let editTargetTimestamp: NSNumber? = SDSDeserialization.optionalNumericAsNSNumber(record.editTargetTimestamp, name: "editTargetTimestamp", conversion: { NSNumber(value: $0) })
let isArchivedObsolete: Bool = record.isArchived
let isMarkedUnreadObsolete: Bool = record.isMarkedUnread
let lastInteractionRowId: UInt64 = record.lastInteractionRowId
let lastSentStoryTimestamp: NSNumber? = SDSDeserialization.optionalNumericAsNSNumber(record.lastSentStoryTimestamp, name: "lastSentStoryTimestamp", conversion: { NSNumber(value: $0) })
let lastVisibleSortIdObsolete: UInt64 = record.lastVisibleSortId
let lastVisibleSortIdOnScreenPercentageObsolete: Double = record.lastVisibleSortIdOnScreenPercentage
let mentionNotificationMode: TSThreadMentionNotificationMode = TSThreadMentionNotificationMode(rawValue: record.mentionNotificationMode) ?? .default
let messageDraft: String? = record.messageDraft
let messageDraftBodyRangesSerialized: Data? = record.messageDraftBodyRanges
let messageDraftBodyRanges: MessageBodyRanges? = try SDSDeserialization.optionalUnarchive(messageDraftBodyRangesSerialized, name: "messageDraftBodyRanges")
let mutedUntilDateObsoleteInterval: Double? = record.mutedUntilDate
let mutedUntilDateObsolete: Date? = SDSDeserialization.optionalDoubleAsDate(mutedUntilDateObsoleteInterval, name: "mutedUntilDateObsolete")
let mutedUntilTimestampObsolete: UInt64 = record.mutedUntilTimestamp
let shouldThreadBeVisible: Bool = record.shouldThreadBeVisible
let storyViewMode: TSThreadStoryViewMode = TSThreadStoryViewMode(rawValue: record.storyViewMode) ?? .default
let contactPhoneNumber: String? = record.contactPhoneNumber
let contactUUID: String? = record.contactUUID
let hasDismissedOffers: Bool = try SDSDeserialization.required(record.hasDismissedOffers, name: "hasDismissedOffers")
return TSContactThread(grdbId: recordId,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
contactPhoneNumber: contactPhoneNumber,
contactUUID: contactUUID,
hasDismissedOffers: hasDismissedOffers)
case .groupThread:
let uniqueId: String = record.uniqueId
let conversationColorNameObsolete: String = record.conversationColorName
let creationDateInterval: Double? = record.creationDate
let creationDate: Date? = SDSDeserialization.optionalDoubleAsDate(creationDateInterval, name: "creationDate")
let editTargetTimestamp: NSNumber? = SDSDeserialization.optionalNumericAsNSNumber(record.editTargetTimestamp, name: "editTargetTimestamp", conversion: { NSNumber(value: $0) })
let isArchivedObsolete: Bool = record.isArchived
let isMarkedUnreadObsolete: Bool = record.isMarkedUnread
let lastInteractionRowId: UInt64 = record.lastInteractionRowId
let lastSentStoryTimestamp: NSNumber? = SDSDeserialization.optionalNumericAsNSNumber(record.lastSentStoryTimestamp, name: "lastSentStoryTimestamp", conversion: { NSNumber(value: $0) })
let lastVisibleSortIdObsolete: UInt64 = record.lastVisibleSortId
let lastVisibleSortIdOnScreenPercentageObsolete: Double = record.lastVisibleSortIdOnScreenPercentage
let mentionNotificationMode: TSThreadMentionNotificationMode = TSThreadMentionNotificationMode(rawValue: record.mentionNotificationMode) ?? .default
let messageDraft: String? = record.messageDraft
let messageDraftBodyRangesSerialized: Data? = record.messageDraftBodyRanges
let messageDraftBodyRanges: MessageBodyRanges? = try SDSDeserialization.optionalUnarchive(messageDraftBodyRangesSerialized, name: "messageDraftBodyRanges")
let mutedUntilDateObsoleteInterval: Double? = record.mutedUntilDate
let mutedUntilDateObsolete: Date? = SDSDeserialization.optionalDoubleAsDate(mutedUntilDateObsoleteInterval, name: "mutedUntilDateObsolete")
let mutedUntilTimestampObsolete: UInt64 = record.mutedUntilTimestamp
let shouldThreadBeVisible: Bool = record.shouldThreadBeVisible
let storyViewMode: TSThreadStoryViewMode = TSThreadStoryViewMode(rawValue: record.storyViewMode) ?? .default
let groupModelSerialized: Data? = record.groupModel
let groupModel: TSGroupModel = try SDSDeserialization.unarchive(groupModelSerialized, name: "groupModel")
return TSGroupThread(grdbId: recordId,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
groupModel: groupModel)
case .privateStoryThread:
let uniqueId: String = record.uniqueId
let conversationColorNameObsolete: String = record.conversationColorName
let creationDateInterval: Double? = record.creationDate
let creationDate: Date? = SDSDeserialization.optionalDoubleAsDate(creationDateInterval, name: "creationDate")
let editTargetTimestamp: NSNumber? = SDSDeserialization.optionalNumericAsNSNumber(record.editTargetTimestamp, name: "editTargetTimestamp", conversion: { NSNumber(value: $0) })
let isArchivedObsolete: Bool = record.isArchived
let isMarkedUnreadObsolete: Bool = record.isMarkedUnread
let lastInteractionRowId: UInt64 = record.lastInteractionRowId
let lastSentStoryTimestamp: NSNumber? = SDSDeserialization.optionalNumericAsNSNumber(record.lastSentStoryTimestamp, name: "lastSentStoryTimestamp", conversion: { NSNumber(value: $0) })
let lastVisibleSortIdObsolete: UInt64 = record.lastVisibleSortId
let lastVisibleSortIdOnScreenPercentageObsolete: Double = record.lastVisibleSortIdOnScreenPercentage
let mentionNotificationMode: TSThreadMentionNotificationMode = TSThreadMentionNotificationMode(rawValue: record.mentionNotificationMode) ?? .default
let messageDraft: String? = record.messageDraft
let messageDraftBodyRangesSerialized: Data? = record.messageDraftBodyRanges
let messageDraftBodyRanges: MessageBodyRanges? = try SDSDeserialization.optionalUnarchive(messageDraftBodyRangesSerialized, name: "messageDraftBodyRanges")
let mutedUntilDateObsoleteInterval: Double? = record.mutedUntilDate
let mutedUntilDateObsolete: Date? = SDSDeserialization.optionalDoubleAsDate(mutedUntilDateObsoleteInterval, name: "mutedUntilDateObsolete")
let mutedUntilTimestampObsolete: UInt64 = record.mutedUntilTimestamp
let shouldThreadBeVisible: Bool = record.shouldThreadBeVisible
let storyViewMode: TSThreadStoryViewMode = TSThreadStoryViewMode(rawValue: record.storyViewMode) ?? .default
let addressesSerialized: Data? = record.addresses
let addresses: [SignalServiceAddress] = try SDSDeserialization.unarchive(addressesSerialized, name: "addresses")
let allowsReplies: Bool = try SDSDeserialization.required(record.allowsReplies, name: "allowsReplies")
let name: String = try SDSDeserialization.required(record.name, name: "name")
return TSPrivateStoryThread(grdbId: recordId,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
addresses: addresses,
allowsReplies: allowsReplies,
name: name)
case .thread:
let uniqueId: String = record.uniqueId
let conversationColorNameObsolete: String = record.conversationColorName
let creationDateInterval: Double? = record.creationDate
let creationDate: Date? = SDSDeserialization.optionalDoubleAsDate(creationDateInterval, name: "creationDate")
let editTargetTimestamp: NSNumber? = SDSDeserialization.optionalNumericAsNSNumber(record.editTargetTimestamp, name: "editTargetTimestamp", conversion: { NSNumber(value: $0) })
let isArchivedObsolete: Bool = record.isArchived
let isMarkedUnreadObsolete: Bool = record.isMarkedUnread
let lastInteractionRowId: UInt64 = record.lastInteractionRowId
let lastSentStoryTimestamp: NSNumber? = SDSDeserialization.optionalNumericAsNSNumber(record.lastSentStoryTimestamp, name: "lastSentStoryTimestamp", conversion: { NSNumber(value: $0) })
let lastVisibleSortIdObsolete: UInt64 = record.lastVisibleSortId
let lastVisibleSortIdOnScreenPercentageObsolete: Double = record.lastVisibleSortIdOnScreenPercentage
let mentionNotificationMode: TSThreadMentionNotificationMode = TSThreadMentionNotificationMode(rawValue: record.mentionNotificationMode) ?? .default
let messageDraft: String? = record.messageDraft
let messageDraftBodyRangesSerialized: Data? = record.messageDraftBodyRanges
let messageDraftBodyRanges: MessageBodyRanges? = try SDSDeserialization.optionalUnarchive(messageDraftBodyRangesSerialized, name: "messageDraftBodyRanges")
let mutedUntilDateObsoleteInterval: Double? = record.mutedUntilDate
let mutedUntilDateObsolete: Date? = SDSDeserialization.optionalDoubleAsDate(mutedUntilDateObsoleteInterval, name: "mutedUntilDateObsolete")
let mutedUntilTimestampObsolete: UInt64 = record.mutedUntilTimestamp
let shouldThreadBeVisible: Bool = record.shouldThreadBeVisible
let storyViewMode: TSThreadStoryViewMode = TSThreadStoryViewMode(rawValue: record.storyViewMode) ?? .default
return TSThread(grdbId: recordId,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode)
default:
owsFailDebug("Unexpected record type: \(record.recordType)")
throw SDSError.invalidValue()
}
}
}
// MARK: - SDSModel
extension TSThread: SDSModel {
public var serializer: SDSSerializer {
// Any subclass can be cast to it's superclass,
// so the order of this switch statement matters.
// We need to do a "depth first" search by type.
switch self {
case let model as TSPrivateStoryThread:
assert(type(of: model) == TSPrivateStoryThread.self)
return TSPrivateStoryThreadSerializer(model: model)
case let model as TSGroupThread:
assert(type(of: model) == TSGroupThread.self)
return TSGroupThreadSerializer(model: model)
case let model as TSContactThread:
assert(type(of: model) == TSContactThread.self)
return TSContactThreadSerializer(model: model)
default:
return TSThreadSerializer(model: self)
}
}
public func asRecord() -> SDSRecord {
serializer.asRecord()
}
public var sdsTableName: String {
ThreadRecord.databaseTableName
}
public static var table: SDSTableMetadata {
TSThreadSerializer.table
}
}
// MARK: - DeepCopyable
extension TSThread: DeepCopyable {
public func deepCopy() throws -> AnyObject {
guard let id = self.grdbId?.int64Value else {
throw OWSAssertionError("Model missing grdbId.")
}
// Any subclass can be cast to its superclass, so the order of these if
// statements matters. We need to do a "depth first" search by type.
if let modelToCopy = self as? TSPrivateStoryThread {
assert(type(of: modelToCopy) == TSPrivateStoryThread.self)
let uniqueId: String = modelToCopy.uniqueId
let conversationColorNameObsolete: String = modelToCopy.conversationColorNameObsolete
let creationDate: Date? = modelToCopy.creationDate
let editTargetTimestamp: NSNumber? = modelToCopy.editTargetTimestamp
let isArchivedObsolete: Bool = modelToCopy.isArchivedObsolete
let isMarkedUnreadObsolete: Bool = modelToCopy.isMarkedUnreadObsolete
let lastInteractionRowId: UInt64 = modelToCopy.lastInteractionRowId
let lastSentStoryTimestamp: NSNumber? = modelToCopy.lastSentStoryTimestamp
let lastVisibleSortIdObsolete: UInt64 = modelToCopy.lastVisibleSortIdObsolete
let lastVisibleSortIdOnScreenPercentageObsolete: Double = modelToCopy.lastVisibleSortIdOnScreenPercentageObsolete
let mentionNotificationMode: TSThreadMentionNotificationMode = modelToCopy.mentionNotificationMode
let messageDraft: String? = modelToCopy.messageDraft
let messageDraftBodyRanges: MessageBodyRanges?
if let messageDraftBodyRangesForCopy = modelToCopy.messageDraftBodyRanges {
messageDraftBodyRanges = try DeepCopies.deepCopy(messageDraftBodyRangesForCopy)
} else {
messageDraftBodyRanges = nil
}
let mutedUntilDateObsolete: Date? = modelToCopy.mutedUntilDateObsolete
let mutedUntilTimestampObsolete: UInt64 = modelToCopy.mutedUntilTimestampObsolete
let shouldThreadBeVisible: Bool = modelToCopy.shouldThreadBeVisible
let storyViewMode: TSThreadStoryViewMode = modelToCopy.storyViewMode
let addresses: [SignalServiceAddress] = try DeepCopies.deepCopy(modelToCopy.addresses)
let allowsReplies: Bool = modelToCopy.allowsReplies
let name: String = modelToCopy.name
return TSPrivateStoryThread(grdbId: id,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
addresses: addresses,
allowsReplies: allowsReplies,
name: name)
}
if let modelToCopy = self as? TSGroupThread {
assert(type(of: modelToCopy) == TSGroupThread.self)
let uniqueId: String = modelToCopy.uniqueId
let conversationColorNameObsolete: String = modelToCopy.conversationColorNameObsolete
let creationDate: Date? = modelToCopy.creationDate
let editTargetTimestamp: NSNumber? = modelToCopy.editTargetTimestamp
let isArchivedObsolete: Bool = modelToCopy.isArchivedObsolete
let isMarkedUnreadObsolete: Bool = modelToCopy.isMarkedUnreadObsolete
let lastInteractionRowId: UInt64 = modelToCopy.lastInteractionRowId
let lastSentStoryTimestamp: NSNumber? = modelToCopy.lastSentStoryTimestamp
let lastVisibleSortIdObsolete: UInt64 = modelToCopy.lastVisibleSortIdObsolete
let lastVisibleSortIdOnScreenPercentageObsolete: Double = modelToCopy.lastVisibleSortIdOnScreenPercentageObsolete
let mentionNotificationMode: TSThreadMentionNotificationMode = modelToCopy.mentionNotificationMode
let messageDraft: String? = modelToCopy.messageDraft
let messageDraftBodyRanges: MessageBodyRanges?
if let messageDraftBodyRangesForCopy = modelToCopy.messageDraftBodyRanges {
messageDraftBodyRanges = try DeepCopies.deepCopy(messageDraftBodyRangesForCopy)
} else {
messageDraftBodyRanges = nil
}
let mutedUntilDateObsolete: Date? = modelToCopy.mutedUntilDateObsolete
let mutedUntilTimestampObsolete: UInt64 = modelToCopy.mutedUntilTimestampObsolete
let shouldThreadBeVisible: Bool = modelToCopy.shouldThreadBeVisible
let storyViewMode: TSThreadStoryViewMode = modelToCopy.storyViewMode
let groupModel: TSGroupModel = try DeepCopies.deepCopy(modelToCopy.groupModel)
return TSGroupThread(grdbId: id,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
groupModel: groupModel)
}
if let modelToCopy = self as? TSContactThread {
assert(type(of: modelToCopy) == TSContactThread.self)
let uniqueId: String = modelToCopy.uniqueId
let conversationColorNameObsolete: String = modelToCopy.conversationColorNameObsolete
let creationDate: Date? = modelToCopy.creationDate
let editTargetTimestamp: NSNumber? = modelToCopy.editTargetTimestamp
let isArchivedObsolete: Bool = modelToCopy.isArchivedObsolete
let isMarkedUnreadObsolete: Bool = modelToCopy.isMarkedUnreadObsolete
let lastInteractionRowId: UInt64 = modelToCopy.lastInteractionRowId
let lastSentStoryTimestamp: NSNumber? = modelToCopy.lastSentStoryTimestamp
let lastVisibleSortIdObsolete: UInt64 = modelToCopy.lastVisibleSortIdObsolete
let lastVisibleSortIdOnScreenPercentageObsolete: Double = modelToCopy.lastVisibleSortIdOnScreenPercentageObsolete
let mentionNotificationMode: TSThreadMentionNotificationMode = modelToCopy.mentionNotificationMode
let messageDraft: String? = modelToCopy.messageDraft
let messageDraftBodyRanges: MessageBodyRanges?
if let messageDraftBodyRangesForCopy = modelToCopy.messageDraftBodyRanges {
messageDraftBodyRanges = try DeepCopies.deepCopy(messageDraftBodyRangesForCopy)
} else {
messageDraftBodyRanges = nil
}
let mutedUntilDateObsolete: Date? = modelToCopy.mutedUntilDateObsolete
let mutedUntilTimestampObsolete: UInt64 = modelToCopy.mutedUntilTimestampObsolete
let shouldThreadBeVisible: Bool = modelToCopy.shouldThreadBeVisible
let storyViewMode: TSThreadStoryViewMode = modelToCopy.storyViewMode
let contactPhoneNumber: String? = modelToCopy.contactPhoneNumber
let contactUUID: String? = modelToCopy.contactUUID
let hasDismissedOffers: Bool = modelToCopy.hasDismissedOffers
return TSContactThread(grdbId: id,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
contactPhoneNumber: contactPhoneNumber,
contactUUID: contactUUID,
hasDismissedOffers: hasDismissedOffers)
}
do {
let modelToCopy = self
assert(type(of: modelToCopy) == TSThread.self)
let uniqueId: String = modelToCopy.uniqueId
let conversationColorNameObsolete: String = modelToCopy.conversationColorNameObsolete
let creationDate: Date? = modelToCopy.creationDate
let editTargetTimestamp: NSNumber? = modelToCopy.editTargetTimestamp
let isArchivedObsolete: Bool = modelToCopy.isArchivedObsolete
let isMarkedUnreadObsolete: Bool = modelToCopy.isMarkedUnreadObsolete
let lastInteractionRowId: UInt64 = modelToCopy.lastInteractionRowId
let lastSentStoryTimestamp: NSNumber? = modelToCopy.lastSentStoryTimestamp
let lastVisibleSortIdObsolete: UInt64 = modelToCopy.lastVisibleSortIdObsolete
let lastVisibleSortIdOnScreenPercentageObsolete: Double = modelToCopy.lastVisibleSortIdOnScreenPercentageObsolete
let mentionNotificationMode: TSThreadMentionNotificationMode = modelToCopy.mentionNotificationMode
let messageDraft: String? = modelToCopy.messageDraft
let messageDraftBodyRanges: MessageBodyRanges?
if let messageDraftBodyRangesForCopy = modelToCopy.messageDraftBodyRanges {
messageDraftBodyRanges = try DeepCopies.deepCopy(messageDraftBodyRangesForCopy)
} else {
messageDraftBodyRanges = nil
}
let mutedUntilDateObsolete: Date? = modelToCopy.mutedUntilDateObsolete
let mutedUntilTimestampObsolete: UInt64 = modelToCopy.mutedUntilTimestampObsolete
let shouldThreadBeVisible: Bool = modelToCopy.shouldThreadBeVisible
let storyViewMode: TSThreadStoryViewMode = modelToCopy.storyViewMode
return TSThread(grdbId: id,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode)
}
}
}
// MARK: - Table Metadata
extension TSThreadSerializer {
// This defines all of the columns used in the table
// where this model (and any subclasses) are persisted.
static var idColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "id", columnType: .primaryKey) }
static var recordTypeColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "recordType", columnType: .int64) }
static var uniqueIdColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "uniqueId", columnType: .unicodeString, isUnique: true) }
// Properties
static var conversationColorNameObsoleteColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "conversationColorNameObsolete", columnType: .unicodeString) }
static var creationDateColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "creationDate", columnType: .double, isOptional: true) }
static var isArchivedObsoleteColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "isArchivedObsolete", columnType: .int) }
static var lastInteractionRowIdColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "lastInteractionRowId", columnType: .int64) }
static var messageDraftColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "messageDraft", columnType: .unicodeString, isOptional: true) }
static var mutedUntilDateObsoleteColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "mutedUntilDateObsolete", columnType: .double, isOptional: true) }
static var shouldThreadBeVisibleColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "shouldThreadBeVisible", columnType: .int) }
static var contactPhoneNumberColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "contactPhoneNumber", columnType: .unicodeString, isOptional: true) }
static var contactUUIDColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "contactUUID", columnType: .unicodeString, isOptional: true) }
static var groupModelColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "groupModel", columnType: .blob, isOptional: true) }
static var hasDismissedOffersColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "hasDismissedOffers", columnType: .int, isOptional: true) }
static var isMarkedUnreadObsoleteColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "isMarkedUnreadObsolete", columnType: .int) }
static var lastVisibleSortIdOnScreenPercentageObsoleteColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "lastVisibleSortIdOnScreenPercentageObsolete", columnType: .double) }
static var lastVisibleSortIdObsoleteColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "lastVisibleSortIdObsolete", columnType: .int64) }
static var messageDraftBodyRangesColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "messageDraftBodyRanges", columnType: .blob, isOptional: true) }
static var mentionNotificationModeColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "mentionNotificationMode", columnType: .int) }
static var mutedUntilTimestampObsoleteColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "mutedUntilTimestampObsolete", columnType: .int64) }
static var allowsRepliesColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "allowsReplies", columnType: .int, isOptional: true) }
static var lastSentStoryTimestampColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "lastSentStoryTimestamp", columnType: .int64, isOptional: true) }
static var nameColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "name", columnType: .unicodeString, isOptional: true) }
static var addressesColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "addresses", columnType: .blob, isOptional: true) }
static var storyViewModeColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "storyViewMode", columnType: .int) }
static var editTargetTimestampColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "editTargetTimestamp", columnType: .int64, isOptional: true) }
public static var table: SDSTableMetadata {
SDSTableMetadata(
tableName: "model_TSThread",
columns: [
idColumn,
recordTypeColumn,
uniqueIdColumn,
conversationColorNameObsoleteColumn,
creationDateColumn,
isArchivedObsoleteColumn,
lastInteractionRowIdColumn,
messageDraftColumn,
mutedUntilDateObsoleteColumn,
shouldThreadBeVisibleColumn,
contactPhoneNumberColumn,
contactUUIDColumn,
groupModelColumn,
hasDismissedOffersColumn,
isMarkedUnreadObsoleteColumn,
lastVisibleSortIdOnScreenPercentageObsoleteColumn,
lastVisibleSortIdObsoleteColumn,
messageDraftBodyRangesColumn,
mentionNotificationModeColumn,
mutedUntilTimestampObsoleteColumn,
allowsRepliesColumn,
lastSentStoryTimestampColumn,
nameColumn,
addressesColumn,
storyViewModeColumn,
editTargetTimestampColumn,
]
)
}
}
// MARK: - Save/Remove/Update
@objc
public extension TSThread {
func anyInsert(transaction: SDSAnyWriteTransaction) {
sdsSave(saveMode: .insert, transaction: transaction)
}
// Avoid this method whenever feasible.
//
// If the record has previously been saved, this method does an overwriting
// update of the corresponding row, otherwise if it's a new record, this
// method inserts a new row.
//
// For performance, when possible, you should explicitly specify whether
// you are inserting or updating rather than calling this method.
func anyUpsert(transaction: SDSAnyWriteTransaction) {
let isInserting: Bool
if TSThread.anyFetch(uniqueId: uniqueId, transaction: transaction) != nil {
isInserting = false
} else {
isInserting = true
}
sdsSave(saveMode: isInserting ? .insert : .update, transaction: transaction)
}
// This method is used by "updateWith..." methods.
//
// This model may be updated from many threads. We don't want to save
// our local copy (this instance) since it may be out of date. We also
// want to avoid re-saving a model that has been deleted. Therefore, we
// use "updateWith..." methods to:
//
// a) Update a property of this instance.
// b) If a copy of this model exists in the database, load an up-to-date copy,
// and update and save that copy.
// b) If a copy of this model _DOES NOT_ exist in the database, do _NOT_ save
// this local instance.
//
// After "updateWith...":
//
// a) Any copy of this model in the database will have been updated.
// b) The local property on this instance will always have been updated.
// c) Other properties on this instance may be out of date.
//
// All mutable properties of this class have been made read-only to
// prevent accidentally modifying them directly.
//
// This isn't a perfect arrangement, but in practice this will prevent
// data loss and will resolve all known issues.
func anyUpdate(transaction: SDSAnyWriteTransaction, block: (TSThread) -> Void) {
block(self)
guard let dbCopy = type(of: self).anyFetch(uniqueId: uniqueId,
transaction: transaction) else {
return
}
// Don't apply the block twice to the same instance.
// It's at least unnecessary and actually wrong for some blocks.
// e.g. `block: { $0 in $0.someField++ }`
if dbCopy !== self {
block(dbCopy)
}
dbCopy.sdsSave(saveMode: .update, transaction: transaction)
}
// This method is an alternative to `anyUpdate(transaction:block:)` methods.
//
// We should generally use `anyUpdate` to ensure we're not unintentionally
// clobbering other columns in the database when another concurrent update
// has occurred.
//
// There are cases when this doesn't make sense, e.g. when we know we've
// just loaded the model in the same transaction. In those cases it is
// safe and faster to do a "overwriting" update
func anyOverwritingUpdate(transaction: SDSAnyWriteTransaction) {
sdsSave(saveMode: .update, transaction: transaction)
}
}
// MARK: - TSThreadCursor
@objc
public class TSThreadCursor: NSObject, SDSCursor {
private let transaction: GRDBReadTransaction
private let cursor: RecordCursor<ThreadRecord>?
init(transaction: GRDBReadTransaction, cursor: RecordCursor<ThreadRecord>?) {
self.transaction = transaction
self.cursor = cursor
}
public func next() throws -> TSThread? {
guard let cursor = cursor else {
return nil
}
guard let record = try cursor.next() else {
return nil
}
let value = try TSThread.fromRecord(record)
SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didReadThread(value, transaction: transaction.asAnyRead)
return value
}
public func all() throws -> [TSThread] {
var result = [TSThread]()
while true {
guard let model = try next() else {
break
}
result.append(model)
}
return result
}
}
// MARK: - Obj-C Fetch
@objc
public extension TSThread {
class func grdbFetchCursor(transaction: GRDBReadTransaction) -> TSThreadCursor {
let database = transaction.database
do {
let cursor = try ThreadRecord.fetchCursor(database)
return TSThreadCursor(transaction: transaction, cursor: cursor)
} catch {
DatabaseCorruptionState.flagDatabaseReadCorruptionIfNecessary(
userDefaults: CurrentAppContext().appUserDefaults(),
error: error
)
owsFailDebug("Read failed: \(error)")
return TSThreadCursor(transaction: transaction, cursor: nil)
}
}
// Fetches a single model by "unique id".
class func anyFetch(uniqueId: String,
transaction: SDSAnyReadTransaction) -> TSThread? {
assert(!uniqueId.isEmpty)
return anyFetch(uniqueId: uniqueId, transaction: transaction, ignoreCache: false)
}
// Fetches a single model by "unique id".
class func anyFetch(uniqueId: String,
transaction: SDSAnyReadTransaction,
ignoreCache: Bool) -> TSThread? {
assert(!uniqueId.isEmpty)
if !ignoreCache,
let cachedCopy = SSKEnvironment.shared.modelReadCachesRef.threadReadCache.getThread(uniqueId: uniqueId, transaction: transaction) {
return cachedCopy
}
switch transaction.readTransaction {
case .grdbRead(let grdbTransaction):
let sql = "SELECT * FROM \(ThreadRecord.databaseTableName) WHERE \(threadColumn: .uniqueId) = ?"
return grdbFetchOne(sql: sql, arguments: [uniqueId], transaction: grdbTransaction)
}
}
// Traverses all records.
// Records are not visited in any particular order.
class func anyEnumerate(
transaction: SDSAnyReadTransaction,
block: (TSThread, UnsafeMutablePointer<ObjCBool>) -> Void
) {
anyEnumerate(transaction: transaction, batched: false, block: block)
}
// Traverses all records.
// Records are not visited in any particular order.
class func anyEnumerate(
transaction: SDSAnyReadTransaction,
batched: Bool = false,
block: (TSThread, UnsafeMutablePointer<ObjCBool>) -> Void
) {
let batchSize = batched ? Batching.kDefaultBatchSize : 0
anyEnumerate(transaction: transaction, batchSize: batchSize, block: block)
}
// Traverses all records.
// Records are not visited in any particular order.
//
// If batchSize > 0, the enumeration is performed in autoreleased batches.
class func anyEnumerate(
transaction: SDSAnyReadTransaction,
batchSize: UInt,
block: (TSThread, UnsafeMutablePointer<ObjCBool>) -> Void
) {
switch transaction.readTransaction {
case .grdbRead(let grdbTransaction):
let cursor = TSThread.grdbFetchCursor(transaction: grdbTransaction)
Batching.loop(batchSize: batchSize,
loopBlock: { stop in
do {
guard let value = try cursor.next() else {
stop.pointee = true
return
}
block(value, stop)
} catch let error {
owsFailDebug("Couldn't fetch model: \(error)")
}
})
}
}
// Traverses all records' unique ids.
// Records are not visited in any particular order.
class func anyEnumerateUniqueIds(
transaction: SDSAnyReadTransaction,
block: (String, UnsafeMutablePointer<ObjCBool>) -> Void
) {
anyEnumerateUniqueIds(transaction: transaction, batched: false, block: block)
}
// Traverses all records' unique ids.
// Records are not visited in any particular order.
class func anyEnumerateUniqueIds(
transaction: SDSAnyReadTransaction,
batched: Bool = false,
block: (String, UnsafeMutablePointer<ObjCBool>) -> Void
) {
let batchSize = batched ? Batching.kDefaultBatchSize : 0
anyEnumerateUniqueIds(transaction: transaction, batchSize: batchSize, block: block)
}
// Traverses all records' unique ids.
// Records are not visited in any particular order.
//
// If batchSize > 0, the enumeration is performed in autoreleased batches.
class func anyEnumerateUniqueIds(
transaction: SDSAnyReadTransaction,
batchSize: UInt,
block: (String, UnsafeMutablePointer<ObjCBool>) -> Void
) {
switch transaction.readTransaction {
case .grdbRead(let grdbTransaction):
grdbEnumerateUniqueIds(transaction: grdbTransaction,
sql: """
SELECT \(threadColumn: .uniqueId)
FROM \(ThreadRecord.databaseTableName)
""",
batchSize: batchSize,
block: block)
}
}
// Does not order the results.
class func anyFetchAll(transaction: SDSAnyReadTransaction) -> [TSThread] {
var result = [TSThread]()
anyEnumerate(transaction: transaction) { (model, _) in
result.append(model)
}
return result
}
// Does not order the results.
class func anyAllUniqueIds(transaction: SDSAnyReadTransaction) -> [String] {
var result = [String]()
anyEnumerateUniqueIds(transaction: transaction) { (uniqueId, _) in
result.append(uniqueId)
}
return result
}
class func anyCount(transaction: SDSAnyReadTransaction) -> UInt {
switch transaction.readTransaction {
case .grdbRead(let grdbTransaction):
return ThreadRecord.ows_fetchCount(grdbTransaction.database)
}
}
class func anyExists(
uniqueId: String,
transaction: SDSAnyReadTransaction
) -> Bool {
assert(!uniqueId.isEmpty)
switch transaction.readTransaction {
case .grdbRead(let grdbTransaction):
let sql = "SELECT EXISTS ( SELECT 1 FROM \(ThreadRecord.databaseTableName) WHERE \(threadColumn: .uniqueId) = ? )"
let arguments: StatementArguments = [uniqueId]
do {
return try Bool.fetchOne(grdbTransaction.database, sql: sql, arguments: arguments) ?? false
} catch {
DatabaseCorruptionState.flagDatabaseReadCorruptionIfNecessary(
userDefaults: CurrentAppContext().appUserDefaults(),
error: error
)
owsFail("Missing instance.")
}
}
}
}
// MARK: - Swift Fetch
public extension TSThread {
class func grdbFetchCursor(sql: String,
arguments: StatementArguments = StatementArguments(),
transaction: GRDBReadTransaction) -> TSThreadCursor {
do {
let sqlRequest = SQLRequest<Void>(sql: sql, arguments: arguments, cached: true)
let cursor = try ThreadRecord.fetchCursor(transaction.database, sqlRequest)
return TSThreadCursor(transaction: transaction, cursor: cursor)
} catch {
DatabaseCorruptionState.flagDatabaseReadCorruptionIfNecessary(
userDefaults: CurrentAppContext().appUserDefaults(),
error: error
)
owsFailDebug("Read failed: \(error)")
return TSThreadCursor(transaction: transaction, cursor: nil)
}
}
class func grdbFetchOne(sql: String,
arguments: StatementArguments = StatementArguments(),
transaction: GRDBReadTransaction) -> TSThread? {
assert(!sql.isEmpty)
do {
let sqlRequest = SQLRequest<Void>(sql: sql, arguments: arguments, cached: true)
guard let record = try ThreadRecord.fetchOne(transaction.database, sqlRequest) else {
return nil
}
let value = try TSThread.fromRecord(record)
SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didReadThread(value, transaction: transaction.asAnyRead)
return value
} catch {
owsFailDebug("error: \(error)")
return nil
}
}
}
// MARK: - SDSSerializer
// The SDSSerializer protocol specifies how to insert and update the
// row that corresponds to this model.
class TSThreadSerializer: SDSSerializer {
private let model: TSThread
public init(model: TSThread) {
self.model = model
}
// MARK: - Record
func asRecord() -> SDSRecord {
let id: Int64? = model.grdbId?.int64Value
let recordType: SDSRecordType = .thread
let uniqueId: String = model.uniqueId
// Properties
let conversationColorName: String = model.conversationColorNameObsolete
let creationDate: Double? = archiveOptionalDate(model.creationDate)
let isArchived: Bool = model.isArchivedObsolete
let lastInteractionRowId: UInt64 = model.lastInteractionRowId
let messageDraft: String? = model.messageDraft
let mutedUntilDate: Double? = archiveOptionalDate(model.mutedUntilDateObsolete)
let shouldThreadBeVisible: Bool = model.shouldThreadBeVisible
let contactPhoneNumber: String? = nil
let contactUUID: String? = nil
let groupModel: Data? = nil
let hasDismissedOffers: Bool? = nil
let isMarkedUnread: Bool = model.isMarkedUnreadObsolete
let lastVisibleSortIdOnScreenPercentage: Double = model.lastVisibleSortIdOnScreenPercentageObsolete
let lastVisibleSortId: UInt64 = model.lastVisibleSortIdObsolete
let messageDraftBodyRanges: Data? = optionalArchive(model.messageDraftBodyRanges)
let mentionNotificationMode: UInt = model.mentionNotificationMode.rawValue
let mutedUntilTimestamp: UInt64 = model.mutedUntilTimestampObsolete
let allowsReplies: Bool? = nil
let lastSentStoryTimestamp: UInt64? = archiveOptionalNSNumber(model.lastSentStoryTimestamp, conversion: { $0.uint64Value })
let name: String? = nil
let addresses: Data? = nil
let storyViewMode: UInt = model.storyViewMode.rawValue
let editTargetTimestamp: UInt64? = archiveOptionalNSNumber(model.editTargetTimestamp, conversion: { $0.uint64Value })
return ThreadRecord(delegate: model, id: id, recordType: recordType, uniqueId: uniqueId, conversationColorName: conversationColorName, creationDate: creationDate, isArchived: isArchived, lastInteractionRowId: lastInteractionRowId, messageDraft: messageDraft, mutedUntilDate: mutedUntilDate, shouldThreadBeVisible: shouldThreadBeVisible, contactPhoneNumber: contactPhoneNumber, contactUUID: contactUUID, groupModel: groupModel, hasDismissedOffers: hasDismissedOffers, isMarkedUnread: isMarkedUnread, lastVisibleSortIdOnScreenPercentage: lastVisibleSortIdOnScreenPercentage, lastVisibleSortId: lastVisibleSortId, messageDraftBodyRanges: messageDraftBodyRanges, mentionNotificationMode: mentionNotificationMode, mutedUntilTimestamp: mutedUntilTimestamp, allowsReplies: allowsReplies, lastSentStoryTimestamp: lastSentStoryTimestamp, name: name, addresses: addresses, storyViewMode: storyViewMode, editTargetTimestamp: editTargetTimestamp)
}
}