Uncodegen TSThread

This commit is contained in:
Max Radermacher 2026-03-05 21:54:46 -06:00 committed by GitHub
parent 214599386b
commit 5d47b5d0e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 1035 additions and 2849 deletions

View File

@ -1,6 +1,5 @@
{
"nsnumber_types": {
"TSThread.archivedAsOfMessageSortId": "UInt64",
"TSIncomingMessage.serverTimestamp": "UInt64",
"TSIncomingMessage.deprecated_sourceDeviceId": "UInt32",
"TSOutgoingMessageRecipientState.deliveryTimestamp": "UInt64",
@ -8,17 +7,11 @@
"OWSBackupFragment.uncompressedDataLength": "UInt64",
"SSKJobRecord.exclusiveProcessIdentifier": "Int32",
"TSMessage.storyTimestamp": "UInt64",
"TSThread.editTargetTimestamp": "UInt64",
"TSThread.lastSentStoryTimestamp": "UInt64",
"TSThread.lastViewedStoryTimestamp": "UInt64",
"TSThread.lastReceivedStoryTimestamp": "UInt64",
"TSMessage.expireTimerVersion": "UInt32"
"TSMessage.expireTimerVersion": "UInt32"
},
"properties_to_ignore": [
"TSYapDatabaseObject.grdbId",
"OWSDynamicOutgoingMessage.block",
"TSThread.isArchivedByLegacyTimestampForSorting",
"TSContactThread.contactThreadSchemaVersion",
"TSCall.callSchemaVersion",
"TSErrorMessage.errorMessageSchemaVersion",
"TSMessage.schemaVersion",
@ -32,11 +25,9 @@
"TSOutgoingMessage.changeActionsProtoData"
],
"class_cache_get_code": {
"TSThread": "SSKEnvironment.shared.modelReadCachesRef.threadReadCache.getThread(uniqueId: uniqueId, transaction: transaction)",
"TSInteraction": "SSKEnvironment.shared.modelReadCachesRef.interactionReadCache.getInteraction(uniqueId: uniqueId, transaction: transaction)"
},
"class_cache_set_code": {
"TSThread": "SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didReadThread",
"TSInteraction": "SSKEnvironment.shared.modelReadCachesRef.interactionReadCache.didReadInteraction"
},
"class_to_skip_serialization": [
@ -62,16 +53,6 @@
"TSOutgoingDeleteMessage"
],
"manually_typed_fields": {
"TSThread.conversationColorName": {
"swift_type": "ConversationColorName",
"objc_initializer_type": "ConversationColorName",
"is_objc_codable": true,
"is_enum": true,
"column_type": ".unicodeString",
"record_swift_type": "String",
"serialize_record_invocation": "%s.rawValue",
"should_use_blob": false
},
"TSInfoMessage.infoMessageUserInfo": {
"swift_type": "[InfoMessageUserInfoKey: AnyObject]",
"objc_initializer_type": "NSDictionary<InfoMessageUserInfoKey, id> *",
@ -81,26 +62,6 @@
"record_swift_type": "Data",
"serialize_record_invocation": "optionalArchive(%s)",
"should_use_blob": true
},
"TSThread.mentionNotificationMode": {
"swift_type": "TSThreadMentionNotificationMode",
"objc_initializer_type": "TSThreadMentionNotificationMode",
"is_objc_codable": true,
"is_enum": true,
"column_type": ".int",
"record_swift_type": "UInt",
"serialize_record_invocation": "%s.rawValue",
"should_use_blob": false
},
"TSThread.storyViewMode": {
"swift_type": "TSThreadStoryViewMode",
"objc_initializer_type": "TSThreadStoryViewMode",
"is_objc_codable": true,
"is_enum": true,
"column_type": ".int",
"record_swift_type": "UInt",
"serialize_record_invocation": "%s.rawValue",
"should_use_blob": false
}
},
"custom_accessors": {
@ -112,26 +73,11 @@
"TSIncomingMessage.viewed": "wasViewed"
},
"custom_column_names": {
"TSInteraction.uniqueThreadId": "threadUniqueId",
"TSThread.lastVisibleSortIdObsolete": "lastVisibleSortId",
"TSThread.lastVisibleSortIdOnScreenPercentageObsolete": "lastVisibleSortIdOnScreenPercentage",
"TSThread.mutedUntilDateObsolete": "mutedUntilDate",
"TSThread.isArchivedObsolete": "isArchived",
"TSThread.isMarkedUnreadObsolete": "isMarkedUnread",
"TSThread.mutedUntilTimestampObsolete": "mutedUntilTimestamp",
"TSThread.conversationColorNameObsolete": "conversationColorName"
"TSInteraction.uniqueThreadId": "threadUniqueId"
},
"aliased_column_names": {
"TSInteraction.sortId": "id",
"SSKJobRecord.sortId": "id"
},
"renamed_column_names": {
"TSThread.lastVisibleSortIdObsolete": "lastVisibleSortId",
"TSThread.lastVisibleSortIdOnScreenPercentageObsolete": "lastVisibleSortIdOnScreenPercentage",
"TSThread.mutedUntilDateObsolete": "mutedUntilDate",
"TSThread.isArchivedObsolete": "isArchived",
"TSThread.isMarkedUnreadObsolete": "isMarkedUnread",
"TSThread.mutedUntilTimestampObsolete": "mutedUntilTimestamp",
"TSThread.conversationColorNameObsolete": "conversationColorName"
}
"renamed_column_names": {}
}

View File

@ -301,7 +301,6 @@ class ParsedClass:
#
# Except the special-cased ones.
force_optional = property.type_info().is_enum
force_optional = force_optional and property.name not in ["mentionNotificationMode", "storyViewMode"]
property.force_optional = force_optional
record_properties.append(property)
for property in subclass_properties:
@ -485,25 +484,7 @@ class TypeInfo:
# Do nothing; we don't need to unpack this non-optional.
pass
if value_name == "mentionNotificationMode":
value_statement = (
"let %s: %s = TSThreadMentionNotificationMode(rawValue: %s) ?? .default"
% (
value_name,
"TSThreadMentionNotificationMode",
value_expr,
)
)
elif value_name == "storyViewMode":
value_statement = (
"let %s: %s = TSThreadStoryViewMode(rawValue: %s) ?? .default"
% (
value_name,
"TSThreadStoryViewMode",
value_expr,
)
)
elif self.is_codable:
if self.is_codable:
value_statement = "let %s: %s = %s" % (
value_name,
initializer_param_type,
@ -1167,7 +1148,7 @@ def generate_swift_extensions_for_model(clazz):
return
has_sds_superclass = clazz.has_sds_superclass()
has_remove_methods = clazz.name not in ("TSThread", "TSInteraction")
has_remove_methods = clazz.name not in ("TSInteraction")
has_grdb_serializer = clazz.name in ("TSInteraction")
swift_filename = os.path.basename(clazz.filepath)
@ -1362,8 +1343,7 @@ public extension %s {
property_name = property.column_name()
swift_type = type_info.swift_type()
did_force_optional = property.name not in ["mentionNotificationMode", "storyViewMode"]
did_force_optional = did_force_optional and type_info.is_enum
did_force_optional = type_info.is_enum
if property_name == "recordType":
# recordType is an enum, but its property info here doesn't
@ -1463,8 +1443,7 @@ extension %s {
value_name = "%s" % property.name
if property.name not in ("uniqueId",):
did_force_optional = property.name not in ["mentionNotificationMode", "storyViewMode"]
did_force_optional = did_force_optional and property.name not in base_property_names
did_force_optional = property.name not in base_property_names
did_force_optional = did_force_optional and not property.is_optional
did_force_optional = did_force_optional or property.type_info().is_enum
for statement in property.deserialize_record_invocation(
@ -1784,8 +1763,7 @@ extension %(class_name)s: DeepCopyable {
for property in deserialize_properties:
value_name = "%s" % property.name
did_force_optional = property.name not in ["mentionNotificationMode", "storyViewMode"]
did_force_optional = did_force_optional and property.name not in base_property_names
did_force_optional = property.name not in base_property_names
did_force_optional = did_force_optional and not property.is_optional
did_force_optional = did_force_optional or property.type_info().is_enum
for statement in property.deep_copy_record_invocation(

View File

@ -99,7 +99,6 @@
05104E3A2C8B541000F8851F /* AccessibleLayoutMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05104E392C8B540C00F8851F /* AccessibleLayoutMetric.swift */; };
0510F69E2C91EB3000FA3FDE /* ScrollBounceBehaviorIfAvailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0510F69D2C91EB2800FA3FDE /* ScrollBounceBehaviorIfAvailable.swift */; };
0512145B2C5BCECF0021EEC9 /* CollectionDifference+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0512145A2C5BCECF0021EEC9 /* CollectionDifference+SSK.swift */; };
0517B9782BFCFF12002CDE7D /* TSThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0517B9772BFCFF12002CDE7D /* TSThreadTests.swift */; };
052647BF2C63BAD60076E99D /* ChatListFilterControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052647BE2C63BAC40076E99D /* ChatListFilterControl.swift */; };
052647C12C6404DD0076E99D /* ChatListFilterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052647C02C6404D70076E99D /* ChatListFilterStore.swift */; };
052A33382C52BF410083D812 /* ChatListFilterActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052A33372C52BF410083D812 /* ChatListFilterActions.swift */; };
@ -3799,29 +3798,17 @@
F9C5CCA4289453B300548EEE /* SSKProto+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9B9289453B100548EEE /* SSKProto+OWS.swift */; };
F9C5CCAC289453B300548EEE /* PreKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9C2289453B100548EEE /* PreKeyManager.swift */; };
F9C5CCB0289453B300548EEE /* RemoteAttestation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9C7289453B100548EEE /* RemoteAttestation.swift */; };
F9C5CCBA289453B300548EEE /* TSThread.h in Headers */ = {isa = PBXBuildFile; fileRef = F9C5C9D2289453B100548EEE /* TSThread.h */; settings = {ATTRIBUTES = (Public, ); }; };
F9C5CCC0289453B300548EEE /* ContactDiscoveryTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9D9289453B100548EEE /* ContactDiscoveryTask.swift */; };
F9C5CCC3289453B300548EEE /* ContactDiscoveryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9DC289453B100548EEE /* ContactDiscoveryError.swift */; };
F9C5CCC5289453B300548EEE /* SignalAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9DE289453B100548EEE /* SignalAccount.swift */; };
F9C5CCC8289453B300548EEE /* SignalRecipient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9E1289453B100548EEE /* SignalRecipient.swift */; };
F9C5CCCA289453B300548EEE /* TSPrivateStoryThread+SDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9E4289453B100548EEE /* TSPrivateStoryThread+SDS.swift */; };
F9C5CCCB289453B300548EEE /* TSPrivateStoryThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9E5289453B100548EEE /* TSPrivateStoryThread.swift */; };
F9C5CCCC289453B300548EEE /* TSPrivateStoryThread.h in Headers */ = {isa = PBXBuildFile; fileRef = F9C5C9E6289453B100548EEE /* TSPrivateStoryThread.h */; settings = {ATTRIBUTES = (Public, ); }; };
F9C5CCCD289453B300548EEE /* TSGroupThread.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9E7289453B100548EEE /* TSGroupThread.m */; };
F9C5CCCE289453B300548EEE /* TSGroupThread+SDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9E8289453B100548EEE /* TSGroupThread+SDS.swift */; };
F9C5CCCF289453B300548EEE /* TSThread+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9E9289453B100548EEE /* TSThread+OWS.swift */; };
F9C5CCD0289453B300548EEE /* TSContactThread+SDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9EA289453B100548EEE /* TSContactThread+SDS.swift */; };
F9C5CCD1289453B300548EEE /* TSContactThread.h in Headers */ = {isa = PBXBuildFile; fileRef = F9C5C9EB289453B100548EEE /* TSContactThread.h */; settings = {ATTRIBUTES = (Public, ); }; };
F9C5CCD2289453B300548EEE /* TSGroupThread.h in Headers */ = {isa = PBXBuildFile; fileRef = F9C5C9EC289453B100548EEE /* TSGroupThread.h */; settings = {ATTRIBUTES = (Public, ); }; };
F9C5CCD3289453B300548EEE /* TSPrivateStoryThread.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9ED289453B100548EEE /* TSPrivateStoryThread.m */; };
F9C5CCD4289453B300548EEE /* TSContactThread.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9EE289453B100548EEE /* TSContactThread.m */; };
F9C5CCD5289453B300548EEE /* TSGroupThread+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9EF289453B100548EEE /* TSGroupThread+OWS.swift */; };
F9C5CCD6289453B300548EEE /* PhoneNumberUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9F0289453B100548EEE /* PhoneNumberUtil.swift */; };
F9C5CCD8289453B300548EEE /* ThreadAssociatedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9F2289453B100548EEE /* ThreadAssociatedData.swift */; };
F9C5CCDA289453B300548EEE /* DisappearingMessagesConfigurationRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9F4289453B100548EEE /* DisappearingMessagesConfigurationRecord.swift */; };
F9C5CCDD289453B300548EEE /* SignalAccountFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9F7289453B100548EEE /* SignalAccountFinder.swift */; };
F9C5CCDE289453B300548EEE /* TSThread.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9F8289453B100548EEE /* TSThread.m */; };
F9C5CCDF289453B300548EEE /* TSThread+SDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9F9289453B100548EEE /* TSThread+SDS.swift */; };
F9C5CCE1289453B300548EEE /* ContactThreadFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9FB289453B100548EEE /* ContactThreadFinder.swift */; };
F9C5CCE2289453B300548EEE /* SignalServiceAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9FC289453B100548EEE /* SignalServiceAddress.swift */; };
F9C5CCE3289453B300548EEE /* TSGroupMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9FD289453B100548EEE /* TSGroupMember.swift */; };
@ -4242,7 +4229,6 @@
05104E392C8B540C00F8851F /* AccessibleLayoutMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibleLayoutMetric.swift; sourceTree = "<group>"; };
0510F69D2C91EB2800FA3FDE /* ScrollBounceBehaviorIfAvailable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollBounceBehaviorIfAvailable.swift; sourceTree = "<group>"; };
0512145A2C5BCECF0021EEC9 /* CollectionDifference+SSK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionDifference+SSK.swift"; sourceTree = "<group>"; };
0517B9772BFCFF12002CDE7D /* TSThreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSThreadTests.swift; sourceTree = "<group>"; };
052647BE2C63BAC40076E99D /* ChatListFilterControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterControl.swift; sourceTree = "<group>"; };
052647C02C6404D70076E99D /* ChatListFilterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterStore.swift; sourceTree = "<group>"; };
052A33372C52BF410083D812 /* ChatListFilterActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterActions.swift; sourceTree = "<group>"; };
@ -8104,29 +8090,17 @@
F9C5C9B9289453B100548EEE /* SSKProto+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SSKProto+OWS.swift"; sourceTree = "<group>"; };
F9C5C9C2289453B100548EEE /* PreKeyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreKeyManager.swift; sourceTree = "<group>"; };
F9C5C9C7289453B100548EEE /* RemoteAttestation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteAttestation.swift; sourceTree = "<group>"; };
F9C5C9D2289453B100548EEE /* TSThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSThread.h; sourceTree = "<group>"; };
F9C5C9D9289453B100548EEE /* ContactDiscoveryTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactDiscoveryTask.swift; sourceTree = "<group>"; };
F9C5C9DC289453B100548EEE /* ContactDiscoveryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactDiscoveryError.swift; sourceTree = "<group>"; };
F9C5C9DE289453B100548EEE /* SignalAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAccount.swift; sourceTree = "<group>"; };
F9C5C9E1289453B100548EEE /* SignalRecipient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalRecipient.swift; sourceTree = "<group>"; };
F9C5C9E4289453B100548EEE /* TSPrivateStoryThread+SDS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TSPrivateStoryThread+SDS.swift"; sourceTree = "<group>"; };
F9C5C9E5289453B100548EEE /* TSPrivateStoryThread.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSPrivateStoryThread.swift; sourceTree = "<group>"; };
F9C5C9E6289453B100548EEE /* TSPrivateStoryThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSPrivateStoryThread.h; sourceTree = "<group>"; };
F9C5C9E7289453B100548EEE /* TSGroupThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGroupThread.m; sourceTree = "<group>"; };
F9C5C9E8289453B100548EEE /* TSGroupThread+SDS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TSGroupThread+SDS.swift"; sourceTree = "<group>"; };
F9C5C9E9289453B100548EEE /* TSThread+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TSThread+OWS.swift"; sourceTree = "<group>"; };
F9C5C9EA289453B100548EEE /* TSContactThread+SDS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TSContactThread+SDS.swift"; sourceTree = "<group>"; };
F9C5C9EB289453B100548EEE /* TSContactThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSContactThread.h; sourceTree = "<group>"; };
F9C5C9EC289453B100548EEE /* TSGroupThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGroupThread.h; sourceTree = "<group>"; };
F9C5C9ED289453B100548EEE /* TSPrivateStoryThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSPrivateStoryThread.m; sourceTree = "<group>"; };
F9C5C9EE289453B100548EEE /* TSContactThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSContactThread.m; sourceTree = "<group>"; };
F9C5C9EF289453B100548EEE /* TSGroupThread+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TSGroupThread+OWS.swift"; sourceTree = "<group>"; };
F9C5C9F0289453B100548EEE /* PhoneNumberUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumberUtil.swift; sourceTree = "<group>"; };
F9C5C9F2289453B100548EEE /* ThreadAssociatedData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadAssociatedData.swift; sourceTree = "<group>"; };
F9C5C9F4289453B100548EEE /* DisappearingMessagesConfigurationRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisappearingMessagesConfigurationRecord.swift; sourceTree = "<group>"; };
F9C5C9F7289453B100548EEE /* SignalAccountFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAccountFinder.swift; sourceTree = "<group>"; };
F9C5C9F8289453B100548EEE /* TSThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSThread.m; sourceTree = "<group>"; };
F9C5C9F9289453B100548EEE /* TSThread+SDS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TSThread+SDS.swift"; sourceTree = "<group>"; };
F9C5C9FB289453B100548EEE /* ContactThreadFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactThreadFinder.swift; sourceTree = "<group>"; };
F9C5C9FC289453B100548EEE /* SignalServiceAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalServiceAddress.swift; sourceTree = "<group>"; };
F9C5C9FD289453B100548EEE /* TSGroupMember.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSGroupMember.swift; sourceTree = "<group>"; };
@ -14666,7 +14640,6 @@
F94261E4289B1B5400460798 /* Util */,
724D47B12B97BE73001BE973 /* ZkParams */,
F942623A289B1B5500460798 /* SSKBaseTest.swift */,
0517B9772BFCFF12002CDE7D /* TSThreadTests.swift */,
D9C964072BE44D510058F143 /* XCTest+Thenable.swift */,
);
path = tests;
@ -15075,9 +15048,6 @@
5033D45E29D4DAAC007FEADA /* ThreadMerger.swift */,
45161BA828A2E54B0055AB45 /* ThreadReplyInfo.swift */,
502D45472A0AD7BE00B8BCE0 /* ThreadReplyInfoStore.swift */,
F9C5C9F9289453B100548EEE /* TSThread+SDS.swift */,
F9C5C9D2289453B100548EEE /* TSThread.h */,
F9C5C9F8289453B100548EEE /* TSThread.m */,
D91444E62CDD72BE00221D81 /* TSThread.swift */,
50D6A93E2AA9167400B7F093 /* UniqueObjectRecipientMerger.swift */,
5037F1932A43A6A300C372AD /* UserProfileMerger.swift */,
@ -15105,18 +15075,9 @@
502D45432A05A34B00B8BCE0 /* ThreadRemover.swift */,
D9F9A63E2C013EF100EF13EC /* ThreadSoftDeleteManager.swift */,
5033D46029D638FD007FEADA /* ThreadStore.swift */,
F9C5C9EA289453B100548EEE /* TSContactThread+SDS.swift */,
F9C5C9EB289453B100548EEE /* TSContactThread.h */,
F9C5C9EE289453B100548EEE /* TSContactThread.m */,
661170C92ABA522200A1B16D /* TSContactThread.swift */,
F9C5C9EF289453B100548EEE /* TSGroupThread+OWS.swift */,
F9C5C9E8289453B100548EEE /* TSGroupThread+SDS.swift */,
F9C5C9EC289453B100548EEE /* TSGroupThread.h */,
F9C5C9E7289453B100548EEE /* TSGroupThread.m */,
880FB40528CD205F00FA1C10 /* TSGroupThread.swift */,
F9C5C9E4289453B100548EEE /* TSPrivateStoryThread+SDS.swift */,
F9C5C9E6289453B100548EEE /* TSPrivateStoryThread.h */,
F9C5C9ED289453B100548EEE /* TSPrivateStoryThread.m */,
F9C5C9E5289453B100548EEE /* TSPrivateStoryThread.swift */,
F9C5C9E9289453B100548EEE /* TSThread+OWS.swift */,
);
@ -15645,10 +15606,8 @@
F9C5CD2C289453B300548EEE /* SSKAccessors+SDS.h in Headers */,
668A01142C2B6077007B8808 /* Threading.h in Headers */,
F9C5CBC8289453B300548EEE /* TSCall.h in Headers */,
F9C5CCD1289453B300548EEE /* TSContactThread.h in Headers */,
F9C5CBDD289453B300548EEE /* TSErrorMessage.h in Headers */,
F9C5CE74289453B400548EEE /* TSGroupModel.h in Headers */,
F9C5CCD2289453B300548EEE /* TSGroupThread.h in Headers */,
F9C5CBDC289453B300548EEE /* TSIncomingMessage.h in Headers */,
F9C5CBED289453B300548EEE /* TSInfoMessage.h in Headers */,
F9C5CBF2289453B300548EEE /* TSInteraction.h in Headers */,
@ -15658,9 +15617,7 @@
F9C5CBE0289453B300548EEE /* TSMessage.h in Headers */,
F9C5CBD2289453B300548EEE /* TSOutgoingMessage.h in Headers */,
F9C5CD82289453B300548EEE /* TSPaymentModels.h in Headers */,
F9C5CCCC289453B300548EEE /* TSPrivateStoryThread.h in Headers */,
F9C5CC03289453B300548EEE /* TSQuotedMessage.h in Headers */,
F9C5CCBA289453B300548EEE /* TSThread.h in Headers */,
F9C5CBF1289453B300548EEE /* TSUnreadIndicatorInteraction.h in Headers */,
F9C5CD61289453B300548EEE /* TSYapDatabaseObject.h in Headers */,
);
@ -19935,8 +19892,6 @@
F9C5CC5D289453B300548EEE /* TSCall.m in Sources */,
D91AC93E2B6337B200814975 /* TSCall.swift in Sources */,
F9C5CE67289453B400548EEE /* TSConstants.swift in Sources */,
F9C5CCD0289453B300548EEE /* TSContactThread+SDS.swift in Sources */,
F9C5CCD4289453B300548EEE /* TSContactThread.m in Sources */,
661170CA2ABA522200A1B16D /* TSContactThread.swift in Sources */,
D93086292C61672E008E3A27 /* TSErrorMessage+Builder.swift in Sources */,
F9C5CBE1289453B300548EEE /* TSErrorMessage+SDS.swift in Sources */,
@ -19948,8 +19903,6 @@
F9C5CE79289453B400548EEE /* TSGroupModel.swift in Sources */,
F9C5CE75289453B400548EEE /* TSGroupModelBuilder.swift in Sources */,
F9C5CCD5289453B300548EEE /* TSGroupThread+OWS.swift in Sources */,
F9C5CCCE289453B300548EEE /* TSGroupThread+SDS.swift in Sources */,
F9C5CCCD289453B300548EEE /* TSGroupThread.m in Sources */,
880FB40628CD205F00FA1C10 /* TSGroupThread.swift in Sources */,
F9C5CBDB289453B300548EEE /* TSIncomingMessage+Builder.swift in Sources */,
F9C5CBD5289453B300548EEE /* TSIncomingMessage+SDS.swift in Sources */,
@ -19995,16 +19948,12 @@
F9C5CD8A289453B300548EEE /* TSPaymentModels.m in Sources */,
F9C5CD83289453B300548EEE /* TSPaymentModels.swift in Sources */,
6642A87C2A8EB0D800E591C2 /* TSPaymentsActivationRequestModel.swift in Sources */,
F9C5CCCA289453B300548EEE /* TSPrivateStoryThread+SDS.swift in Sources */,
F9C5CCD3289453B300548EEE /* TSPrivateStoryThread.m in Sources */,
F9C5CCCB289453B300548EEE /* TSPrivateStoryThread.swift in Sources */,
F9C5CBE4289453B300548EEE /* TSQuotedMessage.m in Sources */,
6615553F2ABA5A7500AA302B /* TSRegistrationState.swift in Sources */,
66C2B1312A05D28A008DDE72 /* TSRequest.swift in Sources */,
6691E7EF2996E8FB0032A68A /* TSRequestOWSURLSessionMock.swift in Sources */,
F9C5CCCF289453B300548EEE /* TSThread+OWS.swift in Sources */,
F9C5CCDF289453B300548EEE /* TSThread+SDS.swift in Sources */,
F9C5CCDE289453B300548EEE /* TSThread.m in Sources */,
D91444E72CDD72C200221D81 /* TSThread.swift in Sources */,
F9C5CBE3289453B300548EEE /* TSUnreadIndicatorInteraction+SDS.swift in Sources */,
F9C5CBD9289453B300548EEE /* TSUnreadIndicatorInteraction.m in Sources */,
@ -20296,7 +20245,6 @@
F942628A289B1B5600460798 /* TSMessageTest.swift in Sources */,
D9495A702C76965600843BC1 /* TSOutgoingMessageRecipientStateTest.swift in Sources */,
F9426289289B1B5600460798 /* TSOutgoingMessageTest.swift in Sources */,
0517B9782BFCFF12002CDE7D /* TSThreadTests.swift in Sources */,
F942628F289B1B5600460798 /* TypingIndicatorMessageTest.swift in Sources */,
726C94252D6F48DA00ABF9B9 /* UncooperativeTimeoutTest.swift in Sources */,
F9426255289B1B5500460798 /* UnfairLockTest.swift in Sources */,

