talk-ios/NextcloudTalk/NCChatController.m

813 строки
39 KiB
Objective-C

/**
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
*
* @author Ivan Sein <ivan@nextcloud.com>
*
* @license GNU GPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#import "NCChatController.h"
#import "NCAPIController.h"
#import "NCChatBlock.h"
#import "NCDatabaseManager.h"
#import "NCIntentController.h"
#import "NCRoomsManager.h"
#import "NextcloudTalk-Swift.h"
NSString * const NCChatControllerDidReceiveInitialChatHistoryNotification = @"NCChatControllerDidReceiveInitialChatHistoryNotification";
NSString * const NCChatControllerDidReceiveInitialChatHistoryOfflineNotification = @"NCChatControllerDidReceiveInitialChatHistoryOfflineNotification";
NSString * const NCChatControllerDidReceiveChatHistoryNotification = @"NCChatControllerDidReceiveChatHistoryNotification";
NSString * const NCChatControllerDidReceiveChatMessagesNotification = @"NCChatControllerDidReceiveChatMessagesNotification";
NSString * const NCChatControllerDidSendChatMessageNotification = @"NCChatControllerDidSendChatMessageNotification";
NSString * const NCChatControllerDidReceiveChatBlockedNotification = @"NCChatControllerDidReceiveChatBlockedNotification";
NSString * const NCChatControllerDidReceiveNewerCommonReadMessageNotification = @"NCChatControllerDidReceiveNewerCommonReadMessageNotification";
NSString * const NCChatControllerDidReceiveUpdateMessageNotification = @"NCChatControllerDidReceiveUpdateMessageNotification";
NSString * const NCChatControllerDidReceiveHistoryClearedNotification = @"NCChatControllerDidReceiveHistoryClearedNotification";
NSString * const NCChatControllerDidReceiveCallStartedMessageNotification = @"NCChatControllerDidReceiveCallStartedMessageNotification";
NSString * const NCChatControllerDidReceiveCallEndedMessageNotification = @"NCChatControllerDidReceiveCallEndedMessageNotification";
@interface NCChatController ()
@property (nonatomic, assign) BOOL stopChatMessagesPoll;
@property (nonatomic, strong) TalkAccount *account;
@property (nonatomic, strong) NSURLSessionTask *getHistoryTask;
@property (nonatomic, strong) NSURLSessionTask *pullMessagesTask;
@end
@implementation NCChatController
- (instancetype)initForRoom:(NCRoom *)room
{
self = [super init];
if (self) {
_room = room;
_account = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:_room.accountId];
}
return self;
}
#pragma mark - Database
- (NSArray *)chatBlocksForRoom
{
RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
// Create an unmanaged copy of the blocks
NSMutableArray *sortedBlocks = [NSMutableArray new];
for (NCChatBlock *managedBlock in managedSortedBlocks) {
NCChatBlock *sortedBlock = [[NCChatBlock alloc] initWithValue:managedBlock];
[sortedBlocks addObject:sortedBlock];
}
return sortedBlocks;
}
- (NSArray *)getBatchOfMessagesInBlock:(NCChatBlock *)chatBlock fromMessageId:(NSInteger)messageId included:(BOOL)included
{
NSInteger fromMessageId = messageId > 0 ? messageId : chatBlock.newestMessageId;
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId >= %ld AND messageId < %ld", _account.accountId, _room.token, (long)chatBlock.oldestMessageId, (long)fromMessageId];
if (included) {
query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId >= %ld AND messageId <= %ld", _account.accountId, _room.token, (long)chatBlock.oldestMessageId, (long)fromMessageId];
}
RLMResults *managedMessages = [NCChatMessage objectsWithPredicate:query];
RLMResults *managedSortedMessages = [managedMessages sortedResultsUsingKeyPath:@"messageId" ascending:YES];
// Create an unmanaged copy of the messages
NSMutableArray *sortedMessages = [NSMutableArray new];
NSInteger startingIndex = managedSortedMessages.count - kReceivedChatMessagesLimit;
startingIndex = (startingIndex < 0) ? 0 : startingIndex;
for (NSInteger i = startingIndex; i < managedSortedMessages.count; i++) {
NCChatMessage *sortedMessage = [[NCChatMessage alloc] initWithValue:managedSortedMessages[i]];
[sortedMessages addObject:sortedMessage];
}
return sortedMessages;
}
- (NSArray *)getNewStoredMessagesInBlock:(NCChatBlock *)chatBlock sinceMessageId:(NSInteger)messageId
{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId > %ld AND messageId <= %ld", _account.accountId, _room.token, (long)messageId, (long)chatBlock.newestMessageId];
RLMResults *managedMessages = [NCChatMessage objectsWithPredicate:query];
RLMResults *managedSortedMessages = [managedMessages sortedResultsUsingKeyPath:@"messageId" ascending:YES];
// Create an unmanaged copy of the messages
NSMutableArray *sortedMessages = [NSMutableArray new];
for (NCChatMessage *managedMessage in managedSortedMessages) {
NCChatMessage *sortedMessage = [[NCChatMessage alloc] initWithValue:managedMessage];
[sortedMessages addObject:sortedMessage];
}
return sortedMessages;
}
- (void)storeMessages:(NSArray *)messages withRealm:(RLMRealm *)realm {
// Add or update messages
for (NSDictionary *messageDict in messages) {
NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict andAccountId:_account.accountId];
NCChatMessage *parent = [NCChatMessage messageWithDictionary:[messageDict objectForKey:@"parent"] andAccountId:_account.accountId];
message.parentId = parent.internalId;
if (message.referenceId && ![message.referenceId isEqualToString:@""]) {
NCChatMessage *managedTemporaryMessage = [NCChatMessage objectsWhere:@"referenceId = %@ AND isTemporary = true", message.referenceId].firstObject;
if (managedTemporaryMessage) {
[realm deleteObject:managedTemporaryMessage];
}
}
NCChatMessage *managedMessage = [NCChatMessage objectsWhere:@"internalId = %@", message.internalId].firstObject;
if (managedMessage) {
[NCChatMessage updateChatMessage:managedMessage withChatMessage:message isRoomLastMessage:NO];
} else if (message) {
[realm addObject:message];
}
NCChatMessage *managedParentMessage = [NCChatMessage objectsWhere:@"internalId = %@", parent.internalId].firstObject;
if (managedParentMessage) {
[NCChatMessage updateChatMessage:managedParentMessage withChatMessage:parent isRoomLastMessage:NO];
} else if (parent) {
[realm addObject:parent];
}
}
}
- (void)storeMessages:(NSArray *)messages
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[self storeMessages:messages withRealm:realm];
}];
}
- (BOOL)hasOlderStoredMessagesThanMessageId:(NSInteger)messageId
{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId < %ld", _account.accountId, _room.token, (long)messageId];
return [NCChatMessage objectsWithPredicate:query].count > 0;
}
- (void)removeAllStoredMessagesAndChatBlocks
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@", _account.accountId, _room.token];
[realm deleteObjects:[NCChatMessage objectsWithPredicate:query]];
[realm deleteObjects:[NCChatBlock objectsWithPredicate:query]];
}];
}
- (void)removeExpiredMessages
{
RLMRealm *realm = [RLMRealm defaultRealm];
NSInteger currentTimestamp = [[NSDate date] timeIntervalSince1970];
[realm transactionWithBlock:^{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND expirationTimestamp > 0 AND expirationTimestamp <= %ld", _account.accountId, _room.token, currentTimestamp];
[realm deleteObjects:[NCChatMessage objectsWithPredicate:query]];
}];
}
- (void)updateLastChatBlockWithNewestKnown:(NSInteger)newestKnown
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
NCChatBlock *lastBlock = managedSortedBlocks.lastObject;
if (newestKnown > 0) {
lastBlock.newestMessageId = newestKnown;
}
}];
}
- (void)updateChatBlocksWithLastKnown:(NSInteger)lastKnown
{
if (lastKnown <= 0) {
return;
}
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
NCChatBlock *lastBlock = managedSortedBlocks.lastObject;
// There is more than one chat block stored
if (managedSortedBlocks.count > 1) {
for (NSInteger i = managedSortedBlocks.count - 2; i >= 0; i--) {
NCChatBlock *block = managedSortedBlocks[i];
// Merge blocks if the lastKnown message is inside the current block
if (lastKnown >= block.oldestMessageId && lastKnown <= block.newestMessageId) {
lastBlock.oldestMessageId = block.oldestMessageId;
[realm deleteObject:block];
break;
// Update lastBlock if the lastKnown message is between the 2 blocks
} else if (lastKnown > block.newestMessageId) {
lastBlock.oldestMessageId = lastKnown;
break;
// The current block is completely included in the retrieved history
// This could happen if we vary the message limit when fetching messages
// Delete included block
} else if (lastKnown < block.oldestMessageId) {
[realm deleteObject:block];
}
}
// There is just one chat block stored
} else {
lastBlock.oldestMessageId = lastKnown;
}
}];
}
- (void)updateChatBlocksWithReceivedMessages:(NSArray *)messages newestKnown:(NSInteger)newestKnown andLastKnown:(NSInteger)lastKnown
{
NSArray *sortedMessages = [self sortedMessagesFromMessageArray:messages];
NCChatMessage *newestMessageReceived = sortedMessages.lastObject;
NSInteger newestMessageKnown = newestKnown > 0 ? newestKnown : newestMessageReceived.messageId;
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
// Create new chat block
NCChatBlock *newBlock = [[NCChatBlock alloc] init];
newBlock.internalId = _room.internalId;
newBlock.accountId = _room.accountId;
newBlock.token = _room.token;
newBlock.oldestMessageId = lastKnown;
newBlock.newestMessageId = newestMessageKnown;
newBlock.hasHistory = YES;
// There is at least one chat block stored
if (managedSortedBlocks.count > 0) {
for (NSInteger i = managedSortedBlocks.count - 1; i >= 0; i--) {
NCChatBlock *block = managedSortedBlocks[i];
// Merge blocks if the lastKnown message is inside the current block
if (lastKnown >= block.oldestMessageId && lastKnown <= block.newestMessageId) {
block.newestMessageId = newestMessageKnown;
break;
// Add new block if it didn't reach the previous block
} else if (lastKnown > block.newestMessageId) {
[realm addObject:newBlock];
break;
// The current block is completely included in the retrieved history
// This could happen if we vary the message limit when fetching messages
// Delete included block
} else if (lastKnown < block.oldestMessageId) {
[realm deleteObject:block];
}
}
// No chat blocks stored yet, add new chat block
} else {
[realm addObject:newBlock];
}
}];
}
- (void)updateHistoryFlagInFirstBlock
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
RLMResults *managedBlocks = [NCChatBlock objectsWhere:@"internalId = %@", _room.internalId];
RLMResults *managedSortedBlocks = [managedBlocks sortedResultsUsingKeyPath:@"newestMessageId" ascending:YES];
NCChatBlock *firstChatBlock = managedSortedBlocks.firstObject;
firstChatBlock.hasHistory = NO;
}];
}
- (void)transactionForMessageWithReferenceId:(NSString *)referenceId withBlock:(void(^)(NCChatMessage *message))block
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
NCChatMessage *managedChatMessage = [NCChatMessage objectsWhere:@"referenceId = %@ AND isTemporary = true", referenceId].firstObject;
block(managedChatMessage);
}];
}
- (NSArray *)sortedMessagesFromMessageArray:(NSArray *)messages
{
NSMutableArray *sortedMessages = [[NSMutableArray alloc] initWithCapacity:messages.count];
for (NSDictionary *messageDict in messages) {
NCChatMessage *message = [NCChatMessage messageWithDictionary:messageDict];
[sortedMessages addObject:message];
}
// Sort by messageId
NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc] initWithKey:@"messageId" ascending:YES];
NSArray *descriptors = [NSArray arrayWithObject:valueDescriptor];
[sortedMessages sortUsingDescriptors:descriptors];
return sortedMessages;
}
#pragma mark - Chat
- (NSMutableArray *)getTemporaryMessages
{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND isTemporary = true", _account.accountId, _room.token];
RLMResults *managedTemporaryMessages = [NCChatMessage objectsWithPredicate:query];
RLMResults *managedSortedTemporaryMessages = [managedTemporaryMessages sortedResultsUsingKeyPath:@"timestamp" ascending:YES];
// Mark temporary messages sent more than 12 hours ago as failed-to-send messages
NSInteger twelveHoursAgoTimestamp = [[NSDate date] timeIntervalSince1970] - (60 * 60 * 12);
for (NCChatMessage *temporaryMessage in managedTemporaryMessages) {
if (temporaryMessage.timestamp < twelveHoursAgoTimestamp) {
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
temporaryMessage.isOfflineMessage = NO;
temporaryMessage.sendingFailed = YES;
}];
}
}
// Create an unmanaged copy of the messages
NSMutableArray *sortedMessages = [NSMutableArray new];
for (NCChatMessage *managedMessage in managedSortedTemporaryMessages) {
NCChatMessage *sortedMessage = [[NCChatMessage alloc] initWithValue:managedMessage];
[sortedMessages addObject:sortedMessage];
}
return sortedMessages;
}
- (void)updateHistoryInBackgroundWithCompletionBlock:(UpdateHistoryInBackgroundCompletionBlock)block
{
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
_pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_room.token fromLastMessageId:lastChatBlock.newestMessageId history:NO includeLastMessage:NO timeout:NO lastCommonReadMessage:_room.lastCommonReadMessage setReadMarker:NO forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSInteger lastCommonReadMessage, NSError *error, NSInteger statusCode) {
if (!self->_stopChatMessagesPoll) {
if (error) {
NSLog(@"Could not get background chat history. Error: %@", error.description);
} else {
// Update chat blocks
[self updateLastChatBlockWithNewestKnown:lastKnownMessage];
// Store new messages
if (messages.count > 0) {
[self storeMessages:messages];
[self checkLastCommonReadMessage:lastCommonReadMessage];
}
}
}
if (block) {
block(error);
}
}];
}
- (void)checkForNewMessagesFromMessageId:(NSInteger)messageId
{
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
NSArray *storedMessages = [self getNewStoredMessagesInBlock:lastChatBlock sinceMessageId:messageId];
if (storedMessages.count > 0) {
NSMutableDictionary *userInfo = [NSMutableDictionary new];
for (NCChatMessage *message in storedMessages) {
// Notify if "call started" have been received
if ([message.systemMessage isEqualToString:@"call_started"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveCallStartedMessageNotification
object:self
userInfo:userInfo];
}
// Notify if "call eneded" have been received
if ([message.systemMessage isEqualToString:@"call_ended"] ||
[message.systemMessage isEqualToString:@"call_ended_everyone"] ||
[message.systemMessage isEqualToString:@"call_missed"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveCallEndedMessageNotification
object:self
userInfo:userInfo];
}
// Notify if an "update messages" have been received
if ([message isUpdateMessage]) {
[userInfo setObject:message forKey:@"updateMessage"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveUpdateMessageNotification
object:self
userInfo:userInfo];
}
// Notify if "history cleared" has been received
if ([message.systemMessage isEqualToString:@"history_cleared"]) {
[userInfo setObject:message forKey:@"historyCleared"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveHistoryClearedNotification
object:self
userInfo:userInfo];
return;
}
}
[userInfo removeAllObjects];
[userInfo setObject:self->_room.token forKey:@"room"];
[userInfo setObject:storedMessages forKey:@"messages"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatMessagesNotification
object:self
userInfo:userInfo];
// Messages are already sorted by messageId here
NCChatMessage *lastMessage = [storedMessages lastObject];
// Make sure we update the unread flags for the room (lastMessage can already be set, but there still might be unread flags)
if (lastMessage.timestamp >= self->_room.lastActivity && !lastMessage.isUpdateMessage) {
self->_room.lastActivity = lastMessage.timestamp;
[[NCRoomsManager sharedInstance] updateLastMessage:lastMessage withNoUnreadMessages:YES forRoom:self->_room];
}
}
}
- (void)getInitialChatHistory
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:_room.token forKey:@"room"];
// Clear expired messages
[self removeExpiredMessages];
NSInteger lastReadMessageId = 0;
if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityChatReadMarker]) {
lastReadMessageId = _room.lastReadMessage;
}
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
if (lastChatBlock.newestMessageId > 0 && lastChatBlock.newestMessageId >= lastReadMessageId) {
NSArray *storedMessages = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:lastChatBlock.newestMessageId included:YES];
[userInfo setObject:storedMessages forKey:@"messages"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveInitialChatHistoryNotification
object:self
userInfo:userInfo];
// Messages are already sorted by messageId here
NCChatMessage *lastMessage = [storedMessages lastObject];
// Make sure we update the unread flags for the room (lastMessage can already be set, but there still might be unread flags)
if (lastMessage.timestamp >= self->_room.lastActivity && !lastMessage.isUpdateMessage) {
self->_room.lastActivity = lastMessage.timestamp;
[[NCRoomsManager sharedInstance] updateLastMessage:lastMessage withNoUnreadMessages:YES forRoom:self->_room];
}
} else {
_pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_room.token fromLastMessageId:lastReadMessageId history:YES includeLastMessage:YES timeout:NO lastCommonReadMessage:_room.lastCommonReadMessage setReadMarker:YES forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSInteger lastCommonReadMessage, NSError *error, NSInteger statusCode) {
if (self->_stopChatMessagesPoll) {
return;
}
if (error) {
if ([self isChatBeingBlocked:statusCode]) {
[self notifyChatIsBlocked];
return;
}
[userInfo setObject:error forKey:@"error"];
NSLog(@"Could not get initial chat history. Error: %@", error.description);
} else {
// Update chat blocks
[self updateChatBlocksWithReceivedMessages:messages newestKnown:lastReadMessageId andLastKnown:lastKnownMessage];
// Store new messages
if (messages.count > 0) {
[self storeMessages:messages];
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
NSArray *storedMessages = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:lastReadMessageId included:YES];
[userInfo setObject:storedMessages forKey:@"messages"];
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveInitialChatHistoryNotification
object:self
userInfo:userInfo];
[self checkLastCommonReadMessage:lastCommonReadMessage];
}];
}
}
- (void)getInitialChatHistoryForOfflineMode
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:_room.token forKey:@"room"];
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
NSArray *storedMessages = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:lastChatBlock.newestMessageId included:YES];
[userInfo setObject:storedMessages forKey:@"messages"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveInitialChatHistoryOfflineNotification
object:self
userInfo:userInfo];
}
- (void)getHistoryBatchFromMessagesId:(NSInteger)messageId
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:_room.token forKey:@"room"];
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
if (lastChatBlock && lastChatBlock.oldestMessageId < messageId) {
NSArray *storedMessages = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:messageId included:NO];
[userInfo setObject:storedMessages forKey:@"messages"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatHistoryNotification
object:self
userInfo:userInfo];
} else {
_getHistoryTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_room.token fromLastMessageId:messageId history:YES includeLastMessage:NO timeout:NO lastCommonReadMessage:_room.lastCommonReadMessage setReadMarker:YES forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSInteger lastCommonReadMessage, NSError *error, NSInteger statusCode) {
if (statusCode == 304) {
[self updateHistoryFlagInFirstBlock];
}
if (error) {
if ([self isChatBeingBlocked:statusCode]) {
[self notifyChatIsBlocked];
return;
}
[userInfo setObject:error forKey:@"error"];
if (statusCode != 304) {
NSLog(@"Could not get chat history. Error: %@", error.description);
}
} else {
// Update chat blocks
[self updateChatBlocksWithLastKnown:lastKnownMessage];
// Store new messages
if (messages.count > 0) {
[self storeMessages:messages];
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
NSArray *historyBatch = [self getBatchOfMessagesInBlock:lastChatBlock fromMessageId:messageId included:NO];
[userInfo setObject:historyBatch forKey:@"messages"];
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatHistoryNotification
object:self
userInfo:userInfo];
}];
}
}
- (void)getHistoryBatchOfflineFromMessagesId:(NSInteger)messageId
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:_room.token forKey:@"room"];
NSArray *chatBlocks = [self chatBlocksForRoom];
NSMutableArray *historyBatch = [NSMutableArray new];
if (chatBlocks.count > 0) {
for (NSInteger i = chatBlocks.count - 1; i >= 0; i--) {
NCChatBlock *currentBlock = chatBlocks[i];
BOOL noMoreMessagesToRetrieveInBlock = NO;
if (currentBlock.oldestMessageId < messageId) {
NSArray *storedMessages = [self getBatchOfMessagesInBlock:currentBlock fromMessageId:messageId included:NO];
historyBatch = [[NSMutableArray alloc] initWithArray:storedMessages];
if (storedMessages.count > 0) {
break;
} else {
// We use this flag in case the rest of the messages in current block
// are system messages invisible for the user.
noMoreMessagesToRetrieveInBlock = YES;
}
}
if (i > 0 && (currentBlock.oldestMessageId == messageId || noMoreMessagesToRetrieveInBlock)) {
NCChatBlock *previousBlock = chatBlocks[i - 1];
NSArray *storedMessages = [self getBatchOfMessagesInBlock:previousBlock fromMessageId:previousBlock.newestMessageId included:YES];
historyBatch = [[NSMutableArray alloc] initWithArray:storedMessages];
[userInfo setObject:@(YES) forKey:@"shouldAddBlockSeparator"];
break;
}
}
}
if (historyBatch.count == 0) {
[userInfo setObject:@(YES) forKey:@"noMoreStoredHistory"];
}
[userInfo setObject:historyBatch forKey:@"messages"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatHistoryNotification
object:self
userInfo:userInfo];
}
- (void)stopReceivingChatHistory
{
[_getHistoryTask cancel];
}
- (void)startReceivingChatMessagesFromMessagesId:(NSInteger)messageId withTimeout:(BOOL)timeout
{
_stopChatMessagesPoll = NO;
[_pullMessagesTask cancel];
_pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_room.token fromLastMessageId:messageId history:NO includeLastMessage:NO timeout:timeout lastCommonReadMessage:_room.lastCommonReadMessage setReadMarker:YES forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSInteger lastCommonReadMessage, NSError *error, NSInteger statusCode) {
if (self->_stopChatMessagesPoll) {
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:self->_room.token forKey:@"room"];
if (error) {
if ([self isChatBeingBlocked:statusCode]) {
[self notifyChatIsBlocked];
return;
}
if (statusCode != 304) {
[userInfo setObject:error forKey:@"error"];
NSLog(@"Could not get new chat messages. Error: %@", error.description);
}
} else {
// Update last chat block
[self updateLastChatBlockWithNewestKnown:lastKnownMessage];
// Store new messages
if (messages.count > 0) {
[self storeMessages:messages];
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
NSArray *storedMessages = [self getNewStoredMessagesInBlock:lastChatBlock sinceMessageId:messageId];
[userInfo setObject:storedMessages forKey:@"messages"];
for (NCChatMessage *message in storedMessages) {
// Update the current room with the new message
if (message.messageId == lastKnownMessage && message.timestamp >= self->_room.lastActivity && !message.isUpdateMessage) {
self->_room.lastActivity = message.timestamp;
[[NCRoomsManager sharedInstance] updateLastMessage:message withNoUnreadMessages:YES forRoom:self->_room];
}
// Notify if "call started" have been received
if ([message.systemMessage isEqualToString:@"call_started"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveCallStartedMessageNotification
object:self
userInfo:userInfo];
}
// Notify if "call eneded" have been received
if ([message.systemMessage isEqualToString:@"call_ended"] ||
[message.systemMessage isEqualToString:@"call_ended_everyone"] ||
[message.systemMessage isEqualToString:@"call_missed"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveCallEndedMessageNotification
object:self
userInfo:userInfo];
}
// Notify if an "update messages" have been received
if ([message isUpdateMessage]) {
[userInfo setObject:message forKey:@"updateMessage"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveUpdateMessageNotification
object:self
userInfo:userInfo];
}
// Notify if "history cleared" has been received
if ([message.systemMessage isEqualToString:@"history_cleared"]) {
[userInfo setObject:message forKey:@"historyCleared"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveHistoryClearedNotification
object:self
userInfo:userInfo];
return;
}
}
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatMessagesNotification
object:self
userInfo:userInfo];
[self checkLastCommonReadMessage:lastCommonReadMessage];
if (error.code != -999) {
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
[self startReceivingChatMessagesFromMessagesId:lastChatBlock.newestMessageId withTimeout:YES];
}
}];
}
- (void)startReceivingNewChatMessages
{
NCChatBlock *lastChatBlock = [self chatBlocksForRoom].lastObject;
[self startReceivingChatMessagesFromMessagesId:lastChatBlock.newestMessageId withTimeout:NO];
}
- (void)stopReceivingNewChatMessages
{
_stopChatMessagesPoll = YES;
[_pullMessagesTask cancel];
}
- (void)sendChatMessage:(NSString *)message replyTo:(NSInteger)replyTo referenceId:(NSString *)referenceId silently:(BOOL)silently
{
BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"NCChatControllerSendMessage" expirationHandler:^(BGTaskHelper *task) {
[NCUtils log:@"ExpirationHandler called - sendChatMessage"];
}];
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:message forKey:@"message"];
__block NSInteger retryCount;
if (referenceId) {
// Reset offline message flag before retrying to send to prevent race conditions and
// possible ending up with multiple identical messages sent
[self transactionForMessageWithReferenceId:referenceId withBlock:^(NCChatMessage *message) {
message.isOfflineMessage = NO;
retryCount = message.offlineMessageRetryCount;
}];
}
[[NCAPIController sharedInstance] sendChatMessage:message toRoom:_room.token displayName:nil replyTo:replyTo referenceId:referenceId silently:silently forAccount:_account withCompletionBlock:^(NSError *error) {
if (referenceId) {
[userInfo setObject:referenceId forKey:@"referenceId"];
}
if (error) {
[userInfo setObject:error forKey:@"error"];
if (referenceId) {
if (retryCount >= 5) {
// After 5 retries, we assume sending is not possible
[self transactionForMessageWithReferenceId:referenceId withBlock:^(NCChatMessage *message) {
message.sendingFailed = YES;
message.isOfflineMessage = NO;
}];
} else {
[self transactionForMessageWithReferenceId:referenceId withBlock:^(NCChatMessage *message) {
message.sendingFailed = NO;
message.isOfflineMessage = YES;
message.offlineMessageRetryCount = (++retryCount);
}];
[userInfo setObject:@(YES) forKey:@"isOfflineMessage"];
}
}
NSLog(@"Could not send chat message. Error: %@", error.description);
} else {
[[NCIntentController sharedInstance] donateSendMessageIntentForRoom:self->_room];
}
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidSendChatMessageNotification
object:self
userInfo:userInfo];
[bgTask stopBackgroundTask];
}];
}
- (void)sendChatMessage:(NCChatMessage *)message
{
[self sendChatMessage:message.sendingMessage replyTo:message.parentMessageId referenceId:message.referenceId silently:message.isSilent];
}
- (void)checkLastCommonReadMessage:(NSInteger)lastCommonReadMessage
{
if (lastCommonReadMessage > 0) {
BOOL newerCommonReadReceived = lastCommonReadMessage > self->_room.lastCommonReadMessage;
if (newerCommonReadReceived) {
self->_room.lastCommonReadMessage = lastCommonReadMessage;
[[NCRoomsManager sharedInstance] updateLastCommonReadMessage:lastCommonReadMessage forRoom:self->_room];
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:self->_room.token forKey:@"room"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveNewerCommonReadMessageNotification
object:self
userInfo:userInfo];
}
}
}
- (BOOL)isChatBeingBlocked:(NSInteger)statusCode
{
if (statusCode == 412) {
return YES;
}
return NO;
}
- (void)notifyChatIsBlocked
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:_room.token forKey:@"room"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCChatControllerDidReceiveChatBlockedNotification
object:self
userInfo:userInfo];
}
- (void)stopChatController
{
[self stopReceivingNewChatMessages];
[self stopReceivingChatHistory];
}
- (void)clearHistoryAndResetChatController
{
[_pullMessagesTask cancel];
[self removeAllStoredMessagesAndChatBlocks];
_room.lastReadMessage = 0;
}
- (BOOL)hasHistoryFromMessageId:(NSInteger)messageId
{
NCChatBlock *firstChatBlock = [self chatBlocksForRoom].firstObject;
if (firstChatBlock && firstChatBlock.oldestMessageId == messageId) {
return firstChatBlock.hasHistory;
}
return YES;
}
@end