Signal-iOS/SignalServiceKit/Messages/Interactions/TSInteraction.m
2026-04-15 01:12:00 -04:00

286 lines
8.4 KiB
Objective-C

//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "TSInteraction.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
NSString *NSStringFromOWSInteractionType(OWSInteractionType value)
{
switch (value) {
case OWSInteractionType_Unknown:
return @"OWSInteractionType_Unknown";
case OWSInteractionType_IncomingMessage:
return @"OWSInteractionType_IncomingMessage";
case OWSInteractionType_OutgoingMessage:
return @"OWSInteractionType_OutgoingMessage";
case OWSInteractionType_Error:
return @"OWSInteractionType_Error";
case OWSInteractionType_Call:
return @"OWSInteractionType_Call";
case OWSInteractionType_Info:
return @"OWSInteractionType_Info";
case OWSInteractionType_ThreadDetails:
return @"OWSInteractionType_ThreadDetails";
case OWSInteractionType_TypingIndicator:
return @"OWSInteractionType_TypingIndicator";
case OWSInteractionType_UnreadIndicator:
return @"OWSInteractionType_UnreadIndicator";
case OWSInteractionType_DateHeader:
return @"OWSInteractionType_DateHeader";
case OWSInteractionType_UnknownThreadWarning:
return @"OWSInteractionType_UnknownThreadWarning";
case OWSInteractionType_DefaultDisappearingMessageTimer:
return @"OWSInteractionType_DefaultDisappearingMessageTimer";
case OWSInteractionType_CollapseSet:
return @"OWSInteractionType_CollapseSet";
}
}
// MARK: -
@interface TSInteraction ()
@property (nonatomic) uint64_t sortId;
@property (nonatomic) uint64_t receivedAtTimestamp;
@property (nonatomic) uint64_t timestamp;
@end
// MARK: -
@implementation TSInteraction
- (instancetype)initWithCustomUniqueId:(NSString *)uniqueId
timestamp:(uint64_t)timestamp
receivedAtTimestamp:(uint64_t)receivedAtTimestamp
thread:(TSThread *)thread
{
self = [super initWithUniqueId:uniqueId];
if (!self) {
return self;
}
_timestamp = timestamp;
_receivedAtTimestamp = receivedAtTimestamp;
_uniqueThreadId = thread.uniqueId;
return self;
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp
receivedAtTimestamp:(uint64_t)receivedAtTimestamp
thread:(TSThread *)thread
{
// Use a sequential UUID for interaction inserts, as an optimization for the
// corresponding insert into the index on `uniqueId`. See comments about
// UUIDv7 for more.
NSString *uniqueId = [[NSUUID sequential] UUIDString];
self = [super initWithUniqueId:uniqueId];
if (!self) {
return self;
}
_timestamp = timestamp;
_receivedAtTimestamp = receivedAtTimestamp;
_uniqueThreadId = thread.uniqueId;
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
receivedAtTimestamp:(uint64_t)receivedAtTimestamp
sortId:(uint64_t)sortId
timestamp:(uint64_t)timestamp
uniqueThreadId:(NSString *)uniqueThreadId
{
self = [super initWithGrdbId:grdbId
uniqueId:uniqueId];
if (!self) {
return self;
}
_receivedAtTimestamp = receivedAtTimestamp;
_sortId = sortId;
_timestamp = timestamp;
_uniqueThreadId = uniqueThreadId;
return self;
}
// clang-format on
// --- CODE GENERATION MARKER
- (void)encodeWithCoder:(NSCoder *)coder
{
[self encodeIdsWithCoder:coder];
[coder encodeObject:[self valueForKey:@"receivedAtTimestamp"] forKey:@"receivedAtTimestamp"];
[coder encodeObject:[self valueForKey:@"sortId"] forKey:@"sortId"];
[coder encodeObject:[self valueForKey:@"timestamp"] forKey:@"timestamp"];
NSString *uniqueThreadId = self.uniqueThreadId;
if (uniqueThreadId != nil) {
[coder encodeObject:uniqueThreadId forKey:@"uniqueThreadId"];
}
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (!self) {
return self;
}
self->_receivedAtTimestamp = [(NSNumber *)[coder decodeObjectOfClass:[NSNumber class]
forKey:@"receivedAtTimestamp"] unsignedLongLongValue];
self->_sortId = [(NSNumber *)[coder decodeObjectOfClass:[NSNumber class] forKey:@"sortId"] unsignedLongValue];
self->_timestamp = [(NSNumber *)[coder decodeObjectOfClass:[NSNumber class]
forKey:@"timestamp"] unsignedLongLongValue];
self->_uniqueThreadId = [coder decodeObjectOfClass:[NSString class] forKey:@"uniqueThreadId"];
// Previously the receivedAtTimestamp field lived on TSMessage, but we've moved it up
// to the TSInteraction superclass.
if (_receivedAtTimestamp == 0) {
// Upgrade from the older "TSMessage.receivedAtDate" and "TSMessage.receivedAt" properties if
// necessary.
NSDate *receivedAtDate = [coder decodeObjectOfClass:[NSDate class] forKey:@"receivedAtDate"];
if (!receivedAtDate) {
receivedAtDate = [coder decodeObjectOfClass:[NSDate class] forKey:@"receivedAt"];
}
if (receivedAtDate) {
_receivedAtTimestamp = [NSDate ows_millisecondsSince1970ForDate:receivedAtDate];
}
// For TSInteractions which are not TSMessage's, the timestamp *is* the receivedAtTimestamp
if (_receivedAtTimestamp == 0) {
_receivedAtTimestamp = _timestamp;
}
}
return self;
}
- (NSUInteger)hash
{
NSUInteger result = [super hash];
result ^= self.receivedAtTimestamp;
result ^= self.sortId;
result ^= self.timestamp;
result ^= self.uniqueThreadId.hash;
return result;
}
- (BOOL)isEqual:(id)other
{
if (![super isEqual:other]) {
return NO;
}
TSInteraction *typedOther = (TSInteraction *)other;
if (self.receivedAtTimestamp != typedOther.receivedAtTimestamp) {
return NO;
}
if (self.sortId != typedOther.sortId) {
return NO;
}
if (self.timestamp != typedOther.timestamp) {
return NO;
}
if (![NSObject isObject:self.uniqueThreadId equalToObject:typedOther.uniqueThreadId]) {
return NO;
}
return YES;
}
#pragma mark Thread
- (nullable TSThread *)threadWithTx:(DBReadTransaction *)tx
{
if (self.uniqueThreadId == nil) {
// This might be true for a few legacy interactions enqueued in the message
// sender. The message sender will handle this case.
return nil;
}
// However, it's also possible that the thread doesn't exist.
return [TSThread fetchViaCacheObjCWithUniqueId:self.uniqueThreadId transaction:tx];
}
#pragma mark Date operations
- (NSDate *)receivedAtDate
{
return [NSDate ows_dateWithMillisecondsSince1970:self.receivedAtTimestamp];
}
- (NSDate *)timestampDate
{
return [NSDate ows_dateWithMillisecondsSince1970:self.timestamp];
}
- (OWSInteractionType)interactionType
{
OWSFailDebug(@"unknown interaction type.");
return OWSInteractionType_Unknown;
}
- (NSString *)description
{
return [NSString
stringWithFormat:@"%@ in thread: %@ timestamp: %llu", [super description], self.uniqueThreadId, self.timestamp];
}
#pragma mark -
- (BOOL)isDynamicInteraction
{
return NO;
}
#pragma mark - sorting migration
- (void)replaceSortId:(uint64_t)sortId
{
_sortId = sortId;
}
#if TESTABLE_BUILD
- (void)replaceTimestamp:(uint64_t)timestamp transaction:(DBWriteTransaction *)transaction
{
[self anyUpdateWithTransaction:transaction
block:^(TSInteraction *interaction) { interaction.timestamp = timestamp; }];
}
- (void)replaceReceivedAtTimestamp:(uint64_t)receivedAtTimestamp
{
self.receivedAtTimestamp = receivedAtTimestamp;
}
- (void)replaceReceivedAtTimestamp:(uint64_t)receivedAtTimestamp transaction:(DBWriteTransaction *)transaction
{
[self anyUpdateWithTransaction:transaction
block:^(TSInteraction *interaction) {
interaction.receivedAtTimestamp = receivedAtTimestamp;
}];
}
#endif
@end
NS_ASSUME_NONNULL_END