View File

@ -350,7 +350,7 @@ extension ConversationViewController: ConversationInputToolbarDelegate {
if
let threadTimestamp = thread.editTargetTimestamp,
threadTimestamp.uint64Value != editTarget?.timestamp ?? 0
threadTimestamp != editTarget?.timestamp ?? 0
{
return true
}

View File

@ -132,7 +132,7 @@ class MyStoriesViewController: OWSViewController, FailedStorySendDisplayControll
StoryManager.storyName(for: rhs),
) == .orderedAscending
}
return (lhs.lastSentStoryTimestamp?.uint64Value ?? 0) > (rhs.lastSentStoryTimestamp?.uint64Value ?? 0)
return (lhs.lastSentStoryTimestamp ?? 0) > (rhs.lastSentStoryTimestamp ?? 0)
})
tableView.reloadData()
}

View File

@ -78,9 +78,9 @@ class MockConversationView: UIView {
}()
// Use a v5 UUID that's in a separate namespace from ACIs/PNIs.
fileprivate static let mockAddress = SignalServiceAddress(try! ServiceId.parseFrom(serviceIdString: "00000000-0000-5000-8000-000000000000"))
fileprivate static let mockAci = try! Aci.parseFrom(serviceIdString: "00000000-0000-5000-8000-000000000000")
private let thread = MockThread(contactAddress: MockConversationView.mockAddress)
private let thread = MockThread(contactAci: MockConversationView.mockAci)
override var frame: CGRect {
didSet {
@ -211,28 +211,39 @@ class MockConversationView: UIView {
// MARK: - Mock Classes
private class MockThread: TSContactThread {
override var shouldBeSaved: Bool {
return false
init(contactAci: Aci) {
super.init(
uniqueId: "MockThread",
contactUUID: contactAci.serviceIdUppercaseString,
contactPhoneNumber: nil,
)
}
override var uniqueId: String { "MockThread" }
required init(inheritableDecoder decoder: any Decoder) throws {
owsFail("not supported")
}
override func anyWillInsert(with transaction: DBWriteTransaction) {
override func anyWillInsert(transaction: DBWriteTransaction) {
// no - op
owsFailDebug("shouldn't save mock thread")
owsFail("shouldn't save mock thread")
}
}
public class MockGroupThread: TSGroupThread {
override public var shouldBeSaved: Bool {
return false
class MockGroupThread: TSGroupThread {
init(groupModel: TSGroupModelV2) {
super.init(
uniqueId: "MockGroupThread",
groupModel: groupModel,
)
}
override public var uniqueId: String { "MockGroupThread" }
required init(inheritableDecoder decoder: any Decoder) throws {
owsFail("not supported")
}
override public func anyWillInsert(with transaction: DBWriteTransaction) {
override func anyWillInsert(transaction: DBWriteTransaction) {
// no - op
owsFailDebug("shouldn't save mock thread")
owsFail("shouldn't save mock thread")
}
}
@ -296,7 +307,7 @@ public class MockOutgoingMessage: TSOutgoingMessage {
override public func readRecipientAddresses() -> [SignalServiceAddress] {
// makes message appear as read
return [MockConversationView.mockAddress]
return [SignalServiceAddress(MockConversationView.mockAci)]
}
override public func recipientState(for recipientAddress: SignalServiceAddress) -> TSOutgoingMessageRecipientState? {

View File

@ -56,8 +56,7 @@ public final class BackupArchiveThreadStore {
context: BackupArchive.ChatRestoringContext,
) throws -> TSContactThread {
let thread = TSContactThread(contactAddress: context.recipientContext.localIdentifiers.aciAddress)
let record = thread.asRecord()
try record.insert(context.tx.database)
try thread.insert(context.tx.database)
return thread
}
@ -66,8 +65,7 @@ public final class BackupArchiveThreadStore {
context: BackupArchive.ChatRestoringContext,
) throws -> TSContactThread {
let thread = TSContactThread(contactAddress: address.asInteropAddress())
let record = thread.asRecord()
try record.insert(context.tx.database)
try thread.insert(context.tx.database)
return thread
}
@ -85,8 +83,7 @@ public final class BackupArchiveThreadStore {
default:
groupThread.storyViewMode = .default
}
let record = groupThread.asRecord()
try record.insert(context.tx.database)
try groupThread.insert(context.tx.database)
return groupThread
}
@ -127,9 +124,9 @@ public final class BackupArchiveThreadStore {
sql: """
UPDATE \(TSThread.databaseTableName)
SET
\(TSThreadSerializer.mentionNotificationModeColumn.columnName) = ?
\(threadColumn: .mentionNotificationMode) = ?
WHERE
\(TSThreadSerializer.idColumn.columnName) = ?;
\(threadColumn: .id) = ?;
""",
arguments: [TSThreadMentionNotificationMode.never.rawValue, thread.threadRowId],
)
@ -144,10 +141,10 @@ public final class BackupArchiveThreadStore {
sql: """
UPDATE \(TSThread.databaseTableName)
SET
\(TSThreadSerializer.shouldThreadBeVisibleColumn.columnName) = 1,
\(TSThreadSerializer.lastInteractionRowIdColumn.columnName) = ?
\(threadColumn: .shouldThreadBeVisible) = 1,
\(threadColumn: .lastInteractionRowId) = ?
WHERE
\(TSThreadSerializer.idColumn.columnName) = ?;
\(threadColumn: .id) = ?;
""",
arguments: [lastInteractionRowId ?? 0, thread.threadRowId],
)

View File

@ -49,7 +49,6 @@ public final class BackupArchiveStoryStore {
allowsReplies: allowReplies,
viewMode: viewMode,
)
var record = myStory.asRecord()
let existingMyStoryRowId = try Int64.fetchOne(
context.tx.database,
@ -59,12 +58,11 @@ public final class BackupArchiveStoryStore {
arguments: [TSPrivateStoryThread.myStoryUniqueId],
)
if let existingMyStoryRowId {
record.id = existingMyStoryRowId
myStory.updateRowId(existingMyStoryRowId)
myStory.id = existingMyStoryRowId
}
// Use save to insert or update as my story might already exist.
try record.save(context.tx.database)
try myStory.save(context.tx.database)
return myStory
}
@ -80,8 +78,7 @@ public final class BackupArchiveStoryStore {
_ storyThread: TSPrivateStoryThread,
context: BackupArchive.RecipientRestoringContext,
) throws {
let record = storyThread.asRecord()
try record.insert(context.tx.database)
try storyThread.insert(context.tx.database)
}
func createStoryContextAssociatedData(

View File

@ -21,7 +21,7 @@ public struct BackupArchiveExportProgress {
// all weighted evenly. Its just an estimate.
return try
SignalRecipient.fetchCount(tx.database)
+ ThreadRecord.fetchCount(tx.database)
+ TSThread.fetchCount(tx.database)
+ InteractionRecord.fetchCount(tx.database)
+ CallLinkRecord.fetchCount(tx.database)
+ StickerPackRecord.fetchCount(tx.database)

View File

@ -253,7 +253,7 @@ class BackupAttachmentDownloadStoreTests: XCTestCase {
private func insertThread(tx: DBWriteTransaction) -> TSThread {
let thread = TSThread(uniqueId: UUID().uuidString)
try! thread.asRecord().insert(tx.database)
try! thread.insert(tx.database)
return thread
}

View File

@ -327,7 +327,7 @@ class BackupAttachmentUploadStoreTests: XCTestCase {
private func insertThread(tx: DBWriteTransaction) -> TSThread {
let thread = TSThread(uniqueId: UUID().uuidString)
try! thread.asRecord().insert(tx.database)
try! thread.insert(tx.database)
return thread
}

View File

@ -323,7 +323,7 @@ public class BackupListMediaManagerTests {
tx: DBWriteTransaction,
) -> Attachment.IDType {
let thread = TSThread(uniqueId: UUID().uuidString)
try! thread.asRecord().insert(tx.database)
try! thread.insert(tx.database)
let attachmentParams = Attachment.ConstructionParams.mockStream(
mediaName: mediaName,
)

View File

@ -4,7 +4,6 @@
//
#import "OWSGroupCallMessage.h"
#import "TSGroupThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -4,7 +4,6 @@
//
#import "TSCall.h"
#import "TSContactThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -21,12 +21,12 @@ public class ContactThreadFinder: NSObject {
func contactThreads(for serviceId: ServiceId, tx: DBReadTransaction) -> [TSContactThread] {
let serviceIdString = serviceId.serviceIdUppercaseString
let sql = "SELECT * FROM \(TSThread.databaseTableName) WHERE \(threadColumn: .contactUUID) = ?"
let sql = "SELECT * FROM \(TSThread.databaseTableName) WHERE \(contactThreadColumn: .contactUUID) = ?"
return fetchContactThreads(sql: sql, arguments: [serviceIdString], tx: tx)
}
func contactThreads(for phoneNumber: String, tx: DBReadTransaction) -> [TSContactThread] {
let sql = "SELECT * FROM \(TSThread.databaseTableName) WHERE \(threadColumn: .contactPhoneNumber) = ?"
let sql = "SELECT * FROM \(TSThread.databaseTableName) WHERE \(contactThreadColumn: .contactPhoneNumber) = ?"
return fetchContactThreads(sql: sql, arguments: [phoneNumber], tx: tx)
}

View File

@ -1,998 +0,0 @@
//
// 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 let lastDraftInteractionRowId: UInt64
public let lastDraftUpdateTimestamp: 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
case lastDraftInteractionRowId
case lastDraftUpdateTimestamp
}
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].flatMap { SDSRecordType(rawValue: $0) }
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]
lastDraftInteractionRowId = row[26]
lastDraftUpdateTimestamp = row[27]
}
}
// 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.missingRequiredField(fieldName: "id") }
guard let recordType = record.recordType else { throw SDSError.missingRequiredField(fieldName: "recordType") }
switch 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 lastDraftInteractionRowId: UInt64 = record.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = record.lastDraftUpdateTimestamp
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 messageDraftBodyRangesSerialized.map({ try SDSDeserialization.unarchivedObject(ofClass: MessageBodyRanges.self, from: $0) })
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,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
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 lastDraftInteractionRowId: UInt64 = record.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = record.lastDraftUpdateTimestamp
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 messageDraftBodyRangesSerialized.map({ try SDSDeserialization.unarchivedObject(ofClass: MessageBodyRanges.self, from: $0) })
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 = try record.groupModel ?? { () -> Data in throw SDSError.missingRequiredField(fieldName: "groupModel") }()
let groupModel: TSGroupModel = try SDSDeserialization.unarchivedObject(ofClass: TSGroupModel.self, from: groupModelSerialized)
return TSGroupThread(grdbId: recordId,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
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 lastDraftInteractionRowId: UInt64 = record.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = record.lastDraftUpdateTimestamp
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 messageDraftBodyRangesSerialized.map({ try SDSDeserialization.unarchivedObject(ofClass: MessageBodyRanges.self, from: $0) })
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 addresses: Data? = SDSDeserialization.optionalData(record.addresses, 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,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
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 lastDraftInteractionRowId: UInt64 = record.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = record.lastDraftUpdateTimestamp
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 messageDraftBodyRangesSerialized.map({ try SDSDeserialization.unarchivedObject(ofClass: MessageBodyRanges.self, from: $0) })
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,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
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: \(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 lastDraftInteractionRowId: UInt64 = modelToCopy.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = modelToCopy.lastDraftUpdateTimestamp
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: Data? = 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,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
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 lastDraftInteractionRowId: UInt64 = modelToCopy.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = modelToCopy.lastDraftUpdateTimestamp
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,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
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 lastDraftInteractionRowId: UInt64 = modelToCopy.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = modelToCopy.lastDraftUpdateTimestamp
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,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
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 lastDraftInteractionRowId: UInt64 = modelToCopy.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = modelToCopy.lastDraftUpdateTimestamp
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,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
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) }
static var lastDraftInteractionRowIdColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "lastDraftInteractionRowId", columnType: .int64) }
static var lastDraftUpdateTimestampColumn: SDSColumnMetadata { SDSColumnMetadata(columnName: "lastDraftUpdateTimestamp", columnType: .int64) }
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,
lastDraftInteractionRowIdColumn,
lastDraftUpdateTimestampColumn,
]
)
}
}
// MARK: - Save/Remove/Update
@objc
public extension TSThread {
func anyInsert(transaction: DBWriteTransaction) {
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: DBWriteTransaction) {
let isInserting: Bool
if TSThread.fetchViaCache(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: DBWriteTransaction, block: (TSThread) -> Void) {
block(self)
// If it's not saved, we don't expect to find it in the database, and we
// won't save any changes we make back into the database.
guard shouldBeSaved else {
return
}
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: DBWriteTransaction) {
sdsSave(saveMode: .update, transaction: transaction)
}
}
// MARK: - TSThreadCursor
@objc
public class TSThreadCursor: NSObject, SDSCursor {
private let transaction: DBReadTransaction
private let cursor: RecordCursor<ThreadRecord>
init(transaction: DBReadTransaction, cursor: RecordCursor<ThreadRecord>) {
self.transaction = transaction
self.cursor = cursor
}
public func next() throws -> TSThread? {
guard let record = try cursor.next() else {
return nil
}
let value = try TSThread.fromRecord(record)
SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didReadThread(value, transaction: transaction)
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 {
@nonobjc
class func grdbFetchCursor(transaction: DBReadTransaction) -> TSThreadCursor {
let database = transaction.database
return failIfThrows {
let cursor = try ThreadRecord.fetchCursor(database)
return TSThreadCursor(transaction: transaction, cursor: cursor)
}
}
// Fetches a single model by "unique id".
class func fetchViaCache(uniqueId: String, transaction: DBReadTransaction) -> TSThread? {
assert(!uniqueId.isEmpty)
if let cachedCopy = SSKEnvironment.shared.modelReadCachesRef.threadReadCache.getThread(uniqueId: uniqueId, transaction: transaction) {
return cachedCopy
}
return anyFetch(uniqueId: uniqueId, transaction: transaction)
}
// Fetches a single model by "unique id".
class func anyFetch(uniqueId: String, transaction: DBReadTransaction) -> TSThread? {
assert(!uniqueId.isEmpty)
let sql = "SELECT * FROM \(ThreadRecord.databaseTableName) WHERE \(threadColumn: .uniqueId) = ?"
return grdbFetchOne(sql: sql, arguments: [uniqueId], transaction: transaction)
}
// Traverses all records.
// Records are not visited in any particular order.
class func anyEnumerate(
transaction: DBReadTransaction,
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: DBReadTransaction,
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: DBReadTransaction,
batchSize: UInt,
block: (TSThread, UnsafeMutablePointer<ObjCBool>) -> Void
) {
let cursor = TSThread.grdbFetchCursor(transaction: transaction)
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)")
}
})
}
// Does not order the results.
class func anyFetchAll(transaction: DBReadTransaction) -> [TSThread] {
var result = [TSThread]()
anyEnumerate(transaction: transaction) { (model, _) in
result.append(model)
}
return result
}
class func anyCount(transaction: DBReadTransaction) -> UInt {
return ThreadRecord.ows_fetchCount(transaction.database)
}
}
// MARK: - Swift Fetch
public extension TSThread {
class func grdbFetchCursor(sql: String,
arguments: StatementArguments = StatementArguments(),
transaction: DBReadTransaction) -> TSThreadCursor {
return failIfThrows {
let sqlRequest = SQLRequest<Void>(sql: sql, arguments: arguments, cached: true)
let cursor = try ThreadRecord.fetchCursor(transaction.database, sqlRequest)
return TSThreadCursor(transaction: transaction, cursor: cursor)
}
}
class func grdbFetchOne(sql: String,
arguments: StatementArguments = StatementArguments(),
transaction: DBReadTransaction) -> 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)
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 })
let lastDraftInteractionRowId: UInt64 = model.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = model.lastDraftUpdateTimestamp
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, lastDraftInteractionRowId: lastDraftInteractionRowId, lastDraftUpdateTimestamp: lastDraftUpdateTimestamp)
}
}

