1052 lines
56 KiB
Swift
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)
|
|
}
|
|
}
|