// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import "TSInteraction.h" #import "TSDatabaseSecondaryIndexes.h" #import "TSThread.h" #import "TSGroupThread.h" #import #import 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_Call: return @"OWSInteractionType_Call"; case OWSInteractionType_Info: return @"OWSInteractionType_Info"; case OWSInteractionType_Offer: return @"OWSInteractionType_Offer"; case OWSInteractionType_TypingIndicator: return @"OWSInteractionType_TypingIndicator"; } } @interface TSInteraction () @property (nonatomic) uint64_t sortId; @end @implementation TSInteraction @synthesize timestamp = _timestamp; + (NSArray *)interactionsWithTimestamp:(uint64_t)timestamp ofClass:(Class)clazz withTransaction:(YapDatabaseReadTransaction *)transaction { // Accept any interaction. return [self interactionsWithTimestamp:timestamp filter:^(TSInteraction *interaction) { return [interaction isKindOfClass:clazz]; } withTransaction:transaction]; } + (NSArray *)interactionsWithTimestamp:(uint64_t)timestamp filter:(BOOL (^_Nonnull)(TSInteraction *))filter withTransaction:(YapDatabaseReadTransaction *)transaction { NSMutableArray *interactions = [NSMutableArray new]; [TSDatabaseSecondaryIndexes enumerateMessagesWithTimestamp:timestamp withBlock:^(NSString *collection, NSString *key, BOOL *stop) { TSInteraction *interaction = [TSInteraction fetchObjectWithUniqueID:key transaction:transaction]; if (!filter(interaction)) { return; } [interactions addObject:interaction]; } usingTransaction:transaction]; return [interactions copy]; } + (NSString *)collection { return @"TSInteraction"; } - (instancetype)initInteractionWithUniqueId:(NSString *)uniqueId timestamp:(uint64_t)timestamp inThread:(TSThread *)thread { self = [super initWithUniqueId:uniqueId]; if (!self) { return self; } _timestamp = timestamp; _uniqueThreadId = thread.uniqueId; return self; } - (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread { self = [super initWithUniqueId:[[NSUUID UUID] UUIDString]]; if (!self) { return self; } _timestamp = timestamp; _uniqueThreadId = thread.uniqueId; _receivedAtTimestamp = [NSDate ows_millisecondTimeStamp]; return self; } - (nullable instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (!self) { return nil; } // 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 decodeObjectForKey:@"receivedAtDate"]; if (!receivedAtDate) { receivedAtDate = [coder decodeObjectForKey:@"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; } #pragma mark Thread - (TSThread *)thread { return [TSThread fetchObjectWithUniqueID:self.uniqueThreadId]; } - (TSThread *)threadWithTransaction:(YapDatabaseReadTransaction *)transaction { return [TSThread fetchObjectWithUniqueID:self.uniqueThreadId transaction:transaction]; } - (void)touchThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { [transaction touchObjectForKey:self.uniqueThreadId inCollection:[TSThread collection]]; } - (void)applyChangeToSelfAndLatestCopy:(YapDatabaseReadWriteTransaction *)transaction changeBlock:(void (^)(id))changeBlock { [super applyChangeToSelfAndLatestCopy:transaction changeBlock:changeBlock]; [self touchThreadWithTransaction:transaction]; } #pragma mark Date operations - (uint64_t)timestampForLegacySorting { return self.timestamp; } - (NSDate *)dateForUI { return [NSDate ows_dateWithMillisecondsSince1970:self.timestamp]; } - (NSDate *)receivedAtDate { // This is only used for sorting threads return [NSDate ows_dateWithMillisecondsSince1970:self.receivedAtTimestamp]; } - (NSComparisonResult)compareForSorting:(TSInteraction *)other { uint64_t sortId1; uint64_t sortId2; // In open groups messages should be sorted by server timestamp. `sortId` represents the order in which messages // were processed. Since in the open group poller we sort messages by their server timestamp, sorting by `sortId` is // effectively the same as sorting by server timestamp. // sortId == serverTimestamp (the sent timestamp) for open group messages. // sortId == timestamp (the sent timestamp) for one-to-one and closed group messages. sortId1 = self.sortId; sortId2 = other.sortId; if (sortId1 > sortId2) { return NSOrderedDescending; } else if (sortId1 < sortId2) { return NSOrderedAscending; } else { return NSOrderedSame; } } - (OWSInteractionType)interactionType { return OWSInteractionType_Unknown; } - (NSString *)description { return [NSString stringWithFormat:@"%@ in thread: %@ timestamp: %lu", [super description], self.uniqueThreadId, (unsigned long)self.timestamp]; } - (uint64_t)sortId { return self.timestamp; } - (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { if (!self.uniqueId) { self.uniqueId = [NSUUID new].UUIDString; } if (self.sortId == 0) { self.sortId = [SSKIncrementingIdFinder nextIdWithKey:[TSInteraction collection] transaction:transaction]; } [super saveWithTransaction:transaction]; TSThread *fetchedThread = [self threadWithTransaction:transaction]; [fetchedThread updateWithLastMessage:self transaction:transaction]; } - (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { [super removeWithTransaction:transaction]; [self touchThreadWithTransaction:transaction]; } - (BOOL)isDynamicInteraction { return NO; } #pragma mark - sorting migration - (void)saveNextSortIdWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { if (self.sortId != 0) { // This could happen if something else in our startup process saved the interaction // e.g. another migration ran. // During the migration, since we're enumerating the interactions in the proper order, // we want to ignore any previously assigned sortId self.sortId = 0; } [self saveWithTransaction:transaction]; } - (void)updateTimestamp:(uint64_t)timestamp { _timestamp = timestamp; } #pragma mark - Message Request Thread Migration - (void)moveToThreadWithId:(NSString *)threadId { _uniqueThreadId = threadId; } @end NS_ASSUME_NONNULL_END