session-ios/Signal/src/util/ThreadUtil.m

305 lines
14 KiB
Mathematica
Raw Normal View History

2017-03-27 23:03:36 +02:00
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "ThreadUtil.h"
#import "OWSContactsManager.h"
2017-03-27 23:03:36 +02:00
#import "Signal-Swift.h"
2017-05-16 17:26:01 +02:00
#import "TSUnreadIndicatorInteraction.h"
2017-03-27 23:03:36 +02:00
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSAddToContactsOfferMessage.h>
#import <SignalServiceKit/OWSBlockingManager.h>
2017-03-27 23:03:36 +02:00
#import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSUnknownContactBlockOfferMessage.h>
#import <SignalServiceKit/TSDatabaseView.h>
2017-03-27 23:03:36 +02:00
#import <SignalServiceKit/TSOutgoingMessage.h>
#import <SignalServiceKit/TSThread.h>
2017-03-29 18:48:50 +02:00
NS_ASSUME_NONNULL_BEGIN
@implementation ThreadOffersAndIndicators
@end
#pragma mark -
2017-03-27 23:03:36 +02:00
@implementation ThreadUtil
+ (void)sendMessageWithText:(NSString *)text inThread:(TSThread *)thread messageSender:(OWSMessageSender *)messageSender
{
OWSAssert([NSThread isMainThread]);
OWSAssert(text.length > 0);
OWSAssert(thread);
OWSAssert(messageSender);
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId];
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:text
attachmentIds:[NSMutableArray new]
expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)];
2017-03-27 23:03:36 +02:00
[messageSender sendMessage:message
success:^{
DDLogInfo(@"%@ Successfully sent message.", self.tag);
}
failure:^(NSError *error) {
DDLogWarn(@"%@ Failed to deliver message with error: %@", self.tag, error);
}];
}
+ (void)sendMessageWithAttachment:(SignalAttachment *)attachment
inThread:(TSThread *)thread
messageSender:(OWSMessageSender *)messageSender
{
OWSAssert([NSThread isMainThread]);
OWSAssert(attachment);
OWSAssert(![attachment hasError]);
OWSAssert([attachment mimeType].length > 0);
OWSAssert(thread);
OWSAssert(messageSender);
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId];
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
isVoiceMessage:[attachment isVoiceMessage]
expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)];
2017-03-27 23:03:36 +02:00
[messageSender sendAttachmentData:attachment.data
2017-04-13 18:55:21 +02:00
contentType:attachment.mimeType
sourceFilename:attachment.filenameOrDefault
2017-03-27 23:03:36 +02:00
inMessage:message
success:^{
DDLogDebug(@"%@ Successfully sent message attachment.", self.tag);
}
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send message attachment with error: %@", self.tag, error);
}];
}
+ (ThreadOffersAndIndicators *)ensureThreadOffersAndIndicators:(TSThread *)thread
storageManager:(TSStorageManager *)storageManager
contactsManager:(OWSContactsManager *)contactsManager
blockingManager:(OWSBlockingManager *)blockingManager
hideUnreadMessagesIndicator:(BOOL)hideUnreadMessagesIndicator
fixedUnreadIndicatorTimestamp:(NSNumber *_Nullable)fixedUnreadIndicatorTimestamp
{
OWSAssert(thread);
OWSAssert(storageManager);
OWSAssert(contactsManager);
OWSAssert(blockingManager);
ThreadOffersAndIndicators *result = [ThreadOffersAndIndicators new];
[storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
const int kMaxBlockOfferOutgoingMessageCount = 10;
__block OWSAddToContactsOfferMessage *addToContactsOffer = nil;
__block OWSUnknownContactBlockOfferMessage *blockOffer = nil;
__block TSUnreadIndicatorInteraction *unreadIndicator = nil;
__block TSIncomingMessage *firstIncomingMessage = nil;
__block TSOutgoingMessage *firstOutgoingMessage = nil;
__block TSIncomingMessage *firstUnreadMessage = nil;
__block long outgoingMessageCount = 0;
[[transaction ext:TSMessageDatabaseViewExtensionName]
enumerateRowsInGroup:thread.uniqueId
usingBlock:^(
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
if ([object isKindOfClass:[OWSUnknownContactBlockOfferMessage class]]) {
OWSAssert(!blockOffer);
blockOffer = (OWSUnknownContactBlockOfferMessage *)object;
} else if ([object isKindOfClass:[OWSAddToContactsOfferMessage class]]) {
OWSAssert(!addToContactsOffer);
addToContactsOffer = (OWSAddToContactsOfferMessage *)object;
} else if ([object isKindOfClass:[TSUnreadIndicatorInteraction class]]) {
OWSAssert(!unreadIndicator);
unreadIndicator = (TSUnreadIndicatorInteraction *)object;
} else if ([object isKindOfClass:[TSIncomingMessage class]]) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object;
2017-04-10 21:14:53 +02:00
if (!firstIncomingMessage) {
firstIncomingMessage = incomingMessage;
2017-04-10 21:14:53 +02:00
} else {
OWSAssert([[firstIncomingMessage receiptDateForSorting]
compare:[incomingMessage receiptDateForSorting]]
== NSOrderedAscending);
}
if (!incomingMessage.wasRead) {
if (!firstUnreadMessage) {
firstUnreadMessage = incomingMessage;
} else {
OWSAssert([[firstUnreadMessage receiptDateForSorting]
compare:[incomingMessage receiptDateForSorting]]
== NSOrderedAscending);
}
}
} else if ([object isKindOfClass:[TSOutgoingMessage class]]) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)object;
2017-04-10 21:14:53 +02:00
if (!firstOutgoingMessage) {
firstOutgoingMessage = outgoingMessage;
2017-04-10 21:14:53 +02:00
} else {
OWSAssert([[firstOutgoingMessage receiptDateForSorting]
compare:[outgoingMessage receiptDateForSorting]]
== NSOrderedAscending);
}
outgoingMessageCount++;
}
}];
TSMessage *firstMessage = firstIncomingMessage;
if (!firstMessage
|| (firstOutgoingMessage &&
[[firstOutgoingMessage receiptDateForSorting] compare:[firstMessage receiptDateForSorting]]
== NSOrderedAscending)) {
firstMessage = firstOutgoingMessage;
}
BOOL shouldHaveBlockOffer = YES;
BOOL shouldHaveAddToContactsOffer = YES;
BOOL isContactThread = [thread isKindOfClass:[TSContactThread class]];
if (!isContactThread) {
// Only create "add to contacts" offers in 1:1 conversations.
shouldHaveAddToContactsOffer = NO;
// Only create block offers in 1:1 conversations.
shouldHaveBlockOffer = NO;
} else {
NSString *recipientId = ((TSContactThread *)thread).contactIdentifier;
if ([[blockingManager blockedPhoneNumbers] containsObject:recipientId]) {
// Only create "add to contacts" offers for users which are not already blocked.
shouldHaveAddToContactsOffer = NO;
// Only create block offers for users which are not already blocked.
shouldHaveBlockOffer = NO;
}
SignalAccount *signalAccount = contactsManager.signalAccountMap[recipientId];
if (signalAccount) {
// Only create "add to contacts" offers for non-contacts.
shouldHaveAddToContactsOffer = NO;
// Only create block offers for non-contacts.
shouldHaveBlockOffer = NO;
}
}
if (!firstMessage) {
shouldHaveAddToContactsOffer = NO;
shouldHaveBlockOffer = NO;
}
if (outgoingMessageCount > kMaxBlockOfferOutgoingMessageCount) {
// If the user has sent more than N messages, don't show a block offer.
shouldHaveBlockOffer = NO;
}
BOOL hasOutgoingBeforeIncomingInteraction = (firstOutgoingMessage
&& (!firstIncomingMessage ||
[[firstOutgoingMessage receiptDateForSorting] compare:[firstIncomingMessage receiptDateForSorting]]
== NSOrderedAscending));
if (hasOutgoingBeforeIncomingInteraction) {
// If there is an outgoing message before an incoming message
// the local user initiated this conversation, don't show a block offer.
shouldHaveBlockOffer = NO;
}
// We use these offset to control the ordering of the offers and indicators.
const int kBlockOfferOffset = -3;
const int kAddToContactsOfferOffset = -2;
const int kUnreadIndicatorOfferOffset = -1;
if (blockOffer && !shouldHaveBlockOffer) {
[blockOffer removeWithTransaction:transaction];
} else if (!blockOffer && shouldHaveBlockOffer) {
DDLogInfo(@"Creating block offer for unknown contact");
// We want the block offer to be the first interaction in their
// conversation's timeline, so we back-date it to slightly before
// the first incoming message (which we know is the first message).
uint64_t blockOfferTimestamp = (uint64_t)((long long)firstMessage.timestamp + kBlockOfferOffset);
NSString *recipientId = ((TSContactThread *)thread).contactIdentifier;
TSMessage *offerMessage =
[OWSUnknownContactBlockOfferMessage unknownContactBlockOfferMessage:blockOfferTimestamp
thread:thread
contactId:recipientId];
[offerMessage saveWithTransaction:transaction];
}
if (addToContactsOffer && !shouldHaveAddToContactsOffer) {
[addToContactsOffer removeWithTransaction:transaction];
} else if (!addToContactsOffer && shouldHaveAddToContactsOffer) {
DDLogInfo(@"Creating 'add to contacts' offer for unknown contact");
// We want the offer to be the first interaction in their
// conversation's timeline, so we back-date it to slightly before
// the first incoming message (which we know is the first message).
uint64_t offerTimestamp = (uint64_t)((long long)firstMessage.timestamp + kAddToContactsOfferOffset);
NSString *recipientId = ((TSContactThread *)thread).contactIdentifier;
TSMessage *offerMessage = [OWSAddToContactsOfferMessage addToContactsOfferMessage:offerTimestamp
thread:thread
contactId:recipientId];
[offerMessage saveWithTransaction:transaction];
}
2017-05-16 17:26:01 +02:00
BOOL shouldHaveUnreadIndicator
= ((firstUnreadMessage != nil || fixedUnreadIndicatorTimestamp != nil) && !hideUnreadMessagesIndicator);
if (!shouldHaveUnreadIndicator) {
if (unreadIndicator) {
[unreadIndicator removeWithTransaction:transaction];
}
} else {
// We want the block offer to appear just before the first unread incoming
// message in the conversation timeline...
//
// ...unless we have a fixed timestamp for the unread indicator.
uint64_t indicatorTimestamp = (uint64_t)(fixedUnreadIndicatorTimestamp
? [fixedUnreadIndicatorTimestamp longLongValue]
: ((long long)firstUnreadMessage.timestamp + kUnreadIndicatorOfferOffset));
if (indicatorTimestamp && unreadIndicator.timestamp == indicatorTimestamp) {
// Keep the existing indicator; it is in the correct position.
result.unreadIndicator = unreadIndicator;
} else {
if (unreadIndicator) {
[unreadIndicator removeWithTransaction:transaction];
}
DDLogInfo(@"%@ Creating TSUnreadIndicatorInteraction", self.tag);
TSUnreadIndicatorInteraction *indicator =
[[TSUnreadIndicatorInteraction alloc] initWithTimestamp:indicatorTimestamp thread:thread];
[indicator saveWithTransaction:transaction];
result.unreadIndicator = indicator;
}
2017-05-16 17:26:01 +02:00
}
}];
return result;
}
2017-03-27 23:03:36 +02:00
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
2017-03-29 18:48:50 +02:00
NS_ASSUME_NONNULL_END