View File

@ -1,147 +0,0 @@
//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import <SignalServiceKit/BaseModel.h>
NS_ASSUME_NONNULL_BEGIN
@class DBReadTransaction;
@class DBWriteTransaction;
@class MessageBody;
@class MessageBodyRanges;
@class SignalServiceAddress;
@class TSInteraction;
@class TSInvalidIdentityKeyReceivingErrorMessage;
@class ThreadReplyInfoObjC;
typedef NS_CLOSED_ENUM(NSUInteger, TSThreadMentionNotificationMode) {
TSThreadMentionNotificationMode_Default = 0,
TSThreadMentionNotificationMode_Always,
TSThreadMentionNotificationMode_Never
};
typedef NS_CLOSED_ENUM(NSUInteger, TSThreadStoryViewMode) {
TSThreadStoryViewMode_Default = 0,
TSThreadStoryViewMode_Explicit,
TSThreadStoryViewMode_BlockList,
TSThreadStoryViewMode_Disabled
};
/**
* TSThread is the superclass of TSContactThread, TSGroupThread, and TSPrivateStoryThread
*/
@interface TSThread : BaseModel
@property (nonatomic) TSThreadStoryViewMode storyViewMode;
@property (nonatomic, nullable) NSNumber *lastSentStoryTimestamp;
@property (nonatomic) BOOL shouldThreadBeVisible;
@property (nonatomic, readonly, nullable) NSDate *creationDate;
@property (nonatomic, readonly) BOOL isArchivedByLegacyTimestampForSorting DEPRECATED_MSG_ATTRIBUTE(
"this property is only to be used in the sortId migration");
@property (nonatomic, readonly) BOOL isArchivedObsolete;
@property (nonatomic, readonly) BOOL isMarkedUnreadObsolete;
// This maintains the row Id that was at the bottom of the conversation
// the last time the user viewed this thread so we can restore their
// scroll position.
//
// If the referenced message is deleted, this value is
// updated to point to the previous message in the conversation.
//
// If a new message is inserted into the conversation, this value
// is cleared. We only restore this state if there are no unread messages.
@property (nonatomic, readonly) uint64_t lastVisibleSortIdObsolete;
@property (nonatomic, readonly) double lastVisibleSortIdOnScreenPercentageObsolete;
@property (nonatomic, copy, nullable) NSString *messageDraft;
@property (nonatomic, nullable) MessageBodyRanges *messageDraftBodyRanges;
// zero if thread has never had an interaction.
// The corresponding interaction may have been deleted.
@property (nonatomic) uint64_t lastInteractionRowId;
// These are used to maintain the ordering of drafts in the chat list.
// When a draft is saved, the lastDraftInteractionRowId for that thread
// should be set to the max lastInteractionRowId across all threads to
// prioritize it in the chat list. lastDraftUpdateTimestamp
// can be used to break ties between threads with the same lastDraftInteractionRowId.
@property (nonatomic) uint64_t lastDraftInteractionRowId;
@property (nonatomic) uint64_t lastDraftUpdateTimestamp;
@property (nonatomic, nullable) NSNumber *editTargetTimestamp;
@property (atomic, readonly) uint64_t mutedUntilTimestampObsolete;
@property (nonatomic, readonly, nullable) NSDate *mutedUntilDateObsolete;
@property (nonatomic) TSThreadMentionNotificationMode mentionNotificationMode;
- (instancetype)initWithUniqueId:(NSString *)uniqueId NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithGrdbId:(int64_t)grdbId uniqueId:(NSString *)uniqueId NS_UNAVAILABLE;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode
NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:conversationColorNameObsolete:creationDate:editTargetTimestamp:isArchivedObsolete:isMarkedUnreadObsolete:lastDraftInteractionRowId:lastDraftUpdateTimestamp:lastInteractionRowId:lastSentStoryTimestamp:lastVisibleSortIdObsolete:lastVisibleSortIdOnScreenPercentageObsolete:mentionNotificationMode:messageDraft:messageDraftBodyRanges:mutedUntilDateObsolete:mutedUntilTimestampObsolete:shouldThreadBeVisible:storyViewMode:));
// clang-format on
// --- CODE GENERATION MARKER
@property (nonatomic, readonly) NSString *conversationColorNameObsolete;
/**
* @returns recipientId for each recipient in the thread
*/
@property (nonatomic, readonly) NSArray<SignalServiceAddress *> *recipientAddressesWithSneakyTransaction;
- (NSArray<SignalServiceAddress *> *)recipientAddressesWithTransaction:(DBReadTransaction *)transaction;
@property (nonatomic, readonly) BOOL isNoteToSelf;
#pragma mark Interactions
- (BOOL)hasSafetyNumbers;
- (nullable TSInteraction *)lastInteractionForInboxForChatListSorting:(BOOL)isForSorting
transaction:(DBReadTransaction *)transaction
NS_SWIFT_NAME(lastInteractionForInbox(forChatListSorting:transaction:));
- (nullable TSInteraction *)firstInteractionAtOrAroundSortId:(uint64_t)sortId
transaction:(DBReadTransaction *)transaction
NS_SWIFT_NAME(firstInteraction(atOrAroundSortId:transaction:));
#pragma mark - Merging
- (void)mergeFrom:(TSThread *)otherThread;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,304 +0,0 @@
//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "TSThread.h"
#import "OWSReadTracking.h"
#import "TSIncomingMessage.h"
#import "TSInfoMessage.h"
#import "TSInteraction.h"
#import "TSInvalidIdentityKeyReceivingErrorMessage.h"
#import "TSOutgoingMessage.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
@import Intents;
NS_ASSUME_NONNULL_BEGIN
@interface TSThread ()
@property (nonatomic, nullable) NSDate *creationDate;
@property (nonatomic) BOOL isArchivedObsolete;
@property (nonatomic) BOOL isMarkedUnreadObsolete;
@property (atomic) uint64_t mutedUntilTimestampObsolete;
@property (nonatomic, nullable) NSDate *mutedUntilDateObsolete;
@property (nonatomic) uint64_t lastVisibleSortIdObsolete;
@property (nonatomic) double lastVisibleSortIdOnScreenPercentageObsolete;
@end
#pragma mark -
@implementation TSThread
- (instancetype)initWithUniqueId:(NSString *)uniqueId
{
self = [super initWithUniqueId:uniqueId];
if (self) {
_creationDate = [NSDate date];
_messageDraft = nil;
_conversationColorNameObsolete = @"Obsolete";
}
return self;
}
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode
{
self = [super initWithGrdbId:grdbId
uniqueId:uniqueId];
if (!self) {
return self;
}
_conversationColorNameObsolete = conversationColorNameObsolete;
_creationDate = creationDate;
_editTargetTimestamp = editTargetTimestamp;
_isArchivedObsolete = isArchivedObsolete;
_isMarkedUnreadObsolete = isMarkedUnreadObsolete;
_lastDraftInteractionRowId = lastDraftInteractionRowId;
_lastDraftUpdateTimestamp = lastDraftUpdateTimestamp;
_lastInteractionRowId = lastInteractionRowId;
_lastSentStoryTimestamp = lastSentStoryTimestamp;
_lastVisibleSortIdObsolete = lastVisibleSortIdObsolete;
_lastVisibleSortIdOnScreenPercentageObsolete = lastVisibleSortIdOnScreenPercentageObsolete;
_mentionNotificationMode = mentionNotificationMode;
_messageDraft = messageDraft;
_messageDraftBodyRanges = messageDraftBodyRanges;
_mutedUntilDateObsolete = mutedUntilDateObsolete;
_mutedUntilTimestampObsolete = mutedUntilTimestampObsolete;
_shouldThreadBeVisible = shouldThreadBeVisible;
_storyViewMode = storyViewMode;
return self;
}
// clang-format on
// --- CODE GENERATION MARKER
- (NSUInteger)hash
{
NSUInteger result = [super hash];
result ^= self.conversationColorNameObsolete.hash;
result ^= self.creationDate.hash;
result ^= self.editTargetTimestamp.hash;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
result ^= self.isArchivedByLegacyTimestampForSorting;
#pragma clang diagnostic pop
result ^= self.isArchivedObsolete;
result ^= self.isMarkedUnreadObsolete;
result ^= self.lastDraftInteractionRowId;
result ^= self.lastDraftUpdateTimestamp;
result ^= self.lastInteractionRowId;
result ^= self.lastSentStoryTimestamp.hash;
result ^= self.lastVisibleSortIdObsolete;
result ^= @(self.lastVisibleSortIdOnScreenPercentageObsolete).stringValue.hash;
result ^= self.mentionNotificationMode;
result ^= self.messageDraft.hash;
result ^= self.messageDraftBodyRanges.hash;
result ^= self.mutedUntilDateObsolete.hash;
result ^= self.mutedUntilTimestampObsolete;
result ^= self.shouldThreadBeVisible;
result ^= self.storyViewMode;
return result;
}
- (BOOL)isEqual:(id)other
{
if (![super isEqual:other]) {
return NO;
}
TSThread *typedOther = (TSThread *)other;
if (![NSObject isObject:self.conversationColorNameObsolete
equalToObject:typedOther.conversationColorNameObsolete]) {
return NO;
}
if (![NSObject isObject:self.creationDate equalToObject:typedOther.creationDate]) {
return NO;
}
if (![NSObject isObject:self.editTargetTimestamp equalToObject:typedOther.editTargetTimestamp]) {
return NO;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (self.isArchivedByLegacyTimestampForSorting != typedOther.isArchivedByLegacyTimestampForSorting) {
return NO;
}
#pragma clang diagnostic pop
if (self.isArchivedObsolete != typedOther.isArchivedObsolete) {
return NO;
}
if (self.isMarkedUnreadObsolete != typedOther.isMarkedUnreadObsolete) {
return NO;
}
if (self.lastDraftInteractionRowId != typedOther.lastDraftInteractionRowId) {
return NO;
}
if (self.lastDraftUpdateTimestamp != typedOther.lastDraftUpdateTimestamp) {
return NO;
}
if (self.lastInteractionRowId != typedOther.lastInteractionRowId) {
return NO;
}
if (![NSObject isObject:self.lastSentStoryTimestamp equalToObject:typedOther.lastSentStoryTimestamp]) {
return NO;
}
if (self.lastVisibleSortIdObsolete != typedOther.lastVisibleSortIdObsolete) {
return NO;
}
if (self.lastVisibleSortIdOnScreenPercentageObsolete != typedOther.lastVisibleSortIdOnScreenPercentageObsolete) {
return NO;
}
if (self.mentionNotificationMode != typedOther.mentionNotificationMode) {
return NO;
}
if (![NSObject isObject:self.messageDraft equalToObject:typedOther.messageDraft]) {
return NO;
}
if (![NSObject isObject:self.messageDraftBodyRanges equalToObject:typedOther.messageDraftBodyRanges]) {
return NO;
}
if (![NSObject isObject:self.mutedUntilDateObsolete equalToObject:typedOther.mutedUntilDateObsolete]) {
return NO;
}
if (self.mutedUntilTimestampObsolete != typedOther.mutedUntilTimestampObsolete) {
return NO;
}
if (self.shouldThreadBeVisible != typedOther.shouldThreadBeVisible) {
return NO;
}
if (self.storyViewMode != typedOther.storyViewMode) {
return NO;
}
return YES;
}
- (void)anyDidInsertWithTransaction:(DBWriteTransaction *)transaction
{
[super anyDidInsertWithTransaction:transaction];
[ThreadAssociatedData createFor:self.uniqueId transaction:transaction];
if (self.shouldThreadBeVisible && ![SSKPreferences hasSavedThreadWithTransaction:transaction]) {
[SSKPreferences setHasSavedThread:YES transaction:transaction];
}
[self _anyDidInsertWithTx:transaction];
[SSKEnvironment.shared.modelReadCachesRef.threadReadCache didInsertOrUpdateThread:self transaction:transaction];
}
- (void)anyDidUpdateWithTransaction:(DBWriteTransaction *)transaction
{
[super anyDidUpdateWithTransaction:transaction];
if (self.shouldThreadBeVisible && ![SSKPreferences hasSavedThreadWithTransaction:transaction]) {
[SSKPreferences setHasSavedThread:YES transaction:transaction];
}
[SSKEnvironment.shared.modelReadCachesRef.threadReadCache didInsertOrUpdateThread:self transaction:transaction];
[PinnedThreadManagerObjcBridge handleUpdatedThread:self transaction:transaction];
}
- (BOOL)isNoteToSelf
{
return NO;
}
#pragma mark - To be subclassed.
- (NSArray<SignalServiceAddress *> *)recipientAddressesWithSneakyTransaction
{
__block NSArray<SignalServiceAddress *> *recipientAddresses;
[SSKEnvironment.shared.databaseStorageRef readWithBlock:^(
DBReadTransaction *transaction) { recipientAddresses = [self recipientAddressesWithTransaction:transaction]; }];
return recipientAddresses;
}
- (NSArray<SignalServiceAddress *> *)recipientAddressesWithTransaction:(DBReadTransaction *)transaction
{
OWSAbstractMethod();
return @[];
}
- (BOOL)hasSafetyNumbers
{
return NO;
}
#pragma mark - Interactions
- (nullable TSInteraction *)lastInteractionForInboxForChatListSorting:(BOOL)isForSorting
transaction:(DBReadTransaction *)transaction
{
OWSAssertDebug(transaction);
return [[[InteractionFinder alloc] initWithThreadUniqueId:self.uniqueId]
mostRecentInteractionForInboxForChatListSorting:isForSorting
transaction:transaction];
}
- (nullable TSInteraction *)firstInteractionAtOrAroundSortId:(uint64_t)sortId
transaction:(DBReadTransaction *)transaction
{
OWSAssertDebug(transaction);
return
[[[InteractionFinder alloc] initWithThreadUniqueId:self.uniqueId] firstInteractionAtOrAroundSortId:sortId
transaction:transaction];
}
#pragma mark - Merging
- (void)mergeFrom:(TSThread *)otherThread
{
self.shouldThreadBeVisible = self.shouldThreadBeVisible || otherThread.shouldThreadBeVisible;
self.lastInteractionRowId = MAX(self.lastInteractionRowId, otherThread.lastInteractionRowId);
// Copy the draft if this thread doesn't have one. We always assign both
// values if we assign one of them since they're related.
if (self.messageDraft == nil) {
self.messageDraft = otherThread.messageDraft;
self.messageDraftBodyRanges = otherThread.messageDraftBodyRanges;
self.lastDraftInteractionRowId = otherThread.lastDraftInteractionRowId;
self.lastDraftUpdateTimestamp = otherThread.lastDraftUpdateTimestamp;
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -3,11 +3,367 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import GRDB
public import GRDB
public import LibSignalClient
extension TSThread {
public static var databaseTableName: String { ThreadRecord.databaseTableName }
public enum TSThreadMentionNotificationMode: UInt {
case `default` = 0
case always = 1
case never = 2
}
public enum TSThreadStoryViewMode: UInt {
case `default` = 0
case explicit = 1
case blockList = 2
case disabled = 3
}
@objc
open class TSThread: NSObject, SDSCodableModel, InheritableRecord {
public static let databaseTableName: String = "model_TSThread"
public class var recordType: SDSRecordType { .thread }
static func concreteType(forRecordType recordType: UInt) -> (any InheritableRecord.Type)? {
switch recordType {
case SDSRecordType.thread.rawValue: TSThread.self
case SDSRecordType.contactThread.rawValue: TSContactThread.self
case SDSRecordType.groupThread.rawValue: TSGroupThread.self
case SDSRecordType.privateStoryThread.rawValue: TSPrivateStoryThread.self
default: nil
}
}
public var id: Int64?
public var sqliteRowId: Int64? { self.id }
@objc
public let uniqueId: String
public let conversationColorNameObsolete: String
public let creationDate: Date?
public let isArchivedObsolete: Bool
// zero if thread has never had an interaction.
// The corresponding interaction may have been deleted.
public internal(set) var lastInteractionRowId: UInt64
public internal(set) var messageDraft: String?
public let mutedUntilDateObsolete: Date?
public internal(set) var shouldThreadBeVisible: Bool
public let isMarkedUnreadObsolete: Bool
public let lastVisibleSortIdOnScreenPercentageObsolete: Double
// This maintains the row Id that was at the bottom of the conversation
// the last time the user viewed this thread so we can restore their
// scroll position.
//
// If the referenced message is deleted, this value is
// updated to point to the previous message in the conversation.
//
// If a new message is inserted into the conversation, this value
// is cleared. We only restore this state if there are no unread messages.
public let lastVisibleSortIdObsolete: UInt64
public private(set) var messageDraftBodyRanges: MessageBodyRanges?
public private(set) var mentionNotificationMode: TSThreadMentionNotificationMode
public let mutedUntilTimestampObsolete: UInt64
public private(set) var lastSentStoryTimestamp: UInt64?
public internal(set) var storyViewMode: TSThreadStoryViewMode
public private(set) var editTargetTimestamp: UInt64?
// These are used to maintain the ordering of drafts in the chat list.
// When a draft is saved, the lastDraftInteractionRowId for that thread
// should be set to the max lastInteractionRowId across all threads to
// prioritize it in the chat list. lastDraftUpdateTimestamp
// can be used to break ties between threads with the same lastDraftInteractionRowId.
public internal(set) var lastDraftInteractionRowId: UInt64
public internal(set) var lastDraftUpdateTimestamp: UInt64
// DEPRECATED_MSG_ATTRIBUTE("this property is only to be used in the sortId migration");
let isArchivedByLegacyTimestampForSorting: Bool = false
public enum CodingKeys: String, CodingKey, ColumnExpression {
case id
case recordType
case uniqueId
case conversationColorNameObsolete = "conversationColorName"
case creationDate
case editTargetTimestamp
case isArchivedObsolete = "isArchived"
case isMarkedUnreadObsolete = "isMarkedUnread"
case lastDraftInteractionRowId
case lastDraftUpdateTimestamp
case lastInteractionRowId
case lastSentStoryTimestamp
case lastVisibleSortIdObsolete = "lastVisibleSortId"
case lastVisibleSortIdOnScreenPercentageObsolete = "lastVisibleSortIdOnScreenPercentage"
case mentionNotificationMode
case messageDraft
case messageDraftBodyRanges
case mutedUntilDateObsolete = "mutedUntilDate"
case mutedUntilTimestampObsolete = "mutedUntilTimestamp"
case shouldThreadBeVisible
case storyViewMode
}
public required init(inheritableDecoder decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(Int64.self, forKey: .id)
self.uniqueId = try container.decode(String.self, forKey: .uniqueId)
self.conversationColorNameObsolete = try container.decode(String.self, forKey: .conversationColorNameObsolete)
self.creationDate = try container.decodeIfPresent(TimeInterval.self, forKey: .creationDate).map(Date.init(timeIntervalSince1970:))
self.editTargetTimestamp = try container.decodeIfPresent(UInt64.self, forKey: .editTargetTimestamp)
self.isArchivedObsolete = try container.decode(Bool.self, forKey: .isArchivedObsolete)
self.isMarkedUnreadObsolete = try container.decode(Bool.self, forKey: .isMarkedUnreadObsolete)
self.lastDraftInteractionRowId = try container.decode(UInt64.self, forKey: .lastDraftInteractionRowId)
self.lastDraftUpdateTimestamp = try container.decode(UInt64.self, forKey: .lastDraftUpdateTimestamp)
self.lastInteractionRowId = try container.decode(UInt64.self, forKey: .lastInteractionRowId)
self.lastSentStoryTimestamp = try container.decodeIfPresent(UInt64.self, forKey: .lastSentStoryTimestamp)
self.lastVisibleSortIdObsolete = try container.decode(UInt64.self, forKey: .lastVisibleSortIdObsolete)
self.lastVisibleSortIdOnScreenPercentageObsolete = try container.decode(Double.self, forKey: .lastVisibleSortIdOnScreenPercentageObsolete)
self.mentionNotificationMode = TSThreadMentionNotificationMode(rawValue: try container.decode(UInt.self, forKey: .mentionNotificationMode)) ?? .default
self.messageDraft = try container.decodeIfPresent(String.self, forKey: .messageDraft)
self.messageDraftBodyRanges = try container.decodeIfPresent(Data.self, forKey: .messageDraftBodyRanges).map({ try LegacySDSSerializer().deserializeLegacySDSData($0, ofClass: MessageBodyRanges.self) })
self.mutedUntilDateObsolete = try container.decodeIfPresent(TimeInterval.self, forKey: .mutedUntilDateObsolete).map(Date.init(timeIntervalSince1970:))
self.mutedUntilTimestampObsolete = try container.decode(UInt64.self, forKey: .mutedUntilTimestampObsolete)
self.shouldThreadBeVisible = try container.decode(Bool.self, forKey: .shouldThreadBeVisible)
self.storyViewMode = TSThreadStoryViewMode(rawValue: try container.decode(UInt.self, forKey: .storyViewMode)) ?? .default
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(Self.recordType.rawValue, forKey: .recordType)
try container.encode(self.uniqueId, forKey: .uniqueId)
try container.encode(self.conversationColorNameObsolete, forKey: .conversationColorNameObsolete)
try container.encode(self.creationDate?.timeIntervalSince1970, forKey: .creationDate)
try container.encode(self.editTargetTimestamp, forKey: .editTargetTimestamp)
try container.encode(self.isArchivedObsolete, forKey: .isArchivedObsolete)
try container.encode(self.lastDraftInteractionRowId, forKey: .lastDraftInteractionRowId)
try container.encode(self.lastDraftUpdateTimestamp, forKey: .lastDraftUpdateTimestamp)
try container.encode(self.lastInteractionRowId, forKey: .lastInteractionRowId)
try container.encode(self.lastSentStoryTimestamp, forKey: .lastSentStoryTimestamp)
try container.encode(self.lastVisibleSortIdObsolete, forKey: .lastVisibleSortIdObsolete)
try container.encode(self.lastVisibleSortIdOnScreenPercentageObsolete, forKey: .lastVisibleSortIdOnScreenPercentageObsolete)
try container.encode(self.mentionNotificationMode.rawValue, forKey: .mentionNotificationMode)
try container.encode(self.messageDraft, forKey: .messageDraft)
let messageDraftBodyRangesData = self.messageDraftBodyRanges.map(LegacySDSSerializer().serializeAsLegacySDSData(_:))
try container.encode(messageDraftBodyRangesData, forKey: .messageDraftBodyRanges)
try container.encode(self.mutedUntilDateObsolete, forKey: .mutedUntilDateObsolete)
try container.encode(self.mutedUntilTimestampObsolete, forKey: .mutedUntilTimestampObsolete)
try container.encode(self.shouldThreadBeVisible, forKey: .shouldThreadBeVisible)
try container.encode(self.storyViewMode.rawValue, forKey: .storyViewMode)
}
init(
id: Int64?,
uniqueId: String,
conversationColorNameObsolete: String,
creationDate: Date?,
editTargetTimestamp: UInt64?,
isArchivedObsolete: Bool,
isMarkedUnreadObsolete: Bool,
lastDraftInteractionRowId: UInt64,
lastDraftUpdateTimestamp: UInt64,
lastInteractionRowId: UInt64,
lastSentStoryTimestamp: UInt64?,
lastVisibleSortIdObsolete: UInt64,
lastVisibleSortIdOnScreenPercentageObsolete: Double,
mentionNotificationMode: TSThreadMentionNotificationMode,
messageDraft: String?,
messageDraftBodyRanges: MessageBodyRanges?,
mutedUntilDateObsolete: Date?,
mutedUntilTimestampObsolete: UInt64,
shouldThreadBeVisible: Bool,
storyViewMode: TSThreadStoryViewMode,
) {
self.id = id
self.uniqueId = uniqueId
self.conversationColorNameObsolete = conversationColorNameObsolete
self.creationDate = creationDate
self.editTargetTimestamp = editTargetTimestamp
self.isArchivedObsolete = isArchivedObsolete
self.isMarkedUnreadObsolete = isMarkedUnreadObsolete
self.lastDraftInteractionRowId = lastDraftInteractionRowId
self.lastDraftUpdateTimestamp = lastDraftUpdateTimestamp
self.lastInteractionRowId = lastInteractionRowId
self.lastSentStoryTimestamp = lastSentStoryTimestamp
self.lastVisibleSortIdObsolete = lastVisibleSortIdObsolete
self.lastVisibleSortIdOnScreenPercentageObsolete = lastVisibleSortIdOnScreenPercentageObsolete
self.mentionNotificationMode = mentionNotificationMode
self.messageDraft = messageDraft
self.messageDraftBodyRanges = messageDraftBodyRanges
self.mutedUntilDateObsolete = mutedUntilDateObsolete
self.mutedUntilTimestampObsolete = mutedUntilTimestampObsolete
self.shouldThreadBeVisible = shouldThreadBeVisible
self.storyViewMode = storyViewMode
}
init(uniqueId: String) {
self.conversationColorNameObsolete = "Obsolete"
self.isArchivedObsolete = false
self.isMarkedUnreadObsolete = false
self.lastDraftInteractionRowId = 0
self.lastDraftUpdateTimestamp = 0
self.lastInteractionRowId = 0
self.lastVisibleSortIdObsolete = 0
self.lastVisibleSortIdOnScreenPercentageObsolete = 0
self.mentionNotificationMode = .default
self.messageDraft = nil
self.mutedUntilDateObsolete = nil
self.mutedUntilTimestampObsolete = 0
self.shouldThreadBeVisible = false
self.storyViewMode = .default
self.uniqueId = uniqueId
self.creationDate = Date()
}
func deepCopy() -> TSThread {
return TSThread(
id: self.id,
uniqueId: self.uniqueId,
conversationColorNameObsolete: self.conversationColorNameObsolete,
creationDate: self.creationDate,
editTargetTimestamp: self.editTargetTimestamp,
isArchivedObsolete: self.isArchivedObsolete,
isMarkedUnreadObsolete: self.isMarkedUnreadObsolete,
lastDraftInteractionRowId: self.lastDraftInteractionRowId,
lastDraftUpdateTimestamp: self.lastDraftUpdateTimestamp,
lastInteractionRowId: self.lastInteractionRowId,
lastSentStoryTimestamp: self.lastSentStoryTimestamp,
lastVisibleSortIdObsolete: self.lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: self.lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: self.mentionNotificationMode,
messageDraft: self.messageDraft,
messageDraftBodyRanges: self.messageDraftBodyRanges,
mutedUntilDateObsolete: self.mutedUntilDateObsolete,
mutedUntilTimestampObsolete: self.mutedUntilTimestampObsolete,
shouldThreadBeVisible: self.shouldThreadBeVisible,
storyViewMode: self.storyViewMode,
)
}
override public var hash: Int {
var hasher = Hasher()
hasher.combine(self.id)
hasher.combine(self.uniqueId)
hasher.combine(self.conversationColorNameObsolete)
hasher.combine(self.creationDate)
hasher.combine(self.editTargetTimestamp)
hasher.combine(self.isArchivedByLegacyTimestampForSorting)
hasher.combine(self.isArchivedObsolete)
hasher.combine(self.isMarkedUnreadObsolete)
hasher.combine(self.lastDraftInteractionRowId)
hasher.combine(self.lastDraftUpdateTimestamp)
hasher.combine(self.lastInteractionRowId)
hasher.combine(self.lastSentStoryTimestamp)
hasher.combine(self.lastVisibleSortIdObsolete)
hasher.combine(self.lastVisibleSortIdOnScreenPercentageObsolete)
hasher.combine(self.mentionNotificationMode)
hasher.combine(self.messageDraft)
hasher.combine(self.messageDraftBodyRanges)
hasher.combine(self.mutedUntilDateObsolete)
hasher.combine(self.mutedUntilTimestampObsolete)
hasher.combine(self.shouldThreadBeVisible)
hasher.combine(self.storyViewMode)
return hasher.finalize()
}
override public func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Self else { return false }
guard self.id == object.id else { return false }
guard self.uniqueId == object.uniqueId else { return false }
guard self.conversationColorNameObsolete == object.conversationColorNameObsolete else { return false }
guard self.creationDate == object.creationDate else { return false }
guard self.editTargetTimestamp == object.editTargetTimestamp else { return false }
guard self.isArchivedByLegacyTimestampForSorting == object.isArchivedByLegacyTimestampForSorting else { return false }
guard self.isArchivedObsolete == object.isArchivedObsolete else { return false }
guard self.isMarkedUnreadObsolete == object.isMarkedUnreadObsolete else { return false }
guard self.lastDraftInteractionRowId == object.lastDraftInteractionRowId else { return false }
guard self.lastDraftUpdateTimestamp == object.lastDraftUpdateTimestamp else { return false }
guard self.lastInteractionRowId == object.lastInteractionRowId else { return false }
guard self.lastSentStoryTimestamp == object.lastSentStoryTimestamp else { return false }
guard self.lastVisibleSortIdObsolete == object.lastVisibleSortIdObsolete else { return false }
guard self.lastVisibleSortIdOnScreenPercentageObsolete == object.lastVisibleSortIdOnScreenPercentageObsolete else { return false }
guard self.mentionNotificationMode == object.mentionNotificationMode else { return false }
guard self.messageDraft == object.messageDraft else { return false }
guard self.messageDraftBodyRanges == object.messageDraftBodyRanges else { return false }
guard self.mutedUntilDateObsolete == object.mutedUntilDateObsolete else { return false }
guard self.mutedUntilTimestampObsolete == object.mutedUntilTimestampObsolete else { return false }
guard self.shouldThreadBeVisible == object.shouldThreadBeVisible else { return false }
guard self.storyViewMode == object.storyViewMode else { return false }
return true
}
public func anyDidFetchOne(transaction: DBReadTransaction) {
SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didReadThread(self, transaction: transaction)
}
public func anyDidEnumerateOne(transaction: DBReadTransaction) {
SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didReadThread(self, transaction: transaction)
}
open func anyWillInsert(transaction: DBWriteTransaction) {
}
public func anyDidInsert(transaction: DBWriteTransaction) {
ThreadAssociatedData.create(for: self.uniqueId, transaction: transaction)
if self.shouldThreadBeVisible, !SSKPreferences.hasSavedThread(transaction: transaction) {
SSKPreferences.setHasSavedThread(true, transaction: transaction)
}
_anyDidInsert(tx: transaction)
SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didInsertOrUpdate(thread: self, transaction: transaction)
}
public func anyWillUpdate(transaction: DBWriteTransaction) {
}
public func anyDidUpdate(transaction: DBWriteTransaction) {
if self.shouldThreadBeVisible, !SSKPreferences.hasSavedThread(transaction: transaction) {
SSKPreferences.setHasSavedThread(true, transaction: transaction)
}
SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didInsertOrUpdate(thread: self, transaction: transaction)
DependenciesBridge.shared.pinnedThreadManager.handleUpdatedThread(self, tx: transaction)
}
public var isNoteToSelf: Bool { false }
public final var recipientAddressesWithSneakyTransaction: [SignalServiceAddress] {
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
return databaseStorage.read { tx in self.recipientAddresses(with: tx) }
}
@objc
public func recipientAddresses(with tx: DBReadTransaction) -> [SignalServiceAddress] {
owsFail("abstract method")
}
public func hasSafetyNumbers() -> Bool { false }
public func lastInteractionForInbox(forChatListSorting isForSorting: Bool, transaction tx: DBReadTransaction) -> TSInteraction? {
return InteractionFinder(threadUniqueId: self.uniqueId).mostRecentInteractionForInbox(forChatListSorting: isForSorting, transaction: tx)
}
public func firstInteraction(atOrAroundSortId sortId: UInt64, transaction tx: DBReadTransaction) -> TSInteraction? {
return InteractionFinder(threadUniqueId: self.uniqueId).firstInteraction(atOrAroundSortId: sortId, transaction: tx)
}
func merge(from otherThread: TSThread) {
self.shouldThreadBeVisible = self.shouldThreadBeVisible || otherThread.shouldThreadBeVisible
self.lastInteractionRowId = max(self.lastInteractionRowId, otherThread.lastInteractionRowId)
// Copy the draft if this thread doesn't have one. We always assign both
// values if we assign one of them since they're related.
if self.messageDraft == nil {
self.messageDraft = otherThread.messageDraft
self.messageDraftBodyRanges = otherThread.messageDraftBodyRanges
self.lastDraftInteractionRowId = otherThread.lastDraftInteractionRowId
self.lastDraftUpdateTimestamp = otherThread.lastDraftUpdateTimestamp
}
}
public typealias RowId = Int64
@ -15,23 +371,21 @@ extension TSThread {
return (self as? TSGroupThread)?.groupId.toHex() ?? self.uniqueId
}
// [SDS] TODO: Replace with SDSCodableModel.
static func anyEnumerate(
transaction: DBReadTransaction,
sql: String,
arguments: StatementArguments,
block: (TSThread, UnsafeMutablePointer<ObjCBool>) -> Void,
) {
let cursor = TSThread.grdbFetchCursor(sql: sql, arguments: arguments, transaction: transaction)
failIfThrows {
var stop: ObjCBool = false
while let thread = try cursor.next() {
block(thread, &stop)
if stop.boolValue {
break
}
}
@objc
public class func fetchViaCacheObjC(uniqueId: String, transaction: DBReadTransaction) -> TSThread? {
return fetchViaCache(uniqueId: uniqueId, transaction: transaction)
}
public class func fetchViaCache(uniqueId: String, transaction: DBReadTransaction) -> Self? {
let cache = SSKEnvironment.shared.modelReadCachesRef.threadReadCache
guard let thread = cache.getThread(uniqueId: uniqueId, transaction: transaction) else {
return nil
}
guard let typedThread = thread as? Self else {
owsFailDebug("object has type \(type(of: thread)), not \(Self.self)")
return nil
}
return typedThread
}
// MARK: - updateWith...
@ -47,7 +401,7 @@ extension TSThread {
anyUpdate(transaction: tx) { thread in
thread.messageDraft = draftMessageBody?.text
thread.messageDraftBodyRanges = draftMessageBody?.ranges
thread.editTargetTimestamp = editTargetTimestamp.map { NSNumber(value: $0) }
thread.editTargetTimestamp = editTargetTimestamp
if draftMessageBody?.text.nilIfEmpty == nil {
// 0 makes these values effectively irrelevant since they will
@ -105,8 +459,8 @@ extension TSThread {
transaction tx: DBWriteTransaction,
) {
anyUpdate(transaction: tx) { thread in
if lastSentStoryTimestamp > (thread.lastSentStoryTimestamp?.uint64Value ?? 0) {
thread.lastSentStoryTimestamp = NSNumber(value: lastSentStoryTimestamp)
if lastSentStoryTimestamp > thread.lastSentStoryTimestamp ?? 0 {
thread.lastSentStoryTimestamp = lastSentStoryTimestamp
}
}
}
@ -376,3 +730,15 @@ extension TSThread {
return groupModel.access.attributes != .administrator && !groupModel.isAnnouncementsOnly
}
}
// MARK: - StringInterpolation
public extension String.StringInterpolation {
mutating func appendInterpolation(threadColumn column: TSThread.CodingKeys) {
appendLiteral(column.rawValue)
}
mutating func appendInterpolation(threadColumnFullyQualified column: TSThread.CodingKeys) {
appendLiteral("\(TSThread.databaseTableName).\(column.rawValue)")
}
}

View File

@ -65,10 +65,7 @@ public class GroupV2UpdatesImpl: GroupV2Updates {
// The "best" is the group that hasn't been refreshed in the longest time.
SSKEnvironment.shared.databaseStorageRef.read { transaction in
var groupInfoToRefresh: GroupInfo?
TSGroupThread.anyEnumerate(
transaction: transaction,
batched: true,
) { thread, stop in
TSThread.anyEnumerate(transaction: transaction, batchingPreference: .batched(Batching.kDefaultBatchSize)) { thread, stop in
guard
let groupThread = thread as? TSGroupThread,
let groupModel = groupThread.groupModel as? TSGroupModelV2,

View File

@ -76,7 +76,7 @@ class GroupsV2ProfileKeyUpdater {
}
func scheduleAllGroupsV2ForProfileKeyUpdate(transaction: DBWriteTransaction) {
TSGroupThread.anyEnumerate(transaction: transaction) { thread, _ in
TSThread.anyEnumerate(transaction: transaction) { thread, _ in
guard let groupThread = thread as? TSGroupThread else {
return
}

View File

@ -1001,7 +1001,7 @@ class AttachmentStoreTests: XCTestCase {
private func insertThread(tx: DBWriteTransaction) -> TSThread {
let thread = TSThread(uniqueId: UUID().uuidString)
try! thread.asRecord().insert(tx.database)
try! thread.insert(tx.database)
return thread
}

View File

@ -270,7 +270,7 @@ class AttachmentDownloadStoreTests: XCTestCase {
private func insertAttachment(tx: DBWriteTransaction) -> Attachment.IDType {
let thread = TSThread(uniqueId: UUID().uuidString)
try! thread.asRecord().insert(tx.database)
try! thread.insert(tx.database)
let interaction = TSInteraction(timestamp: 0, receivedAtTimestamp: 0, thread: thread)
try! interaction.asRecord().insert(tx.database)

View File

@ -8,7 +8,6 @@
#import "TSIncomingMessage.h"
#import "TSInteraction.h"
#import "TSOutgoingMessage.h"
#import "TSThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -4,7 +4,6 @@
//
#import "TSErrorMessage.h"
#import "TSContactThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -4,8 +4,6 @@
//
#import "TSIncomingMessage.h"
#import "TSContactThread.h"
#import "TSGroupThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -4,7 +4,6 @@
//
#import "TSInteraction.h"
#import "TSThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@ -215,7 +214,7 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value)
}
// However, it's also possible that the thread doesn't exist.
return [TSThread fetchViaCacheWithUniqueId:self.uniqueThreadId transaction:tx];
return [TSThread fetchViaCacheObjCWithUniqueId:self.uniqueThreadId transaction:tx];
}
#pragma mark Date operations

View File

@ -5,7 +5,6 @@
#import "TSMessage.h"
#import "TSQuotedMessage.h"
#import "TSThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -4,8 +4,6 @@
//
#import "TSOutgoingMessage.h"
#import "TSContactThread.h"
#import "TSGroupThread.h"
#import "TSQuotedMessage.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
@ -333,7 +331,7 @@ NSUInteger const TSOutgoingMessageSchemaVersion = 1;
[recipientAddresses addObject:localAddress];
} else {
// Most messages should only be sent to the current members of the group.
[recipientAddresses addObjectsFromArray:[thread recipientAddressesWithTransaction:transaction]];
[recipientAddresses addObjectsFromArray:[thread recipientAddressesWith:transaction]];
// Some messages (eg certain call messages) go to a subset of the group.
if (explicitRecipients.count > 0) {
NSMutableSet<SignalServiceAddress *> *explicitRecipientAddresses = [[NSMutableSet alloc] init];

View File

@ -4,7 +4,6 @@
//
#import "TSInvalidIdentityKeyReceivingErrorMessage.h"
#import "TSContactThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -4,7 +4,6 @@
//
#import "TSInvalidIdentityKeySendingErrorMessage.h"
#import "TSContactThread.h"
#import "TSOutgoingMessage.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>

View File

@ -4,7 +4,6 @@
//
#import "OWSAddToProfileWhitelistOfferMessage.h"
#import "TSThread.h"
NS_ASSUME_NONNULL_BEGIN

View File

@ -4,7 +4,6 @@
//
#import "OWSRecoverableDecryptionPlaceholder.h"
#import "TSThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -33,10 +33,8 @@ FOUNDATION_EXPORT const unsigned char SignalServiceKitVersionString[];
#import <SignalServiceKit/OWSVerificationStateChangeMessage.h>
#import <SignalServiceKit/SSKAccessors+SDS.h>
#import <SignalServiceKit/TSCall.h>
#import <SignalServiceKit/TSContactThread.h>
#import <SignalServiceKit/TSErrorMessage.h>
#import <SignalServiceKit/TSGroupModel.h>
#import <SignalServiceKit/TSGroupThread.h>
#import <SignalServiceKit/TSIncomingMessage.h>
#import <SignalServiceKit/TSInfoMessage.h>
#import <SignalServiceKit/TSInteraction.h>
@ -46,9 +44,7 @@ FOUNDATION_EXPORT const unsigned char SignalServiceKitVersionString[];
#import <SignalServiceKit/TSMessage.h>
#import <SignalServiceKit/TSOutgoingMessage.h>
#import <SignalServiceKit/TSPaymentModels.h>
#import <SignalServiceKit/TSPrivateStoryThread.h>
#import <SignalServiceKit/TSQuotedMessage.h>
#import <SignalServiceKit/TSThread.h>
#import <SignalServiceKit/TSUnreadIndicatorInteraction.h>
#import <SignalServiceKit/TSYapDatabaseObject.h>
#import <SignalServiceKit/Threading.h>

View File

@ -5096,7 +5096,7 @@ public class GRDBSchemaMigrator {
migrator.registerMigration(.dataMigration_removeOversizedGroupAvatars) { transaction in
var thrownError: Error?
TSGroupThread.anyEnumerate(transaction: transaction) { (thread: TSThread, stop: UnsafeMutablePointer<ObjCBool>) in
TSThread.anyEnumerate(transaction: transaction) { thread, stop in
guard let groupThread = thread as? TSGroupThread else { return }
guard let avatarData = groupThread.groupModel.legacyAvatarData else { return }
guard !TSGroupModel.isValidGroupAvatarData(avatarData) else { return }
@ -5119,7 +5119,7 @@ public class GRDBSchemaMigrator {
migrator.registerMigration(.dataMigration_scheduleStorageServiceUpdateForMutedThreads) { transaction in
TSThread.anyEnumerate(
transaction: transaction,
sql: "SELECT * FROM \(TSThread.databaseTableName) WHERE \(threadColumn: .mutedUntilTimestamp) > 0",
sql: "SELECT * FROM \(TSThread.databaseTableName) WHERE \(threadColumn: .mutedUntilTimestampObsolete) > 0",
arguments: [],
) { thread, _ in
if let thread = thread as? TSContactThread {

View File

@ -20,7 +20,7 @@ public class ThreadFinder {
/// Fetch a thread with the given SQLite row ID, if one exists.
public func fetch(rowId: Int64, tx: DBReadTransaction) -> TSThread? {
guard
let thread = TSThread.grdbFetchOne(
let thread = TSThread.anyFetch(
sql: """
SELECT *
FROM \(TSThread.databaseTableName)
@ -63,15 +63,11 @@ public class ThreadFinder {
FROM \(TSThread.databaseTableName)
WHERE \(threadColumn: .recordType) = \(SDSRecordType.privateStoryThread.rawValue)
"""
let cursor = try ThreadRecord.fetchCursor(
let cursor = try TSPrivateStoryThread.fetchCursor(
transaction.database,
sql: sql,
)
while let threadRecord = try cursor.next() {
guard let storyThread = (try TSThread.fromRecord(threadRecord)) as? TSPrivateStoryThread else {
owsFailDebug("Skipping thread that's not a story.")
continue
}
while let storyThread = try cursor.next() {
guard try block(storyThread) else {
break
}
@ -89,16 +85,16 @@ public class ThreadFinder {
let sql = """
SELECT *
FROM \(TSThread.databaseTableName)
WHERE \(threadColumn: .groupModel) IS NOT NULL
WHERE \(groupThreadColumn: .groupModel) IS NOT NULL
ORDER BY \(threadColumn: .lastInteractionRowId) DESC
"""
let cursor = try ThreadRecord.fetchCursor(
let cursor = try TSThread.fetchCursor(
transaction.database,
sql: sql,
)
while let threadRecord = try cursor.next() {
guard let groupThread = (try TSThread.fromRecord(threadRecord)) as? TSGroupThread else {
guard let groupThread = threadRecord as? TSGroupThread else {
owsFailDebug("Skipping thread that's not a group.")
continue
}
@ -122,15 +118,12 @@ public class ThreadFinder {
WHERE \(threadColumn: .recordType) IS NOT ?
"""
let cursor = try ThreadRecord.fetchCursor(
let cursor = try TSThread.fetchCursor(
transaction.database,
sql: sql,
arguments: [SDSRecordType.privateStoryThread.rawValue],
)
while
let thread = try cursor.next().map({ try TSThread.fromRecord($0) }),
try block(thread)
{}
while let thread = try cursor.next(), try block(thread) {}
}
public func visibleThreadCount(
@ -171,11 +164,11 @@ public class ThreadFinder {
"""
failIfThrows {
try ThreadRecord.fetchCursor(
try TSThread.fetchCursor(
transaction.database,
sql: sql,
).forEach { threadRecord in
block(try TSThread.fromRecord(threadRecord))
).forEach { thread in
block(thread)
}
}
}

View File

@ -5,11 +5,9 @@
#import <Foundation/Foundation.h>
#import <SignalServiceKit/TSCall.h>
#import <SignalServiceKit/TSContactThread.h>
#import <SignalServiceKit/TSIncomingMessage.h>
#import <SignalServiceKit/TSInvalidIdentityKeyReceivingErrorMessage.h>
#import <SignalServiceKit/TSInvalidIdentityKeySendingErrorMessage.h>
#import <SignalServiceKit/TSThread.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -256,7 +256,7 @@ class MediaGalleryAttachmentFinderTest: XCTestCase {
let interaction = TSInteraction(timestamp: 0, receivedAtTimestamp: 0, thread: thread)
db.write { tx in
try! thread.asRecord().insert(tx.database)
try! thread.insert(tx.database)
try! interaction.asRecord().insert(tx.database)
}

View File

@ -34,7 +34,7 @@ public import LibSignalClient
/// messageFactory.create(count: 100)
///
public protocol Factory {
associatedtype ObjectType: TSYapDatabaseObject
associatedtype ObjectType
static func write(block: @escaping (DBWriteTransaction) -> Void)
func write(block: @escaping (DBWriteTransaction) -> Void)

View File

@ -1,93 +0,0 @@
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
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: - Typed Convenience Methods
@objc
public extension TSContactThread {
// NOTE: This method will fail if the object has unexpected type.
class func fetchContactThreadViaCache(
uniqueId: String,
transaction: DBReadTransaction
) -> TSContactThread? {
assert(!uniqueId.isEmpty)
guard let object = fetchViaCache(uniqueId: uniqueId, transaction: transaction) else {
return nil
}
guard let instance = object as? TSContactThread else {
owsFailDebug("Object has unexpected type: \(type(of: object))")
return nil
}
return instance
}
// NOTE: This method will fail if the object has unexpected type.
func anyUpdateContactThread(transaction: DBWriteTransaction, block: (TSContactThread) -> Void) {
anyUpdate(transaction: transaction) { (object) in
guard let instance = object as? TSContactThread else {
owsFailDebug("Object has unexpected type: \(type(of: object))")
return
}
block(instance)
}
}
}
// MARK: - SDSSerializer
// The SDSSerializer protocol specifies how to insert and update the
// row that corresponds to this model.
class TSContactThreadSerializer: SDSSerializer {
private let model: TSContactThread
public init(model: TSContactThread) {
self.model = model
}
// MARK: - Record
func asRecord() -> SDSRecord {
let id: Int64? = model.grdbId?.int64Value
let recordType: SDSRecordType = .contactThread
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? = model.contactPhoneNumber
let contactUUID: String? = model.contactUUID
let groupModel: Data? = nil
let hasDismissedOffers: Bool? = model.hasDismissedOffers
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 })
let lastDraftInteractionRowId: UInt64 = model.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = model.lastDraftUpdateTimestamp
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, lastDraftInteractionRowId: lastDraftInteractionRowId, lastDraftUpdateTimestamp: lastDraftUpdateTimestamp)
}
}

View File

@ -1,93 +0,0 @@
//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import <SignalServiceKit/TSThread.h>
NS_ASSUME_NONNULL_BEGIN
@class SignalServiceAddress;
@interface TSContactThread : TSThread
/// Represents the uppercase ServiceId string for this contact.
/// - Note
/// This property name includes `UUID` for compatibility with SDS (to match the
/// SQLite column), but **may not contain a valid UUID string**.
@property (nonatomic, nullable) NSString *contactUUID;
@property (nonatomic, nullable) NSString *contactPhoneNumber;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithUniqueId:(NSString *)uniqueId NS_UNAVAILABLE;
- (instancetype)initWithContactUUID:(nullable NSString *)contactUUID
contactPhoneNumber:(nullable NSString *)contactPhoneNumber NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode NS_UNAVAILABLE;
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode
contactPhoneNumber:(nullable NSString *)contactPhoneNumber
contactUUID:(nullable NSString *)contactUUID
hasDismissedOffers:(BOOL)hasDismissedOffers
NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:conversationColorNameObsolete:creationDate:editTargetTimestamp:isArchivedObsolete:isMarkedUnreadObsolete:lastDraftInteractionRowId:lastDraftUpdateTimestamp:lastInteractionRowId:lastSentStoryTimestamp:lastVisibleSortIdObsolete:lastVisibleSortIdOnScreenPercentageObsolete:mentionNotificationMode:messageDraft:messageDraftBodyRanges:mutedUntilDateObsolete:mutedUntilTimestampObsolete:shouldThreadBeVisible:storyViewMode:contactPhoneNumber:contactUUID:hasDismissedOffers:));
// clang-format on
// --- CODE GENERATION MARKER
@property (nonatomic, readonly) SignalServiceAddress *contactAddress;
@property (nonatomic) BOOL hasDismissedOffers; // deprecated
+ (nullable SignalServiceAddress *)contactAddressFromThreadId:(NSString *)threadId
transaction:(DBReadTransaction *)transaction;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,157 +0,0 @@
//
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "TSContactThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@implementation TSContactThread
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode
contactPhoneNumber:(nullable NSString *)contactPhoneNumber
contactUUID:(nullable NSString *)contactUUID
hasDismissedOffers:(BOOL)hasDismissedOffers
{
self = [super initWithGrdbId:grdbId
uniqueId:uniqueId
conversationColorNameObsolete:conversationColorNameObsolete
creationDate:creationDate
editTargetTimestamp:editTargetTimestamp
isArchivedObsolete:isArchivedObsolete
isMarkedUnreadObsolete:isMarkedUnreadObsolete
lastDraftInteractionRowId:lastDraftInteractionRowId
lastDraftUpdateTimestamp:lastDraftUpdateTimestamp
lastInteractionRowId:lastInteractionRowId
lastSentStoryTimestamp:lastSentStoryTimestamp
lastVisibleSortIdObsolete:lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:mentionNotificationMode
messageDraft:messageDraft
messageDraftBodyRanges:messageDraftBodyRanges
mutedUntilDateObsolete:mutedUntilDateObsolete
mutedUntilTimestampObsolete:mutedUntilTimestampObsolete
shouldThreadBeVisible:shouldThreadBeVisible
storyViewMode:storyViewMode];
if (!self) {
return self;
}
_contactPhoneNumber = contactPhoneNumber;
_contactUUID = contactUUID;
_hasDismissedOffers = hasDismissedOffers;
return self;
}
// clang-format on
// --- CODE GENERATION MARKER
- (NSUInteger)hash
{
NSUInteger result = [super hash];
result ^= self.contactPhoneNumber.hash;
result ^= self.contactUUID.hash;
result ^= self.hasDismissedOffers;
return result;
}
- (BOOL)isEqual:(id)other
{
if (![super isEqual:other]) {
return NO;
}
TSContactThread *typedOther = (TSContactThread *)other;
if (![NSObject isObject:self.contactPhoneNumber equalToObject:typedOther.contactPhoneNumber]) {
return NO;
}
if (![NSObject isObject:self.contactUUID equalToObject:typedOther.contactUUID]) {
return NO;
}
if (self.hasDismissedOffers != typedOther.hasDismissedOffers) {
return NO;
}
return YES;
}
- (instancetype)initWithContactUUID:(nullable NSString *)contactUUID
contactPhoneNumber:(nullable NSString *)contactPhoneNumber
{
NSString *uniqueId = [[self class] generateUniqueId];
if (self = [super initWithUniqueId:uniqueId]) {
_contactUUID = [contactUUID copy];
_contactPhoneNumber = [contactPhoneNumber copy];
}
return self;
}
- (SignalServiceAddress *)contactAddress
{
return [[SignalServiceAddress alloc] initWithServiceIdString:self.contactUUID phoneNumber:self.contactPhoneNumber];
}
- (NSArray<SignalServiceAddress *> *)recipientAddressesWithTransaction:(DBReadTransaction *)transaction
{
return @[ self.contactAddress ];
}
- (BOOL)isNoteToSelf
{
return self.contactAddress.isLocalAddress;
}
- (BOOL)hasSafetyNumbers
{
return [OWSIdentityManagerObjCBridge identityKeyForAddress:self.contactAddress] != nil;
}
+ (nullable SignalServiceAddress *)contactAddressFromThreadId:(NSString *)threadId
transaction:(DBReadTransaction *)transaction
{
return [TSContactThread fetchContactThreadViaCacheWithUniqueId:threadId transaction:transaction].contactAddress;
}
- (void)anyDidInsertWithTransaction:(DBWriteTransaction *)transaction
{
[super anyDidInsertWithTransaction:transaction];
OWSLogInfo(@"Inserted contact thread: %@", self.contactAddress);
}
@end
NS_ASSUME_NONNULL_END

View File

@ -4,8 +4,177 @@
//
import LibSignalClient
public import GRDB
extension TSContactThread {
open class TSContactThread: TSThread {
override public class var recordType: SDSRecordType { .contactThread }
/// Represents the uppercase ServiceId string for this contact.
/// - Note
/// This property name includes `UUID` for compatibility with SDS (to match the
/// SQLite column), but **may not contain a valid UUID string**.
public internal(set) var contactUUID: String?
public internal(set) var contactPhoneNumber: String?
public let hasDismissedOffers: Bool
public enum CodingKeys: String, CodingKey, ColumnExpression {
case contactPhoneNumber
case contactUUID
case hasDismissedOffers
}
public required init(inheritableDecoder decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.contactUUID = try container.decodeIfPresent(String.self, forKey: .contactUUID)
self.contactPhoneNumber = try container.decodeIfPresent(String.self, forKey: .contactPhoneNumber)
self.hasDismissedOffers = try container.decode(Bool.self, forKey: .hasDismissedOffers)
try super.init(inheritableDecoder: decoder)
}
override public func encode(to encoder: any Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.contactUUID, forKey: .contactUUID)
try container.encode(self.contactPhoneNumber, forKey: .contactPhoneNumber)
try container.encode(self.hasDismissedOffers, forKey: .hasDismissedOffers)
}
override public var hash: Int {
var hasher = Hasher()
hasher.combine(super.hash)
hasher.combine(self.contactPhoneNumber)
hasher.combine(self.contactUUID)
hasher.combine(self.hasDismissedOffers)
return hasher.finalize()
}
override public func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Self else { return false }
guard super.isEqual(object) else { return false }
guard self.contactPhoneNumber == object.contactPhoneNumber else { return false }
guard self.contactUUID == object.contactUUID else { return false }
guard self.hasDismissedOffers == object.hasDismissedOffers else { return false }
return true
}
init(
id: Int64?,
uniqueId: String,
conversationColorNameObsolete: String,
creationDate: Date?,
editTargetTimestamp: UInt64?,
isArchivedObsolete: Bool,
isMarkedUnreadObsolete: Bool,
lastDraftInteractionRowId: UInt64,
lastDraftUpdateTimestamp: UInt64,
lastInteractionRowId: UInt64,
lastSentStoryTimestamp: UInt64?,
lastVisibleSortIdObsolete: UInt64,
lastVisibleSortIdOnScreenPercentageObsolete: Double,
mentionNotificationMode: TSThreadMentionNotificationMode,
messageDraft: String?,
messageDraftBodyRanges: MessageBodyRanges?,
mutedUntilDateObsolete: Date?,
mutedUntilTimestampObsolete: UInt64,
shouldThreadBeVisible: Bool,
storyViewMode: TSThreadStoryViewMode,
contactUUID: String?,
contactPhoneNumber: String?,
hasDismissedOffers: Bool,
) {
self.contactUUID = contactUUID
self.contactPhoneNumber = contactPhoneNumber
self.hasDismissedOffers = hasDismissedOffers
super.init(
id: id,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
)
}
public init(
uniqueId: String = UUID().uuidString,
contactUUID: String?,
contactPhoneNumber: String?,
) {
self.contactUUID = contactUUID
self.contactPhoneNumber = contactPhoneNumber
self.hasDismissedOffers = false
super.init(uniqueId: uniqueId)
}
override func deepCopy() -> TSThread {
return TSContactThread(
id: self.id,
uniqueId: self.uniqueId,
conversationColorNameObsolete: self.conversationColorNameObsolete,
creationDate: self.creationDate,
editTargetTimestamp: self.editTargetTimestamp,
isArchivedObsolete: self.isArchivedObsolete,
isMarkedUnreadObsolete: self.isMarkedUnreadObsolete,
lastDraftInteractionRowId: self.lastDraftInteractionRowId,
lastDraftUpdateTimestamp: self.lastDraftUpdateTimestamp,
lastInteractionRowId: self.lastInteractionRowId,
lastSentStoryTimestamp: self.lastSentStoryTimestamp,
lastVisibleSortIdObsolete: self.lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: self.lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: self.mentionNotificationMode,
messageDraft: self.messageDraft,
messageDraftBodyRanges: self.messageDraftBodyRanges,
mutedUntilDateObsolete: self.mutedUntilDateObsolete,
mutedUntilTimestampObsolete: self.mutedUntilTimestampObsolete,
shouldThreadBeVisible: self.shouldThreadBeVisible,
storyViewMode: self.storyViewMode,
contactUUID: self.contactUUID,
contactPhoneNumber: self.contactPhoneNumber,
hasDismissedOffers: self.hasDismissedOffers,
)
}
class func fetchContactThreadViaCache(uniqueId: String, transaction: DBReadTransaction) -> TSContactThread? {
return fetchViaCache(uniqueId: uniqueId, transaction: transaction)
}
public var contactAddress: SignalServiceAddress {
return SignalServiceAddress(serviceIdString: self.contactUUID, phoneNumber: self.contactPhoneNumber)
}
override public func recipientAddresses(with tx: DBReadTransaction) -> [SignalServiceAddress] {
return [self.contactAddress]
}
override public var isNoteToSelf: Bool { self.contactAddress.isLocalAddress }
override public func hasSafetyNumbers() -> Bool {
return OWSIdentityManagerObjCBridge.identityKey(forAddress: self.contactAddress) != nil
}
static func contactAddress(fromThreadId threadUniqueId: String, transaction tx: DBReadTransaction) -> SignalServiceAddress? {
return (TSThread.fetchViaCache(uniqueId: threadUniqueId, transaction: tx) as? TSContactThread)?.contactAddress
}
override public func anyDidInsert(transaction: DBWriteTransaction) {
super.anyDidInsert(transaction: transaction)
Logger.info("Inserted contact thread: \(self.contactAddress)")
}
@objc
public convenience init(contactAddress: SignalServiceAddress) {
@ -88,3 +257,15 @@ extension TSContactThread {
return ContactThreadFinder().contactThread(for: contactAddress, tx: transaction)
}
}
// MARK: - StringInterpolation
public extension String.StringInterpolation {
mutating func appendInterpolation(contactThreadColumn column: TSContactThread.CodingKeys) {
appendLiteral(column.rawValue)
}
mutating func appendInterpolation(contactThreadColumnFullyQualified column: TSContactThread.CodingKeys) {
appendLiteral("\(TSThread.databaseTableName).\(column.rawValue)")
}
}

View File

@ -1,93 +0,0 @@
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
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: - Typed Convenience Methods
@objc
public extension TSGroupThread {
// NOTE: This method will fail if the object has unexpected type.
class func fetchGroupThreadViaCache(
uniqueId: String,
transaction: DBReadTransaction
) -> TSGroupThread? {
assert(!uniqueId.isEmpty)
guard let object = fetchViaCache(uniqueId: uniqueId, transaction: transaction) else {
return nil
}
guard let instance = object as? TSGroupThread else {
owsFailDebug("Object has unexpected type: \(type(of: object))")
return nil
}
return instance
}
// NOTE: This method will fail if the object has unexpected type.
func anyUpdateGroupThread(transaction: DBWriteTransaction, block: (TSGroupThread) -> Void) {
anyUpdate(transaction: transaction) { (object) in
guard let instance = object as? TSGroupThread else {
owsFailDebug("Object has unexpected type: \(type(of: object))")
return
}
block(instance)
}
}
}
// MARK: - SDSSerializer
// The SDSSerializer protocol specifies how to insert and update the
// row that corresponds to this model.
class TSGroupThreadSerializer: SDSSerializer {
private let model: TSGroupThread
public init(model: TSGroupThread) {
self.model = model
}
// MARK: - Record
func asRecord() -> SDSRecord {
let id: Int64? = model.grdbId?.int64Value
let recordType: SDSRecordType = .groupThread
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? = optionalArchive(model.groupModel)
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 })
let lastDraftInteractionRowId: UInt64 = model.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = model.lastDraftUpdateTimestamp
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, lastDraftInteractionRowId: lastDraftInteractionRowId, lastDraftUpdateTimestamp: lastDraftUpdateTimestamp)
}
}

View File

@ -1,88 +0,0 @@
//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import <SignalServiceKit/TSGroupModel.h>
#import <SignalServiceKit/TSThread.h>
NS_ASSUME_NONNULL_BEGIN
@class DBReadTransaction;
@class DBWriteTransaction;
@class MessageBodyRanges;
@class TSGroupModelV2;
extern NSString *const TSGroupThreadAvatarChangedNotification;
extern NSString *const TSGroupThread_NotificationKey_UniqueId;
@interface TSGroupThread : TSThread
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithUniqueId:(NSString *)uniqueId NS_UNAVAILABLE;
- (instancetype)initWithGroupModel:(TSGroupModelV2 *)groupModel NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode NS_UNAVAILABLE;
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode
groupModel:(TSGroupModel *)groupModel
NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:conversationColorNameObsolete:creationDate:editTargetTimestamp:isArchivedObsolete:isMarkedUnreadObsolete:lastDraftInteractionRowId:lastDraftUpdateTimestamp:lastInteractionRowId:lastSentStoryTimestamp:lastVisibleSortIdObsolete:lastVisibleSortIdOnScreenPercentageObsolete:mentionNotificationMode:messageDraft:messageDraftBodyRanges:mutedUntilDateObsolete:mutedUntilTimestampObsolete:shouldThreadBeVisible:storyViewMode:groupModel:));
// clang-format on
// --- CODE GENERATION MARKER
@property (nonatomic) TSGroupModel *groupModel;
@property (nonatomic, readonly) NSString *groupNameOrDefault;
@property (nonatomic, readonly, class) NSString *defaultGroupName;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,168 +0,0 @@
//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "TSGroupThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const TSGroupThreadAvatarChangedNotification = @"TSGroupThreadAvatarChangedNotification";
NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_NotificationKey_UniqueId";
#pragma mark -
@implementation TSGroupThread
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode
groupModel:(TSGroupModel *)groupModel
{
self = [super initWithGrdbId:grdbId
uniqueId:uniqueId
conversationColorNameObsolete:conversationColorNameObsolete
creationDate:creationDate
editTargetTimestamp:editTargetTimestamp
isArchivedObsolete:isArchivedObsolete
isMarkedUnreadObsolete:isMarkedUnreadObsolete
lastDraftInteractionRowId:lastDraftInteractionRowId
lastDraftUpdateTimestamp:lastDraftUpdateTimestamp
lastInteractionRowId:lastInteractionRowId
lastSentStoryTimestamp:lastSentStoryTimestamp
lastVisibleSortIdObsolete:lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:mentionNotificationMode
messageDraft:messageDraft
messageDraftBodyRanges:messageDraftBodyRanges
mutedUntilDateObsolete:mutedUntilDateObsolete
mutedUntilTimestampObsolete:mutedUntilTimestampObsolete
shouldThreadBeVisible:shouldThreadBeVisible
storyViewMode:storyViewMode];
if (!self) {
return self;
}
_groupModel = groupModel;
return self;
}
// clang-format on
// --- CODE GENERATION MARKER
- (NSUInteger)hash
{
NSUInteger result = [super hash];
result ^= self.groupModel.hash;
return result;
}
- (BOOL)isEqual:(id)other
{
if (![super isEqual:other]) {
return NO;
}
TSGroupThread *typedOther = (TSGroupThread *)other;
if (![NSObject isObject:self.groupModel equalToObject:typedOther.groupModel]) {
return NO;
}
return YES;
}
- (instancetype)initWithGroupModel:(TSGroupModelV2 *)groupModel
{
OWSAssertDebug(groupModel);
OWSAssertDebug(groupModel.groupId.length > 0);
#ifdef DEBUG
for (SignalServiceAddress *address in groupModel.groupMembers) {
OWSAssertDebug(address.isValid);
}
#endif
NSString *uniqueIdentifier = [[self class] defaultThreadIdForGroupId:groupModel.groupId];
self = [super initWithUniqueId:uniqueIdentifier];
if (!self) {
return self;
}
_groupModel = groupModel;
return self;
}
- (NSArray<SignalServiceAddress *> *)recipientAddressesWithTransaction:(DBReadTransaction *)transaction
{
NSMutableArray<SignalServiceAddress *> *groupMembers = [self.groupModel.groupMembers mutableCopy];
if (groupMembers == nil) {
return @[];
}
[groupMembers removeObject:[TSAccountManagerObjcBridge localAciAddressWith:transaction]];
return [groupMembers copy];
}
- (NSString *)groupNameOrDefault
{
return self.groupModel.groupNameOrDefault;
}
+ (NSString *)defaultGroupName
{
return OWSLocalizedString(@"NEW_GROUP_DEFAULT_TITLE", @"");
}
#pragma mark -
- (void)anyWillInsertWithTransaction:(DBWriteTransaction *)transaction
{
[super anyWillInsertWithTransaction:transaction];
[self updateGroupMemberRecordsWithTransaction:transaction];
}
- (void)anyWillUpdateWithTransaction:(DBWriteTransaction *)transaction
{
[super anyWillUpdateWithTransaction:transaction];
// We used to update the group member records here, but there are many updates that don't touch membership.
// Now it's done explicitly where we update the group model, and not for other updates.
}
- (void)anyDidInsertWithTransaction:(DBWriteTransaction *)transaction
{
[super anyDidInsertWithTransaction:transaction];
OWSLogInfo(@"Inserted group thread: %@", self.groupId.hexadecimalString);
}
@end
NS_ASSUME_NONNULL_END

View File

@ -4,8 +4,181 @@
//
public import LibSignalClient
public import GRDB
extension Notification.Name {
public static let TSGroupThreadAvatarChanged = Notification.Name("TSGroupThreadAvatarChangedNotification")
}
public let TSGroupThread_NotificationKey_UniqueId = "TSGroupThread_NotificationKey_UniqueId"
open class TSGroupThread: TSThread {
override public class var recordType: SDSRecordType { .groupThread }
public private(set) var groupModel: TSGroupModel
public enum CodingKeys: String, CodingKey, ColumnExpression {
case groupModel
}
public required init(inheritableDecoder decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let groupModelData = try container.decode(Data.self, forKey: .groupModel)
self.groupModel = try LegacySDSSerializer().deserializeLegacySDSData(groupModelData, ofClass: TSGroupModel.self)
try super.init(inheritableDecoder: decoder)
}
override public func encode(to encoder: any Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(LegacySDSSerializer().serializeAsLegacySDSData(self.groupModel), forKey: .groupModel)
}
override public var hash: Int {
var hasher = Hasher()
hasher.combine(super.hash)
hasher.combine(self.groupModel)
return hasher.finalize()
}
override public func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Self else { return false }
guard super.isEqual(object) else { return false }
guard self.groupModel == object.groupModel else { return false }
return true
}
init(
id: Int64?,
uniqueId: String,
conversationColorNameObsolete: String,
creationDate: Date?,
editTargetTimestamp: UInt64?,
isArchivedObsolete: Bool,
isMarkedUnreadObsolete: Bool,
lastDraftInteractionRowId: UInt64,
lastDraftUpdateTimestamp: UInt64,
lastInteractionRowId: UInt64,
lastSentStoryTimestamp: UInt64?,
lastVisibleSortIdObsolete: UInt64,
lastVisibleSortIdOnScreenPercentageObsolete: Double,
mentionNotificationMode: TSThreadMentionNotificationMode,
messageDraft: String?,
messageDraftBodyRanges: MessageBodyRanges?,
mutedUntilDateObsolete: Date?,
mutedUntilTimestampObsolete: UInt64,
shouldThreadBeVisible: Bool,
storyViewMode: TSThreadStoryViewMode,
groupModel: TSGroupModel,
) {
self.groupModel = groupModel
super.init(
id: id,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
)
}
public init(
uniqueId: String,
groupModel: TSGroupModelV2,
) {
owsAssertDebug(!groupModel.groupId.isEmpty)
#if DEBUG
groupModel.groupMembers.forEach({ owsAssertDebug($0.isValid) })
#endif
self.groupModel = groupModel
super.init(uniqueId: uniqueId)
}
public convenience init(groupModel: TSGroupModelV2) {
self.init(
uniqueId: Self.defaultThreadId(forGroupId: groupModel.groupId),
groupModel: groupModel,
)
}
override func deepCopy() -> TSThread {
return TSGroupThread(
id: self.id,
uniqueId: self.uniqueId,
conversationColorNameObsolete: self.conversationColorNameObsolete,
creationDate: self.creationDate,
editTargetTimestamp: self.editTargetTimestamp,
isArchivedObsolete: self.isArchivedObsolete,
isMarkedUnreadObsolete: self.isMarkedUnreadObsolete,
lastDraftInteractionRowId: self.lastDraftInteractionRowId,
lastDraftUpdateTimestamp: self.lastDraftUpdateTimestamp,
lastInteractionRowId: self.lastInteractionRowId,
lastSentStoryTimestamp: self.lastSentStoryTimestamp,
lastVisibleSortIdObsolete: self.lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: self.lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: self.mentionNotificationMode,
messageDraft: self.messageDraft,
messageDraftBodyRanges: self.messageDraftBodyRanges,
mutedUntilDateObsolete: self.mutedUntilDateObsolete,
mutedUntilTimestampObsolete: self.mutedUntilTimestampObsolete,
shouldThreadBeVisible: self.shouldThreadBeVisible,
storyViewMode: self.storyViewMode,
groupModel: self.groupModel,
)
}
public class func fetchGroupThreadViaCache(uniqueId: String, transaction: DBReadTransaction) -> TSGroupThread? {
return fetchViaCache(uniqueId: uniqueId, transaction: transaction)
}
override public func recipientAddresses(with tx: DBReadTransaction) -> [SignalServiceAddress] {
var groupMembers = self.groupModel.groupMembers
groupMembers.removeAll(where: { $0.isLocalAddress })
return groupMembers
}
public var groupNameOrDefault: String {
return self.groupModel.groupNameOrDefault
}
@objc
public class var defaultGroupName: String {
return OWSLocalizedString("NEW_GROUP_DEFAULT_TITLE", comment: "")
}
override open func anyWillInsert(transaction: DBWriteTransaction) {
super.anyWillInsert(transaction: transaction)
updateGroupMemberRecords(transaction: transaction)
}
override public func anyWillUpdate(transaction: DBWriteTransaction) {
super.anyWillUpdate(transaction: transaction)
// We used to update the group member records here, but there are many
// updates that don't touch membership. Now it's done explicitly where we
// update the group model, and not for other updates.
}
override public func anyDidInsert(transaction: DBWriteTransaction) {
super.anyDidInsert(transaction: transaction)
Logger.info("Inserted group thread: \(self.groupId.hexadecimalString)")
}
extension TSGroupThread {
func update(
with newGroupModel: TSGroupModel,
transaction tx: DBWriteTransaction,
@ -15,7 +188,7 @@ extension TSGroupThread {
let oldGroupMembers = groupModel.groupMembers
anyUpdateGroupThread(transaction: tx) { groupThread in
anyUpdate(transaction: tx) { groupThread in
if let oldGroupModelV2 = groupThread.groupModel as? TSGroupModelV2 {
if let newGroupModelV2 = newGroupModel as? TSGroupModelV2 {
owsPrecondition(oldGroupModelV2.revision <= newGroupModelV2.revision)
@ -135,7 +308,7 @@ extension TSGroupThread {
// MARK: -
override open func updateWithInsertedInteraction(
override public func updateWithInsertedInteraction(
_ interaction: TSInteraction,
tx: DBWriteTransaction,
) {
@ -203,7 +376,7 @@ extension TSGroupThread {
) -> TSGroupThread {
let groupThreadId = TSGroupThread.defaultThreadId(forGroupId: groupId)
let groupThread = TSGroupThread(
grdbId: 1,
id: 1,
uniqueId: groupThreadId,
conversationColorNameObsolete: "",
creationDate: nil,
@ -241,7 +414,7 @@ extension TSGroupThread {
addedByAddress: nil,
),
)
groupThread.clearRowId()
groupThread.id = nil
return groupThread
}
#endif
@ -278,3 +451,15 @@ public extension TSThreadStoryViewMode {
}
}
}
// MARK: - StringInterpolation
public extension String.StringInterpolation {
mutating func appendInterpolation(groupThreadColumn column: TSGroupThread.CodingKeys) {
appendLiteral(column.rawValue)
}
mutating func appendInterpolation(groupThreadColumnFullyQualified column: TSGroupThread.CodingKeys) {
appendLiteral("\(TSThread.databaseTableName).\(column.rawValue)")
}
}

View File

@ -1,93 +0,0 @@
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
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: - Typed Convenience Methods
@objc
public extension TSPrivateStoryThread {
// NOTE: This method will fail if the object has unexpected type.
class func fetchPrivateStoryThreadViaCache(
uniqueId: String,
transaction: DBReadTransaction
) -> TSPrivateStoryThread? {
assert(!uniqueId.isEmpty)
guard let object = fetchViaCache(uniqueId: uniqueId, transaction: transaction) else {
return nil
}
guard let instance = object as? TSPrivateStoryThread else {
owsFailDebug("Object has unexpected type: \(type(of: object))")
return nil
}
return instance
}
// NOTE: This method will fail if the object has unexpected type.
func anyUpdatePrivateStoryThread(transaction: DBWriteTransaction, block: (TSPrivateStoryThread) -> Void) {
anyUpdate(transaction: transaction) { (object) in
guard let instance = object as? TSPrivateStoryThread else {
owsFailDebug("Object has unexpected type: \(type(of: object))")
return
}
block(instance)
}
}
}
// MARK: - SDSSerializer
// The SDSSerializer protocol specifies how to insert and update the
// row that corresponds to this model.
class TSPrivateStoryThreadSerializer: SDSSerializer {
private let model: TSPrivateStoryThread
public init(model: TSPrivateStoryThread) {
self.model = model
}
// MARK: - Record
func asRecord() -> SDSRecord {
let id: Int64? = model.grdbId?.int64Value
let recordType: SDSRecordType = .privateStoryThread
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? = model.allowsReplies
let lastSentStoryTimestamp: UInt64? = archiveOptionalNSNumber(model.lastSentStoryTimestamp, conversion: { $0.uint64Value })
let name: String? = model.name
let addresses: Data? = model.addresses
let storyViewMode: UInt = model.storyViewMode.rawValue
let editTargetTimestamp: UInt64? = archiveOptionalNSNumber(model.editTargetTimestamp, conversion: { $0.uint64Value })
let lastDraftInteractionRowId: UInt64 = model.lastDraftInteractionRowId
let lastDraftUpdateTimestamp: UInt64 = model.lastDraftUpdateTimestamp
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, lastDraftInteractionRowId: lastDraftInteractionRowId, lastDraftUpdateTimestamp: lastDraftUpdateTimestamp)
}
}

View File

@ -1,92 +0,0 @@
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import <SignalServiceKit/SignalServiceKit.h>
NS_ASSUME_NONNULL_BEGIN
@class SignalServiceAddress;
/// Represents a story distribution list.
@interface TSPrivateStoryThread : TSThread
@property (nonatomic) BOOL allowsReplies;
@property (nonatomic) NSString *name;
/// deprecated
@property (nonatomic, nullable) NSData *addresses;
@property (nonatomic, readonly) BOOL isMyStory;
- (instancetype)initWithUniqueId:(NSString *)uniqueId
name:(NSString *)name
allowsReplies:(BOOL)allowsReplies
viewMode:(TSThreadStoryViewMode)viewMode NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithName:(NSString *)name
allowsReplies:(BOOL)allowsReplies
viewMode:(TSThreadStoryViewMode)viewMode NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithUniqueId:(NSString *)uniqueId NS_UNAVAILABLE;
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode NS_UNAVAILABLE;
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode
addresses:(nullable NSData *)addresses
allowsReplies:(BOOL)allowsReplies
name:(NSString *)name
NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:conversationColorNameObsolete:creationDate:editTargetTimestamp:isArchivedObsolete:isMarkedUnreadObsolete:lastDraftInteractionRowId:lastDraftUpdateTimestamp:lastInteractionRowId:lastSentStoryTimestamp:lastVisibleSortIdObsolete:lastVisibleSortIdOnScreenPercentageObsolete:mentionNotificationMode:messageDraft:messageDraftBodyRanges:mutedUntilDateObsolete:mutedUntilTimestampObsolete:shouldThreadBeVisible:storyViewMode:addresses:allowsReplies:name:));
// clang-format on
// --- CODE GENERATION MARKER
@end
NS_ASSUME_NONNULL_END

View File

@ -1,146 +0,0 @@
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "TSPrivateStoryThread.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
@implementation TSPrivateStoryThread
- (instancetype)initWithUniqueId:(NSString *)uniqueId
name:(NSString *)name
allowsReplies:(BOOL)allowsReplies
viewMode:(TSThreadStoryViewMode)viewMode
{
self = [super initWithUniqueId:uniqueId];
if (self) {
self.name = name;
self.allowsReplies = allowsReplies;
self.storyViewMode = viewMode;
}
return self;
}
- (NSUInteger)hash
{
NSUInteger result = [super hash];
result ^= self.addresses.hash;
result ^= self.allowsReplies;
result ^= self.name.hash;
return result;
}
- (BOOL)isEqual:(id)other
{
if (![super isEqual:other]) {
return NO;
}
TSPrivateStoryThread *typedOther = (TSPrivateStoryThread *)other;
if (![NSObject isObject:self.addresses equalToObject:typedOther.addresses]) {
return NO;
}
if (self.allowsReplies != typedOther.allowsReplies) {
return NO;
}
if (![NSObject isObject:self.name equalToObject:typedOther.name]) {
return NO;
}
return YES;
}
- (instancetype)initWithName:(NSString *)name allowsReplies:(BOOL)allowsReplies viewMode:(TSThreadStoryViewMode)viewMode
{
NSString *uniqueId = [[self class] generateUniqueId];
self = [super initWithUniqueId:uniqueId];
if (self) {
self.name = name;
self.allowsReplies = allowsReplies;
self.storyViewMode = viewMode;
}
return self;
}
// --- CODE GENERATION MARKER
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.
// clang-format off
- (instancetype)initWithGrdbId:(int64_t)grdbId
uniqueId:(NSString *)uniqueId
conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
creationDate:(nullable NSDate *)creationDate
editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
isArchivedObsolete:(BOOL)isArchivedObsolete
isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
lastInteractionRowId:(uint64_t)lastInteractionRowId
lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
messageDraft:(nullable NSString *)messageDraft
messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
storyViewMode:(TSThreadStoryViewMode)storyViewMode
addresses:(nullable NSData *)addresses
allowsReplies:(BOOL)allowsReplies
name:(NSString *)name
{
self = [super initWithGrdbId:grdbId
uniqueId:uniqueId
conversationColorNameObsolete:conversationColorNameObsolete
creationDate:creationDate
editTargetTimestamp:editTargetTimestamp
isArchivedObsolete:isArchivedObsolete
isMarkedUnreadObsolete:isMarkedUnreadObsolete
lastDraftInteractionRowId:lastDraftInteractionRowId
lastDraftUpdateTimestamp:lastDraftUpdateTimestamp
lastInteractionRowId:lastInteractionRowId
lastSentStoryTimestamp:lastSentStoryTimestamp
lastVisibleSortIdObsolete:lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:lastVisibleSortIdOnScreenPercentageObsolete
mentionNotificationMode:mentionNotificationMode
messageDraft:messageDraft
messageDraftBodyRanges:messageDraftBodyRanges
mutedUntilDateObsolete:mutedUntilDateObsolete
mutedUntilTimestampObsolete:mutedUntilTimestampObsolete
shouldThreadBeVisible:shouldThreadBeVisible
storyViewMode:storyViewMode];
if (!self) {
return self;
}
_addresses = addresses;
_allowsReplies = allowsReplies;
_name = name;
return self;
}
// clang-format on
// --- CODE GENERATION MARKER
- (BOOL)isMyStory
{
return [self.uniqueId isEqualToString:[self class].myStoryUniqueId];
}
- (NSString *)name
{
if (self.isMyStory) {
return OWSLocalizedString(
@"MY_STORY_NAME", @"Name for the 'My Story' default story that sends to all the user's contacts.");
}
return _name;
}
@end

View File

@ -4,8 +4,160 @@
//
import Foundation
public import GRDB
/// Represents a story distribution list.
public final class TSPrivateStoryThread: TSThread {
override public class var recordType: SDSRecordType { .privateStoryThread }
public private(set) var allowsReplies: Bool
public private(set) var _name: String
/// deprecated
public let addresses: Data?
public enum CodingKeys: String, CodingKey, ColumnExpression {
case allowsReplies
case name
case addresses
}
required init(inheritableDecoder decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.allowsReplies = try container.decode(Bool.self, forKey: .allowsReplies)
self._name = try container.decode(String.self, forKey: .name)
self.addresses = try container.decodeIfPresent(Data.self, forKey: .addresses)
try super.init(inheritableDecoder: decoder)
}
override public func encode(to encoder: any Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.allowsReplies, forKey: .allowsReplies)
try container.encode(self._name, forKey: .name)
try container.encode(self.addresses, forKey: .addresses)
}
init(
id: Int64?,
uniqueId: String,
conversationColorNameObsolete: String,
creationDate: Date?,
editTargetTimestamp: UInt64?,
isArchivedObsolete: Bool,
isMarkedUnreadObsolete: Bool,
lastDraftInteractionRowId: UInt64,
lastDraftUpdateTimestamp: UInt64,
lastInteractionRowId: UInt64,
lastSentStoryTimestamp: UInt64?,
lastVisibleSortIdObsolete: UInt64,
lastVisibleSortIdOnScreenPercentageObsolete: Double,
mentionNotificationMode: TSThreadMentionNotificationMode,
messageDraft: String?,
messageDraftBodyRanges: MessageBodyRanges?,
mutedUntilDateObsolete: Date?,
mutedUntilTimestampObsolete: UInt64,
shouldThreadBeVisible: Bool,
storyViewMode: TSThreadStoryViewMode,
allowsReplies: Bool,
name: String,
addresses: Data?,
) {
self.allowsReplies = allowsReplies
self._name = name
self.addresses = addresses
super.init(
id: id,
uniqueId: uniqueId,
conversationColorNameObsolete: conversationColorNameObsolete,
creationDate: creationDate,
editTargetTimestamp: editTargetTimestamp,
isArchivedObsolete: isArchivedObsolete,
isMarkedUnreadObsolete: isMarkedUnreadObsolete,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
lastInteractionRowId: lastInteractionRowId,
lastSentStoryTimestamp: lastSentStoryTimestamp,
lastVisibleSortIdObsolete: lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: mentionNotificationMode,
messageDraft: messageDraft,
messageDraftBodyRanges: messageDraftBodyRanges,
mutedUntilDateObsolete: mutedUntilDateObsolete,
mutedUntilTimestampObsolete: mutedUntilTimestampObsolete,
shouldThreadBeVisible: shouldThreadBeVisible,
storyViewMode: storyViewMode,
)
}
public init(uniqueId: String = UUID().uuidString, name: String, allowsReplies: Bool, viewMode: TSThreadStoryViewMode) {
self._name = name
self.allowsReplies = allowsReplies
self.addresses = nil
super.init(uniqueId: uniqueId)
self.storyViewMode = viewMode
}
override func deepCopy() -> TSThread {
return TSPrivateStoryThread(
id: self.id,
uniqueId: self.uniqueId,
conversationColorNameObsolete: self.conversationColorNameObsolete,
creationDate: self.creationDate,
editTargetTimestamp: self.editTargetTimestamp,
isArchivedObsolete: self.isArchivedObsolete,
isMarkedUnreadObsolete: self.isMarkedUnreadObsolete,
lastDraftInteractionRowId: self.lastDraftInteractionRowId,
lastDraftUpdateTimestamp: self.lastDraftUpdateTimestamp,
lastInteractionRowId: self.lastInteractionRowId,
lastSentStoryTimestamp: self.lastSentStoryTimestamp,
lastVisibleSortIdObsolete: self.lastVisibleSortIdObsolete,
lastVisibleSortIdOnScreenPercentageObsolete: self.lastVisibleSortIdOnScreenPercentageObsolete,
mentionNotificationMode: self.mentionNotificationMode,
messageDraft: self.messageDraft,
messageDraftBodyRanges: self.messageDraftBodyRanges,
mutedUntilDateObsolete: self.mutedUntilDateObsolete,
mutedUntilTimestampObsolete: self.mutedUntilTimestampObsolete,
shouldThreadBeVisible: self.shouldThreadBeVisible,
storyViewMode: self.storyViewMode,
allowsReplies: self.allowsReplies,
name: self.name,
addresses: self.addresses,
)
}
override public var hash: Int {
var hasher = Hasher()
hasher.combine(super.hash)
hasher.combine(self.addresses)
hasher.combine(self.allowsReplies)
hasher.combine(self.name)
return hasher.finalize()
}
override public func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Self else { return false }
guard super.isEqual(object) else { return false }
guard self.addresses == object.addresses else { return false }
guard self.allowsReplies == object.allowsReplies else { return false }
guard self.name == object.name else { return false }
return true
}
public class func fetchPrivateStoryThreadViaCache(uniqueId: String, transaction: DBReadTransaction) -> TSPrivateStoryThread? {
return fetchViaCache(uniqueId: uniqueId, transaction: transaction)
}
public var isMyStory: Bool {
return self.uniqueId == Self.myStoryUniqueId
}
public var name: String {
if self.isMyStory {
return OWSLocalizedString("MY_STORY_NAME", comment: "Name for the 'My Story' default story that sends to all the user's contacts.")
}
return _name
}
extension TSPrivateStoryThread {
public typealias RowId = Int64
@objc
@ -60,7 +212,7 @@ extension TSPrivateStoryThread {
updateStorageService: Bool,
transaction tx: DBWriteTransaction,
) {
anyUpdatePrivateStoryThread(transaction: tx) { privateStoryThread in
anyUpdate(transaction: tx) { privateStoryThread in
privateStoryThread.allowsReplies = allowsReplies
}
@ -76,8 +228,8 @@ extension TSPrivateStoryThread {
updateStorageService: Bool,
transaction tx: DBWriteTransaction,
) {
anyUpdatePrivateStoryThread(transaction: tx) { privateStoryThread in
privateStoryThread.name = name
anyUpdate(transaction: tx) { privateStoryThread in
privateStoryThread._name = name
}
if updateStorageService, let distributionListIdentifier {
@ -113,7 +265,7 @@ extension TSPrivateStoryThread {
)
}
anyUpdatePrivateStoryThread(transaction: tx) { privateStoryThread in
anyUpdate(transaction: tx) { privateStoryThread in
privateStoryThread.storyViewMode = storyViewMode
}

View File

@ -136,7 +136,7 @@ extension TSThread {
@objc
public func editTarget(transaction: DBReadTransaction) -> TSOutgoingMessage? {
guard
let editTargetTimestamp = editTargetTimestamp?.uint64Value,
let editTargetTimestamp,
let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: transaction)?.aciAddress
else {
return nil

View File

@ -327,13 +327,13 @@ public class MockThreadStore: ThreadStore {
}
public func insertThread(_ thread: TSThread) {
thread.updateRowId(nextRowId)
thread.id = nextRowId
threads.append(thread)
nextRowId += 1
}
public func fetchThread(rowId threadRowId: Int64, tx: DBReadTransaction) -> TSThread? {
threads.first(where: { $0.sqliteRowId == threadRowId })
threads.first(where: { $0.id == threadRowId })
}
public func fetchThread(uniqueId: String, tx: DBReadTransaction) -> TSThread? {

View File

@ -356,7 +356,7 @@ public class ThreadReadCache: NSObject {
}
override func copy(value: TSThread) throws -> TSThread {
return try DeepCopies.deepCopy(value)
return value.deepCopy()
}
}

View File

@ -27,7 +27,7 @@ final class CallRecordQuerierTest: XCTestCase {
private func insertThread(db: Database) -> (thread: TSThread, threadRowId: Int64) {
let thread = TSThread(uniqueId: UUID().uuidString)
try! thread.asRecord().insert(db)
try! thread.insert(db)
return (thread, thread.sqliteRowId!)
}
@ -51,7 +51,7 @@ final class CallRecordQuerierTest: XCTestCase {
let (thread, threadRowId): (TSThread, Int64) = {
if let knownThreadRowId {
return (
try! TSThread.fromRecord(ThreadRecord.fetchOne(db, key: knownThreadRowId)!),
try! TSThread.fetchOne(db, key: knownThreadRowId)!,
knownThreadRowId,
)
} else {

View File

@ -45,7 +45,7 @@ final class CallRecordStoreTest: XCTestCase {
let interaction = TSInteraction(timestamp: 0, receivedAtTimestamp: 0, thread: thread)
inMemoryDB.write { tx in
try! thread.asRecord().insert(tx.database)
try! thread.insert(tx.database)
try! interaction.asRecord().insert(tx.database)
}

View File

@ -41,7 +41,7 @@ final class GroupCallRecordManagerTest: XCTestCase {
private func createInteraction() -> (TSGroupThread, OWSGroupCallMessage) {
let thread = TSGroupThread.randomForTesting()
thread.updateRowId(.maxRandom)
thread.id = .maxRandom
let interaction = OWSGroupCallMessage(
joinedMemberAcis: [],

View File

@ -36,7 +36,7 @@ final class IndividualCallRecordManagerTest: XCTestCase {
callType: RPRecentCallType = .incomingIncomplete,
) -> (TSContactThread, TSCall) {
let thread = TSContactThread(contactAddress: .isolatedRandomForTesting())
thread.updateRowId(.maxRandom)
thread.id = .maxRandom
let interaction = TSCall(callType: callType, offerType: .audio, thread: thread, sentAtTimestamp: .maxRandom)
interaction.updateRowId(.maxRandom)

View File

@ -20,7 +20,7 @@ final class DeletedCallRecordStoreTest: XCTestCase {
let thread = TSThread(uniqueId: UUID().uuidString)
inMemoryDB.write { tx in
try! thread.asRecord().insert(tx.database)
try! thread.insert(tx.database)
}
return thread.sqliteRowId!

View File

@ -97,7 +97,7 @@ struct BackupEnablementReminderMegaphoneTests {
db.write { tx in
let db = tx.database
try! contactThread.asRecord().insert(db)
try! contactThread.insert(db)
}
for i in 0..<2000 {
@ -123,7 +123,7 @@ struct BackupEnablementReminderMegaphoneTests {
db.write { tx in
let db = tx.database
try! contactThread.asRecord().insert(db)
try! contactThread.insert(db)
}
for i in 0..<2000 {

View File

@ -17,28 +17,8 @@ class EditManagerTests: SSKBaseTest {
super.setUp()
db = InMemoryDB()
authorAci = Aci.constantForTesting("00000000-0000-4000-8000-000000000000")
thread = TSThread(
grdbId: 1,
uniqueId: "1",
conversationColorNameObsolete: "Obsolete",
creationDate: nil,
editTargetTimestamp: nil,
isArchivedObsolete: false,
isMarkedUnreadObsolete: false,
lastDraftInteractionRowId: 0,
lastDraftUpdateTimestamp: 0,
lastInteractionRowId: 0,
lastSentStoryTimestamp: nil,
lastVisibleSortIdObsolete: 0,
lastVisibleSortIdOnScreenPercentageObsolete: 0,
mentionNotificationMode: .always,
messageDraft: nil,
messageDraftBodyRanges: nil,
mutedUntilDateObsolete: nil,
mutedUntilTimestampObsolete: 0,
shouldThreadBeVisible: true,
storyViewMode: .default,
)
thread = TSThread(uniqueId: "1")
thread.id = 1
}
func testBasicValidation() throws {

View File

@ -54,8 +54,8 @@ struct PinnedMessageManagerTest {
@Test
func testFetchPinnedMessagesForThread() throws {
let thread = db.write { tx in
let thread = TSThread()
try! thread.asRecord().insert(tx.database)
let thread = TSThread(uniqueId: "")
try! thread.insert(tx.database)
return thread
}
@ -82,8 +82,8 @@ struct PinnedMessageManagerTest {
@Test
func testSortedPinnedMessages() throws {
let thread = db.write { tx in
let thread = TSThread()
try! thread.asRecord().insert(tx.database)
let thread = TSThread(uniqueId: "")
try! thread.insert(tx.database)
return thread
}

View File

@ -11,8 +11,6 @@ import XCTest
class ThreadFinderTests: XCTestCase {
private var db = InMemoryDB()
private let threadFinder = ThreadFinder()
private var contactThread1: TSContactThread!
private var contactThread2: TSContactThread!
enum ChatListType: CaseIterable {
case inbox
@ -20,62 +18,37 @@ class ThreadFinderTests: XCTestCase {
case archive
}
override func setUp() {
super.setUp()
let testPhone1 = E164("+16505550101")!
let testACI1 = Aci.constantForTesting("00000000-0000-4000-8000-00000000000A")
contactThread1 = TSContactThread(contactAddress: SignalServiceAddress(
serviceId: testACI1,
phoneNumber: testPhone1.stringValue,
cache: SignalServiceAddressCache(),
))
let testPhone2 = E164("+16505550100")!
let testACI2 = Aci.constantForTesting("00000000-0000-4000-A000-00000000000B")
contactThread2 = TSContactThread(contactAddress: SignalServiceAddress(
serviceId: testACI2,
phoneNumber: testPhone2.stringValue,
cache: SignalServiceAddressCache(),
))
}
func buildThreadRecord(
uniqueID: String,
contactThread: TSContactThread,
draft: String?,
lastInteractionRowID: UInt64,
lastDraftInteractionRowId: UInt64,
lastDraftUpdateTimestamp: UInt64,
) -> ThreadRecord {
ThreadRecord(
delegate: contactThread,
recordType: .contactThread,
) -> TSContactThread {
return TSContactThread(
id: nil,
uniqueId: uniqueID,
conversationColorName: "Obsolete",
creationDate: Date.now.timeIntervalSince1970,
isArchived: false,
lastInteractionRowId: lastInteractionRowID,
messageDraft: draft,
mutedUntilDate: nil,
shouldThreadBeVisible: true,
contactPhoneNumber: nil,
contactUUID: nil,
groupModel: nil,
hasDismissedOffers: false,
isMarkedUnread: false,
lastVisibleSortIdOnScreenPercentage: 0.0,
lastVisibleSortId: 0,
messageDraftBodyRanges: nil,
mentionNotificationMode: 0,
mutedUntilTimestamp: 0,
allowsReplies: nil,
lastSentStoryTimestamp: nil,
name: nil,
addresses: nil,
storyViewMode: 0,
conversationColorNameObsolete: "Obsolete",
creationDate: Date.now,
editTargetTimestamp: nil,
isArchivedObsolete: false,
isMarkedUnreadObsolete: false,
lastDraftInteractionRowId: lastDraftInteractionRowId,
lastDraftUpdateTimestamp: lastDraftUpdateTimestamp,
lastInteractionRowId: lastInteractionRowID,
lastSentStoryTimestamp: nil,
lastVisibleSortIdObsolete: 0,
lastVisibleSortIdOnScreenPercentageObsolete: 0.0,
mentionNotificationMode: .default,
messageDraft: draft,
messageDraftBodyRanges: nil,
mutedUntilDateObsolete: nil,
mutedUntilTimestampObsolete: 0,
shouldThreadBeVisible: true,
storyViewMode: .default,
contactUUID: nil,
contactPhoneNumber: nil,
hasDismissedOffers: false,
)
}
@ -100,7 +73,6 @@ class ThreadFinderTests: XCTestCase {
// New draft.
try buildThreadRecord(
uniqueID: "UUID1",
contactThread: contactThread1,
draft: "test draft",
lastInteractionRowID: 0,
lastDraftInteractionRowId: 1,
@ -116,7 +88,6 @@ class ThreadFinderTests: XCTestCase {
// Non-draft that has more recent lastInteractionRowID.
try buildThreadRecord(
uniqueID: "UUID2",
contactThread: contactThread2,
draft: nil,
lastInteractionRowID: 1,
lastDraftInteractionRowId: 0,
@ -165,7 +136,6 @@ class ThreadFinderTests: XCTestCase {
// New draft that is not the latest activity on the thread.
try buildThreadRecord(
uniqueID: "UUID1",
contactThread: contactThread1,
draft: "test draft",
lastInteractionRowID: 3,
lastDraftInteractionRowId: 1,
@ -181,7 +151,6 @@ class ThreadFinderTests: XCTestCase {
// Non-draft that has less recent lastInteractionRowID.
try buildThreadRecord(
uniqueID: "UUID2",
contactThread: contactThread2,
draft: nil,
lastInteractionRowID: 2,
lastDraftInteractionRowId: 0,
@ -230,7 +199,6 @@ class ThreadFinderTests: XCTestCase {
// Thread 1, has a draft after latest TSInteraction, but less recent than UUID2.
try buildThreadRecord(
uniqueID: "UUID1",
contactThread: contactThread1,
draft: "test draft",
lastInteractionRowID: 2,
lastDraftInteractionRowId: 2,
@ -246,7 +214,6 @@ class ThreadFinderTests: XCTestCase {
// Thread 2, has a more recent draft based on timestamp.
try buildThreadRecord(
uniqueID: "UUID2",
contactThread: contactThread2,
draft: "test draft",
lastInteractionRowID: 1,
lastDraftInteractionRowId: 2,
@ -295,7 +262,6 @@ class ThreadFinderTests: XCTestCase {
// New draft that is not the latest activity on the thread.
try buildThreadRecord(
uniqueID: "UUID1",
contactThread: contactThread1,
draft: "test draft",
lastInteractionRowID: 1,
lastDraftInteractionRowId: 2,
@ -311,7 +277,6 @@ class ThreadFinderTests: XCTestCase {
// Non-draft that has less recent lastInteractionRowID.
try buildThreadRecord(
uniqueID: "UUID2",
contactThread: contactThread2,
draft: nil,
lastInteractionRowID: 3,
lastDraftInteractionRowId: 0,

View File

@ -53,8 +53,8 @@ struct PollManagerTest {
private func insertIncomingPollMessage(question: String, timestamp: UInt64? = nil) -> TSIncomingMessage {
db.write { tx in
let db = tx.database
if try! groupThread.asRecord().exists(db) == false {
try! groupThread!.asRecord().insert(db)
if try! groupThread.exists(db) == false {
try! groupThread!.insert(db)
}
let incomingMessage = createIncomingMessage(with: groupThread) { builder in
@ -73,8 +73,8 @@ struct PollManagerTest {
private func insertOutgoingPollMessage(question: String) -> TSOutgoingMessage {
db.write { tx in
let db = tx.database
if try! groupThread.asRecord().exists(db) == false {
try! groupThread!.asRecord().insert(db)
if try! groupThread.exists(db) == false {
try! groupThread!.insert(db)
}
let outgoingMessage = TSOutgoingMessage(in: groupThread, question: question)

View File

@ -1,18 +0,0 @@
//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalServiceKit
import XCTest
final class TSThreadTests: XCTestCase {
func testInitCallsDesignatedInit() throws {
let thread = TSThread()
XCTAssertEqual(thread.conversationColorNameObsolete, "Obsolete")
XCTAssertNil(thread.messageDraft)
let now = Date()
let creationDate = try XCTUnwrap(thread.creationDate)
XCTAssertEqual(creationDate.timeIntervalSinceReferenceDate, now.timeIntervalSinceReferenceDate, accuracy: 0.01 /* 10 ms */ )
}
}

View File

@ -297,12 +297,12 @@ public struct StoryConversationItem {
) -> UInt64 {
if
let thread = thread as? TSGroupThread,
associatedData?.lastReceivedTimestamp ?? 0 > thread.lastSentStoryTimestamp?.uint64Value ?? 0
associatedData?.lastReceivedTimestamp ?? 0 > thread.lastSentStoryTimestamp ?? 0
{
return associatedData?.lastReceivedTimestamp ?? 0
}
return thread.lastSentStoryTimestamp?.uint64Value ?? 0
return thread.lastSentStoryTimestamp ?? 0
}
let threads = ThreadFinder().storyThreads(