Store chat messages in the database.

Signed-off-by: Ivan Sein <ivan@nextcloud.com>
This commit is contained in:
Ivan Sein 2020-03-06 19:39:59 +01:00
Родитель 5d1cc57d5b
Коммит 15548aebf4
7 изменённых файлов: 178 добавлений и 77 удалений

Просмотреть файл

@ -42,7 +42,7 @@ typedef void (^GetPeersForCallCompletionBlock)(NSMutableArray *peers, NSError *e
typedef void (^JoinCallCompletionBlock)(NSError *error);
typedef void (^LeaveCallCompletionBlock)(NSError *error);
typedef void (^GetChatMessagesCompletionBlock)(NSMutableArray *messages, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode);
typedef void (^GetChatMessagesCompletionBlock)(NSArray *messages, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode);
typedef void (^SendChatMessagesCompletionBlock)(NSError *error);
typedef void (^GetMentionSuggestionsCompletionBlock)(NSMutableArray *mentions, NSError *error);
@ -63,6 +63,8 @@ typedef void (^UnsubscribeToNextcloudServerCompletionBlock)(NSError *error);
typedef void (^SubscribeToPushProxyCompletionBlock)(NSError *error);
typedef void (^UnsubscribeToPushProxyCompletionBlock)(NSError *error);
extern NSInteger const kReceivedChatMessagesLimit;
@interface OCURLSessionManager : AFURLSessionManager
@end

Просмотреть файл

@ -22,6 +22,8 @@
NSString * const kNCOCSAPIVersion = @"/ocs/v2.php";
NSString * const kNCSpreedAPIVersion = @"/apps/spreed/api/v1";
NSInteger const kReceivedChatMessagesLimit = 100;
@interface NCAPIController () <NSURLSessionTaskDelegate, NSURLSessionDelegate>
@property (nonatomic, strong) NCAPISessionManager *defaultAPISessionManager;
@ -775,7 +777,7 @@ NSString * const kNCSpreedAPIVersion = @"/apps/spreed/api/v1";
{
NSString *URLString = [self getRequestURLForAccount:account withEndpoint:[NSString stringWithFormat:@"chat/%@", token]];
NSDictionary *parameters = @{@"lookIntoFuture" : history ? @(0) : @(1),
@"limit" : @(100),
@"limit" : @(kReceivedChatMessagesLimit),
@"timeout" : timeout ? @(30) : @(0),
@"lastKnownMessageId" : @(messageId),
@"setReadMarker" : @(1),
@ -784,17 +786,6 @@ NSString * const kNCSpreedAPIVersion = @"/apps/spreed/api/v1";
NCAPISessionManager *apiSessionManager = [_apiSessionManagers objectForKey:account.accountId];
NSURLSessionDataTask *task = [apiSessionManager GET:URLString parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSArray *responseMessages = [[responseObject objectForKey:@"ocs"] objectForKey:@"data"];
NSMutableArray *messages = [[NSMutableArray alloc] initWithCapacity:responseMessages.count];
for (NSDictionary *message in responseMessages) {
NCChatMessage *ncMessage = [NCChatMessage messageWithDictionary:message];
[messages addObject:ncMessage];
}
// Sort by messageId
NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc] initWithKey:@"messageId" ascending:YES];
NSArray *descriptors = [NSArray arrayWithObject:valueDescriptor];
[messages sortUsingDescriptors:descriptors];
// Get X-Chat-Last-Given header
NSHTTPURLResponse *response = ((NSHTTPURLResponse *)[task response]);
NSDictionary *headers = [response allHeaderFields];
@ -805,7 +796,7 @@ NSString * const kNCSpreedAPIVersion = @"/apps/spreed/api/v1";
}
if (block) {
block(messages, lastKnownMessage, nil, 0);
block(responseMessages, lastKnownMessage, nil, 0);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSInteger statusCode = 0;

Просмотреть файл

@ -70,6 +70,8 @@ extern NSString * const NCRoomObjectTypeSharePassword;
@property (nonatomic, assign) BOOL canStartCall;
@property (nonatomic, assign) BOOL hasCall;
@property (nonatomic, assign) NSInteger lastUpdate;
@property (nonatomic, assign) NSInteger oldestMessageReceived;
@property (nonatomic, assign) NSInteger newestMessageReceived;
+ (instancetype)roomWithDictionary:(NSDictionary *)roomDict;
+ (instancetype)roomWithDictionary:(NSDictionary *)roomDict andAccountId:(NSString *)accountId;

Просмотреть файл

@ -61,6 +61,9 @@ NSString * const NCRoomObjectTypeSharePassword = @"share:password";
room.displayName = [displayName stringValue];
}
room.oldestMessageReceived = -1;
room.newestMessageReceived = -1;
return room;
}

