diff --git a/src/Contacts/TSThread.m b/src/Contacts/TSThread.m index 97d21d2c4..6ac555148 100644 --- a/src/Contacts/TSThread.m +++ b/src/Contacts/TSThread.m @@ -189,6 +189,23 @@ NS_ASSUME_NONNULL_BEGIN return hasUnread; } +- (NSArray> *)unseenMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + NSMutableArray> *messages = [NSMutableArray new]; + [[transaction ext:TSUnseenDatabaseViewExtensionName] + enumerateRowsInGroup:self.uniqueId + usingBlock:^( + NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) { + + if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { + DDLogError(@"%@ Unexpected object in unseen messages: %@", self.tag, object); + } + [messages addObject:(id)object]; + }]; + + return [messages copy]; +} + - (NSArray > *)unreadMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction { NSMutableArray > *messages = [NSMutableArray new]; @@ -218,9 +235,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { - for (id message in [self unreadMessagesWithTransaction:transaction]) { + for (id message in [self unseenMessagesWithTransaction:transaction]) { [message markAsReadLocallyWithTransaction:transaction]; } + + // Just to be defensive, we'll also check for unread messages. + OWSAssert([self unreadMessagesWithTransaction:transaction].count < 1); } - (void)markAllAsRead @@ -255,7 +275,7 @@ NS_ASSUME_NONNULL_BEGIN } - (void)updateWithLastMessage:(TSInteraction *)lastMessage transaction:(YapDatabaseReadWriteTransaction *)transaction { - NSDate *lastMessageDate = [lastMessage receiptDateForSorting]; + NSDate *lastMessageDate = [lastMessage dateForSorting]; if ([lastMessage isKindOfClass:[TSErrorMessage class]]) { TSErrorMessage *errorMessage = (TSErrorMessage *)lastMessage; diff --git a/src/Messages/Interactions/TSErrorMessage.h b/src/Messages/Interactions/TSErrorMessage.h index b43c18b44..585b48782 100644 --- a/src/Messages/Interactions/TSErrorMessage.h +++ b/src/Messages/Interactions/TSErrorMessage.h @@ -2,12 +2,13 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +#import "OWSReadTracking.h" #import "OWSSignalServiceProtos.pb.h" #import "TSMessage.h" NS_ASSUME_NONNULL_BEGIN -@interface TSErrorMessage : TSMessage +@interface TSErrorMessage : TSMessage typedef NS_ENUM(int32_t, TSErrorMessageType) { TSErrorMessageNoSession, @@ -56,6 +57,8 @@ typedef NS_ENUM(int32_t, TSErrorMessageType) { @property (nonatomic, readonly) TSErrorMessageType errorType; @property (nullable, nonatomic, readonly) NSString *recipientId; +- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; + @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSErrorMessage.m b/src/Messages/Interactions/TSErrorMessage.m index b4fd6e874..0623f84f4 100644 --- a/src/Messages/Interactions/TSErrorMessage.m +++ b/src/Messages/Interactions/TSErrorMessage.m @@ -9,14 +9,38 @@ #import "TSErrorMessage_privateConstructor.h" #import "TSMessagesManager.h" #import "TextSecureKitEnv.h" +#import NS_ASSUME_NONNULL_BEGIN +NSUInteger TSErrorMessageSchemaVersion = 1; + +@interface TSErrorMessage () + +@property (nonatomic, getter=wasRead) BOOL read; + +@property (nonatomic, readonly) NSUInteger errorMessageSchemaVersion; + +@end + +#pragma mark - + @implementation TSErrorMessage - (instancetype)initWithCoder:(NSCoder *)coder { - return [super initWithCoder:coder]; + self = [super initWithCoder:coder]; + if (!self) { + return self; + } + + if (self.errorMessageSchemaVersion < 1) { + _read = YES; + } + + _errorMessageSchemaVersion = TSErrorMessageSchemaVersion; + + return self; } - (instancetype)initWithTimestamp:(uint64_t)timestamp @@ -44,6 +68,7 @@ NS_ASSUME_NONNULL_BEGIN _errorType = errorMessageType; _recipientId = recipientId; + _errorMessageSchemaVersion = TSErrorMessageSchemaVersion; return self; } @@ -135,6 +160,40 @@ NS_ASSUME_NONNULL_BEGIN recipientId:recipientId]; } +#pragma mark - OWSReadTracking + +- (BOOL)shouldAffectUnreadCounts +{ + return NO; +} + +- (void)markAsReadLocally +{ + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self markAsReadLocallyWithTransaction:transaction]; + }]; +} + +- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +{ + OWSAssert(transaction); + DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); + _read = YES; + [self saveWithTransaction:transaction]; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSIncomingMessage.h b/src/Messages/Interactions/TSIncomingMessage.h index cb1b43d83..3b254920a 100644 --- a/src/Messages/Interactions/TSIncomingMessage.h +++ b/src/Messages/Interactions/TSIncomingMessage.h @@ -107,7 +107,6 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification; // This will be 0 for messages created before we were tracking sourceDeviceId @property (nonatomic, readonly) UInt32 sourceDeviceId; -@property (nonatomic, readonly, getter=wasRead) BOOL read; /* * Marks a message as having been read on this device (as opposed to responding to a remote read receipt). diff --git a/src/Messages/Interactions/TSIncomingMessage.m b/src/Messages/Interactions/TSIncomingMessage.m index c91ea7cce..6e9db8409 100644 --- a/src/Messages/Interactions/TSIncomingMessage.m +++ b/src/Messages/Interactions/TSIncomingMessage.m @@ -12,11 +12,24 @@ NS_ASSUME_NONNULL_BEGIN NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingMessageWasReadOnThisDeviceNotification"; +@interface TSIncomingMessage () + +@property (nonatomic, getter=wasRead) BOOL read; + +@end + +#pragma mark - + @implementation TSIncomingMessage - (instancetype)initWithCoder:(NSCoder *)coder { - return [super initWithCoder:coder]; + self = [super initWithCoder:coder]; + if (!self) { + return self; + } + + return self; } - (instancetype)initWithTimestamp:(uint64_t)timestamp @@ -57,8 +70,6 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM _sourceDeviceId = sourceDeviceId; _read = NO; - OWSAssert(self.receivedAtDate); - return self; } @@ -97,6 +108,13 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM return foundMessage; } +#pragma mark - OWSReadTracking + +- (BOOL)shouldAffectUnreadCounts +{ + return YES; +} + - (void)markAsReadFromReadReceipt { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { diff --git a/src/Messages/Interactions/TSInfoMessage.h b/src/Messages/Interactions/TSInfoMessage.h index 9ff08ed40..ddae00f44 100644 --- a/src/Messages/Interactions/TSInfoMessage.h +++ b/src/Messages/Interactions/TSInfoMessage.h @@ -2,11 +2,12 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +#import "OWSReadTracking.h" #import "TSMessage.h" NS_ASSUME_NONNULL_BEGIN -@interface TSInfoMessage : TSMessage +@interface TSInfoMessage : TSMessage typedef NS_ENUM(NSInteger, TSInfoMessageType) { TSInfoMessageTypeSessionDidEnd, @@ -41,6 +42,8 @@ typedef NS_ENUM(NSInteger, TSInfoMessageType) { expiresInSeconds:(uint32_t)expiresInSeconds expireStartedAt:(uint64_t)expireStartedAt NS_UNAVAILABLE; +- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; + @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSInfoMessage.m b/src/Messages/Interactions/TSInfoMessage.m index 31464fb7d..5bc1f16d5 100644 --- a/src/Messages/Interactions/TSInfoMessage.m +++ b/src/Messages/Interactions/TSInfoMessage.m @@ -2,16 +2,40 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // -#import "NSDate+millisecondTimeStamp.h" #import "TSInfoMessage.h" +#import "NSDate+millisecondTimeStamp.h" +#import NS_ASSUME_NONNULL_BEGIN +NSUInteger TSInfoMessageSchemaVersion = 1; + +@interface TSInfoMessage () + +@property (nonatomic, getter=wasRead) BOOL read; + +@property (nonatomic, readonly) NSUInteger infoMessageSchemaVersion; + +@end + +#pragma mark - + @implementation TSInfoMessage - (instancetype)initWithCoder:(NSCoder *)coder { - return [super initWithCoder:coder]; + self = [super initWithCoder:coder]; + if (!self) { + return self; + } + + if (self.infoMessageSchemaVersion < 1) { + _read = YES; + } + + _infoMessageSchemaVersion = TSInfoMessageSchemaVersion; + + return self; } - (instancetype)initWithTimestamp:(uint64_t)timestamp @@ -30,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN } _messageType = infoMessage; + _infoMessageSchemaVersion = TSInfoMessageSchemaVersion; return self; } @@ -74,6 +99,40 @@ NS_ASSUME_NONNULL_BEGIN return @"Unknown Info Message Type"; } +#pragma mark - OWSReadTracking + +- (BOOL)shouldAffectUnreadCounts +{ + return NO; +} + +- (void)markAsReadLocally +{ + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self markAsReadLocallyWithTransaction:transaction]; + }]; +} + +- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +{ + OWSAssert(transaction); + DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); + _read = YES; + [self saveWithTransaction:transaction]; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSInteraction.h b/src/Messages/Interactions/TSInteraction.h index c89e5a8a1..cc1bdc00e 100644 --- a/src/Messages/Interactions/TSInteraction.h +++ b/src/Messages/Interactions/TSInteraction.h @@ -16,7 +16,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) TSThread *thread; @property (nonatomic, readonly) uint64_t timestamp; -- (NSDate *)date; - (NSString *)description; /** @@ -33,7 +32,10 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)interactionForTimestamp:(uint64_t)timestamp withTransaction:(YapDatabaseReadWriteTransaction *)transaction; -- (nullable NSDate *)receiptDateForSorting; +// NSDate has second precision, uint64_t timestamps have millisecond precision +// so prefer timestampForSorting over dateForSorting. +- (NSDate *)dateForSorting; +- (uint64_t)timestampForSorting; // "Dynamic" interactions are not messages or static events (like // info messages, error messages, etc.). They are interactions diff --git a/src/Messages/Interactions/TSInteraction.m b/src/Messages/Interactions/TSInteraction.m index 1c7e33425..6aee59449 100644 --- a/src/Messages/Interactions/TSInteraction.m +++ b/src/Messages/Interactions/TSInteraction.m @@ -3,6 +3,7 @@ // #import "TSInteraction.h" +#import "NSDate+millisecondTimeStamp.h" #import "TSDatabaseSecondaryIndexes.h" #import "TSStorageManager+messageIDs.h" #import "TSThread.h" @@ -52,7 +53,6 @@ NS_ASSUME_NONNULL_BEGIN return self; } - #pragma mark Thread - (TSThread *)thread @@ -72,11 +72,6 @@ NS_ASSUME_NONNULL_BEGIN return self.timestamp; } -- (NSDate *)date { - uint64_t seconds = self.timestamp / 1000; - return [NSDate dateWithTimeIntervalSince1970:seconds]; -} - + (NSString *)stringFromTimeStamp:(uint64_t)timestamp { return [[NSNumber numberWithUnsignedLongLong:timestamp] stringValue]; } @@ -88,9 +83,14 @@ NS_ASSUME_NONNULL_BEGIN return [myNumber unsignedLongLongValue]; } -- (nullable NSDate *)receiptDateForSorting +- (NSDate *)dateForSorting { - return self.date; + return [NSDate ows_dateWithMillisecondsSince1970:self.timestampForSorting]; +} + +- (uint64_t)timestampForSorting +{ + return self.timestamp; } - (NSString *)description { diff --git a/src/Messages/Interactions/TSMessage.h b/src/Messages/Interactions/TSMessage.h index 7bba2b825..bbb568978 100644 --- a/src/Messages/Interactions/TSMessage.h +++ b/src/Messages/Interactions/TSMessage.h @@ -21,9 +21,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) uint64_t expiresAt; @property (nonatomic, readonly) BOOL isExpiringMessage; @property (nonatomic, readonly) BOOL shouldStartExpireTimer; -// _DO NOT_ access this property directly. You almost certainly -// want to use receiptDateForSorting instead. -@property (nonatomic, readonly) NSDate *receivedAtDate; - (instancetype)initWithTimestamp:(uint64_t)timestamp; diff --git a/src/Messages/Interactions/TSMessage.m b/src/Messages/Interactions/TSMessage.m index 280ae1162..b5c8b9552 100644 --- a/src/Messages/Interactions/TSMessage.m +++ b/src/Messages/Interactions/TSMessage.m @@ -35,6 +35,13 @@ static const NSUInteger OWSMessageSchemaVersion = 3; */ @property (nonatomic, readonly) NSUInteger schemaVersion; +// The timestamp property is populated by the envelope, +// which is created by the sender. +// +// We typically want to order messages locally by when +// they were received & decrypted, not by when they were sent. +@property (nonatomic, readonly) uint64_t receivedAtTimestamp; + @end #pragma mark - @@ -104,7 +111,12 @@ static const NSUInteger OWSMessageSchemaVersion = 3; _expiresInSeconds = expiresInSeconds; _expireStartedAt = expireStartedAt; [self updateExpiresAt]; - _receivedAtDate = [NSDate date]; + + if ([self shouldUseReceiptDateForSorting]) { + _receivedAtTimestamp = self.timestamp; + } else { + _receivedAtTimestamp = [NSDate ows_millisecondTimeStamp]; + } return self; } @@ -133,10 +145,20 @@ static const NSUInteger OWSMessageSchemaVersion = 3; _attachmentIds = [NSMutableArray new]; } - if (!_receivedAtDate) { - // TSIncomingMessage.receivedAt has been superceded by TSMessage.receivedAtDate. - NSDate *receivedAt = [coder decodeObjectForKey:@"receivedAt"]; - _receivedAtDate = receivedAt; + if (_receivedAtTimestamp == 0) { + // Upgrade from the older "receivedAtDate" and "receivedAt" properties if + // necessary. + NSDate *receivedAtDate = [coder decodeObjectForKey:@"receivedAtDate"]; + if (!receivedAtDate) { + receivedAtDate = [coder decodeObjectForKey:@"receivedAt"]; + } + if (receivedAtDate) { + _receivedAtTimestamp = [NSDate ows_millisecondsSince1970ForDate:receivedAtDate]; + } + } + + if ([self shouldUseReceiptDateForSorting]) { + _receivedAtTimestamp = self.timestamp; } _schemaVersion = OWSMessageSchemaVersion; @@ -224,10 +246,19 @@ static const NSUInteger OWSMessageSchemaVersion = 3; return self.expiresInSeconds > 0; } -- (nullable NSDate *)receiptDateForSorting +- (uint64_t)timestampForSorting +{ + if ([self shouldUseReceiptDateForSorting] && self.receivedAtTimestamp > 0) { + return self.receivedAtTimestamp; + } else { + OWSAssert(self.timestamp > 0); + return self.timestamp; + } +} + +- (BOOL)shouldUseReceiptDateForSorting { - // Prefer receivedAtDate if set, otherwise fallback to date. - return self.receivedAtDate ?: self.date; + return YES; } #pragma mark - Logging diff --git a/src/Messages/Interactions/TSOutgoingMessage.m b/src/Messages/Interactions/TSOutgoingMessage.m index c2adb595a..f5e0c3059 100644 --- a/src/Messages/Interactions/TSOutgoingMessage.m +++ b/src/Messages/Interactions/TSOutgoingMessage.m @@ -186,8 +186,6 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec _attachmentFilenameMap = [NSMutableDictionary new]; - OWSAssert(self.receivedAtDate); - return self; } diff --git a/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m b/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m index 556e53b31..ec5f65df5 100644 --- a/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m +++ b/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m @@ -1,9 +1,5 @@ // -// TSInvalidIdentityKeyErrorMessage.m -// Signal -// -// Created by Frederic Jacobs on 15/02/15. -// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "TSInvalidIdentityKeyErrorMessage.h" @@ -29,6 +25,11 @@ NS_ASSUME_NONNULL_BEGIN return nil; } +- (BOOL)isDynamicInteraction +{ + return YES; +} + @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSAddToContactsOfferMessage.m b/src/Messages/OWSAddToContactsOfferMessage.m index a0a830cee..b09686a0b 100644 --- a/src/Messages/OWSAddToContactsOfferMessage.m +++ b/src/Messages/OWSAddToContactsOfferMessage.m @@ -32,14 +32,11 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (nullable NSDate *)receiptDateForSorting +- (BOOL)shouldUseReceiptDateForSorting { - // Always use date, since we're creating these interactions after the fact - // and back-dating them. - // - // By default [TSMessage receiptDateForSorting] will prefer to use receivedAtDate - // which is not back-dated. - return self.date; + // Use the timestamp, not the "received at" timestamp to sort, + // since we're creating these interactions after the fact and back-dating them. + return NO; } - (BOOL)isDynamicInteraction diff --git a/src/Messages/OWSReadTracking.h b/src/Messages/OWSReadTracking.h index a3402b1c9..fa981fc9a 100644 --- a/src/Messages/OWSReadTracking.h +++ b/src/Messages/OWSReadTracking.h @@ -15,6 +15,10 @@ */ @property (nonatomic, readonly, getter=wasRead) BOOL read; +@property (nonatomic, readonly) NSString *uniqueThreadId; + +- (BOOL)shouldAffectUnreadCounts; + /** * Call when the user viewed the message/call on this device. "locally" as opposed to being notified via a read receipt * sync message of a remote read. @@ -22,6 +26,4 @@ - (void)markAsReadLocally; - (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; -@property (nonatomic, readonly) NSString *uniqueThreadId; - @end diff --git a/src/Messages/OWSUnknownContactBlockOfferMessage.m b/src/Messages/OWSUnknownContactBlockOfferMessage.m index a574c929b..3c9de57e1 100644 --- a/src/Messages/OWSUnknownContactBlockOfferMessage.m +++ b/src/Messages/OWSUnknownContactBlockOfferMessage.m @@ -34,14 +34,11 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (nullable NSDate *)receiptDateForSorting +- (BOOL)shouldUseReceiptDateForSorting { - // Always use date, since we're creating these interactions after the fact - // and back-dating them. - // - // By default [TSMessage receiptDateForSorting] will prefer to use receivedAtDate - // which is not back-dated. - return self.date; + // Use the timestamp, not the "received at" timestamp to sort, + // since we're creating these interactions after the fact and back-dating them. + return NO; } - (BOOL)isDynamicInteraction diff --git a/src/Messages/TSCall.m b/src/Messages/TSCall.m index 87aad358a..d8b9e459b 100644 --- a/src/Messages/TSCall.m +++ b/src/Messages/TSCall.m @@ -13,13 +13,15 @@ NSUInteger TSCallCurrentSchemaVersion = 1; @interface TSCall () +@property (nonatomic, getter=wasRead) BOOL read; + @property (nonatomic, readonly) NSUInteger callSchemaVersion; @end -@implementation TSCall +#pragma mark - -@synthesize read = _read; +@implementation TSCall - (instancetype)initWithTimestamp:(uint64_t)timestamp withCallNumber:(NSString *)contactNumber @@ -77,6 +79,11 @@ NSUInteger TSCallCurrentSchemaVersion = 1; #pragma mark - OWSReadTracking +- (BOOL)shouldAffectUnreadCounts +{ + return YES; +} + - (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); diff --git a/src/Storage/TSDatabaseView.h b/src/Storage/TSDatabaseView.h index 8beb55895..3f614fd54 100644 --- a/src/Storage/TSDatabaseView.h +++ b/src/Storage/TSDatabaseView.h @@ -15,12 +15,20 @@ extern NSString *TSSecondaryDevicesGroup; extern NSString *TSThreadDatabaseViewExtensionName; extern NSString *TSMessageDatabaseViewExtensionName; extern NSString *TSUnreadDatabaseViewExtensionName; +extern NSString *TSUnseenDatabaseViewExtensionName; extern NSString *TSDynamicMessagesDatabaseViewExtensionName; extern NSString *TSSecondaryDevicesDatabaseViewExtensionName; + (BOOL)registerThreadDatabaseView; -+ (BOOL)registerBuddyConversationDatabaseView; ++ (BOOL)registerThreadInteractionsDatabaseView; +// Instances of OWSReadTracking for wasRead is NO and shouldAffectUnreadCounts is YES. +// +// Should be used for "unread message counts". + (BOOL)registerUnreadDatabaseView; +// Should be used for "unread indicator". +// +// Instances of OWSReadTracking for wasRead is NO. ++ (BOOL)registerUnseenDatabaseView; + (BOOL)registerDynamicMessagesDatabaseView; + (void)asyncRegisterSecondaryDevicesDatabaseView; diff --git a/src/Storage/TSDatabaseView.m b/src/Storage/TSDatabaseView.m index d31d70962..f6f31acd2 100644 --- a/src/Storage/TSDatabaseView.m +++ b/src/Storage/TSDatabaseView.m @@ -3,15 +3,13 @@ // #import "TSDatabaseView.h" - -#import - #import "OWSDevice.h" #import "OWSReadTracking.h" #import "TSIncomingMessage.h" #import "TSOutgoingMessage.h" #import "TSStorageManager.h" #import "TSThread.h" +#import NSString *TSInboxGroup = @"TSInboxGroup"; NSString *TSArchiveGroup = @"TSArchiveGroup"; @@ -22,12 +20,15 @@ NSString *TSSecondaryDevicesGroup = @"TSSecondaryDevicesGroup"; NSString *TSThreadDatabaseViewExtensionName = @"TSThreadDatabaseViewExtensionName"; NSString *TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionName"; NSString *TSUnreadDatabaseViewExtensionName = @"TSUnreadDatabaseViewExtensionName"; +NSString *TSUnseenDatabaseViewExtensionName = @"TSUnseenDatabaseViewExtensionName"; NSString *TSDynamicMessagesDatabaseViewExtensionName = @"TSDynamicMessagesDatabaseViewExtensionName"; NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesDatabaseViewExtensionName"; @implementation TSDatabaseView -+ (BOOL)registerMessageDatabaseViewWithName:(NSString *)viewName viewGrouping:(YapDatabaseViewGrouping *)viewGrouping ++ (BOOL)registerMessageDatabaseViewWithName:(NSString *)viewName + viewGrouping:(YapDatabaseViewGrouping *)viewGrouping + version:(NSString *)version { OWSAssert(viewName.length > 0); OWSAssert((viewGrouping)); @@ -45,7 +46,7 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[TSInteraction collection]]]; YapDatabaseView *view = - [[YapDatabaseView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"1" options:options]; + [[YapDatabaseView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:version options:options]; return [[TSStorageManager sharedManager].database registerExtension:view withName:viewName]; } @@ -56,14 +57,34 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { if ([object conformsToProtocol:@protocol(OWSReadTracking)]) { id possiblyRead = (id)object; - if (possiblyRead.read == NO) { + if (!possiblyRead.wasRead && possiblyRead.shouldAffectUnreadCounts) { return possiblyRead.uniqueThreadId; } } return nil; }]; - return [self registerMessageDatabaseViewWithName:TSUnreadDatabaseViewExtensionName viewGrouping:viewGrouping]; + return [self registerMessageDatabaseViewWithName:TSUnreadDatabaseViewExtensionName + viewGrouping:viewGrouping + version:@"1"]; +} + ++ (BOOL)registerUnseenDatabaseView +{ + YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( + YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { + if ([object conformsToProtocol:@protocol(OWSReadTracking)]) { + id possiblyRead = (id)object; + if (!possiblyRead.wasRead) { + return possiblyRead.uniqueThreadId; + } + } + return nil; + }]; + + return [self registerMessageDatabaseViewWithName:TSUnseenDatabaseViewExtensionName + viewGrouping:viewGrouping + version:@"1"]; } + (BOOL)registerDynamicMessagesDatabaseView @@ -81,11 +102,12 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData return nil; }]; - return - [self registerMessageDatabaseViewWithName:TSDynamicMessagesDatabaseViewExtensionName viewGrouping:viewGrouping]; + return [self registerMessageDatabaseViewWithName:TSDynamicMessagesDatabaseViewExtensionName + viewGrouping:viewGrouping + version:@"2"]; } -+ (BOOL)registerBuddyConversationDatabaseView ++ (BOOL)registerThreadInteractionsDatabaseView { YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { @@ -95,7 +117,9 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData return nil; }]; - return [self registerMessageDatabaseViewWithName:TSMessageDatabaseViewExtensionName viewGrouping:viewGrouping]; + return [self registerMessageDatabaseViewWithName:TSMessageDatabaseViewExtensionName + viewGrouping:viewGrouping + version:@"1"]; } + (BOOL)registerThreadDatabaseView { @@ -194,19 +218,12 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData TSInteraction *message1 = (TSInteraction *)object1; TSInteraction *message2 = (TSInteraction *)object2; - NSDate *date1 = [self localTimeReceiveDateForInteraction:message1]; - NSDate *date2 = [self localTimeReceiveDateForInteraction:message2]; + uint64_t timestamp1 = message1.timestampForSorting; + uint64_t timestamp2 = message2.timestampForSorting; - NSComparisonResult result = [date1 compare:date2]; - - // NSDates are only accurate to the second, we might want finer precision - if (result != NSOrderedSame) { - return result; - } - - if (message1.timestamp > message2.timestamp) { + if (timestamp1 > timestamp2) { return NSOrderedDescending; - } else if (message1.timestamp < message2.timestamp) { + } else if (timestamp1 < timestamp2) { return NSOrderedAscending; } else { return NSOrderedSame; @@ -274,10 +291,6 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData }]; } -+ (NSDate *)localTimeReceiveDateForInteraction:(TSInteraction *)interaction { - return [interaction receiptDateForSorting]; -} - #pragma mark - Logging + (NSString *)tag diff --git a/src/Storage/TSStorageManager.m b/src/Storage/TSStorageManager.m index c32c1df11..7879c7bba 100644 --- a/src/Storage/TSStorageManager.m +++ b/src/Storage/TSStorageManager.m @@ -197,8 +197,9 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; { // Register extensions which are essential for rendering threads synchronously [TSDatabaseView registerThreadDatabaseView]; - [TSDatabaseView registerBuddyConversationDatabaseView]; + [TSDatabaseView registerThreadInteractionsDatabaseView]; [TSDatabaseView registerUnreadDatabaseView]; + [TSDatabaseView registerUnseenDatabaseView]; [TSDatabaseView registerDynamicMessagesDatabaseView]; [self.database registerExtension:[TSDatabaseSecondaryIndexes registerTimeStampIndex] withName:@"idx"]; diff --git a/src/Util/Asserts.h b/src/Util/Asserts.h index 6ec08413e..1c0e83151 100755 --- a/src/Util/Asserts.h +++ b/src/Util/Asserts.h @@ -13,24 +13,40 @@ #define CONVERT_TO_STRING(X) #X #define CONVERT_EXPR_TO_STRING(X) CONVERT_TO_STRING(X) -#define OWSAssert(X) \ -if (!(X)) { \ -NSLog(@"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ -NSAssert(0, @"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ -} - -// OWSAssert() should be used in Obj-C and Swift methods. -// OWSCAssert() should be used in free functions. -#define OWSCAssert(X) \ -if (!(X)) { \ -NSLog(@"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ -NSCAssert(0, @"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ -} +// OWSAssert() and OWSFail() should be used in Obj-C methods. +// OWSCAssert() and OWSCFail() should be used in free functions. + +#define OWSAssert(X) \ + if (!(X)) { \ + NSLog(@"%s Assertion failed: %s", __PRETTY_FUNCTION__, CONVERT_EXPR_TO_STRING(X)); \ + NSAssert(0, @"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + } + +#define OWSCAssert(X) \ + if (!(X)) { \ + NSLog(@"%s Assertion failed: %s", __PRETTY_FUNCTION__, CONVERT_EXPR_TO_STRING(X)); \ + NSCAssert(0, @"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + } + +#define OWSFail(X) \ + { \ + NSLog(@"%s %@", __PRETTY_FUNCTION__, X); \ + NSAssert(0, X); \ + } + +#define OWSCFail(X) \ + { \ + if (!(X)) { \ + NSLog(@"%s %@", __PRETTY_FUNCTION__, X); \ + NSCAssert(0, X); \ + } #else #define OWSAssert(X) #define OWSCAssert(X) +#define OWSFail(X) +#define OWSCFail(X) #endif