From b281b3763797e342caf9c898ff55daba14487537 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sun, 23 Sep 2018 14:48:44 -0600 Subject: [PATCH] replace thread.lastMessageDate/archivalDate -> thread.lastSortId, thread.archivedAsOfSortId Update migration accordingly Date shown on home view cell is message.receivedAt --- .../ConversationViewController.m | 10 --- .../ViewModels/ThreadViewModel.swift | 6 +- .../migrations/OWS110SortIdMigration.swift | 23 ++++++ SignalServiceKit/src/Contacts/TSThread.h | 30 +++----- SignalServiceKit/src/Contacts/TSThread.m | 73 +++++++++++-------- .../src/Messages/Interactions/TSInteraction.m | 2 +- SignalServiceKit/src/Storage/TSDatabaseView.m | 37 ++-------- .../src/Storage/TSYapDatabaseObject.h | 1 + .../src/Storage/TSYapDatabaseObject.m | 12 ++- 9 files changed, 96 insertions(+), 98 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index c12916e36..62bbb9e5b 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2778,7 +2778,6 @@ typedef enum : NSUInteger { OWSAssertIsOnMainThread(); OWSAssertDebug(message); - [self updateLastVisibleSortId:message.sortId]; self.lastMessageSentDate = [NSDate new]; [self clearUnreadMessagesIndicator]; self.inputToolbar.quotedReply = nil; @@ -3902,15 +3901,6 @@ typedef enum : NSUInteger { self.hasUnreadMessages = numberOfUnreadMessages > 0; } -- (void)updateLastVisibleSortId:(uint64_t)sortId -{ - OWSAssertDebug(sortId > 0); - - self.lastVisibleSortId = MAX(self.lastVisibleSortId, sortId); - - [self ensureScrollDownButton]; -} - - (void)markVisibleMessagesAsRead { if (self.presentedViewController) { diff --git a/SignalMessaging/ViewModels/ThreadViewModel.swift b/SignalMessaging/ViewModels/ThreadViewModel.swift index 36cbcd0df..16f8fa16a 100644 --- a/SignalMessaging/ViewModels/ThreadViewModel.swift +++ b/SignalMessaging/ViewModels/ThreadViewModel.swift @@ -25,12 +25,14 @@ public class ThreadViewModel: NSObject { @objc public init(thread: TSThread, transaction: YapDatabaseReadTransaction) { self.threadRecord = thread - self.lastMessageDate = thread.lastMessageDate() + self.isGroupThread = thread.isGroupThread() self.name = thread.name() self.isMuted = thread.isMuted self.lastMessageText = thread.lastMessageText(transaction: transaction) - self.lastMessageForInbox = thread.lastInteractionForInbox(transaction: transaction) + let lastInteraction = thread.lastInteractionForInbox(transaction: transaction) + self.lastMessageForInbox = lastInteraction + self.lastMessageDate = lastInteraction?.receivedAtDate() ?? thread.creationDate if let contactThread = thread as? TSContactThread { self.contactIdentifier = contactThread.contactIdentifier() diff --git a/SignalMessaging/environment/migrations/OWS110SortIdMigration.swift b/SignalMessaging/environment/migrations/OWS110SortIdMigration.swift index 0187c6aa1..a65d54901 100644 --- a/SignalMessaging/environment/migrations/OWS110SortIdMigration.swift +++ b/SignalMessaging/environment/migrations/OWS110SortIdMigration.swift @@ -16,6 +16,21 @@ class OWS110SortIdMigration: OWSDatabaseMigration { // TODO batch this? self.dbReadWriteConnection().readWrite { transaction in + + var archivedThreads: [TSThread] = [] + + // get archived threads before migration + TSThread.enumerateCollectionObjects({ (object, _) in + guard let thread = object as? TSThread else { + owsFailDebug("unexpected object: \(type(of: object))") + return + } + + if thread.isArchivedByLegacyTimestampForSorting { + archivedThreads.append(thread) + } + }) + guard let legacySorting: YapDatabaseAutoViewTransaction = transaction.extension(TSMessageDatabaseViewExtensionName_Legacy) as? YapDatabaseAutoViewTransaction else { owsFailDebug("legacySorting was unexpectedly nil") return @@ -33,6 +48,14 @@ class OWS110SortIdMigration: OWSDatabaseMigration { Logger.debug("thread: \(interaction.uniqueThreadId), timestampForLegacySorting:\(interaction.timestampForLegacySorting()), sortId: \(interaction.sortId)") } } + + Logger.info("re-archiving \(archivedThreads.count) threads which were previously archived") + for archivedThread in archivedThreads { + // latestMessageSortId will have been modified by saving all + // the interactions above, make sure we get the latest value. + archivedThread.reload(with: transaction) + archivedThread.archiveThread(with: transaction) + } } completion() diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h index 8812ee0cb..5f1d1ed36 100644 --- a/SignalServiceKit/src/Contacts/TSThread.h +++ b/SignalServiceKit/src/Contacts/TSThread.h @@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN // YES IFF this thread has ever had a message. @property (nonatomic) BOOL hasEverHadMessage; +@property (nonatomic, readonly) NSDate *creationDate; +@property (nonatomic, readonly) BOOL isArchivedByLegacyTimestampForSorting; /** * Whether the object is a group thread or not. @@ -71,12 +73,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; /** - * Returns the latest date of a message in the thread or the thread creation date if there are no messages in that - *thread. - * - * @return The date of the last message or thread creation date. + * @return the latest sortId of a message in the thread or 0 if there are no messages in that + * thread. */ -- (NSDate *)lastMessageDate; +@property (nonatomic, readonly) uint64_t latestMessageSortId; /** * Returns the string that will be displayed typically in a conversations view as a preview of the last message @@ -101,31 +101,19 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark Archival /** - * Returns the last date at which a string was archived or nil if the thread was never archived or brought back to the - *inbox. - * - * @return Last archival date. + * @return YES if no new messages have been sent or received since the thread was last archived. */ -- (nullable NSDate *)archivalDate; +- (BOOL)isArchived; /** - * Archives a thread with the current date. + * Archives a thread * * @param transaction Database transaction. */ - (void)archiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; /** - * Archives a thread with the reference date. This is currently only used for migrating older data that has already - * been archived. - * - * @param transaction Database transaction. - * @param date Date at which the thread was archived. - */ -- (void)archiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction referenceDate:(NSDate *)date; - -/** - * Unarchives a thread that was archived previously. + * Unarchives a thread * * @param transaction Database transaction. */ diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m index 023a9b2a1..cf8cc7b17 100644 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ b/SignalServiceKit/src/Contacts/TSThread.m @@ -22,9 +22,11 @@ NS_ASSUME_NONNULL_BEGIN @interface TSThread () @property (nonatomic) NSDate *creationDate; -@property (nonatomic, copy, nullable) NSDate *archivalDate; @property (nonatomic, nullable) NSString *conversationColorName; -@property (nonatomic, nullable) NSDate *lastMessageDate; + +@property (nonatomic) uint64_t latestMessageSortId; +@property (nonatomic) uint64_t archivedAsOfMessageSortId; + @property (nonatomic, copy, nullable) NSString *messageDraft; @property (atomic, nullable) NSDate *mutedUntilDate; @@ -43,8 +45,6 @@ NS_ASSUME_NONNULL_BEGIN self = [super initWithUniqueId:uniqueId]; if (self) { - _archivalDate = nil; - _lastMessageDate = nil; _creationDate = [NSDate date]; _messageDraft = nil; @@ -76,7 +76,12 @@ NS_ASSUME_NONNULL_BEGIN _conversationColorName = [self.class stableConversationColorNameForString:self.uniqueId]; } } - + + NSDate *_Nullable lastMessageDate = [coder decodeObjectOfClass:NSDate.class forKey:@"lastMessageDate"]; + NSDate *_Nullable archivalDate = [coder decodeObjectOfClass:NSDate.class forKey:@"archivalDate"]; + _isArchivedByLegacyTimestampForSorting = + [self.class legacyIsArchivedWithLastMessageDate:lastMessageDate archivalDate:archivalDate]; + return self; } @@ -300,14 +305,6 @@ NS_ASSUME_NONNULL_BEGIN return last; } -- (NSDate *)lastMessageDate { - if (_lastMessageDate) { - return _lastMessageDate; - } else { - return _creationDate; - } -} - - (NSString *)lastMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction { TSInteraction *interaction = [self lastInteractionForInboxWithTransaction:transaction]; @@ -354,12 +351,8 @@ NS_ASSUME_NONNULL_BEGIN } self.hasEverHadMessage = YES; - - // MJK FIXME - this needs to use sortId - NSDate *lastMessageDate = lastMessage.dateForLegacySorting; - if (!_lastMessageDate || [lastMessageDate timeIntervalSinceDate:self.lastMessageDate] > 0) { - _lastMessageDate = lastMessageDate; - + if (lastMessage.sortId > self.latestMessageSortId) { + self.latestMessageSortId = lastMessage.sortId; [self saveWithTransaction:transaction]; } } @@ -384,30 +377,46 @@ NS_ASSUME_NONNULL_BEGIN } } -#pragma mark Archival +#pragma mark - Archival -- (nullable NSDate *)archivalDate +- (BOOL)isArchived { - return _archivalDate; + return self.archivedAsOfMessageSortId >= self.latestMessageSortId; } -- (void)archiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { - [self archiveThreadWithTransaction:transaction referenceDate:[NSDate date]]; ++ (BOOL)legacyIsArchivedWithLastMessageDate:(nullable NSDate *)lastMessageDate + archivalDate:(nullable NSDate *)archivalDate +{ + if (!archivalDate) { + return NO; + } + + if (!lastMessageDate) { + return YES; + } + + return [archivalDate compare:lastMessageDate] != NSOrderedAscending; } -- (void)archiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction referenceDate:(NSDate *)date { - [self markAllAsReadWithTransaction:transaction]; - _archivalDate = date; +- (void)archiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +{ + [self applyChangeToSelfAndLatestCopy:transaction + changeBlock:^(TSThread *thread) { + thread.archivedAsOfMessageSortId = self.latestMessageSortId; + }]; - [self saveWithTransaction:transaction]; + [self markAllAsReadWithTransaction:transaction]; } -- (void)unarchiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { - _archivalDate = nil; - [self saveWithTransaction:transaction]; +- (void)unarchiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +{ + [self applyChangeToSelfAndLatestCopy:transaction + changeBlock:^(TSThread *thread) { + thread.archivedAsOfMessageSortId = 0; + }]; } -#pragma mark Drafts +#pragma mark - Drafts - (NSString *)currentDraftWithTransaction:(YapDatabaseReadTransaction *)transaction { TSThread *thread = [TSThread fetchObjectWithUniqueID:self.uniqueId transaction:transaction]; diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m index 6434d259d..715a08cbe 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m +++ b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m @@ -170,7 +170,7 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value) [super saveWithTransaction:transaction]; - TSThread *fetchedThread = [TSThread fetchObjectWithUniqueID:self.uniqueThreadId transaction:transaction]; + TSThread *fetchedThread = [self threadWithTransaction:transaction]; [fetchedThread updateWithLastMessage:self transaction:transaction]; } diff --git a/SignalServiceKit/src/Storage/TSDatabaseView.m b/SignalServiceKit/src/Storage/TSDatabaseView.m index dcea9568e..4e1b4435e 100644 --- a/SignalServiceKit/src/Storage/TSDatabaseView.m +++ b/SignalServiceKit/src/Storage/TSDatabaseView.m @@ -293,13 +293,7 @@ NSString *const TSLazyRestoreAttachmentsGroup = @"TSLazyRestoreAttachmentsGroup" } } - if (thread.archivalDate) { - return ([self threadShouldBeInInbox:thread]) ? TSInboxGroup : TSArchiveGroup; - } else if (thread.archivalDate) { - return TSArchiveGroup; - } else { - return TSInboxGroup; - } + return thread.isArchived ? TSArchiveGroup : TSInboxGroup; }]; YapDatabaseViewSorting *viewSorting = [self threadSorting]; @@ -315,29 +309,6 @@ NSString *const TSLazyRestoreAttachmentsGroup = @"TSLazyRestoreAttachmentsGroup" [storage asyncRegisterExtension:databaseView withName:TSThreadDatabaseViewExtensionName]; } -/** - * Determines whether a thread belongs to the archive or inbox - * - * @param thread TSThread - * - * @return Inbox if true, Archive if false - */ - -+ (BOOL)threadShouldBeInInbox:(TSThread *)thread { - NSDate *lastMessageDate = thread.lastMessageDate; - NSDate *archivalDate = thread.archivalDate; - if (lastMessageDate && archivalDate) { // this is what is called - return ([lastMessageDate timeIntervalSinceDate:archivalDate] > 0) - ? YES - : NO; // if there hasn't been a new message since the archive date, it's in the archive. an issue is - // that empty threads are always given with a lastmessage date of the present on every launch - } else if (archivalDate) { - return NO; - } - - return YES; -} - + (YapDatabaseViewSorting *)threadSorting { return [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, NSString *group, @@ -358,7 +329,11 @@ NSString *const TSLazyRestoreAttachmentsGroup = @"TSLazyRestoreAttachmentsGroup" TSThread *thread1 = (TSThread *)object1; TSThread *thread2 = (TSThread *)object2; if ([group isEqualToString:TSArchiveGroup] || [group isEqualToString:TSInboxGroup]) { - return [thread1.lastMessageDate compare:thread2.lastMessageDate]; + if (thread1.latestMessageSortId == 0 && thread2.latestMessageSortId == 0) { + return [thread1.creationDate compare:thread2.creationDate]; + } + + return [@(thread1.latestMessageSortId) compare:@(thread2.latestMessageSortId)]; } return NSOrderedSame; diff --git a/SignalServiceKit/src/Storage/TSYapDatabaseObject.h b/SignalServiceKit/src/Storage/TSYapDatabaseObject.h index 0958def56..b8f69de09 100644 --- a/SignalServiceKit/src/Storage/TSYapDatabaseObject.h +++ b/SignalServiceKit/src/Storage/TSYapDatabaseObject.h @@ -95,6 +95,7 @@ NS_ASSUME_NONNULL_BEGIN * Assign the latest persisted values from the database. */ - (void)reload; +- (void)reloadWithTransaction:(YapDatabaseReadTransaction *)transaction; /** * Saves the object with the shared readWrite connection - does not block. diff --git a/SignalServiceKit/src/Storage/TSYapDatabaseObject.m b/SignalServiceKit/src/Storage/TSYapDatabaseObject.m index d8a0eddbd..639f6760b 100644 --- a/SignalServiceKit/src/Storage/TSYapDatabaseObject.m +++ b/SignalServiceKit/src/Storage/TSYapDatabaseObject.m @@ -218,9 +218,19 @@ NS_ASSUME_NONNULL_BEGIN } } +#pragma mark Reload + - (void)reload { - TSYapDatabaseObject *latest = [[self class] fetchObjectWithUniqueID:self.uniqueId]; + [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + [self reloadWithTransaction:transaction]; + }]; +} + + +- (void)reloadWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + TSYapDatabaseObject *latest = [[self class] fetchObjectWithUniqueID:self.uniqueId transaction:transaction]; if (!latest) { OWSFailDebug(@"`latest` was unexpectedly nil"); return;