Просмотреть файл

@ -18,13 +18,14 @@ extern NSString * const NCRoomControllerDidReceiveChatBlockedNotification;
@interface NCRoomController : NSObject
@property (nonatomic, strong) NSString *accountId;
@property (nonatomic, strong) NSString *userSessionId;
@property (nonatomic, strong) NSString *roomToken;
@property (nonatomic, assign) BOOL inCall;
@property (nonatomic, assign) BOOL inChat;
@property (nonatomic, assign) BOOL hasHistory;
- (instancetype)initForUser:(NSString *)sessionId inRoom:(NSString *)token;
- (instancetype)initForAccountId:(NSString *)accountId withSessionId:(NSString *)sessionId inRoom:(NSString *)token;
- (void)sendChatMessage:(NSString *)message replyTo:(NSInteger)replyTo;
- (void)getInitialChatHistory:(NSInteger)lastReadMessage;
- (void)getChatHistoryFromMessagesId:(NSInteger)messageId;

Просмотреть файл

@ -9,6 +9,8 @@
#import "NCRoomController.h"
#import "NCAPIController.h"
#import "NCDatabaseManager.h"
#import "NCRoomsManager.h"
#import "NCSettingsController.h"
NSString * const NCRoomControllerDidReceiveInitialChatHistoryNotification = @"NCRoomControllerDidReceiveInitialChatHistoryNotification";
@ -19,8 +21,8 @@ NSString * const NCRoomControllerDidReceiveChatBlockedNotification = @"
@interface NCRoomController ()
@property (nonatomic, assign) NSInteger oldestMessageId;
@property (nonatomic, assign) NSInteger newestMessageId;
@property (nonatomic, strong) TalkAccount *account;
@property (nonatomic, strong) NCRoom *room;
@property (nonatomic, assign) BOOL stopChatMessagesPoll;
@property (nonatomic, strong) NSURLSessionTask *getHistoryTask;
@property (nonatomic, strong) NSURLSessionTask *pullMessagesTask;
@ -29,81 +31,181 @@ NSString * const NCRoomControllerDidReceiveChatBlockedNotification = @"
@implementation NCRoomController
- (instancetype)initForUser:(NSString *)sessionId inRoom:(NSString *)token
- (instancetype)initForAccountId:(NSString *)accountId withSessionId:(NSString *)sessionId inRoom:(NSString *)token
{
self = [super init];
if (self) {
_accountId = accountId;
_account = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:accountId];
_userSessionId = sessionId;
_roomToken = token;
_oldestMessageId = -1;
_newestMessageId = -1;
_room = [[NCRoomsManager sharedInstance] roomWithToken:token forAccountId:accountId];
_hasHistory = YES;
}
return self;
}
#pragma mark - Chat
#pragma mark - Database
- (void)getInitialChatHistory:(NSInteger)lastReadMessage
- (NSArray *)getStoredMessagesFromMessageId:(NSInteger)messageId included:(BOOL)included
{
_pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_roomToken fromLastMessageId:lastReadMessage history:YES includeLastMessage:YES timeout:NO forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSMutableArray *messages, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode) {
if (_stopChatMessagesPoll) {
return;
}
_oldestMessageId = lastKnownMessage;
NSMutableDictionary *userInfo = [NSMutableDictionary new];
if (error) {
if ([self isChatBeingBlocked:statusCode]) {
[self notifyChatIsBlocked];
return;
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId < %ld", _account.accountId, _room.token, (long)messageId];
if (included) {
query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId <= %ld", _account.accountId, _room.token, (long)messageId];
}
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 *)getNewStoredMessagesSinceMessageId:(NSInteger)messageId
{
NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@ AND messageId > %ld", _account.accountId, _room.token, (long)messageId];
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
{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
// 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;
NCChatMessage *managedMessage = [NCChatMessage objectsWhere:@"internalId = %@", message.internalId].firstObject;
if (managedMessage) {
[NCChatMessage updateChatMessage:managedMessage withChatMessage:message];
} else if (message) {
[realm addObject:message];
}
NCChatMessage *managedParentMessage = [NCChatMessage objectsWhere:@"internalId = %@", parent.internalId].firstObject;
if (managedParentMessage) {
[NCChatMessage updateChatMessage:managedParentMessage withChatMessage:parent];
} else if (parent) {
[realm addObject:parent];
}
[userInfo setObject:error forKey:@"error"];
NSLog(@"Could not get initial chat history. Error: %@", error.description);
}
if (messages.count > 0) {
NCChatMessage *lastMessage = messages.lastObject;
_newestMessageId = lastMessage.messageId;
[userInfo setObject:messages forKey:@"messages"];
}
[userInfo setObject:_roomToken forKey:@"room"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCRoomControllerDidReceiveInitialChatHistoryNotification
object:self
userInfo:userInfo];
if (!error) {
[self startReceivingChatMessagesFromMessagesId:_newestMessageId withTimeout:NO];
}
}];
}
- (void)getChatHistoryFromMessagesId:(NSInteger)messageId
- (void)updateOldestAndNewestStoredMessagesForRoom
{
_getHistoryTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_roomToken fromLastMessageId:messageId history:YES includeLastMessage:NO timeout:NO forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSMutableArray *messages, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode) {
if (statusCode == 304) {
_hasHistory = NO;
}
_oldestMessageId = lastKnownMessage > 0 ? lastKnownMessage : _oldestMessageId;
NSMutableDictionary *userInfo = [NSMutableDictionary new];
if (error) {
if ([self isChatBeingBlocked:statusCode]) {
[self notifyChatIsBlocked];
NCRoom *managedRoom = [NCRoom objectsWhere:@"internalId = %@", _room.internalId].firstObject;
RLMResults *managedMessages = [NCChatMessage objectsWhere:@"accountId = %@ AND token = %@", _account.accountId, _room.token];
RLMResults *managedSortedMessages = [managedMessages sortedResultsUsingKeyPath:@"messageId" ascending:YES];
NCChatMessage *firstMessage = managedSortedMessages.firstObject;
NCChatMessage *lastMessage = managedSortedMessages.lastObject;
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
managedRoom.oldestMessageReceived = firstMessage.messageId;
managedRoom.newestMessageReceived = lastMessage.messageId;
}];
_room = [[NCRoom alloc] initWithValue:managedRoom];
}
#pragma mark - Chat
- (void)getInitialChatHistory:(NSInteger)lastReadMessage
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:_roomToken forKey:@"room"];
if (_room.newestMessageReceived >= lastReadMessage) {
NSArray *storedMessages = [self getStoredMessagesFromMessageId:lastReadMessage included:YES];
[userInfo setObject:storedMessages forKey:@"messages"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCRoomControllerDidReceiveInitialChatHistoryNotification
object:self
userInfo:userInfo];
[self startReceivingChatMessagesFromMessagesId:_room.newestMessageReceived withTimeout:NO];
} else {
_pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_roomToken fromLastMessageId:lastReadMessage history:YES includeLastMessage:YES timeout:NO forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode) {
if (_stopChatMessagesPoll) {
return;
}
[userInfo setObject:error forKey:@"error"];
if (statusCode != 304) {
NSLog(@"Could not get chat history. Error: %@", error.description);
if (error) {
if ([self isChatBeingBlocked:statusCode]) {
[self notifyChatIsBlocked];
return;
}
[userInfo setObject:error forKey:@"error"];
NSLog(@"Could not get initial chat history. Error: %@", error.description);
}
}
if (messages.count > 0) {
[userInfo setObject:messages forKey:@"messages"];
}
[userInfo setObject:_roomToken forKey:@"room"];
if (messages.count > 0) {
[self storeMessages:messages];
NSArray *storedMessages = [self getStoredMessagesFromMessageId:lastReadMessage included:YES];
[userInfo setObject:storedMessages forKey:@"messages"];
[self updateOldestAndNewestStoredMessagesForRoom];
}
[[NSNotificationCenter defaultCenter] postNotificationName:NCRoomControllerDidReceiveInitialChatHistoryNotification
object:self
userInfo:userInfo];
if (!error) {
[self startReceivingChatMessagesFromMessagesId:_room.newestMessageReceived withTimeout:NO];
}
}];
}
}
- (void)getChatHistoryFromMessagesId:(NSInteger)messageId
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:_roomToken forKey:@"room"];
if (_room.oldestMessageReceived > 0 && _room.oldestMessageReceived < messageId) {
NSArray *storedMessages = [self getStoredMessagesFromMessageId:messageId included:NO];
[userInfo setObject:storedMessages forKey:@"messages"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCRoomControllerDidReceiveChatHistoryNotification
object:self
userInfo:userInfo];
}];
} else {
_getHistoryTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_roomToken fromLastMessageId:messageId history:YES includeLastMessage:NO timeout:NO forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode) {
if (statusCode == 304) {
_hasHistory = NO;
}
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);
}
}
if (messages.count > 0) {
[self storeMessages:messages];
NSArray *storedMessages = [self getStoredMessagesFromMessageId:messageId included:NO];
[userInfo setObject:storedMessages forKey:@"messages"];
[self updateOldestAndNewestStoredMessagesForRoom];
}
[[NSNotificationCenter defaultCenter] postNotificationName:NCRoomControllerDidReceiveChatHistoryNotification
object:self
userInfo:userInfo];
}];
}
}
- (void)stopReceivingChatHistory
@ -115,13 +217,10 @@ NSString * const NCRoomControllerDidReceiveChatBlockedNotification = @"
{
_stopChatMessagesPoll = NO;
[_pullMessagesTask cancel];
_newestMessageId = messageId;
_pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_roomToken fromLastMessageId:messageId history:NO includeLastMessage:NO timeout:timeout forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSMutableArray *messages, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode) {
_pullMessagesTask = [[NCAPIController sharedInstance] receiveChatMessagesOfRoom:_roomToken fromLastMessageId:messageId history:NO includeLastMessage:NO timeout:timeout forAccount:_account withCompletionBlock:^(NSArray *messages, NSInteger lastKnownMessage, NSError *error, NSInteger statusCode) {
if (_stopChatMessagesPoll) {
return;
}
_newestMessageId = lastKnownMessage > 0 ? lastKnownMessage : _newestMessageId;
NSMutableDictionary *userInfo = [NSMutableDictionary new];
if (error) {
if ([self isChatBeingBlocked:statusCode]) {
@ -134,14 +233,17 @@ NSString * const NCRoomControllerDidReceiveChatBlockedNotification = @"
}
}
if (messages.count > 0) {
[userInfo setObject:messages forKey:@"messages"];
[self storeMessages:messages];
NSArray *storedMessages = [self getNewStoredMessagesSinceMessageId:messageId];
[userInfo setObject:storedMessages forKey:@"messages"];
[self updateOldestAndNewestStoredMessagesForRoom];
}
[userInfo setObject:_roomToken forKey:@"room"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCRoomControllerDidReceiveChatMessagesNotification
object:self
userInfo:userInfo];
if (error.code != -999) {
[self startReceivingChatMessagesFromMessagesId:_newestMessageId withTimeout:YES];
[self startReceivingChatMessagesFromMessagesId:_room.newestMessageReceived withTimeout:YES];
}
}];
}
@ -156,7 +258,7 @@ NSString * const NCRoomControllerDidReceiveChatBlockedNotification = @"
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
[userInfo setObject:message forKey:@"message"];
[[NCAPIController sharedInstance] sendChatMessage:message toRoom:_roomToken displayName:nil replyTo:replyTo forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSError *error) {
[[NCAPIController sharedInstance] sendChatMessage:message toRoom:_roomToken displayName:nil replyTo:replyTo forAccount:_account withCompletionBlock:^(NSError *error) {
if (error) {
[userInfo setObject:error forKey:@"error"];
NSLog(@"Could not send chat message. Error: %@", error.description);

Просмотреть файл

@ -86,21 +86,21 @@ NSString * const NCRoomsManagerDidReceiveChatMessagesNotification = @"ChatMess
{
NSMutableDictionary *userInfo = [NSMutableDictionary new];
NCRoomController *roomController = [_activeRooms objectForKey:token];
TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
if (!roomController) {
_joiningRoom = token;
_joinRoomTask = [[NCAPIController sharedInstance] joinRoom:token forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSString *sessionId, NSError *error, NSInteger statusCode) {
_joinRoomTask = [[NCAPIController sharedInstance] joinRoom:token forAccount:activeAccount withCompletionBlock:^(NSString *sessionId, NSError *error, NSInteger statusCode) {
if (!_joiningRoom) {
NSLog(@"Not joining the room any more. Ignore response.");
return;
}
if (!error) {
NCRoomController *controller = [[NCRoomController alloc] initForUser:sessionId inRoom:token];
NCRoomController *controller = [[NCRoomController alloc] initForAccountId:activeAccount.accountId withSessionId:sessionId inRoom:token];
controller.inChat = !call;
controller.inCall = call;
[_activeRooms setObject:controller forKey:token];
[_joinRoomAttempts removeObjectForKey:token];
[userInfo setObject:controller forKey:@"roomController"];
TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
NCExternalSignalingController *extSignalingController = [[NCSettingsController sharedInstance] externalSignalingControllerForAccountId:activeAccount.accountId];
if ([extSignalingController isEnabled]) {
[extSignalingController joinRoom:token withSessionId:sessionId];