session-ios/SessionMessagingKit/Threads/TSThread.m
Morgan Pretty 78c0d000be Removed the OWSBlockingManager replacing it with the config sync
Fixed an issue where the "block" button would appear in the NoteToSelf swipe menu
Removed the OWSBlockingManager and supporting files
Removed a number of unused classes and methods
Refactored the BlockListUIUtils to Swift
2022-03-23 09:59:38 +11:00

459 lines
16 KiB
Objective-C

//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "TSThread.h"
#import "OWSDisappearingMessagesConfiguration.h"
#import <SignalCoreKit/Cryptography.h>
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalCoreKit/NSString+OWS.h>
#import <YapDatabase/YapDatabase.h>
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import <Curve25519Kit/Curve25519.h>
NS_ASSUME_NONNULL_BEGIN
BOOL IsNoteToSelfEnabled(void)
{
return YES;
}
@interface TSThread ()
@property (nonatomic) NSDate *creationDate;
@property (nonatomic, nullable) NSDate *lastInteractionDate;
@property (nonatomic, nullable) NSNumber *archivedAsOfMessageSortId;
@property (nonatomic, copy, nullable) NSString *messageDraft;
@property (atomic, nullable) NSDate *mutedUntilDate;
@end
@implementation TSThread
#pragma mark Dependencies
- (TSAccountManager *)tsAccountManager
{
return SSKEnvironment.shared.tsAccountManager;
}
#pragma mark Initialization
+ (NSString *)collection {
return @"TSThread";
}
- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId
{
self = [super initWithUniqueId:uniqueId];
if (self) {
_creationDate = [NSDate date];
_messageDraft = nil;
}
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (!self) {
return self;
}
// renamed `hasEverHadMessage` -> `shouldBeVisible`
if (!_shouldBeVisible) {
NSNumber *_Nullable legacy_hasEverHadMessage = [coder decodeObjectForKey:@"hasEverHadMessage"];
if (legacy_hasEverHadMessage != nil) {
_shouldBeVisible = legacy_hasEverHadMessage.boolValue;
}
}
NSDate *_Nullable lastMessageDate = [coder decodeObjectOfClass:NSDate.class forKey:@"lastMessageDate"];
NSDate *_Nullable archivalDate = [coder decodeObjectOfClass:NSDate.class forKey:@"archivalDate"];
return self;
}
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
[super saveWithTransaction:transaction];
[SSKPreferences setHasSavedThreadWithValue:YES transaction:transaction];
}
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self removeAllThreadInteractionsWithTransaction:transaction];
[super removeWithTransaction:transaction];
}
- (void)removeAllThreadInteractionsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
// We can't safely delete interactions while enumerating them, so
// we collect and delete separately.
//
// We don't want to instantiate the interactions when collecting them
// or when deleting them.
NSMutableArray<NSString *> *interactionIds = [NSMutableArray new];
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
__block BOOL didDetectCorruption = NO;
[interactionsByThread enumerateKeysInGroup:self.uniqueId
usingBlock:^(NSString *collection, NSString *key, NSUInteger index, BOOL *stop) {
if (![key isKindOfClass:[NSString class]] || key.length < 1) {
didDetectCorruption = YES;
return;
}
[interactionIds addObject:key];
}];
if (didDetectCorruption) {
[OWSPrimaryStorage incrementVersionOfDatabaseExtension:TSMessageDatabaseViewExtensionName];
}
for (NSString *interactionId in interactionIds) {
// We need to fetch each interaction, since [TSInteraction removeWithTransaction:] does important work.
TSInteraction *_Nullable interaction =
[TSInteraction fetchObjectWithUniqueID:interactionId transaction:transaction];
if (!interaction) {
continue;
}
[interaction removeWithTransaction:transaction];
}
}
- (BOOL)isNoteToSelf
{
if (!IsNoteToSelfEnabled()) { return NO; }
if (![self isKindOfClass:TSContactThread.class]) { return NO; }
return [self.contactSessionID isEqual:[SNGeneralUtilities getUserPublicKey]];
}
// Override in ContactThread
- (BOOL)isMessageRequest {
return NO;
}
// Override in ContactThread
- (BOOL)isMessageRequestUsingTransaction:(YapDatabaseReadTransaction *)transaction {
return NO;
}
// Override in ContactThread
- (BOOL)isBlocked {
return NO;
}
// Override in ContactThread
- (BOOL)isBlockedUsingTransaction:(YapDatabaseReadTransaction *)transaction {
return NO;
}
#pragma mark To be subclassed.
- (BOOL)isGroupThread {
return NO;
}
// Override in ContactThread
- (nullable NSString *)contactSessionID
{
return nil;
}
- (NSString *)name {
return nil;
}
- (NSString *)nameWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return nil;
}
- (NSArray<NSString *> *)recipientIdentifiers
{
return @[];
}
#pragma mark Interactions
/**
* Iterate over this thread's interactions
*/
- (void)enumerateInteractionsWithTransaction:(YapDatabaseReadTransaction *)transaction
usingBlock:(void (^)(TSInteraction *interaction, BOOL *stop))block
{
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
[interactionsByThread
enumerateKeysAndObjectsInGroup:self.uniqueId
usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) {
TSInteraction *interaction = object;
block(interaction, stop);
}];
}
/**
* Enumerates all the threads interactions. Note this will explode if you try to create a transaction in the block.
* If you need a transaction, use the sister method: `enumerateInteractionsWithTransaction:usingBlock`
*/
- (void)enumerateInteractionsUsingBlock:(void (^)(TSInteraction *interaction))block
{
[self.dbReadWriteConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self enumerateInteractionsWithTransaction:transaction
usingBlock:^(
TSInteraction *interaction, BOOL *stop) {
block(interaction);
}];
}];
}
- (TSInteraction *)lastInteraction
{
__block TSInteraction *interaction;
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
interaction = [self getLastInteractionWithTransaction:transaction];
}];
return interaction;
}
- (TSInteraction *)getLastInteractionWithTransaction:(YapDatabaseReadTransaction *)transaction
{
YapDatabaseViewTransaction *interactions = [transaction ext:TSMessageDatabaseViewExtensionName];
return [interactions lastObjectInGroup:self.uniqueId];
}
/**
* Useful for tests and debugging. In production use an enumeration method.
*/
- (NSArray<TSInteraction *> *)allInteractions
{
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
[self enumerateInteractionsUsingBlock:^(TSInteraction *interaction) {
[interactions addObject:interaction];
}];
return [interactions copy];
}
- (NSUInteger)numberOfInteractions
{
__block NSUInteger count;
[[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) {
count = [self numberOfInteractionsWithTransaction:transaction];
}];
return count;
}
- (NSUInteger)numberOfInteractionsWithTransaction:(YapDatabaseReadTransaction *)transaction
{
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
return [interactionsByThread numberOfItemsInGroup:self.uniqueId];
}
- (NSArray<id<OWSReadTracking>> *)unseenMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction
{
NSMutableArray<id<OWSReadTracking>> *messages = [NSMutableArray new];
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
enumerateKeysAndObjectsInGroup:self.uniqueId
usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) {
if (![object conformsToProtocol:@protocol(OWSReadTracking)]) {
return;
}
id<OWSReadTracking> unread = (id<OWSReadTracking>)object;
if (unread.read) {
NSLog(@"Found an already read message in the * unseen * messages list.");
return;
}
[messages addObject:unread];
}];
return [messages copy];
}
- (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction
{
YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName];
return [unreadMessages numberOfItemsInGroup:self.uniqueId];
// FIXME: Why did we have to do as the following?
// __block NSUInteger count = 0;
//
// YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName];
// [unreadMessages enumerateKeysAndObjectsInGroup:self.uniqueId
// usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) {
// if (![object conformsToProtocol:@protocol(OWSReadTracking)]) {
// return;
// }
// id<OWSReadTracking> unread = (id<OWSReadTracking>)object;
// if (unread.read) {
// NSLog(@"Found an already read message in the * unread * messages list.");
// return;
// }
// count += 1;
// }];
// return count;
}
- (NSUInteger)unreadMentionMessageCount
{
__block NSUInteger unreadMentionMessageCount;
[[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) {
unreadMentionMessageCount = [self unreadMentionMessageCountWithTransaction:transaction];
}];
return unreadMentionMessageCount;
}
- (NSUInteger)unreadMentionMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction
{
YapDatabaseViewTransaction *unreadMentions = [transaction ext:TSUnreadMentionDatabaseViewExtensionName];
return [unreadMentions numberOfItemsInGroup:self.uniqueId];
}
- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
for (id<OWSReadTracking> message in [self unseenMessagesWithTransaction:transaction]) {
[message markAsReadAtTimestamp:[NSDate ows_millisecondTimeStamp] trySendReadReceipt:YES transaction:transaction];
}
[super saveWithTransaction:transaction];
}
- (nullable TSInteraction *)lastInteractionForInboxWithTransaction:(YapDatabaseReadTransaction *)transaction
{
__block NSUInteger missedCount = 0;
__block TSInteraction *last = nil;
[[transaction ext:TSMessageDatabaseViewExtensionName]
enumerateKeysAndObjectsInGroup:self.uniqueId
withOptions:NSEnumerationReverse
usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) {
missedCount++;
TSInteraction *interaction = (TSInteraction *)object;
if ([TSThread shouldInteractionAppearInInbox:interaction]) {
last = interaction;
// For long ignored threads, with lots of SN changes this can get really slow.
// I see this in development because I have a lot of long forgotten threads with
// members who's test devices are constantly reinstalled. We could add a
// purpose-built DB view, but I think in the real world this is rare to be a
// hotspot.
*stop = YES;
}
}];
return last;
}
- (NSString *)lastMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction
{
TSInteraction *interaction = [self lastInteractionForInboxWithTransaction:transaction];
if ([interaction conformsToProtocol:@protocol(OWSPreviewText)]) {
id<OWSPreviewText> previewable = (id<OWSPreviewText>)interaction;
return [previewable previewTextWithTransaction:transaction].filterStringForDisplay;
} else {
return @"";
}
}
// Returns YES IFF the interaction should show up in the inbox as the last message.
+ (BOOL)shouldInteractionAppearInInbox:(TSInteraction *)interaction
{
if (interaction.isDynamicInteraction) {
return NO;
}
if ([interaction isKindOfClass:[TSMessage class]]) {
TSMessage *message = (TSMessage *)interaction;
if (message.isDeleted) {
return NO;
}
}
return YES;
}
- (void)updateWithLastMessage:(TSInteraction *)lastMessage transaction:(YapDatabaseReadWriteTransaction *)transaction {
if (![self.class shouldInteractionAppearInInbox:lastMessage]) {
return;
}
if ([_lastInteractionDate compare: lastMessage.receivedAtDate] == NSOrderedAscending) {
_lastInteractionDate = lastMessage.receivedAtDate;
[super saveWithTransaction:transaction];
}
if (!self.shouldBeVisible) {
self.shouldBeVisible = YES;
[self saveWithTransaction:transaction];
} else {
[self touchWithTransaction:transaction];
}
}
#pragma mark Disappearing Messages
- (OWSDisappearingMessagesConfiguration *)disappearingMessagesConfigurationWithTransaction:
(YapDatabaseReadTransaction *)transaction
{
return [OWSDisappearingMessagesConfiguration fetchOrBuildDefaultWithThreadId:self.uniqueId transaction:transaction];
}
- (uint32_t)disappearingMessagesDurationWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSDisappearingMessagesConfiguration *config = [self disappearingMessagesConfigurationWithTransaction:transaction];
if (!config.isEnabled) {
return 0;
} else {
return config.durationSeconds;
}
}
#pragma mark Drafts
- (NSString *)currentDraftWithTransaction:(YapDatabaseReadTransaction *)transaction {
TSThread *thread = [TSThread fetchObjectWithUniqueID:self.uniqueId transaction:transaction];
if (thread.messageDraft) {
return thread.messageDraft;
} else {
return @"";
}
}
- (void)setDraft:(NSString *)draftString transaction:(YapDatabaseReadWriteTransaction *)transaction {
TSThread *thread = [TSThread fetchObjectWithUniqueID:self.uniqueId transaction:transaction];
thread.messageDraft = draftString;
[thread saveWithTransaction:transaction];
}
#pragma mark Muting
- (BOOL)isMuted
{
NSDate *mutedUntilDate = self.mutedUntilDate;
NSDate *now = [NSDate date];
return (mutedUntilDate != nil && [mutedUntilDate timeIntervalSinceDate:now] > 0);
}
- (void)updateWithMutedUntilDate:(NSDate * _Nullable)mutedUntilDate transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSThread *thread) {
[thread setMutedUntilDate:mutedUntilDate];
}];
[transaction addCompletionQueue:dispatch_get_main_queue() completionBlock:^{
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.muteSettingUpdated object:self.uniqueId];
}];
}
@end
NS_ASSUME_NONNULL_END