Antidote/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerChatsImpl.m

346 lines
14 KiB
Objective-C

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import "OCTSubmanagerChatsImpl.h"
#import "OCTTox.h"
#import "OCTRealmManager.h"
#import "OCTMessageAbstract.h"
#import "OCTMessageText.h"
#import "OCTChat.h"
#import "OCTLogging.h"
#import "OCTSendMessageOperation.h"
#import "OCTTox+Private.h"
#import "OCTToxOptions+Private.h"
//#import "Firebase.h"
@interface OCTSubmanagerChatsImpl ()
@property (strong, nonatomic, readonly) NSOperationQueue *sendMessageQueue;
@end
@implementation OCTSubmanagerChatsImpl
@synthesize dataSource = _dataSource;
- (instancetype)init
{
self = [super init];
if (! self) {
return nil;
}
_sendMessageQueue = [NSOperationQueue new];
_sendMessageQueue.maxConcurrentOperationCount = 1;
return self;
}
- (void)dealloc
{
[self.dataSource.managerGetNotificationCenter removeObserver:self];
}
- (void)configure
{
[self.dataSource.managerGetNotificationCenter addObserver:self
selector:@selector(friendConnectionStatusChangeNotification:)
name:kOCTFriendConnectionStatusChangeNotification
object:nil];
}
#pragma mark - Public
- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend
{
return [[self.dataSource managerGetRealmManager] getOrCreateChatWithFriend:friend];
}
- (void)removeMessages:(NSArray<OCTMessageAbstract *> *)messages
{
[[self.dataSource managerGetRealmManager] removeMessages:messages];
[self.dataSource.managerGetNotificationCenter postNotificationName:kOCTScheduleFileTransferCleanupNotification object:nil];
}
- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat
{
[[self.dataSource managerGetRealmManager] removeAllMessagesInChat:chat removeChat:removeChat];
[self.dataSource.managerGetNotificationCenter postNotificationName:kOCTScheduleFileTransferCleanupNotification object:nil];
}
- (void)sendMessageToChat:(OCTChat *)chat
text:(NSString *)text
type:(OCTToxMessageType)type
successBlock:(void (^)(OCTMessageAbstract *message))userSuccessBlock
failureBlock:(void (^)(NSError *error))userFailureBlock
{
NSParameterAssert(chat);
NSParameterAssert(text);
OCTFriend *friend = [chat.friends firstObject];
uint8_t *message_v3_hash_bin = calloc(1, TOX_MSGV3_MSGID_LENGTH);
uint8_t *message_v3_hash_hexstr = calloc(1, (TOX_MSGV3_MSGID_LENGTH * 2) + 1);
NSString *msgv3HashHex = nil;
UInt32 msgv3tssec = 0;
// if ((message_v3_hash_bin) && (message_v3_hash_hexstr))
// {
// tox_messagev3_get_new_message_id(message_v3_hash_bin);
// bin_to_hex((const char *)message_v3_hash_bin, (size_t)TOX_MSGV3_MSGID_LENGTH, message_v3_hash_hexstr);
//
// msgv3HashHex = [[NSString alloc] initWithBytes:message_v3_hash_hexstr length:(TOX_MSGV3_MSGID_LENGTH * 2) encoding:NSUTF8StringEncoding];
//
// // HINT: set sent timestamp to now() as unixtimestamp value
// msgv3tssec = [[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] integerValue];
//
// free(message_v3_hash_bin);
// free(message_v3_hash_hexstr);
// }
__weak OCTSubmanagerChatsImpl *weakSelf = self;
OCTSendMessageOperationSuccessBlock successBlock = ^(OCTToxMessageId messageId) {
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
BOOL sent_push = NO;
if (messageId == -1) {
if ((friend.pushToken != nil) && (friend.pushToken.length > 5)) {
// check push url starts with allowed values
if (
([friend.pushToken hasPrefix:@"https://tox.zoff.xyz/toxfcm/fcm.php?id="])
||
([friend.pushToken hasPrefix:@"https://gotify1.unifiedpush.org/UP?token="])
||
([friend.pushToken hasPrefix:@"https://ntfy.sh/"])
) {
sent_push = YES;
}
}
}
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
OCTMessageAbstract *message = [realmManager addMessageWithText:text type:type chat:chat sender:nil messageId:messageId msgv3HashHex:msgv3HashHex sentPush:sent_push tssent:msgv3tssec tsrcvd:0];
if (userSuccessBlock) {
userSuccessBlock(message);
}
};
OCTSendMessageOperationFailureBlock failureBlock = ^(NSError *error) {
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
if ((error.code == OCTToxErrorFriendSendMessageFriendNotConnected) &&
[strongSelf.dataSource managerUseFauxOfflineMessaging]) {
successBlock(-1);
return;
}
if (userFailureBlock) {
userFailureBlock(error);
}
};
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
friendNumber:friend.friendNumber
messageType:type
message:text
msgv3HashHex:msgv3HashHex
msgv3tssec:msgv3tssec
successBlock:successBlock
failureBlock:failureBlock];
[self.sendMessageQueue addOperation:operation];
}
- (BOOL)setIsTyping:(BOOL)isTyping inChat:(OCTChat *)chat error:(NSError **)error
{
NSParameterAssert(chat);
OCTFriend *friend = [chat.friends firstObject];
OCTTox *tox = [self.dataSource managerGetTox];
return [tox setUserIsTyping:isTyping forFriendNumber:friend.friendNumber error:error];
}
#pragma mark - NSNotification
- (void)friendConnectionStatusChangeNotification:(NSNotification *)notification
{
OCTFriend *friend = notification.object;
if (! friend) {
OCTLogWarn(@"no friend received in notification %@, exiting", notification);
return;
}
if (friend.isConnected) {
[self resendUndeliveredMessagesToFriend:friend];
}
}
#pragma mark - Private
- (void)resendUndeliveredMessagesToFriend:(OCTFriend *)friend
{
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@"
@" AND senderUniqueIdentifier == nil"
@" AND messageText.isDelivered == NO",
chat.uniqueIdentifier];
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
for (OCTMessageAbstract *message in results) {
OCTLogInfo(@"Resending message to friend %@", friend);
__weak OCTSubmanagerChatsImpl *weakSelf = self;
OCTSendMessageOperationSuccessBlock successBlock = ^(OCTToxMessageId messageId) {
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
[realmManager updateObject:message withBlock:^(OCTMessageAbstract *theMessage) {
theMessage.messageText.messageId = messageId;
}];
};
OCTSendMessageOperationFailureBlock failureBlock = ^(NSError *error) {
OCTLogWarn(@"Cannot resend message to friend %@, error %@", friend, error);
};
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
friendNumber:friend.friendNumber
messageType:message.messageText.type
message:message.messageText.text
msgv3HashHex:message.messageText.msgv3HashHex
msgv3tssec:message.tssent
successBlock:successBlock
failureBlock:failureBlock];
[self.sendMessageQueue addOperation:operation];
}
}
#pragma mark - OCTToxDelegate
/*
* send mesgV3 high level ACK message.
*/
- (void)tox:(OCTTox *)tox sendFriendHighlevelACK:(NSString *)message
friendNumber:(OCTToxFriendNumber)friendNumber
msgv3HashHex:(NSString *)msgv3HashHex
sendTimestamp:(uint32_t)sendTimestamp
{
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
friendNumber:friendNumber
messageType:OCTToxMessageTypeHighlevelack
message:message
msgv3HashHex:msgv3HashHex
msgv3tssec:sendTimestamp
successBlock:nil
failureBlock:nil];
[self.sendMessageQueue addOperation:operation];
}
/*
* Process incoming text message from friend.
*/
- (void)tox:(OCTTox *)tox friendMessage:(NSString *)message
type:(OCTToxMessageType)type
friendNumber:(OCTToxFriendNumber)friendNumber
msgv3HashHex:(NSString *)msgv3HashHex
sendTimestamp:(uint32_t)sendTimestamp
{
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
if (msgv3HashHex != nil)
{
// HINT: check for double message, but only select incoming messages (senderUniqueIdentifier != NULL)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier != nil",
chat.uniqueIdentifier, msgv3HashHex];
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
OCTMessageAbstract *message_found = [results firstObject];
if (message_found) {
OCTLogInfo(@"friendMessage ignoring double message i %@", chat.uniqueIdentifier);
OCTLogInfo(@"friendMessage ignoring double message f %@", friend);
return;
}
}
[realmManager addMessageWithText:message type:type chat:chat sender:friend messageId:0 msgv3HashHex:msgv3HashHex sentPush:NO tssent:sendTimestamp tsrcvd:0];
}
- (void)tox:(OCTTox *)tox friendHighLevelACK:(NSString *)message
friendNumber:(OCTToxFriendNumber)friendNumber
msgv3HashHex:(NSString *)msgv3HashHex
sendTimestamp:(uint32_t)sendTimestamp
{
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
// HINT: only select outgoing messages
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier == nil",
chat.uniqueIdentifier, msgv3HashHex];
// HINT: we still sort and use only 1 result row, just in case more than 1 row is returned.
// but if more than 1 row is returned that would actually be an error.
// we use the newest message with this Hash
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
results = [results sortedResultsUsingKeyPath:@"dateInterval" ascending:YES];
OCTMessageAbstract *message_found = [results firstObject];
if (! message_found) {
return;
}
OCTLogInfo(@"friendHighLevelACK recevied from friend %@", friend);
[realmManager updateObject:message_found withBlock:^(OCTMessageAbstract *theMessage) {
theMessage.messageText.isDelivered = YES;
}];
}
- (void)tox:(OCTTox *)tox messageDelivered:(OCTToxMessageId)messageId friendNumber:(OCTToxFriendNumber)friendNumber
{
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.messageId == %d",
chat.uniqueIdentifier, messageId];
// messageId is reset on every launch, so we want to update delivered status on latest message.
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
results = [results sortedResultsUsingKeyPath:@"dateInterval" ascending:NO];
OCTMessageAbstract *message = [results firstObject];
if (! message) {
return;
}
[realmManager updateObject:message withBlock:^(OCTMessageAbstract *theMessage) {
theMessage.messageText.isDelivered = YES;
}];
}
@end